From ffe54bc7661c81ea4cc7e0b2a24d4b1079ad1cc6 Mon Sep 17 00:00:00 2001 From: ctao Date: Sat, 25 Jul 2026 12:33:09 +0200 Subject: [PATCH] fix: light dismiss for header menu/search overlays (outside tap closes, never activates) --- public/search-client.js | 14 ++++++++++++++ src/layouts/Base.astro | 32 ++++++++++++++++++++++++++++---- src/styles/global.css | 38 ++++++++++++++++++++++++++------------ 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/public/search-client.js b/public/search-client.js index b2655d1..d49dec8 100644 --- a/public/search-client.js +++ b/public/search-client.js @@ -104,6 +104,20 @@ const a = e.target.closest && e.target.closest('a'); remember((a && a.dataset.q) || input.value); }); + // Dropdown dismiss (opt-in — /search keeps its results list). A suggestion + // list is a NON-MODAL combobox popup (ARIA combobox pattern): interacting + // outside the field+list closes it but the interaction is NOT swallowed — + // unlike the modal header panels, which scrim-dismiss in Base.astro. + if (opts.dismiss) { + const box = input.closest('form') || input.parentElement; + const hide = () => { clearTimeout(timer); list.innerHTML = ''; status.textContent = ''; }; + document.addEventListener('pointerdown', (e) => { if (list.firstChild && !box.contains(e.target)) hide(); }); + // relatedTarget may be null mid-click on our own links — pointerdown covers that path + box.addEventListener('focusout', (e) => { if (e.relatedTarget && !box.contains(e.relatedTarget)) hide(); }); + input.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && list.firstChild) { hide(); e.stopPropagation(); } // panel Escape stays a second step + }); + } return run; // callers may run() immediately (e.g. /search?q=…) }; })(); diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index ffac499..5dd7ff2 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -110,11 +110,35 @@ const links = [
diff --git a/src/styles/global.css b/src/styles/global.css index 5f4270d..787f992 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -22,7 +22,8 @@ --on-galaxy-muted: #c7cde9; --tint-cherenkov: color-mix(in srgb, var(--cherenkov) 16%, transparent); /* highlight only */ --cherenkov-hover: color-mix(in oklab, var(--cherenkov), var(--galaxy) 12%); /* CTA hover state */ - --ico-ext: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"%3E%3Cpath d="M7 17 17 7M9 7h8v8" fill="none" stroke="black" stroke-width="2.5"/%3E%3C/svg%3E'); + /* stroke 3: at ~0.72em rendering this matches the .ico family's ~1.5px optical weight */ + --ico-ext: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"%3E%3Cpath d="M7 17 17 7M9 7h8v8" fill="none" stroke="black" stroke-width="3"/%3E%3C/svg%3E'); /* Type — fonts (BRAND D.3.1/D.3.2) and the ONLY font sizes in this codebase */ --font-body: "Inter", "Inter Fallback", Arial, system-ui, sans-serif; --font-display: "Space Grotesk", var(--font-body); /* headlines only */ @@ -213,7 +214,8 @@ img { max-width: 100%; height: auto; display: block; } spec-required search). Chrome stays whisper-quiet: hairline on navy. Flexes 140-200px (only it and the spacer flex — one-row invariant). */ .nav-search { position: relative; display: flex; align-items: center; flex: 0 1 200px; min-width: 140px; margin-left: 6px; } -.nav-search .search-ico { left: 11px; width: 14px; height: 14px; color: rgba(255, 255, 255, 0.7); } +/* stroke 2.6 ≈ the family's 1.5px rendered weight at 14px (icons must not thin out when small) */ +.nav-search .search-ico { left: 11px; width: 14px; height: 14px; color: rgba(255, 255, 255, 0.7); stroke-width: 2.6; } .nav-search input { width: 100%; min-width: 0; font: inherit; font-size: var(--fs-s); color: #fff; background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.45); /* 3.55:1 vs Galaxy — WCAG 1.4.11 */ @@ -345,6 +347,7 @@ a.btn { display: inline-flex; align-items: center; gap: 6px; text-decoration: no } .suggest:empty { display: none; } .suggest li { display: flex; align-items: flex-start; gap: var(--space-s); padding: 0 12px; border-radius: calc(var(--radius) - 8px); } +.suggest li + li { margin-top: 2px; } /* rows breathe — hover fills read as discrete items */ .suggest li:hover, .suggest li:focus-within { background: var(--moon); } /* Focused-empty state labels ("Recent" / "Latest news") — quiet, non-interactive */ li.suggest-label { @@ -352,16 +355,20 @@ li.suggest-label { text-transform: uppercase; letter-spacing: 0.08em; padding-top: 8px; } .suggest li.suggest-label:hover { background: none; } +/* Each later group opens with a hairline — label + its rows read as one unit */ +.suggest li.suggest-label:not(:first-child) { + margin-top: 6px; padding-top: 12px; border-top: 1px solid var(--border); border-radius: 0; +} .search-results li.suggest-label { background: none; border: 0; box-shadow: none; padding: 8px 0 0; } /* The link holds -split text nodes — it must lay out as flowing text, never as a flex row (flex would turn every fragment into its own item and stop titles from wrapping). ≥44px target comes from the block padding. */ .suggest a { - flex: 1; min-width: 0; display: block; padding-block: 10px; + flex: 1; min-width: 0; display: block; padding-block: 12px; /* row ≥44px — real tap target */ color: var(--text); font-weight: 500; font-size: var(--fs-s); text-decoration: none; overflow-wrap: break-word; } -.suggest small { margin-top: 12px; } /* date tracks the first title line */ +.suggest small { margin-top: 14px; } /* date tracks the first title line */ .suggest::after { content: "Press Enter for all results"; display: block; margin-top: 4px; padding: 8px 12px 2px; border-top: 1px solid var(--border); @@ -494,7 +501,7 @@ span.page-step { color: var(--muted); } /* disabled end stop — non-interactive /* Inline rendering (< 1200px): light disclosure row with a chevron */ .toc--inline { border: 1px solid var(--border); border-radius: var(--radius); padding: 4px var(--space-m); margin-bottom: var(--space-l); } .toc--inline summary { display: flex; align-items: center; justify-content: space-between; gap: var(--space-s); min-height: 40px; } -.toc--inline summary .ico { width: 14px; height: 14px; } +.toc--inline summary .ico { width: 14px; height: 14px; stroke-width: 2.6; } /* optical weight match at 14px */ .toc--inline[open] summary .ico { transform: rotate(180deg); } .toc--inline ol { padding-bottom: var(--space-s); } .toc--rail { display: none; } @@ -576,17 +583,24 @@ span.page-step { color: var(--muted); } /* disabled end stop — non-interactive .notice { color: var(--muted); font-size: var(--fs-s); margin-top: 10px; } /* Search results (list built by the inline script on /search) — card-like rows; - same single-signal chrome as cards (borderless on Moon, shadow hover) */ -.search-results { list-style: none; margin: var(--space-m) 0 0; padding: 0; max-width: 720px; display: grid; gap: var(--space-s); } + same single-signal chrome as cards (borderless on Moon, shadow hover). + Margins, not grid gap: idle-state group labels must hug their rows. */ +.search-results { list-style: none; margin: var(--space-m) 0 0; padding: 0; max-width: 720px; } +.search-results li + li { margin-top: var(--space-m); } /* each article reads as its own card */ +.search-results li.suggest-label + li { margin-top: var(--space-s); } /* rows hug their group label */ .search-results li { display: flex; align-items: flex-start; gap: var(--space-s); background: #fff; border-radius: var(--radius); - padding: 12px 18px; box-shadow: var(--shadow-ambient); + padding: var(--space-m); box-shadow: var(--shadow-ambient); } .search-results li:hover { box-shadow: var(--shadow-lifted); } -/* Title flows as inline text (never display:flex — see .suggest a note) */ -.search-results a { flex: 1; min-width: 0; overflow-wrap: break-word; text-decoration: none; font-weight: 500; } -.search-results a:hover { text-decoration: underline; } +/* Title flows as inline text (never display:flex — see .suggest a note); + card-title treatment (fs-l Galaxy) so titles outweigh the muted meta */ +.search-results a { + flex: 1; min-width: 0; overflow-wrap: break-word; text-decoration: none; + font-weight: 600; font-size: var(--fs-l); line-height: 1.3; color: var(--galaxy); +} +.search-results a:hover { color: var(--link); text-decoration: underline; } /* Dates on results and suggestions — plain muted text, no chip chrome */ .search-results small, .suggest small { color: var(--muted); white-space: nowrap; font-size: var(--fs-xs); } .search-results small { margin-top: 5px; } /* optical align with the first title line */ @@ -709,7 +723,7 @@ mark { background: var(--tint-cherenkov); color: inherit; border-radius: calc(va border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease); } .card, .search-results li { transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease); } - .card:hover { transform: translateY(-2px); } /* the one elevate gesture (no cover zoom) */ + .card:hover, .search-results li:hover { transform: translateY(-2px); } /* the one elevate gesture (no cover zoom) */ } @keyframes nebula { /* inert unless applied inside the block above */ from { transform: translate3d(-8%, -5%, 0) rotate(-6deg) scale(1); }