Skip to content

Commit 73aebfd

Browse files
KSemenenkoclaude
andcommitted
Fix landing header collapse and benchmark dropdown chevrons
The header no longer wraps into two rows on narrow screens: it stays one glass capsule with the brand, GitHub and a menu control that opens the same navigation as a popover sheet. Selecting a link closes the sheet without cancelling the fragment scroll. Dropdowns get a round chevron key that rotates while the picker is open; long values fade before it on an opaque select colour instead of running under the arrow. Verified in a real Chromium preview at 1440/768/760/390/320px (no page overflow, single-row header) and with an open base-select picker. The site TUnit suite needs a clean exact-revision checkout and GitHub archive, so it was not run locally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 0d78eb4 commit 73aebfd

6 files changed

Lines changed: 72 additions & 29 deletions

File tree

‎docs/Features/BenchmarkComparisons.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ The owner requested a proper product design and Three.js. [ADR-040](../ADR/ADR-0
316316

317317
| Requirement | Measurable acceptance | Test or review mapping |
318318
|---|---|---|
319-
| REQ-BC-011: product-first responsive design | AC-BC-011: purpose/status/navigation visible at1440/768/390/320px, no page overflow or clipped controls; labelled table scroll only | Qualified H: SiteBuildTests plus explicit real-browser desktop/mobile visual review |
319+
| REQ-BC-011: product-first responsive design | AC-BC-011: purpose/status/navigation visible at1440/768/390/320px in a single-row header; at ≤760px the same links open from a visible menu control and close after a selection without losing the section scroll. No page overflow, wrapped header or clipped controls; select text fades before its chevron key; labelled table scroll only | Qualified H: SiteBuildTests plus explicit real-browser desktop/mobile visual review |
320320
| REQ-BC-012: bounded conceptual Three RF3 | AC-BC-012: Three0.186.1 native backend recorded; one renderer/canvas, correct cancellation/disposal/resize/pause; DPR≤1.5,≤1M pixels,≤30 calls,≤5K triangles, no idle loop | Qualified H: vendor/build TUnit contracts; explicit real-browser graphics lifecycle/device evidence exception |
321321
| REQ-BC-013: accessible independent content | AC-BC-013: semantic labels/focus/tabs and no-JS/reduced/coarse/unavailable-graphics retain text/poster/evidence access | Qualified H: SiteBuildTests plus required keyboard/no-JS/reduced-motion browser review |
322322
| REQ-BC-014: unchanged historical arithmetic and controls | AC-BC-014: all6 scenarios/11 metrics/profile/repetition/log/whiskers/table/downloads match independent oracle over authentic successful-CI reports | Qualified H: SiteMeasurementTests/SiteMeasurementOracle using actual JS child process; real browser control review |

‎site/Features/BenchmarkComparisons/bootstrap.mjs‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IDS, SELECTORS, CONFIG, TEXT } from './contracts.mjs';
22
import { mountIsolatedLab } from './isolated-lab.mjs';
33

4-
const EVENTS = Object.freeze({ hide: 'pagehide', show: 'pageshow', click: 'click' });
4+
const EVENTS = Object.freeze({ hide: 'pagehide', show: 'pageshow', click: 'click', change: 'change' });
55
const SCENE = Object.freeze({ module: './cluster-scene.mjs', unavailable: 'Static architectural illustration', delay: 100,
66
hidden: 'hidden', initialGeneration: 0, nextGeneration: 1 });
77
let mounted = false;
@@ -71,7 +71,21 @@ function bindCopy() {
7171
});
7272
}
7373

74+
function bindMenu() {
75+
const menu = document.getElementById(IDS.menu);
76+
if (!menu?.hidePopover) return;
77+
const close = () => { if (menu.matches(SELECTORS.menuOpen)) menu.hidePopover(); };
78+
menu.addEventListener(EVENTS.click, event => {
79+
if (!event.target.closest(SELECTORS.menuLink)) return;
80+
// Focus left inside the popover is restored to the toggle on hide, which cancels the fragment scroll.
81+
document.activeElement?.blur();
82+
close();
83+
});
84+
matchMedia(CONFIG.wideNavigationQuery).addEventListener(EVENTS.change, close);
85+
}
86+
7487
window.addEventListener(EVENTS.hide, stop);
7588
window.addEventListener(EVENTS.show, start);
7689
bindCopy();
90+
bindMenu();
7791
start();

