diff --git a/app/globals.css b/app/globals.css index 2597936..85521b4 100644 --- a/app/globals.css +++ b/app/globals.css @@ -6,15 +6,15 @@ --whisper: #0f9d6a; /* Intentionally 0: anchor jumps land flush at viewport top (under the fixed nav). Section headers don't get clipped because every - Frame's --frame-pad-y floor (5rem = 80px) sits comfortably above - the current fixed-nav height (py-5 + 30px wordmark ≈ 70px), + Frame's --spacing-frame-pad-y floor (5rem = 80px) sits comfortably + above the current fixed-nav height (py-5 + 30px wordmark ≈ 70px), leaving ~10px of breathing room below the nav. If the nav grows past 5rem, set --nav-h to the measured nav height — Frame already applies scroll-mt-[var(--nav-h)] on every section, so anchor offsets pick it up automatically. (Bumping the 5rem floor on - --frame-pad-y only helps below ~889px — above that the clamp's - 9vw term already exceeds 5rem, so a bigger floor does nothing on - desktop.) */ + --spacing-frame-pad-y only helps below ~889px — above that the + clamp's 9vw term already exceeds 5rem, so a bigger floor does + nothing on desktop.) */ --nav-h: 0; } @@ -22,17 +22,16 @@ --color-ink: var(--ink); --color-paper: var(--paper); --color-whisper: var(--whisper); - --font-sans: var(--font-geist-sans); - /* Shared content rail: every Frame centers within --frame-max. */ - --frame-max: 1280px; - --frame-gutter: clamp(1.5rem, 4vw, 4rem); - --frame-pad-y: clamp( + /* Shared content rail: every Frame centers within --container-frame. */ + --container-frame: 1280px; + --spacing-frame-gutter: clamp(1.5rem, 4vw, 4rem); + --spacing-frame-pad-y: clamp( 5rem, 9vw, 7rem ); /* floor coupled to nav height — see --nav-h note above */ - --frame-min-h-cap: 54rem; + --spacing-frame-min-h: min(100svh, 54rem); /* Type scale: micro and CTA are fixed; body and headlines scale with viewport. Named under Tailwind v4's --text-* namespace so `text-body`, `text-h1`, … @@ -1009,7 +1008,7 @@ a.underline-brutal:hover .arrow { .mm-toggle { position: fixed; top: 24px; - right: var(--frame-gutter); + right: var(--spacing-frame-gutter); z-index: 65; width: 22px; height: 22px; diff --git a/components/site/Chrome.tsx b/components/site/Chrome.tsx index 0117ed5..7700361 100644 --- a/components/site/Chrome.tsx +++ b/components/site/Chrome.tsx @@ -6,6 +6,7 @@ import { usePathname } from "next/navigation"; import { links } from "@/lib/links"; import { homeHashSectionId, shouldInterceptNavClick } from "@/lib/scroll"; import { MobileMenu } from "@/components/site/MobileMenu"; +import { resolveActiveSection } from "@/components/site/chrome-active"; const SECTION_IDS = ["intro", "compare", "method", "faq", "contact"] as const; // Hash anchors are written `/#section`. Required because this nav also @@ -169,12 +170,12 @@ export function Chrome() { return () => window.removeEventListener("scroll", onScroll); }, [pathname]); - // Guard against a stale `active` landing in `DARK_SECTIONS` and painting - // `text-paper` over a paper-toned page: off-home the IO observes nothing, - // and on the first render after a client nav `active` still holds the - // previous route's last value until the [pathname] effects re-sync. - // `scrolled` doubles as a proxy for "past the intro hero". - const effectiveActive = pathname === "/" && scrolled ? active : "intro"; + const effectiveActive = resolveActiveSection( + pathname, + scrolled, + active, + "intro", + ); const onDark = DARK_SECTIONS.has(effectiveActive); // The toggle is portalled to (see MobileMenu.tsx) and lives // on top of the paper panel while the drawer is open — force it to @@ -188,11 +189,11 @@ export function Chrome() { data-scrolled={scrolled ? "true" : undefined} data-tone={onDark ? "ink" : "paper"} data-mobile-open={mobileOpen ? "true" : undefined} - className={`chrome-nav fixed inset-x-0 top-0 z-50 px-[var(--frame-gutter)] py-5 ${ + className={`chrome-nav fixed inset-x-0 top-0 z-50 px-frame-gutter py-5 ${ onDark ? "text-paper" : "text-ink" }`} > -
+
-
+