/* Geist — self-hosted (replaces Nohemi). Sans for UI/headlines + stats/body (Black for the
   hero wordmark), Pixel for the nav + footer wordmark. */
@font-face {
    font-family: 'Geist Sans';
    src: url('geist-font-v1.7.2/geist-font/Geist/webfonts/Geist-Regular.woff2') format('woff2');
    font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Geist Sans';
    src: url('geist-font-v1.7.2/geist-font/Geist/webfonts/Geist-Black.woff2') format('woff2');
    font-weight: 900; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Geist Pixel';
    src: url('geist-font-v1.7.2/geist-font/GeistPixel/webfonts/GeistPixel-Square.woff2') format('woff2');
    font-weight: 400; font-style: normal; font-display: swap;
}

/* Instrument Serif is GONE from the live site. It carried every display heading — hero
   tagline, work titles, case displays and quotes, the menu overlay — and is one of the
   two most ubiquitous free faces on the web, which on a studio site reads as a template
   rather than an identity. Geist Pixel has that job now: the site already speaks pixel
   through the tulip logo, the nav and the footer wordmark, so the display voice is the
   same voice instead of a fourth one.

   Geist Pixel is MONOSPACED and runs far wider per character than the serif did, so
   every size that used this token was retuned downward — see the notes at each one.
   Do not port an old serif size across without re-measuring.

   The .woff2 files stay in Instrument_Serif/ because three lookdev harnesses
   (lake, relief-proto, typewriter) declare their own @font-face against them. No live
   page loads either file — the preload in index.html and both prefetch rows in
   intro-load.js came out with this change. */

:root {
    /* The display voice. Kept under the neutral name --font-display (it was
       --font-serif) so that swapping the face is a one-line change and the token never
       has to be renamed again — a pixel face was tried here and reverted. */
    --font-display: 'Geist Pixel', monospace;
    --font-sans: 'Geist Sans', sans-serif;
    --font-pixel: 'Geist Pixel', monospace;
    --color-bg: #f1f1f1;
    --color-text: #010101;

    /* currentColor, not var(--color-text) — they resolve to the same colour, but
       only one of them MOVES. The footer inversion transitions `color` on
       html/body, and every element that INHERITS its colour rides that animation
       for free; an element that names the variable instead gets a value that is
       simply different on the next frame, and snaps. Measured before the change:
       every hairline on the page — the FAQ rules, the work-list dividers — hit
       its end colour instantly while the type around it was still easing. */
    --border-style: 2px solid currentColor;

    /* ONE source of truth for the horizontal page gutter — the nav's left/right padding,
       which every other edge (container, case wrapper, full-bleed break-outs, the menu
       overlay via --edge) is aligned to. Halved from the original 5rem, so change it
       HERE and the whole site follows instead of drifting apart. */
    --gutter: 2.5rem;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/* ---- Keyboard focus -------------------------------------------------------
   The site had no focus rule at all. Nothing ever set `outline: none`, so the
   browser default did survive — but a hairline default ring is close to
   invisible against both this palette's blacks and its whites, and tabbing
   through is the first thing anyone auditing the site does.

   :focus-visible, NOT :focus. The ring appears for keyboard and programmatic
   focus and stays off for mouse clicks, so nothing about the pointer
   experience changes — which is the whole point: no visual change to what a
   normal visitor sees.

   currentColor keeps it right in both directions with one rule: white pages
   draw a black ring, the black Studio page and the footer draw a white one.

   Deliberately NO border-radius here. Setting one would flatten every element
   that has its own — the round WhatsApp button, the toggle capsules — for as
   long as it held focus, which IS a visual change. Browsers already curve the
   outline to follow the element's own radius, so there is nothing to add. */
:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 3px;
}

/* ---- Skip link ------------------------------------------------------------
   First tab stop on every page: jumps past the nav to the page's content.

   `position: fixed` with a negative top, rather than the usual `left: -9999px`
   off-screen trick — an absolutely positioned element that far out can widen
   the document, and the case pages already have a horizontal-overflow problem
   that must not be fed. Fixed takes it out of flow entirely, so it can affect
   nothing until it is focused.

   EVERY length here is px, position included. The root font-size on this site
   is fluid (0.83333vw desktop, 1.143vw mobile), so rem and px do not keep step:
   the box is sized in px and comes to 40px tall, but -6rem resolves to -80px on
   a desktop and only -25.7px at 375px wide — which left 14px of the chip
   permanently on screen on every phone, eating taps in the top-left corner.
   Mixing the two units is the bug; do not reintroduce a rem here. */
.skip-link {
    position: fixed;
    top: -100px;
    left: 8px;
    z-index: 10000;
    padding: 12px 16px;
    background: #000;
    color: #fff;
    border: 1px solid #fff;
    font-family: var(--font-sans);
    font-size: 14px;
    line-height: 1;
    text-decoration: none;
}

.skip-link:focus {
    top: 8px;
}

/* The skip target takes tabindex="-1" so focus really lands there; that makes
   it focusable, and without this it would draw the ring above on arrival. */
[tabindex="-1"]:focus {
    outline: none;
}

/* Background on <html> too, so the page is never white — including the instant the body
   is hidden during a scramble transition. Without this, hiding <body> exposes the
   browser's default white canvas, which reads as a visible page reload. */
html {
    background-color: var(--color-bg);
    /* Fluid scale: every size on the site is rem, so the root maps the 1920px design
       onto the viewport — full size at ≥1920, shrinking proportionally down to the
       820px breakpoint (where the mobile root below takes over). 16/1920 = 0.83333vw. */
    font-size: min(0.83333vw, 16px);
}

body {
    background-color: var(--color-bg);
    color: var(--color-text);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    letter-spacing: -0.02em; /* matches Figma tracking on all text */
}

/* NO NATIVE SCROLLBAR, anywhere.
   Most of the site never had one: Locomotive sets html and body to
   overflow:hidden and scrolls a transformed container, so the pages it runs on
   have shown none all along. The pages it does not run on were the odd ones out
   — the Studio page carried a 15px bar on the desktop, and every page shows one
   once Locomotive stands down for touch. This makes them agree.

   It also settles a smaller thing: `100vw` includes the scrollbar's width while
   `100%` does not, so a full-bleed element built with 100vw overhangs by exactly
   that bar. Several here are. With no bar, the two measures are the same number.

   The cost is real and worth stating: there is no longer a position indicator on
   the long pages. Scrolling itself is untouched — wheel, keys, touch, and the
   scrollbar's own accessibility role was already absent from most of the site.
   Two properties because they are two engines; neither is a fallback for the
   other. */
html, body {
    scrollbar-width: none;              /* Firefox */
}
html::-webkit-scrollbar,
body::-webkit-scrollbar {
    display: none;                      /* Chromium, Safari */
}

/* Hidden before paint when arriving via a scramble transition, so there's no flash of
   unscrambled text; transition.js reveals it instantly once the scramble is in place. */
.fl-enter body { opacity: 0; }

.container {
    width: 100%;
    max-width: 120rem;
    margin: 0 auto;
    padding: 2.5rem var(--gutter);
    display: flex;
    flex-direction: column;
    overflow-x: clip; /* contains the hero word's horizontal scroll-drift */
}

.viewport-section {
    min-height: calc(100vh - 5rem); /* 100vh minus container vertical padding */
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    margin-bottom: 12.5rem;
}

/* Header — fixed, floats over the content and inverts via difference blend so it stays
   legible over both the light page and the dark type that scrolls beneath it. Lives
   OUTSIDE the locomotive scroll container so it stays put on scroll. */
header {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 100;
    max-width: 120rem;
    margin: 0 auto;
    /* Top padding is 80% deeper than the bottom on purpose — the bar sits lower in its
       strip. Horizontal is the shared --gutter, so the wordmark and "Reach out" stay on
       the same edges as all page content. */
    padding: 0.675rem var(--gutter) 0.375rem;
    background: var(--color-bg);    /* solid, so content no longer bleeds through the nav — same colour as the page, so it just occludes */
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-family: var(--font-pixel);   /* nav in Geist Pixel */
    font-weight: 400;
    font-size: 1.75rem;
    color: var(--color-text);    /* crisp dark nav by default — no blend, so no grayscale-AA bolding */
    pointer-events: none;        /* gaps pass clicks through to the hero; links re-enable below */
    transition: color 0.25s ease;
}
header a { pointer-events: auto; color: inherit; text-decoration: none; }

/* Over a dark-background section, nav-theme.js adds .nav-light → white text + white logo. */
header.nav-light { color: #fff; }

.nav-logo {
    width: 3rem;                      /* 48px at max root = 1:1 with the 48px pixel art */
    height: 3rem;
    display: block;
    /* Pixel-art tulip (favicon.png): black ink on transparent — shown as-is on
       the light nav, inverted to white over dark sections (.nav-light). */
    image-rendering: pixelated;
    image-rendering: crisp-edges;
    filter: none;
    transition: filter 0.25s ease;
}
header.nav-light .nav-logo { filter: invert(1); }

/* The Studio / Work pair (third nav group). */
.nav-links {
    display: flex;
    gap: 2.5rem;
}

/* Hero — editorial masthead with a physics-driven wordmark */
.hero {
    position: relative;
    flex: 1;
    width: 100%;
    min-height: 20rem;
}

/* Physics stage — covers the whole hero so FLORIUM falls full height and
   piles up on the left. Sits behind the framing text. */
.hero-physics {
    position: absolute;
    inset: 0;
    overflow: hidden;
    z-index: 1;
}

.phys-letter {
    position: absolute;
    top: 0;
    left: 0;
    font-family: var(--font-sans);
    font-weight: 900;
    font-size: clamp(8.75rem, 18vw, 20rem);
    line-height: 1;
    text-transform: uppercase;
    transform-origin: center center;
    will-change: transform;
    visibility: hidden;
    user-select: none;
    -webkit-user-select: none;
}

.hero-physics.ready .phys-letter {
    visibility: visible;
}

/* Framing text — overlaid on top, positioned as in the masthead layout */
.hero-frame {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;   /* base meta sits at the bottom; tagline is centered separately */
    pointer-events: none;
}

.hero-meta {
    display: flex;
    font-family: var(--font-sans);
    font-size: 0.9375rem;
}

.hero-meta--top {
    justify-content: space-between;
}

.hero-tagline {
    position: absolute;
    top: 50%;
    right: 0;
    transform: translateY(-50%);
    text-align: right;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: clamp(1.3rem, 2.35vw, 2.25rem);
    line-height: 1.05;
    max-width: 16ch;
}

.hero-meta--base {
    align-self: flex-end;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.375rem;
}

/* Stats Section */
.stats-section {
    display: flex;
    flex-direction: column;
    gap: 7.5rem;                       /* the two blocks hug their content; the 12.5rem margins buffer neighbours */
    margin-bottom: 12.5rem;
}

.stat-block, .team-block {
    width: 50%;
    min-width: 45rem;
}

.stat-block h2, .team-block h2 {
    font-family: var(--font-sans);
    font-size: 3.75rem;
    font-weight: 400;
    margin-bottom: 1rem;
}

.divider {
    border-bottom: var(--border-style);
    margin-bottom: 1.5rem;
}

.stat-list {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.stat-item {
    font-family: var(--font-sans);
    font-size: 1.875rem;
}

.team-list {
    display: grid;
    grid-template-columns: 1fr auto;   /* role column sizes to its content so it never wraps */
    row-gap: 0.75rem;
    column-gap: 2.5rem;
}

.team-member {
    font-family: var(--font-sans);
    font-size: 1.875rem;
    white-space: nowrap;
}

.team-role {
    font-family: var(--font-sans);
    font-size: 1.875rem;
    text-align: right;
    white-space: nowrap;
}

/* Featured Work */
.work-section {
    margin-bottom: 12.5rem;
}

.work-label {
    font-family: var(--font-sans);
    font-size: 2rem;
    font-weight: 400;   /* it's an <h2> for structure; keep the regular look */
    margin-bottom: 0.75rem;
}

.work-list {
    border-top: var(--border-style);
    display: flex;
    flex-direction: column;
}

.work-item {
    font-family: var(--font-display);
    font-size: 6.8rem;
    text-align: center;
    padding: 1.5rem 0;
    border-bottom: var(--border-style);
}

/* The closing statement under Featured Work. Body voice, not display voice: it is a
   paragraph of argument, and at 7.65rem in the display face it was competing with the
   work titles above it for the same attention. Geist Sans at 3.5rem lets it read as
   something you actually read rather than something you look at. */
.work-description {
    font-family: var(--font-sans);
    font-size: 3.5rem;
    line-height: 1.25;
    /* Full measure. It was capped at 68rem, which set it in a column down the left
       half and read as a leftover paragraph; across the whole width it reads as the
       section's closing statement, which is what it is. */
    max-width: none;
    /* Matches .work-section's 12.5rem margin-bottom, so the statement sits in a band
       with the same air above it as below rather than hugging the rule. NOTE: this
       margin COLLAPSES with the .divider's 1.5rem margin-bottom (block siblings), so
       the gap is 12.5rem, not 14rem — the two sides really are equal. */
    margin-top: 12.5rem;
}

/* Featured Work — hover interaction: scramble text + cursor-tracking photo */
.work-item {
    position: relative;
    /* These rows are <a> elements now (each one opens its case study; the last
       opens the Work page). display:block is what keeps that from changing the
       layout — an anchor is inline by default, which would have collapsed the
       row's padding and left text-align:center with nothing to centre. The
       colour and underline resets keep them looking exactly as they did as
       divs. Both hover-peek.js and nav.js select `.work-item`, so the scramble
       and the pixel-peek carry over untouched. */
    display: block;
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}

.work-item__label {
    position: relative;
    z-index: 2;
}

/* Hover peek (hover-peek.js) — two pixel images flank the hovered row's
   centered text and de-pixelate through the case-study hard-cut steps.
   Boxes are positioned by JS against the label's edges; they never move
   the label (absolute, out of flow). */
.peek {
    position: absolute;
    top: 50%;
    width: 13rem;
    height: 9rem;
    transform: translateY(-50%);
    overflow: hidden;
    pointer-events: none;
    z-index: 1;               /* under the label (z 2), so text always wins */
    display: none;
}
.peek.is-on { display: block; }
.peek img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.peek canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
}

/* FAQ / Extra Content */
.faq-section {
    margin-bottom: 12.5rem;
}

.faq-header {
    font-family: var(--font-sans);
    font-size: 3.75rem;
    margin-bottom: 1rem;
    font-weight: 400;
}

.faq-container {
    border-top: var(--border-style);
    display: grid;
    grid-template-columns: 1fr 2fr;
    padding-top: 1.5rem;
}

.faq-label {
    font-family: var(--font-sans);
    font-size: 2.5rem;
}

.faq-list {
    display: flex;
    flex-direction: column;
}

.faq-item {
    font-family: var(--font-sans);
    font-size: 2.5rem;
    text-align: right;
    padding-bottom: 1rem;
    margin-bottom: 1rem;
    /* currentColor for the same reason as --border-style: a named variable is a
       different colour next frame, an inherited one is an animating one. */
    border-bottom: 1px solid currentColor;
}
.faq-item:last-child {
    border-bottom: none;
    margin-bottom: 0;
}

/* Accordion Q&A: <details>/<summary> — native semantics, content indexed while
   collapsed. Same motif as the consent panel: the +/− indicator flips with NO
   transition, it's a state change. */
.faq-item__q {
    cursor: pointer;
    list-style: none;                 /* no disclosure triangle */
}
.faq-item__q::-webkit-details-marker { display: none; }
.faq-item__q::after {
    content: "+";
    display: inline-block;
    margin-left: 0.6em;
    width: 1em;                       /* reserved box: + → − never shifts the question */
    text-align: center;
}
.faq-item[open] > .faq-item__q::after { content: "−"; }
/* The body wrapper animates height 0↔measured (faq.js). The vertical spacing
   lives on the INNER .faq-item__a, NOT here: padding on the animated element
   can't collapse at height:0, so it would leave a ~28px sliver of text peeking
   for a beat on close. With no padding, height:0 truly collapses to nothing;
   the child's padding still counts toward scrollHeight, so the open measures
   true. No-JS: no inline height, no motion. */
.faq-item__body {
    overflow: hidden;
    transition: height 0.56s cubic-bezier(0.23, 1, 0.32, 1);
}
.faq-item__a {
    max-width: 42rem;
    margin: 0 0 0 auto;               /* hugs the right column, under the question */
    padding: 1.25rem 0 0.5rem;        /* the question↔answer gap — here, not on the wrapper (collapse-safe) */
    font-size: 1.75rem;                /* body voice — same as the nav/agency copy */
    line-height: 2rem;
    text-align: left;
}
.faq-item__a a { color: inherit; }
/* Typewriter scramble (faq.js): the ghost is the real letter and owns the
   width; the live overlay cycles glyphs on top, so nothing ever reflows. */
.faq-word { display: inline-block; }
.faq-scr { position: relative; display: inline-block; }
.faq-scr.is-pending .faq-scr__ghost { visibility: hidden; }
.faq-scr__live { position: absolute; left: 0; top: 0; }
@media (prefers-reduced-motion: reduce) {
    .faq-item__body { transition: none; }
}

/* Footer */
footer {
    /* No border-top. The 12.5rem of air the last section leaves above the footer
       already separates it; the rule on top of that was a second, redundant
       separator and it closed the page off rather than letting it end.
       To put it back: `border-top: var(--border-style);` here AND in the mobile
       block below, and origin.css's border-top-color override becomes live again. */
    padding-top: 1.5rem;
    padding-bottom: 2.5rem;
    container-type: inline-size;
}

.footer-top {
    /* A TRIO, built the way the Work page builds its duets: a proportional fr
       grid, not flex with a gap. The duet is 928fr/832fr (work.css, matching the
       Studio page's label/intro split); halving the second half gives the third
       column, so the trio is derived from the pair rather than being three
       arbitrary thirds sitting next to it. */
    display: grid;
    grid-template-columns: 928fr 416fr 416fr;
    /* Items align to the TOP of the row, so all three lists begin on one line
       and read down from it. End-aligned they hung from a shared BASELINE
       instead, which with two items in one column and four in another started
       each list at a different height. align-content keeps the block itself
       sitting low in whatever space it is given. */
    align-items: start;
    align-content: end;
    font-family: var(--font-display);
    /* Matches the nav's 1.75rem — the pixel face reads as one size across the page's
       two chrome rows instead of the footer sitting a notch under the header. It was
       1.625rem, tuned down from the serif's 2.5rem because a 1.54x wider face ran this
       space-between row into itself; 1.75rem still leaves ~470px of free track between
       the four groups at the narrowest desktop width, so the row is nowhere near tight. */
    font-size: 1.75rem;
    margin-bottom: 5rem;
}

/* Two clusters with ONE gap between them, instead of four items sharing three
   identical gaps. Measured before: items of 190 / 238 / 61 / 255px separated by
   161 / 161 / 161 — equal spacing between unrelated things, which reads as
   arithmetic rather than composition, with ©2026 stranded in the middle. */
.footer-list { list-style: none; }
.footer-list li { display: block; }
/* Air between the rows. line-height alone set them exactly one line apart,
   which reads as a block of text rather than as three lists. */
.footer-list li + li { margin-top: 0.75rem; }
/* The last column hugs the right gutter. Only the BLOCK moves — the items
   inside stay stacked and left-aligned, so the list reads as it did, just
   parked at the edge instead of stranded with a third of the row empty. */
/* last-of-TYPE, not last-child: the meta row is the last child of the
   row now, so :last-child stopped matching any list at all and the
   right-hand column quietly drifted back in from the gutter. */
.footer-list:last-of-type { justify-self: end; }

/* ©2026 and the consent control. A quiet line under the trio rather than a
   fourth column, so the three lists stay a trio and the only [data-cc-open] on
   the site still has somewhere to live. */
.footer-meta {
    grid-column: 1 / -1;
    justify-self: start;
    display: flex;
    gap: 2.5rem;
    align-items: baseline;
    margin-top: 3rem;
    font-family: var(--font-display);
    font-size: 1.75rem;
    /* No `color` of its own — inherited, so it eases with the page. See
       --border-style above. The opacity is what makes it quiet. */
    opacity: .55;
}

/* Every item in the row is a link or a button. The hover affordance is the
   SCRAMBLE (nav.js) — the same one the nav, the work tiles, the case meta and
   the reach-out rows use, so the footer stops being the one interactive row on
   the site that answers differently. No underline: it was a second, unrelated
   affordance, and the scramble already says "this responds".

   Keyboard focus is NOT left bare — the global :focus-visible ring at the top
   of this file covers it, and a ring is the right answer there anyway since a
   keyboard user never triggers pointerenter. */
.footer-link {
    color: inherit;
    text-decoration: none;
    position: relative;
}

/* The old .footer-socials/.footer-phone/.footer-email/.footer-copyright grid
   areas are gone with the markup; .footer-copyright survives only as a label
   and needs no rule of its own on desktop. */

/* The footer "Cookies" item is a real <button> (opens the consent popup) styled
   to read exactly like its sibling spans. */
.footer-cookies {
    appearance: none;
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    letter-spacing: inherit;
    color: inherit;
    cursor: pointer;
    position: relative;               /* anchors the enlarged hit area below */
}
/* The label is ~46x24 on a phone. It has to keep reading as a plain footer span
   next to its siblings, so the target grows as a pseudo-element rather than as
   padding, which would break the row it sits in. */
.footer-cookies::after {
    content: "";
    position: absolute;
    /* px, not rem — a touch target is a physical size, not a design unit. */
    inset: -10px -8px;
}

.footer-big {
    width: 100%;
    margin-top: -1.25rem; /* Pull it slightly up */
    /* Holds the band's height once the SVG inside is hidden. Same ratio as the
       viewBox it replaces, so the wordmark occupies exactly the space it always
       did and nothing below it moves when the canvas takes over. */
    aspect-ratio: 1833 / 300;
    position: relative;
}

/* Out of flow, both of them. In flow the SVG's own 226px sat UNDER the
   aspect-ratio box's 226px and the host measured 452 — which is what handed the
   camera half the aspect it needed. Absolutely positioned, the host's height
   comes from aspect-ratio alone and the two children simply fill it. */
.footer-big svg,
.footer-big canvas {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
}

/* The SVG wordmark is the FALLBACK, not a placeholder to be swapped in later:
   it is what the footer shows with no JS, no WebGL, or a throw in the module,
   and it is what search engines and screen readers get. footer-word.js adds
   .is-live only once it has actually drawn. */
.footer-big.is-live svg { display: none; }

/* One fallback per shape. Two CLASSES so this beats `.footer-big svg`'s own
   `display: block` above (two classes outrank one class and a type), and
   `.footer-big.is-live svg` still outranks both — so once the canvas is up,
   neither shows. */
.footer-big .footer-svg-stack { display: none; }
@media (max-width: 819px) {
    .footer-big .footer-svg-wide  { display: none; }
    .footer-big .footer-svg-stack { display: block; }
}

/* The band carries the PAGE's ground, and transitions with it.
   The canvas alone could not: `screen` blends against the backdrop within the
   nearest stacking context, and the smooth-scroll container's transform makes
   one — so the blend never reached the page's background and the black stayed
   black. Painting --color-bg on the parent puts a backdrop directly beneath the
   canvas, inside the same context, which is both what makes the blend work and
   what makes the band turn over with everything else. */
.footer-big {
    background-color: var(--color-bg);
    transition: background-color .45s ease;
}
@media (prefers-reduced-motion: reduce) { .footer-big { transition: none; } }

/* SCREEN, so the canvas has no ground of its own.
   The composer writes an opaque black behind the letters — alpha:true does not
   survive a bloom pass and an OutputPass — so the band sat as a black rectangle
   on the page and stayed black through the whole inversion while everything
   around it was still turning. Under `screen`, black contributes exactly
   nothing and the page's own background shows through, so the letters' ground
   IS the page and transitions with it. White stays white, which is all the
   wordmark is. */
.footer-big canvas {
    mix-blend-mode: screen;
    /* THE EDGE. The bloom's widest mip spreads a faint lift across the WHOLE
       canvas, so the pixels out at its border are not quite black; screened over
       the band they sit a hair above it and the canvas draws its own rectangle.
       Fading the canvas out at its edges lets that glow end instead of being
       cut. The stops are sized off the wordmark's own margins — it clears 2% of
       the width and about 17% of the height — so the fade finishes before it
       reaches a letter. */
    -webkit-mask-image:
        linear-gradient(to right, transparent 0, #000 1.8%, #000 98.2%, transparent 100%),
        linear-gradient(to bottom, transparent 0, #000 12%, #000 88%, transparent 100%);
    -webkit-mask-composite: source-in;
    mask-image:
        linear-gradient(to right, transparent 0, #000 1.8%, #000 98.2%, transparent 100%),
        linear-gradient(to bottom, transparent 0, #000 12%, #000 88%, transparent 100%);
    mask-composite: intersect;
}

/* SVG font-family attribute can't read a CSS var(); set it here so the
   wordmark renders in the pixel face (Figma: 400 / 273px). */
.footer-big text {
    font-family: var(--font-pixel);
}

/* ============================================================
   THE FOOTER INVERSION
   Scrolling into the footer turns the whole page over: black ground, white
   type. Same mechanism the Studio page already uses for its cube overlay
   (origin.css: html.cube-up flips the two tokens and inverts the logo), so the
   site has ONE way of going dark rather than two that drift.

   Everything downstream reads --color-bg / --color-text, so flipping the pair
   carries the header, the body and the footer together. The transition sits on
   the properties rather than on a filter: `filter: invert()` would flip the
   photography and the case-study thumbnails too, which is not the same thing at
   all.

   footer-mount.js adds the class when the footer's top passes the middle of the
   viewport, and takes it off on the way back up. Pages whose footer carries
   data-no-invert opt out — the Studio page is already black.
   ============================================================ */
html.footer-dark { --color-bg: #000000; --color-text: #ffffff; }
/* The same two declarations nav-theme.js's .nav-light uses, applied by the
   inversion itself. nav-theme.js only flips the nav once a [data-nav-dark]
   element reaches the strip the nav occupies, which happens LATER than this
   does — between the two the page would be black under a black nav. Both
   firing is harmless; they set the same thing. */
html.footer-dark header { color: #fff; }
html.footer-dark header .nav-logo { filter: invert(1); }   /* black pixel tulip → white */

html, body { transition: background-color .45s ease, color .45s ease; }
header, footer { transition: background-color .45s ease, color .45s ease; }
@media (prefers-reduced-motion: reduce) {
    html, body, header, footer { transition: none; }
}

/* ============================================================
   Mobile menu — shared base (all viewports)
   The "Menu" toggle and the red full-screen overlay are hidden on desktop and
   only surface inside the mobile breakpoint below. Kept here (not per-page) so
   any page carrying the markup gets the same behaviour.
   ============================================================ */
.nav-menu-toggle {
    display: none;              /* revealed in the mobile query */
    pointer-events: auto;       /* header itself is pointer-events:none */
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    color: inherit;
    font: inherit;
    letter-spacing: inherit;
    cursor: pointer;
}

.nav-menu-overlay {
    display: none;              /* becomes a flex column in the mobile query */
}

/* ============================================================
   Mobile layout — ALL PAGES. Everything below is authored in rem; the fluid
   root maps the 1400px-wide Figma mobile frames 1:1 onto the phone's width,
   so a value is simply (figma-px / 16)rem.
   ============================================================ */
@media (max-width: 819px) {
    /* Fluid mobile root: 1rem = one 16px design unit of the 1400px mobile
       artboard (16/1400 = 1.143vw), clamped so small phones stay legible and
       wide phones/tablets don't oversize. */
    html {
        font-size: clamp(3.4px, 1.143vw, 5.7px);

        /* Shared CONTENT EDGE for full-bleed elements.
           `header` and `.container` are both max-width:120rem and centred, so once the
           viewport is wider than that cap their content edge moves inward — a flat
           4.4375rem on a full-bleed element does NOT follow it (at 768px the cap is
           684px, so the header's edge sits 42px further right). Anything full-bleed
           must pad to var(--edge) so nothing pokes out to the left of the
           "FloriumStudio" wordmark or to the right of the "Menu" toggle. */
        --edge: calc(max(0px, (100vw - 120rem) / 2) + var(--gutter));

        /* Mobile gutter — halved from the original 4.4375rem, same as desktop. */
        --gutter: 2.21875rem;
    }

    /* ---- Header: Florium Studio | logo | Menu (3-up grid, logo centred) ---- */
    header {
        display: grid;
        grid-template-columns: 1fr auto 1fr;
        align-items: center;
        gap: 2rem;
        padding: 2.7rem var(--gutter) 1.5rem;   /* top 80% deeper, as on desktop */
        font-family: var(--font-pixel);  /* Geist Pixel, same voice as desktop nav */
        font-size: 4.2rem;               /* 67.2 design px = 18px @375 — matched to locomotive.ca's mobile nav */
        letter-spacing: -0.02em;
        transition: none;                /* no ease on menu open OR close */
    }
    .nav-wordmark { justify-self: start; }
    .nav-logo {
        justify-self: center;
        width: 6rem;                     /* 2× the 48px pixel art — even pixel blocks */
        height: 6rem;
        transition: none;                /* logo invert flips instantly with the menu */
    }
    .nav-menu-toggle {
        display: block;
        justify-self: end;
        text-align: right;               /* both labels hug the right edge inside the reserved width */
        position: relative;              /* anchors the enlarged hit area below */
    }
    /* The label alone measures ~48x23 at 375px — under the 44px touch target Apple
       and Google both ask for, and it is the ONLY nav control on a phone. Grow the
       HIT AREA with a pseudo-element instead of padding: padding would push "Menu"
       off the baseline it shares with the wordmark and the centred logo.
       Biased downward, because the button already sits ~13px from the top edge. */
    .nav-menu-toggle::after {
        content: "";
        position: absolute;
        /* px, not rem: a touch target is a physical size and must not ride the
           fluid root that scales the type. 23 + 8 + 14 = 45px tall. */
        inset: -8px -10px -14px -10px;
    }
    /* The desktop link groups collapse into the overlay. */
    .nav-links,
    .nav-reach { display: none; }

    /* ---- Red full-screen overlay (sits below the fixed header) ---- */
    .nav-menu-overlay {
        position: fixed;
        inset: 0;
        z-index: 90;                     /* header is z-100, stays legible on top */
        display: none;                   /* .menu-open flips it on — instantly */
        flex-direction: column;
        /* Red stays full-bleed; only the CONTENT is inset to the shared edge, so the
           overlay's links line up with the wordmark and ©2026 with "Menu". */
        padding: 0 var(--edge) 5vh;
        background: #eb4236;
    }
    .menu-open .nav-menu-overlay { display: flex; }   /* instant — no animation */
    /* Header recolours to white over the red while the menu is open — also instant. */
    .menu-open header { color: #fff; background: transparent; transition: none; }   /* let the red overlay show through the nav strip */
    .menu-open .nav-logo { filter: invert(1); transition: none; }   /* black tulip → white over the red */

    .nav-menu-links {
        list-style: none;
        margin-top: 28vh;                /* upper-middle, clearing the header */
    }
    .nav-menu-links a {
        display: inline-block;
        font-family: var(--font-display);
        font-size: 10.15rem;            /* 250 design px */
        line-height: 1.3;
        color: #fff;
        text-decoration: none;           /* pages without a local `a` reset (index) showed underlines */
    }

    .nav-menu-footer {
        margin-top: auto;                /* pinned to the bottom */
        display: flex;
        justify-content: space-between;
        align-items: flex-end;
        gap: 2rem;
        font-family: var(--font-pixel);
        font-size: 4.2rem;               /* base text: 18px @375, matched to locomotive.ca */
        line-height: 1.3;
        color: #fff;
    }
    .nav-menu-note { max-width: 40rem; }   /* widened so the doubled type still wraps to ~2 lines */
    .nav-menu-copy { white-space: nowrap; }

    /* Keep the page scrollable while the menu is open: locking it drops the scrollbar, which
       changes the viewport width and jolts the fixed nav sideways. The opaque overlay covers
       the page, so whatever scrolls underneath stays invisible. */

    /* ---- Container gutters match the Figma 71px side margin ---- */
    .container {
        padding: 0 var(--gutter);        /* top handled per-section/page */
    }

    /* ---- Footer: two columns of links, then the big pixel wordmark ---- */
    footer {
        /* No border-top — see the desktop rule. */
        /* 6rem, not the desktop's 1.5. The gap above the footer is the section's
           12.5rem margin plus this, which came to 60px here against 168px on the
           desktop — 7% of the phone's viewport against 19% of the laptop's, so
           the footer arrived while the last answer was still finishing. 6rem
           takes it to ~79px. The section margin is left alone: it sets the
           rhythm between every section, and this gap is the footer's business. */
        padding-top: 6rem;
        padding-bottom: 2.5rem;
    }

    /* TWO columns, not three: the nav down the left, the socials starting at the
       duet's boundary, and the phone and email dropped entirely — see below.

       THE DUET'S OWN GRID, 928fr/832fr — the Work page's label/text split
       (work.css), which the desktop footer's trio is already derived from by
       halving the second half. Two lists is exactly what that grid is for, so
       the phone gets the proportion itself rather than the trio's leftovers.
       It was `1fr auto`, which sized the second column to whatever the longest
       social name happened to be — a measurement, not a proportion. */
    .footer-top {
        display: grid;
        grid-template-columns: 928fr 832fr;
        /* 7rem ≈ 30px at 375, not the old 1.5rem ≈ 6px. The gap is sized by the
           TOUCH TARGETS, not by taste: each row's links are 15px tall and want a
           ~44px target, so the hit areas below reach 14px above and beneath
           them. At a 6px row gap those areas would have overlapped by 23px and
           a thumb between the two rows would have been a coin flip. */
        row-gap: 6rem;
        font-family: var(--font-display);
        /* 4.2rem — the mobile default, same as the nav and the body. It was
           2.73rem because the OLD three-column nowrap row overflowed above ~3rem.
           That row is gone; what is left is a constraint on the contact pair,
           handled by stacking it below rather than by shrinking the type.
           Measured at 375: phone + email side by side come to 373px in a 354px
           row, so side by side is simply not available at this size. */
        font-size: 4.2rem;
        margin-bottom: 3rem;
    }
    /* THE PHONE AND EMAIL COME OUT on the phone. Both are still one tap away —
       "Reach out" is in the nav beside them and reachout.html carries the mailto
       — and at this width a five-row left column pushed the wordmark most of a
       screen further down for two rows that repeat what the nav already offers.
       display:none, not visibility or opacity: it should cost no space and be
       announced to nobody, since it is not hidden information, just information
       that lives somewhere else here. */
    .footer-list:nth-of-type(1) { display: none; }
    /* Nav left, socials right — the reverse of the desktop's reading order,
       where the nav is the middle column. Navigation is what a footer is for on
       a phone; the socials are the aside. */
    .footer-list:nth-of-type(2) { grid-column: 1; grid-row: 1; }   /* nav     */
    .footer-list:nth-of-type(3) { grid-column: 2; grid-row: 1; }   /* socials */
    /* TIGHTER: 3rem, down from 6rem. That 6 was set by the touch targets rather
       than by the eye — enough air that two 45px hit areas could not overlap —
       and it read as a list of things filed apart rather than a list. Halved,
       the rows pitch ~36px instead of ~49px and the hit areas below are cut to
       match so they still cannot overlap. 35px is under Apple's 44pt guidance
       and well over the 24px WCAG 2.2 AA minimum; that is the trade this makes. */
    .footer-list li + li { margin-top: 3rem; }
    .footer-list > * { white-space: nowrap; }
    /* NO right-pull. The desktop pulls its last column to the gutter, which under
       an `auto` track was the same thing as leaving it alone. Under the duet it
       is not: the socials' track is twice the width of the longest name in it,
       so end-aligning them would park them back at the edge and hide the split
       the grid is there to make. They start where their column starts. */
    .footer-list,
    .footer-list:last-of-type { justify-self: start; }   /* :last-of-type to match
                                  the desktop rule's specificity — one class was
                                  a weaker selector than the one being undone */

    /* ©2026 sits under both columns. It carries its own font-size, so without
       this it kept the desktop's 1.75rem — 7.5px at 375, half the size of every
       other line in the footer. The row gap above it is already 6rem; the
       desktop's 3rem top margin on top of that was a second, unequal seam. */
    .footer-meta {
        font-size: 4.2rem;
        margin-top: 0;
    }

    /* A footer link is ~24px tall here and they sit close together. Grow the HIT
       AREA with a pseudo-element rather than padding, exactly as .footer-cookies
       already does — padding would break the baseline the row is aligned on.
       px, not rem: a touch target is a physical size, not a design unit. */
    .footer-link::after {
        content: "";
        position: absolute;
        /* Sized off the ROW PITCH, not off a target guideline: at a 3rem gap the
           rows sit ~36px apart, so anything over 6px each side would have the
           hit areas of two adjacent links overlapping and a tap between them
           going to whichever is later in the DOM. 6px on 23px of type gives 35px
           — the most this spacing can carry without that ambiguity. */
        inset: -6px -6px;
    }
    /* Cookies sits in the same row as the social links and carries its own hit
       area from the base rules — 35px, which left one control in the row eight
       pixels shorter than its neighbours. Matched. */
    .footer-cookies::after { inset: -11px -8px; }

    /* THE BAND, RESHAPED FOR TWO LINES.
       footer-word.js breaks the word at its space below 819px, so the grid it
       measures goes from 180x20 cells to 89x42 and the box has to follow it or
       the letters overflow the band. The number is derived, not chosen:

         cell   = width * (1 - 2*margin) / cols   =  width * 0.96 / 89
         type   = 42 cells tall                   =  width * 0.4530
         ratio  = 0.4530 / 0.652                  =  1.44

       where 0.652 is the share of the band the desktop gives its type — the
       same air above and below the wordmark, at both sizes. Change `margin` or
       `lineGap` in footer-word.js and this number moves with them; the console
       line prints the grid it actually built. */
    .footer-big { aspect-ratio: 1.44; }
}

/* ============================================================
   Mobile — homepage sections (hero / stats / featured work / FAQ).
   Same 1400px-artboard scale as the shell above.
   ============================================================ */
@media (max-width: 819px) {
    /* Hero — serif tagline steps up to display scale; the studio line is dropped */
    .hero-tagline {
        font-size: 5.25rem;              /* 84 design px */
        max-width: 12ch;
    }
    /* The falling FLORIUM letters double on phones (desktop keeps its clamp) */
    .phys-letter { font-size: clamp(17.5rem, 36vw, 40rem); }
    .hero-meta { font-size: 4.2rem; }    /* base text: 18px @375, matched to locomotive.ca */
    .hero-meta--base span:first-child { display: none; }   /* "Independent design studio" */

    /* ---- The meta line moves up next to the tagline (phones only) ----
       It used to inherit the desktop placement: a column pinned bottom-right,
       ending at the very last pixel of the hero. That is precisely where the
       wordmark comes to rest — seven letters at 36vw are ~135px wide each in a
       353px box, so they stack several rows deep and cover the full width — and
       "Real-time 3D" and "©2026" ended up underneath the settled F and L. The
       first thing a visitor on a phone sees, and unfixable by nudging: the pile
       is physics, its height is not knowable from CSS.

       So the two lines leave the floor entirely and join the tagline as one
       centred group, side by side instead of stacked — "Real-time 3D" at the
       left edge, "©2026" at the right, the full measure between them.

       Done with flow, not offsets: the tagline goes back to static and the
       frame centres its children, so the gap is a real margin. An absolutely
       positioned meta would need a magic translateY equal to half the tagline's
       height — four lines today, and silently wrong the first time that copy is
       edited. */
    .hero-frame {
        justify-content: center;
    }

    .hero-tagline {
        position: static;
        transform: none;
        margin-left: auto;               /* keeps it hugging the right edge, as designed */
    }

    .hero-meta--base {
        align-self: stretch;             /* full measure, so the two ends can split */
        flex-direction: row;
        justify-content: space-between;
        align-items: baseline;
        margin-top: 3.5rem;
    }

    .viewport-section { margin-bottom: 25rem; }

    /* Stats + team — full-width stacks */
    .stats-section { gap: 15rem; margin-bottom: 25rem; }
    .stat-block, .team-block { width: 100%; min-width: 0; }
    .stat-block h2, .team-block h2 { font-size: 5.25rem; margin-bottom: 1.5rem; }
    .stat-item, .team-member, .team-role { font-size: 4.2rem; }
    /* At the bumped body size a name and its role no longer fit side by side (both are
       nowrap — measured 380 into 354), so the two-column team grid stacks: role under name. */
    .team-list { grid-template-columns: 1fr; row-gap: 0; }
    .team-member { margin-top: 1.5rem; }
    .team-member:first-child { margin-top: 0; }
    .team-role { text-align: left; }

    /* Featured work list — no cursor-tracking photo on touch; scramble (JS,
       fine-pointer-gated) is unaffected */
    .work-section { margin-bottom: 25rem; }
    .work-label { font-size: 4.2rem; margin-bottom: 1rem; }
    .work-item { font-size: 6.5rem; padding: 2rem 0; }
    /* Scroll-driven peek (hover-peek.js) — sized to the room actually there:
       the tightest row ("Athoneli Art") leaves 90px either side of its label,
       and the 2.5rem gap eats 11 of it, so the box may be at most ~79px wide.
       16rem/11rem is 69×47px at 375, which also clears the 74px row height. */
    .peek { width: 16rem; height: 11rem; }
    /* margin-top mirrors .work-section's 25rem below it, as on desktop. */
    .work-description { font-size: 5.25rem; max-width: none; margin-top: 25rem; }

    /* FAQ keeps the DESKTOP structure on phones — the "FAQ" label holds the left
       third, the questions the remaining two thirds — but the questions are
       left-aligned inside their column rather than right-aligned as on desktop. */
    .faq-header { font-size: 5.25rem; }
    .faq-container { grid-template-columns: 1fr 2fr; row-gap: 3rem; padding-top: 1.5rem; }
    .faq-label { display: block; font-size: 4.2rem; }
    .faq-item {
        font-size: 4.2rem;
        text-align: left;             /* questions read from the left edge of their column
                                         (desktop keeps its right-aligned rag) */
        padding-bottom: 1.5rem;
        margin-bottom: 1.5rem;
    }
    /* The question row is 23px tall on a phone. It is wide, so it was never hard to
       hit, but it sat ~8px from its neighbour and a thumb landing between two rows
       was a coin flip. Grow the hit area only — padding here would open a second gap
       between the question and its answer, which is spaced by .faq-item__a. ::before
       rather than ::after because ::after already draws the +/- indicator. */
    /* The +/− is an inline-block, and an atomic inline gets a wrap opportunity
       before it — so on a question that fills its last line the marker dropped
       onto a line of its own ("...for my business?" then a lone "+"). Pinning it
       to the right edge removes the wrap by construction, and on a left-aligned
       list it is the better pattern anyway: the six markers line up as a column
       of controls instead of trailing each question at a different x.
       DESKTOP IS UNTOUCHED — there the questions are right-aligned and short
       enough that the marker never wraps. */
    .faq-item__q { position: relative; padding-right: 2.2em; }
    .faq-item__q::after {
        position: absolute;
        right: 0;
        top: 0;
        margin-left: 0;
    }
    .faq-item__q::before {
        content: "";
        position: absolute;
        /* px, not rem — a touch target is a physical size. 23 + 9 + 9 = 41px, and the
           rows stay ~8px apart so neighbouring targets still cannot overlap. */
        inset: -9px 0;
    }
    .faq-item__a {
        max-width: none;              /* the right column is already the constraint */
        margin: 0;
        /* The 2.2em on the right matches the gutter the question above reserves
           for its pinned +/− marker. Without it the answers ran 38px wider than
           the questions and the column lost its right edge (measured: question
           text ending at x=322, answer at x=360).

           Folded into the SHORTHAND deliberately: written as a separate
           `padding-right` before this line it was silently reset to 0 by the
           shorthand that followed it. Top/bottom keep the collapse-safe gap. */
        padding: 2rem 2.2em 0.5rem 0;
        font-size: 4.2rem;            /* base text: 18px @375, matched to locomotive.ca */
        line-height: 1.4;
        text-align: left;             /* answers stay ragged-right, like desktop */
    }
}

/* ############################################################################
   ▼▼▼  TYPESCALE FLATTEN — 2026-08-20  —  REVERTIBLE  ▼▼▼

   WHAT: every paragraph / body / label size on the HOMEPAGE collapses onto the
   site's one body default — 2rem desktop, 4.2rem mobile. The page ends up with
   a two-step scale instead of five: display type (hero wordmark, tagline, work
   titles, footer wordmark) and one small default. Nothing in between.

   2rem, not 1.75rem: the default was set to the nav's 1.75rem first, then
   raised to match .work-label ("Featured Work"), which was out of scope for
   this block and so was the one survivor of the old scale. Content is 2rem;
   the CHROME — nav and footer row — deliberately stays at 1.75rem. See the
   note on that at the end of this block.

   WHY: the in-between sizes (3.75 / 2.5 / 2 / 1.875rem) are what make a layout
   read as a template. Every reference the site is aiming at — 2xa, Trionn,
   revelatio — runs exactly two registers, and lets POSITION, RULES and CASE do
   the hierarchy that a mid-size heading was doing here.

   NOT TOUCHED, deliberately: the Featured Work section (.work-label,
   .work-item, .work-description), the footer row, the mobile nav overlay, the
   hero masthead (.hero-tagline is display type, .hero-meta is a credit line),
   and every other page — case studies, Work, Studio, Reach out.

   TO REVERT: delete everything between this banner and the matching
   "▲▲▲ END TYPESCALE FLATTEN ▲▲▲" banner at the bottom of this file. No rule
   above this point was edited, so the original values come straight back.
   ############################################################################ */

/* ---- Stats section: keep the gem aligned by construction ----
   REQUIRED BY THE FLATTEN, not a taste change. .stats-section carries
   `min-height: 520px` (set inline in index.html) purely so #diamond-stage —
   which is top:0/bottom:0 inside it — has a height to fill, and that number was
   chosen because the two text blocks happened to come to ~454px. At the flat
   size they come to 369px, so the gem hung 151px BELOW the text column: exactly
   the misalignment that inline comment was written to kill, inverted.

   space-between rather than a bigger `gap`, for the reason the original comment
   gives: no magic number. The first block's top and the last block's bottom
   become the section's own edges, which are the gem's edges, so the two line up
   at every width — and if the text ever grows past 520px this is a silent no-op.
   Mobile is unaffected: there .stats-section is min-height:0 and the stage is
   static, so an auto-height flex column makes space-between do nothing. */
.stats-section { justify-content: space-between; }

/* ---- Stats + team ---- "Who we build for" / "Two people, no handoffs" ----
   Both were 3.75rem <h2>s over 1.875rem lists. The <h2> keeps its 1rem gap and
   the 2px .divider under it, which is now the ONLY thing separating heading
   from list — that rule is doing the work the size drop gave up. */
.stat-block h2,
.team-block h2 { font-size: 2rem; }
.stat-item     { font-size: 2rem; }
.team-member   { font-size: 2rem; }
.team-role     { font-size: 2rem; }

/* ---- FAQ ---- "Good to know" header, the "FAQ" label, and the questions ----
   .faq-item__a (the answers) was ALREADY 1.75rem — it is the rule the rest of
   the page is being pulled onto, not a rule being changed. Question and answer
   are now the same size; what separates them on desktop is the alignment split
   (.faq-item is right-aligned, .faq-item__a left) plus the +/− indicator. */
.faq-header { font-size: 2rem; }
.faq-label  { font-size: 2rem; }
.faq-item   { font-size: 2rem; }
/* .faq-item__a (the answers) has to be restated now. It was ALREADY on the old
   1.75rem default, which is why it was absent from the first version of this
   block — at 2rem it would otherwise be the one body element left behind. Its
   line-height rides up with it: the original pairing was 1.75/2rem, a 1.14
   ratio, and leaving 2rem line-height under 2rem type would set it solid. */
.faq-item__a { font-size: 2rem; line-height: 2.25rem; }

/* ---- Mobile ------------------------------------------------------------
   These MUST be restated. Media queries add no specificity, so the desktop
   rules above — later in the file than the original @media block — would
   otherwise win inside the mobile breakpoint too. Every value here is 4.2rem,
   the mobile default (18px @375, the size the mobile nav speaks at); the ones
   that were already 4.2rem are repeated only to out-rank the block above. */
@media (max-width: 819px) {
    .stat-block h2,
    .team-block h2 { font-size: 4.2rem; }
    .stat-item,
    .team-member,
    .team-role     { font-size: 4.2rem; }
    .faq-header    { font-size: 4.2rem; }
    .faq-label     { font-size: 4.2rem; }
    .faq-item      { font-size: 4.2rem; }
    /* Pinned for the same reason as the rest of this block — without it the
       2rem desktop rule out-ranks the original mobile one. Line-height goes
       back to the mobile value (1.4) that the desktop rem figure displaced. */
    .faq-item__a   { font-size: 4.2rem; line-height: 1.4; }
}

/* ---- Why the nav and footer row stay at 1.75rem ------------------------
   They are CHROME, not content, and chrome sitting a step under body copy is
   normal — 2xa's nav and Trionn's pills are both smaller than their body type.
   The practical reason matters more: `header` is shared markup on all eight
   pages, so raising it to 2rem would reach straight into the case studies,
   Reach out and Studio, every one of which was ruled out of this pass. Nav and
   footer agree with each other at 1.75rem, content agrees with itself at 2rem;
   that is two consistent registers, not a leftover. One line each if you want
   them merged — but it is a site-wide change, not a homepage one. */

/* ############################################################################
   ▲▲▲  END TYPESCALE FLATTEN — 2026-08-20  ▲▲▲
   ############################################################################ */

/* ############################################################################
   ▼▼▼  HERO-BLACK PREVIEW — 2026-08-20  —  REVERTIBLE, OFF BY DEFAULT  ▼▼▼

   Only active with ?heroblack in the URL (see the two marked script tags in
   index.html). Nothing here can affect a normal visit — every selector is
   gated behind html.hero-black.

   Look at it at http://localhost:8123/index.html?heroblack
   ############################################################################ */

/* Full-bleed black behind the hero band. The band is drawn as a ::before on
   .viewport-section rather than a background on it, because the section sits
   inside .container's 2.5rem top padding and its var(--gutter) sides — a plain
   background would leave a light L-shaped frame around the black. This reaches
   y=0 (up behind the fixed nav) and out to both viewport edges.

   100vw + left:50% + translateX(-50%): the section's own box is
   (100vw - 2*gutter), so this recentres to the true viewport width. .container
   has `overflow-x: clip` and clips at its padding box, which IS 100vw — so the
   band lands exactly edge to edge and adds no horizontal scroll. */
.hero-black .viewport-section { position: relative; }
.hero-black .viewport-section::before {
    content: "";
    position: absolute;
    /* Both insets are .container's 2.5rem padding, and that is what makes the
       band exactly ONE SCREEN tall: .viewport-section is min-height
       calc(100vh - 5rem), so 2.5 + (100vh - 5) + 2.5 = 100vh, from y=0 to the
       fold. With `bottom: 0` it came up 27px short at 1280x720 and left a light
       sliver along the bottom of the first screen — measured, not guessed. */
    top: -2.5rem;
    bottom: -2.5rem;
    left: 50%;
    transform: translateX(-50%);
    width: 100vw;
    background: #050505;      /* not pure #000 — the diamond stage below is #000,
                                 and two different blacks read as a mistake */
    z-index: 0;
}
/* The hero's own children already stack internally (.hero-physics z1,
   .hero-frame z2); this lifts the whole group clear of the band. */
.hero-black .viewport-section > .hero { position: relative; z-index: 1; }

/* Ink inverts inside the band only. */
.hero-black .phys-letter,
.hero-black .hero-tagline,
.hero-black .hero-meta { color: var(--color-bg); }

/* The nav is `background: var(--color-bg)` solid so it occludes content — over
   the black band that would paint a light strip across the top of it. Scoped to
   .nav-light, which nav-theme.js only sets while the band is under the strip,
   so the nav gets its solid backing straight back on scroll. */
.hero-black header.nav-light { background: transparent; }

/* ############################################################################
   ▲▲▲  END HERO-BLACK PREVIEW — 2026-08-20  ▲▲▲
   ############################################################################ */

