diff --git a/assets/app.js b/assets/app.js
index e3e3724..a17c164 100644
--- a/assets/app.js
+++ b/assets/app.js
@@ -19,6 +19,3 @@ import './styles/form.css';
import './styles/notice.css';
import './styles/spinner.css';
import './styles/a2hs.css';
-
-
-console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉');
diff --git a/assets/bootstrap.js b/assets/bootstrap.js
index d564fdb..71f0f99 100644
--- a/assets/bootstrap.js
+++ b/assets/bootstrap.js
@@ -2,9 +2,10 @@ import { startStimulusApp } from '@symfony/stimulus-bundle';
import ArticleCommentsController from './controllers/article_comments_controller.js';
import CommentReplyController from './controllers/comment_reply_controller.js';
import CopyTextController from './controllers/copy_text_controller.js';
-import ArticleHighlightController from './controllers/article_highlight_controller.js';
-
const app = startStimulusApp();
+if (typeof app.debug === 'boolean') {
+ app.debug = false;
+}
// Ensure lazy comment loader is registered (Asset Mapper discovery can miss new files until rebuild).
try {
@@ -22,8 +23,3 @@ try {
} catch {
/* already registered by the bundle */
}
-try {
- app.register('article-highlight', ArticleHighlightController);
-} catch {
- /* already registered by the bundle */
-}
diff --git a/assets/controllers/article_comments_controller.js b/assets/controllers/article_comments_controller.js
index c5fa54c..4cc5fe2 100644
--- a/assets/controllers/article_comments_controller.js
+++ b/assets/controllers/article_comments_controller.js
@@ -79,9 +79,9 @@ export default class extends Controller {
}
const ms = Math.round(performance.now() - t0);
if (attempt > 1) {
- console.info(`[article-comments] fragment OK in ${ms}ms (after ${attempt} attempts)`, this.urlValue);
+ console.debug(`[article-comments] fragment OK in ${ms}ms (after ${attempt} attempts)`, this.urlValue);
} else {
- console.info(`[article-comments] fragment OK in ${ms}ms`, this.urlValue);
+ console.debug(`[article-comments] fragment OK in ${ms}ms`, this.urlValue);
}
window.clearTimeout(timer);
return;
diff --git a/assets/controllers/article_highlight_controller.js b/assets/controllers/article_highlight_controller.js
deleted file mode 100644
index b5c4090..0000000
--- a/assets/controllers/article_highlight_controller.js
+++ /dev/null
@@ -1,290 +0,0 @@
-import { Controller } from '@hotwired/stimulus';
-
-const MARK_SEL = 'mark.article-body-highlight';
-
-/**
- * In-article NIP-84 marks: show popover on hover / click; #h-<64-hex> scrolls and focuses the mark.
- */
-export default class extends Controller {
- static targets = ['article', 'meta', 'popover', 'popoverInner'];
-
- connect() {
- this._metaById = {};
- if (this.hasMetaTarget) {
- try {
- this._metaById = JSON.parse(this.metaTarget.textContent || '{}');
- } catch {
- this._metaById = {};
- }
- }
- this._pinnedId = null;
- this._openId = null;
- this._hoverLeaveTimer = null;
- this._onHash = () => {
- this._scrollToHash();
- };
- this._onScrollReposition = () => {
- if (this._openId && this.hasPopoverTarget) {
- const a = this._getMarkByEventId(this._openId);
- if (a) {
- this._placePopover(a);
- }
- }
- };
- this._onPopoverPointerEnter = () => {
- this._clearHoverLeaveTimer();
- };
- this._onPopoverPointerLeave = () => {
- if (!this._pinnedId) {
- this._hoverLeaveTimer = setTimeout(() => {
- this._hoverLeaveTimer = null;
- this._hideUnpinnedPopover();
- }, 200);
- }
- };
-
- this._onMarkPointerEnter = (e) => {
- const a = e.target && e.target.closest && e.target.closest(MARK_SEL);
- if (!a) {
- return;
- }
- this._clearHoverLeaveTimer();
- this._openForMark(a, false);
- };
- this._onMarkPointerLeave = (e) => {
- const a = e.target && e.target.closest && e.target.closest(MARK_SEL);
- if (!a) {
- return;
- }
- this._onMarkLeave(a);
- };
- this._onMarkClick = (e) => {
- const a = e.target && e.target.closest && e.target.closest(MARK_SEL);
- if (!a) {
- return;
- }
- e.preventDefault();
- e.stopPropagation();
- const id = (a.getAttribute('data-event-id') || '').toLowerCase();
- if (this._pinnedId === id) {
- this._closePinned();
- this._hideUnpinnedPopover();
- return;
- }
- this._openForMark(a, true);
- };
- this._onMarkFocus = (e) => {
- const t = e.target;
- if (!t || !t.classList || !t.classList.contains('article-body-highlight')) {
- return;
- }
- this._openForMark(t, false);
- };
- this._onMarkKeydown = (e) => {
- if (e.key !== 'Enter' && e.key !== ' ') {
- return;
- }
- const t = e.target;
- if (!t || !t.classList || !t.classList.contains('article-body-highlight')) {
- return;
- }
- e.preventDefault();
- this._openForMark(t, true);
- };
-
- if (this.hasArticleTarget) {
- this.articleTarget.addEventListener('pointerenter', this._onMarkPointerEnter, true);
- this.articleTarget.addEventListener('pointerleave', this._onMarkPointerLeave, true);
- this.articleTarget.addEventListener('click', this._onMarkClick, true);
- this.articleTarget.addEventListener('focusin', this._onMarkFocus, true);
- this.articleTarget.addEventListener('keydown', this._onMarkKeydown, true);
- }
- if (this.hasPopoverTarget) {
- this.popoverTarget.addEventListener('pointerenter', this._onPopoverPointerEnter);
- this.popoverTarget.addEventListener('pointerleave', this._onPopoverPointerLeave);
- }
- window.addEventListener('scroll', this._onScrollReposition, true);
- window.addEventListener('resize', this._onScrollReposition);
- this._scrollToHash();
- window.addEventListener('hashchange', this._onHash);
- }
-
- disconnect() {
- window.removeEventListener('hashchange', this._onHash);
- window.removeEventListener('scroll', this._onScrollReposition, true);
- window.removeEventListener('resize', this._onScrollReposition);
- if (this.hasArticleTarget) {
- this.articleTarget.removeEventListener('pointerenter', this._onMarkPointerEnter, true);
- this.articleTarget.removeEventListener('pointerleave', this._onMarkPointerLeave, true);
- this.articleTarget.removeEventListener('click', this._onMarkClick, true);
- this.articleTarget.removeEventListener('focusin', this._onMarkFocus, true);
- this.articleTarget.removeEventListener('keydown', this._onMarkKeydown, true);
- }
- if (this.hasPopoverTarget) {
- this.popoverTarget.removeEventListener('pointerenter', this._onPopoverPointerEnter);
- this.popoverTarget.removeEventListener('pointerleave', this._onPopoverPointerLeave);
- }
- this._clearHoverLeaveTimer();
- }
-
- closeOnOutside(event) {
- if (!this.hasPopoverTarget) {
- return;
- }
- if (!this._pinnedId && !this._openId) {
- return;
- }
- const t = event.target;
- if (this.popoverTarget.contains(t)) {
- return;
- }
- if (t && t.closest && t.closest('mark.article-body-highlight')) {
- return;
- }
- if (this._pinnedId) {
- this._closePinned();
- } else {
- this._hideUnpinnedPopover();
- }
- }
-
- onKeydown(event) {
- if (event.key === 'Escape') {
- this._closePinned();
- this._hideUnpinnedPopover();
- }
- }
-
- closePopover() {
- this._closePinned();
- this._hideUnpinnedPopover();
- }
-
- _onMarkLeave(mark) {
- this._clearHoverLeaveTimer();
- if (this._pinnedId) {
- return;
- }
- this._hoverLeaveTimer = setTimeout(() => {
- this._hoverLeaveTimer = null;
- if (this._openId === (mark.getAttribute('data-event-id') || '').toLowerCase()) {
- this._hideUnpinnedPopover();
- }
- }, 200);
- }
-
- _clearHoverLeaveTimer() {
- if (this._hoverLeaveTimer) {
- clearTimeout(this._hoverLeaveTimer);
- this._hoverLeaveTimer = null;
- }
- }
-
- _getMarkByEventId(id) {
- if (!id) {
- return null;
- }
- return (
- this.element.querySelector(`#highlight-${id}`) ||
- this.element.querySelector(`mark.article-body-highlight[data-event-id="${CSS.escape(id)}"]`)
- );
- }
-
- _openForMark(mark, pin) {
- if (!this.hasPopoverTarget || !this.hasPopoverInnerTarget) {
- return;
- }
- const id = (mark.getAttribute('data-event-id') || '').toLowerCase();
- if (!id || 64 !== id.length) {
- return;
- }
- const meta = this._metaById[id];
- if (!meta) {
- return;
- }
- this._openId = id;
- if (pin) {
- this._pinnedId = id;
- } else {
- this._clearHoverLeaveTimer();
- }
- const head = meta.headHtml || '';
- const body = (meta.bodyHtml || '').trim();
- this.popoverInnerTarget.innerHTML =
- head + (body !== '' ? `
${body}
` : '');
- this._placePopover(mark);
- this.popoverTarget.hidden = false;
- }
-
- _placePopover(anchor) {
- if (!this.hasPopoverTarget) {
- return;
- }
- const p = this.popoverTarget;
- p.style.position = 'fixed';
- p.style.zIndex = '200';
- p.style.left = '0';
- p.style.top = '0';
- const r = anchor.getBoundingClientRect();
- p.hidden = false;
- const pr = p.getBoundingClientRect();
- let left = r.left + r.width / 2 - pr.width / 2;
- let top = r.bottom + 8;
- if (left < 8) {
- left = 8;
- }
- if (left + pr.width > window.innerWidth - 8) {
- left = Math.max(8, window.innerWidth - 8 - pr.width);
- }
- if (top + pr.height > window.innerHeight - 8) {
- top = Math.max(8, r.top - 8 - pr.height);
- }
- p.style.left = `${Math.round(left)}px`;
- p.style.top = `${Math.round(top)}px`;
- }
-
- _hideUnpinnedPopover() {
- this._openId = null;
- this._clearHoverLeaveTimer();
- if (this._pinnedId) {
- return;
- }
- if (this.hasPopoverTarget) {
- this.popoverTarget.hidden = true;
- }
- if (this.hasPopoverInnerTarget) {
- this.popoverInnerTarget.innerHTML = '';
- }
- }
-
- _closePinned() {
- this._pinnedId = null;
- if (this.hasPopoverTarget) {
- this.popoverTarget.hidden = true;
- }
- if (this.hasPopoverInnerTarget) {
- this.popoverInnerTarget.innerHTML = '';
- }
- this._openId = null;
- }
-
- _scrollToHash() {
- const raw = window.location.hash || '';
- if (!raw.startsWith('#h-')) {
- return;
- }
- const id = raw.slice(3).toLowerCase().replace(/[^0-9a-f]/g, '');
- if (id.length !== 64) {
- return;
- }
- const el = this.element.querySelector(`#highlight-${id}`) || this._getMarkByEventId(id);
- if (!el) {
- return;
- }
- el.classList.remove('article-body-highlight--target');
- void el.offsetWidth;
- el.classList.add('article-body-highlight--target');
- el.scrollIntoView({ block: 'center', behavior: 'smooth' });
- this._openForMark(el, false);
- }
-}
diff --git a/assets/controllers/service-worker_controller.js b/assets/controllers/service-worker_controller.js
index b2bba42..9fe7d7e 100644
--- a/assets/controllers/service-worker_controller.js
+++ b/assets/controllers/service-worker_controller.js
@@ -4,7 +4,9 @@ export default class extends Controller {
connect() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
- .then(reg => console.log('SW registered:', reg))
+ .then(() => {
+ /* optional: console.debug('SW registered') */
+ })
.catch(err => console.error('SW failed:', err));
}
}
diff --git a/assets/styles/app.css b/assets/styles/app.css
index c4ad10c..64ebd22 100644
--- a/assets/styles/app.css
+++ b/assets/styles/app.css
@@ -243,7 +243,8 @@ svg.icon {
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
- color: hsl(var(--tile-hue) 26% 32%);
+ /* Blend hue toward body mid-gray so every tile hue stays ≥4.5:1 on dark head strip */
+ color: color-mix(in srgb, hsl(var(--tile-hue) 26% 50%) 38%, var(--color-text-mid) 62%);
line-height: 1.35;
}
@@ -355,7 +356,7 @@ svg.icon {
font-size: 0.78rem;
font-weight: 400;
line-height: 1.35;
- color: color-mix(in srgb, var(--color-text-mid) 48%, var(--color-bg) 52%);
+ color: var(--color-text-mid);
margin: 0.15rem 0 0.45rem;
}
@@ -385,7 +386,7 @@ svg.icon {
font-size: 0.78rem;
font-weight: 400;
line-height: 1.35;
- color: color-mix(in srgb, var(--color-text-mid) 48%, var(--color-bg) 52%);
+ color: var(--color-text-mid);
}
.article-list .metadata p {
diff --git a/assets/styles/article.css b/assets/styles/article.css
index c8af856..c9c259c 100644
--- a/assets/styles/article.css
+++ b/assets/styles/article.css
@@ -422,79 +422,7 @@
background: #fdecec;
}
-/* NIP-84: kind-9802 marks in .article-main + client popover (no separate thread list) */
-.article-body-highlight {
- cursor: pointer;
- scroll-margin-top: 6rem;
-}
-
-.article-body-highlight--target {
- box-shadow: none;
- background: transparent;
- text-decoration: underline;
- text-decoration-color: color-mix(in srgb, var(--color-text) 35%, transparent);
- text-underline-offset: 0.12em;
- transition: text-decoration-color 0.25s ease;
-}
-
-.article-body-highlight:focus-visible {
- outline: 2px solid var(--color-focus-ring, var(--color-primary));
- outline-offset: 2px;
-}
-
-.article-body-highlight__popover[hidden] {
- display: none;
-}
-
-.article-body-highlight__popover {
- position: fixed;
- z-index: 200;
- max-width: min(24rem, calc(100vw - 1.5rem));
- margin: 0;
- padding: 0.65rem 2rem 0.85rem 0.85rem;
- border: 1px solid color-mix(in srgb, var(--color-border) 75%, var(--color-bg) 25%);
- border-radius: 0.45rem;
- color: var(--color-text);
- /* Slightly above page bg so the card reads as a raised surface */
- background: color-mix(in srgb, var(--color-bg) 88%, #fff 12%);
- box-shadow: 0 0.4rem 1.25rem rgba(0, 0, 0, 0.14);
- pointer-events: auto;
-}
-
-.article-body-highlight__close {
- position: absolute;
- top: 0.2rem;
- right: 0.35rem;
- margin: 0;
- border: 0;
- padding: 0.15rem 0.4rem;
- line-height: 1;
- font-size: 1.35rem;
- color: var(--color-text-mid);
- background: transparent;
- border-radius: 0.2rem;
- cursor: pointer;
-}
-
-.article-body-highlight__close:hover,
-.article-body-highlight__close:focus-visible {
- color: var(--color-text);
- outline: 2px solid var(--color-focus-ring, var(--color-primary));
-}
-
-.article-body-highlight__inner {
- position: relative;
-}
-
-.article-body-highlight__head {
- display: flex;
- flex-wrap: wrap;
- align-items: baseline;
- justify-content: space-between;
- gap: 0.4rem 0.75rem;
- margin: 0;
- font-size: 0.86rem;
-}
+/* NIP-84: kind-9802 marks in .article-main (fragment id highlight- for deep links) */
/* Full `context` quote + optional on the `content` substring (body copy, not a box) */
.user-highlight__body {
@@ -505,34 +433,25 @@
font-family: var(--main-body-font), serif;
}
-/* In-flow article body: interactive but no highlighter fill (cards below own the color) */
-.article-main mark.user-highlight__marker {
- margin: 0;
- padding: 0;
- border-radius: 0;
- font: inherit;
- line-height: inherit;
- color: inherit;
- background: transparent;
- box-shadow: none;
- box-decoration-break: clone;
- -webkit-box-decoration-break: clone;
-}
-
-/* Popover + home aside: full context where present, `content` visibly marked */
-.article-body-highlight__body mark.user-highlight__marker,
+/* In-flow + aside: same NIP-84 mark treatment; scroll-margin in article for #highlight-… links */
+.article-main mark.user-highlight__marker,
.home-aside-highlights__quote--html mark.user-highlight__marker {
margin: 0;
padding: 0.08em 0.1em 0.12em;
border-radius: 0.12em;
font: inherit;
line-height: inherit;
- color: inherit;
- background: color-mix(in srgb, #7ad67a 38%, #f0e8a0 62%);
+ color: var(--color-highlight-mark-fg);
+ background: color-mix(in srgb, #7ad67a 30%, #f0e8a0 70%);
+ box-shadow: none;
box-decoration-break: clone;
-webkit-box-decoration-break: clone;
}
+.article-main mark.user-highlight__marker {
+ scroll-margin-top: calc(var(--site-fixed-header-offset, 140px) + 0.75rem);
+}
+
/* When `content` is not a substring of `context` (rare) */
.user-highlight__marker-orphan {
margin: 0.5rem 0 0;
diff --git a/assets/styles/event.css b/assets/styles/event.css
index 3af21d6..65e1e83 100644
--- a/assets/styles/event.css
+++ b/assets/styles/event.css
@@ -98,7 +98,7 @@
font-size: 0.78rem;
font-weight: 400;
line-height: 1.35;
- color: color-mix(in srgb, var(--color-text-mid) 50%, var(--color-bg) 50%);
+ color: var(--color-text-mid);
}
.event-page a:focus-visible {
diff --git a/assets/styles/layout.css b/assets/styles/layout.css
index 2416c7c..969531f 100644
--- a/assets/styles/layout.css
+++ b/assets/styles/layout.css
@@ -463,8 +463,18 @@ a.nostr-share-menu__action {
}
/* Main content */
+:root {
+ /* Clears fixed #site-header; keep in sync with main margin-top per breakpoint below. */
+ --site-fixed-header-offset: 140px;
+}
+
+/* #:target / in-page links: scroll position leaves room under the fixed bar (scroll-margin on inline is unreliable alone). */
+html {
+ scroll-padding-top: calc(var(--site-fixed-header-offset) + 0.75rem);
+}
+
main {
- margin-top: 140px;
+ margin-top: var(--site-fixed-header-offset);
flex-grow: 1;
min-width: 0; /* flex item: allow shrinking below wide images / intrinsic min-content */
padding: 1em;
@@ -480,19 +490,19 @@ main {
}
@media (min-width: 1025px) {
- /* Match extra header padding-top so content and menu clear the fixed bar */
- main {
- margin-top: 152px;
+ :root {
+ --site-fixed-header-offset: 152px;
}
+ /* Match extra header padding-top so content and menu clear the fixed bar */
/* In-flow left column: