Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 30 additions & 19 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
--shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
/* mvp.css caps header/footer/main at --width-content (1080px by default).
Report pages and listings should fill the view: content uses 95% of the
viewport width instead. */
viewport width instead. (The top menu opts out entirely — #top-menu
sets max-width: none so the menu surface reaches the page edges.) */
--width-content: min(3840px, 95vw);
}

Expand Down Expand Up @@ -289,14 +290,24 @@ pre {
whole rendered box (padding contributes outside max-height). */
padding: 0;
max-height: var(--menu-max-height);
/* mvp.css also caps every header at --width-content; the menu surface
must reach the left and right edges of the page instead. */
max-width: none;
}

.menu-bar {
display: flex;
align-items: center;
gap: 0.4rem;
flex-wrap: nowrap;
max-width: 1200px;
/* The menu surface (#top-menu) spans the full viewport, but its content
keeps the centered --width-content column. border-box makes the cap
bound the rendered box (padding inside), matching .theme-bar so the
two rows' content edges align. The margin: 0 auto both centers the
column and resets mvp.css's 7rem nav margin — without the reset the
bar's bottom margin pushes #top-menu past its max-height cap. */
max-width: var(--width-content);
box-sizing: border-box;
margin: 0 auto;
padding: 0.3rem 0.8rem;
min-height: 48px;
Expand Down Expand Up @@ -382,12 +393,12 @@ pre {
/* ---- Breadcrumbs (user/report trail under the top menu) ---- */
.breadcrumbs {
/* Lives on the .theme-bar row (rendered by app/layout.jsx), sharing the
line with the color theme toggle: the row owns the menu-column
alignment formula (max-width: var(--width-content) + 0.8rem horizontal
padding, see .theme-bar above), so the trail must add no box-model
offsets of its own — flex-grow fills the row's left side, keeping the
first crumb's glyph at the menu bar's left content edge while the
toggle pins to the right edge, both vertically centered. */
line with the color theme toggle: the row carries the menu-column
alignment formula (max-width: var(--width-content) + 0.8rem horizontal
padding, see .theme-bar below), so the trail must add no box-model
offsets of its own — flex-grow fills the row's left side, keeping the
first crumb's glyph at the menu bar's left content edge while the
toggle pins to the right edge, both vertically centered. */
flex: 1 1 auto;
min-width: 0;
/* mvp.css gives bare nav a 7rem bottom margin, bold text and a spaced flex
Expand Down Expand Up @@ -459,9 +470,9 @@ pre {

.hero-search {
position: relative;
/* Exactly half the rendered width of the top menu (#top-menu is capped by
mvp.css at --width-content = 1080px, full width below that) and centered
under it. */
/* Exactly half the menu content column (.menu-bar is capped at
--width-content = 1080px, full width below that) and centered under
the edge-to-edge header. */
width: min(540px, 50vw);
margin: 1rem auto;
text-align: left;
Expand Down Expand Up @@ -716,14 +727,14 @@ pre {
/* ---- Color theme toggle (three radio modes, pure-CSS switching) ---- */
/* Row below the site header (rendered by app/layout.jsx): the breadcrumb
trail (when the route has one) on the left and the toggle on the right,
vertically centered on one line. The alignment formula .breadcrumbs used
to carry lives here now: --width-content column, 0.8rem horizontal
padding, border-box — keeping the first crumb's glyph at the menu bar's
left content edge and the toggle at its right content edge at every
viewport, the two edges mirroring each other. #top-menu is capped by
mvp.css at --width-content, so the row and the menu bar can never drift
apart. Crumb routes grow the trail (flex: 1 1 auto); crumb-less routes
leave only the toggle, pinned right by flex-end. */
vertically centered on one line. The row shares the menu bar's centered
--width-content content column (the #top-menu surface around it is edge
to edge, but the bar keeps the column): 0.8rem horizontal padding,
border-box — keeping the first crumb's glyph at the menu bar's left
content edge and the toggle at its right content edge at every viewport,
the two edges mirroring each other, so the row and the menu bar can
never drift apart. Crumb routes grow the trail (flex: 1 1 auto);
crumb-less routes leave only the toggle, pinned right by flex-end. */
.theme-bar {
display: flex;
align-items: center;
Expand Down
34 changes: 29 additions & 5 deletions tests/unit/layout-styles.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,37 @@ describe('layout styles (mvp.css section-flex reset)', () => {
expect(body).toMatch(/margin:\s*0\s+auto\s*;/);
});

test('the menu surface spans the viewport, its content stays centered', () => {
// mvp.css caps every header (and footer/main) at --width-content; the
// menu surface must reach the page's left/right edges instead, while
// the inner bar keeps the centered --width-content content column and
// the margin reset of mvp.css's 7rem nav margin. The .theme-bar row
// below mirrors the bar's content edges (guarded in
// tests/unit/theme-css.test.js and the E2E alignment checks).
// ruleMap keeps only the last block per selector (#top-menu and
// .menu-bar also appear inside the 900px media query), so scan every
// block in the raw source instead.
const menuBlocks = [...css.matchAll(/#top-menu\s*\{([^}]*)\}/g)]
.map((m) => m[1]);
expect(menuBlocks.length).toBeGreaterThan(0);
expect(menuBlocks.some((b) => /max-width:\s*none/.test(b))).toBe(true);
const barBlocks = [...css.matchAll(/\.menu-bar\s*\{([^}]*)\}/g)]
.map((m) => m[1]);
expect(barBlocks.length).toBeGreaterThan(0);
expect(barBlocks.some((b) => /max-width:\s*var\(--width-content\)/.test(b))).toBe(true);
// border-box keeps the padding inside the capped column, matching
// .theme-bar's geometry (guarded in tests/unit/theme-css.test.js).
expect(barBlocks.some((b) => /box-sizing:\s*border-box/.test(b))).toBe(true);
expect(barBlocks.some((b) => /margin:\s*0\s+auto/.test(b))).toBe(true);
});

test('the breadcrumb trail stretches inside the theme bar row (menu column)', () => {
// The trail now shares the .theme-bar row with the color theme toggle:
// the row carries the alignment formula (max-width: var(--width-content)
// + 0.8rem horizontal padding, guarded in tests/unit/theme-css.test.js),
// so .breadcrumbs must not add its own box-model offsets — it grows to
// fill the row's left side, keeping the first crumb's glyph at the menu
// column's left content edge while the toggle pins to the right edge.
// the row is edge to edge like the menu bar (guarded in
// tests/unit/theme-css.test.js), so .breadcrumbs must not add its own
// box-model offsets — it grows to fill the row's left side, keeping the
// first crumb's glyph at the menu bar's left content edge while the
// toggle pins to the right edge.
const body = rules.get('.breadcrumbs');
expect(body).toBeTruthy();
expect(body).toMatch(/flex:\s*1\s+1\s+auto/);
Expand Down
11 changes: 6 additions & 5 deletions tests/unit/theme-css.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,12 @@ describe('toggle styles', () => {
expect(height).toBeGreaterThanOrEqual(1.5);
});

it('the row below the header mirrors the breadcrumb alignment formula', () => {
// Same invariant layout-styles.test.js pinned for the standalone
// .breadcrumbs bar: the row shares #top-menu's --width-content column
// and the 0.8rem horizontal padding insets its children to the menu
// bar's content edges at every viewport — the toggle on the right, the
it('the row below the header mirrors the menu content column', () => {
// The top menu surface is edge to edge (globals.css overrides mvp.css's
// --width-content cap on #top-menu), but the .menu-bar content inside
// keeps the centered --width-content column; the row must share it so
// its 0.8rem horizontal padding insets its children to the menu bar's
// content edges at every viewport — the toggle on the right, the
// breadcrumb trail on the left (flex-grow), vertically centered.
const row = rules.get('.theme-bar');
expect(row).toBeTruthy();
Expand Down
Loading