‎site/Features/BenchmarkComparisons/contracts.mjs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
export const IDS = Object.freeze({ benchmarks: 'benchmarks', scene: 'cluster-scene', motion: 'scene-motion' });
1+
export const IDS = Object.freeze({ benchmarks: 'benchmarks', scene: 'cluster-scene', motion: 'scene-motion', menu: 'site-menu' });
22

33
export const SELECTORS = Object.freeze({ command: '.command-box code', copy: '#copy-command',
4-
poster: '.cluster-poster', sceneStatus: '[data-scene-status]' });
4+
poster: '.cluster-poster', sceneStatus: '[data-scene-status]', menuLink: 'a[href]', menuOpen: ':popover-open' });
55

66
export const CONFIG = Object.freeze({
77
median: 'median',
88
units: Object.freeze({ millisecondsPerSecond: 1000, bytesPerKiB: 1024, bytesPerMiB: 1048576, percent: 100 }),
9-
copyResetMs: 1800, revisionPattern: /^[a-f0-9]{40}$/,
9+
copyResetMs: 1800, wideNavigationQuery: '(min-width: 761px)', revisionPattern: /^[a-f0-9]{40}$/,
1010
evidencePattern: /^https:\/\/github\.com\/managedcode\/KeyLoad\/actions\/runs\/[1-9]\d*$/,
1111
});
1212

‎site/Features/BenchmarkComparisons/index.html‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,16 @@
6767
<img src="./favicon.svg" width="32" height="32" alt="">
6868
<span>KeyLoad</span>
6969
</a>
70-
<nav aria-label="Main navigation">
70+
<nav class="site-nav" id="site-menu" aria-label="Main navigation" popover>
7171
<a href="#product">Product</a>
7272
<a href="#engine">Engine</a>
7373
<a href="#benchmarks">Benchmarks</a>
7474
<a href="https://github.com/managedcode/KeyLoad/tree/main/docs">Docs</a>
75-
<a class="nav-cta" href="https://github.com/managedcode/KeyLoad" target="_blank" rel="noopener">GitHub <span aria-hidden="true">↗</span></a>
7675
</nav>
76+
<div class="site-header-actions">
77+
<a class="nav-cta" href="https://github.com/managedcode/KeyLoad" target="_blank" rel="noopener">GitHub <span aria-hidden="true">↗</span></a>
78+
<button class="nav-toggle" type="button" popovertarget="site-menu" aria-label="Menu"><span class="nav-toggle-bars" aria-hidden="true"></span></button>
79+
</div>
7780
</div>
7881
</header>
7982

‎site/Features/BenchmarkComparisons/styles.css‎

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,25 @@ main > section + section { margin-top: var(--section-gap); }
3131
.section-intro { margin: 0; max-width: 30rem; color: var(--kl-muted); font-size: 1.05rem; }
3232

3333
/* Header: a floating Liquid Glass capsule */
34-
.site-header { position: sticky; top: 0; z-index: 30; padding: 14px var(--gutter) 0; pointer-events: none; }
35-
.site-header-inner { width: min(100%, 980px); min-height: 56px; margin: 0 auto; padding: 0 8px 0 18px; display: flex; align-items: center; justify-content: space-between; gap: 1.2rem; border: 1px solid var(--kl-stroke); border-radius: 999px; background: var(--kl-sheen), rgba(250, 249, 246, .72); box-shadow: var(--kl-shadow); backdrop-filter: var(--kl-glass); pointer-events: auto; }
36-
.site-header nav { display: flex; align-items: center; gap: .15rem; min-width: 0; }
37-
.site-header nav a { padding: .45rem .8rem; border-radius: 999px; color: var(--kl-ink-2); font-size: .86rem; font-weight: 550; text-decoration: none; white-space: nowrap; }
38-
.site-header nav a:hover { background: var(--kl-surface-3); color: var(--kl-ink); }
39-
.site-header nav .nav-cta { margin-left: .35rem; padding-inline: 1rem; background: var(--kl-graphite); color: #fff; }
40-
.site-header nav .nav-cta:hover { background: var(--kl-graphite-3); color: #fff; }
34+
.site-header { position: sticky; top: 0; z-index: 30; padding: var(--header-top) var(--gutter) 0; pointer-events: none; }
35+
.site-header-inner { width: min(100%, 980px); height: var(--header-height); margin: 0 auto; padding: 0 8px 0 18px; display: flex; align-items: center; gap: 1rem; border: 1px solid var(--kl-stroke); border-radius: 999px; background: var(--kl-sheen), rgba(250, 249, 246, .72); box-shadow: var(--kl-shadow); backdrop-filter: var(--kl-glass); pointer-events: auto; }
36+
.site-header .brand { flex: none; }
37+
/* The nav is a popover so phones get a menu sheet; wide screens reset the UA popover box to an inline row. */
38+
.site-nav { position: static; display: flex; align-items: center; gap: .15rem; width: auto; height: auto; min-width: 0; margin: 0 0 0 auto; padding: 0; overflow: visible; border: 0; background: none; color: inherit; }
39+
.site-nav a { padding: .45rem .8rem; border-radius: 999px; color: var(--kl-ink-2); font-size: .86rem; font-weight: 550; text-decoration: none; white-space: nowrap; }
40+
.site-nav a:hover { background: var(--kl-surface-3); color: var(--kl-ink); }
41+
.site-header-actions { display: flex; flex: none; align-items: center; gap: .4rem; }
42+
.nav-cta { display: inline-flex; align-items: center; gap: .3rem; height: 40px; padding: 0 1.05rem; border-radius: 999px; background: var(--kl-graphite); color: #fff; font-size: .86rem; font-weight: 600; text-decoration: none; white-space: nowrap; }
43+
.nav-cta:hover { background: var(--kl-graphite-3); }
44+
.nav-toggle { display: none; place-items: center; width: 40px; height: 40px; padding: 0; border: 1px solid var(--kl-line); border-radius: 50%; background: rgba(255, 255, 255, .7); color: var(--kl-ink); cursor: pointer; }
45+
.nav-toggle:hover { background: #fff; }
46+
.nav-toggle-bars { position: relative; width: 16px; height: 10px; }
47+
.nav-toggle-bars::before, .nav-toggle-bars::after { content: ""; position: absolute; left: 0; width: 100%; height: 1.6px; border-radius: 2px; background: currentColor; transition: transform .3s var(--kl-ease); }
48+
.nav-toggle-bars::before { top: 0; }
49+
.nav-toggle-bars::after { bottom: 0; }
50+
.site-header-inner:has(.site-nav:popover-open) .nav-toggle { background: #fff; }
51+
.site-header-inner:has(.site-nav:popover-open) .nav-toggle-bars::before { transform: translateY(4.2px) rotate(45deg); }
52+
.site-header-inner:has(.site-nav:popover-open) .nav-toggle-bars::after { transform: translateY(-4.2px) rotate(-45deg); }
4153

4254
/* Hero: oversized editorial type over a glass stage */
4355
.hero { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); align-items: end; gap: 2.2rem 4rem; padding-top: clamp(3.5rem, 8vw, 7rem); }
@@ -149,15 +161,21 @@ h1 .kl-mark { background-size: 100% .11em; background-position: 0 94%; }
149161
.qualification-note p { margin: 0; }
150162
.qualification-mark { display: grid; place-items: center; width: 1.4rem; height: 1.4rem; flex: none; border-radius: 50%; background: var(--kl-graphite); color: #fff; font: 700 .72rem/1 var(--kl-font-mono); }
151163
.instrument { border: 1px solid var(--kl-stroke); border-radius: var(--kl-radius-xl); background: var(--kl-sheen), var(--kl-surface); box-shadow: var(--kl-shadow-lg); backdrop-filter: var(--kl-glass); }
152-
.control { position: relative; display: grid; gap: .5rem; min-width: 0; color: var(--kl-muted); font: 600 .68rem/1.2 var(--kl-font-mono); letter-spacing: .06em; text-transform: uppercase; }
153-
.control::after { content: ""; position: absolute; right: 1.1rem; bottom: 1.2rem; width: .4rem; height: .4rem; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(45deg); pointer-events: none; }
154-
.control select { -webkit-appearance: none; appearance: none; display: block; width: 100%; min-width: 0; height: 46px; padding: .65rem 2.7rem .65rem 1rem; overflow: hidden; border: 1px solid var(--kl-line-strong); border-radius: 999px; background: var(--kl-sheen), var(--kl-surface); box-shadow: var(--kl-shadow-sm), inset 0 1px 0 #fff; color: var(--kl-ink); font: 500 .9rem/1.3 var(--kl-font-sans); letter-spacing: -.01em; text-align: left; text-overflow: ellipsis; text-transform: none; white-space: nowrap; cursor: pointer; }
155-
.control select:hover:not(:disabled) { border-color: var(--kl-muted); background-color: #fff; }
156-
.control select:focus-visible { border-color: var(--kl-ink); background-color: #fff; }
157-
.control select:disabled { color: var(--kl-muted); opacity: .6; cursor: not-allowed; }
164+
.control { --select-bg: var(--kl-surface-solid); --well: var(--kl-surface-3); --glyph: var(--kl-ink-2); position: relative; display: grid; gap: .5rem; min-width: 0; color: var(--kl-muted); font: 600 .68rem/1.2 var(--kl-font-mono); letter-spacing: .06em; text-transform: uppercase; }
165+
.control:hover, .control:focus-within { --select-bg: #fff; }
166+
.control:hover { --well: rgba(17, 18, 20, .08); }
167+
.control:has(select:open) { --well: var(--kl-graphite); --glyph: #fff; }
168+
.control:has(select:disabled) { --select-bg: var(--kl-surface-solid); --well: var(--kl-surface-3); --glyph: var(--kl-faint); }
169+
/* Trailing key of the 46px select: the text fades out on the opaque select colour, then a 30px well holds a 12px chevron. */
170+
.control::before, .control::after { content: ""; position: absolute; z-index: 1; pointer-events: none; }
171+
.control::before { right: 1px; bottom: 1px; width: 3.6rem; height: 44px; border-radius: 0 999px 999px 0; background: radial-gradient(circle at calc(100% - 22px) 50%, var(--well) 15px, transparent 15.5px), linear-gradient(90deg, transparent, var(--select-bg) .9rem); }
172+
.control::after { right: 17px; bottom: 17px; width: 12px; height: 12px; background: var(--glyph); -webkit-mask: var(--chevron) center / contain no-repeat; mask: var(--chevron) center / contain no-repeat; transition: transform .3s var(--kl-ease); }
173+
.control:has(select:open)::after { transform: rotate(180deg); }
174+
.control select { -webkit-appearance: none; appearance: none; display: block; width: 100%; min-width: 0; height: 46px; padding: .65rem 3.1rem .65rem 1.1rem; overflow: hidden; border: 1px solid var(--kl-line-strong); border-radius: 999px; background: var(--select-bg); box-shadow: var(--kl-shadow-sm), inset 0 1px 0 #fff; color: var(--kl-ink); font: 500 .9rem/1.3 var(--kl-font-sans); letter-spacing: -.01em; text-align: left; text-overflow: ellipsis; text-transform: none; white-space: nowrap; cursor: pointer; }
175+
.control select:hover:not(:disabled) { border-color: var(--kl-muted); }
176+
.control select:focus-visible { border-color: var(--kl-ink); }
177+
.control select:disabled { border-color: var(--kl-line); color: var(--kl-faint); cursor: not-allowed; }
158178
@supports (appearance: base-select) {
159-
.control::before { content: ""; position: absolute; z-index: 1; right: 1px; bottom: 1px; width: 2.7rem; height: 44px; border-radius: 0 999px 999px 0; background: linear-gradient(90deg, transparent, var(--kl-surface-solid) .65rem); pointer-events: none; }
160-
.control::after { z-index: 2; }
161179
.control select, .control select::picker(select) { appearance: base-select; }
162180
.control select::picker-icon { display: none; }
163181
.control select::picker(select) { max-width: calc(100vw - 2rem); max-height: min(24rem, calc(50vh - 2rem)); margin-block: .4rem; padding: .4rem; border: 1px solid var(--kl-line-strong); border-radius: 20px; background: var(--kl-surface-solid); box-shadow: var(--kl-shadow-lg); }
@@ -197,9 +215,15 @@ tr:last-child td { border-bottom: 0; }
197215
}
198216

199217
@media (max-width: 760px) {
200-
.site-header-inner { flex-wrap: wrap; gap: .2rem; padding: .55rem .55rem .45rem .9rem; border-radius: 22px; }
201-
.site-header nav { width: 100%; margin: 0 -.3rem; overflow-x: auto; scrollbar-width: none; }
202-
.site-header nav a { padding: .4rem .6rem; font-size: .82rem; }
218+
.site-header-inner { justify-content: space-between; padding: 0 7px 0 14px; }
219+
.nav-toggle { display: inline-grid; }
220+
.site-nav { position: fixed; inset: calc(var(--header-top) + var(--header-height) + 10px) var(--gutter) auto; display: none; flex-direction: column; align-items: stretch; gap: 0; margin: 0; padding: .45rem; border: 1px solid var(--kl-stroke); border-radius: 24px; background: var(--kl-sheen), rgba(250, 249, 246, .9); box-shadow: var(--kl-shadow-lg); backdrop-filter: var(--kl-glass); opacity: 0; transform: translateY(-8px) scale(.98); transform-origin: top right; transition: opacity .2s var(--kl-ease), transform .3s var(--kl-ease), display .3s allow-discrete, overlay .3s allow-discrete; }
221+
.site-nav:popover-open { display: flex; opacity: 1; transform: none; }
222+
.site-nav::backdrop { background: transparent; }
223+
.site-nav a { padding: .95rem 1rem; border-radius: 16px; color: var(--kl-ink); font: 650 1.12rem/1.2 var(--kl-font-display); letter-spacing: -.02em; }
224+
.site-nav a + a { background: linear-gradient(var(--kl-line), var(--kl-line)) no-repeat top center / calc(100% - 2rem) 1px; }
225+
.site-nav a:hover, .site-nav a:focus-visible { background-color: var(--kl-surface-3); }
226+
@starting-style { .site-nav:popover-open { opacity: 0; transform: translateY(-8px) scale(.98); } }
203227
.section-head { grid-template-columns: minmax(0, 1fr); }
204228
.stage { padding: 6px; border-radius: 24px; }
205229
.scene-frame { min-height: 0; aspect-ratio: 1.15; border-radius: 19px; }
@@ -213,9 +237,6 @@ tr:last-child td { border-bottom: 0; }
213237

214238
@media (max-width: 480px) {
215239
body { font-size: 16px; }
216-
.site-header nav { justify-content: space-between; margin: 0; }
217-
.site-header nav a:first-child { display: none; }
218-
.site-header nav .nav-cta { margin-left: 0; }
219240
.hero-facts, .bento { grid-template-columns: minmax(0, 1fr); }
220241
.hero-facts li, .hero-facts li + li { padding: 1rem 0 0; border-left: 0; }
221242
.hero-facts li + li { margin-top: 1rem; border-top: 1px solid var(--kl-line); }
@@ -228,5 +249,7 @@ tr:last-child td { border-bottom: 0; }
228249
}
229250

230251
@media (max-width: 360px) {
231-
.site-header nav a { padding: .4rem .45rem; font-size: .78rem; }
252+
.site-header-inner { gap: .5rem; padding-left: 12px; }
253+
.nav-cta { padding: 0 .85rem; }
254+
.hero-status { font-size: .64rem; letter-spacing: .01em; }
232255
}

‎site/Features/BenchmarkComparisons/tokens.css‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@
88
--tok-key: #c9b4ff;
99
--tok-str: #9fb4ff;
1010
--tok-val: #ffc9ad;
11+
--header-top: 14px;
12+
--header-height: 56px;
13+
--chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.75 4.5 6 7.75 9.25 4.5' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
1114
}

0 commit comments

Comments
 (0)