/* ==========================================================================
   Homepage — the instrument and the press.

   ONE REGISTER. There is a single black on this site (--ink) and the type on it
   is cream. `.screen` and `.paper` survive as SEMANTIC section names — which
   movement is the instrument reading the signal, and which is what came off the
   press — but they are not two colour schemes any more. Both are dark.

   HISTORY, because the comment that used to sit here was actively misleading:
   .paper was originally a real cream surface. That was the light-mode experiment,
   and it was reverted (Aug 2026) — `.paper` was repointed at --ink and the cream
   never came back. The old header still described "cream, ink rules, ink type"
   and carried a rule about green-on-cream, which had nothing left to govern.

   The contrast rule it stated is still TRUE and still worth knowing, it just no
   longer applies to anything here — it applies to the GREEN FIELDS (§4), which
   are the only light-ish surfaces left:
     ink on green 15.15:1 · cream on green 1.13:1 · orange on green 2.72:1
     => anything sitting on a green field must be INK. Never cream, never orange.
   That constraint is live on .door, .cs-fold, .site-nav and the services plate.
   ========================================================================== */

:root {
  --phosphor: 127, 255, 0;
  /* Glow belongs to THICK structural lines and display type — never to the graticule. */

  /* Stacking order, named. These were eleven bare numbers, and the fragile pair was the FAB
     scrim at 139 sitting one below its own button at 140 — nudge the scrim up and the menu
     becomes unclickable with nothing to point at. Written down, the order is arguable
     instead of accidental. Ascending = closer to the viewer. */
  --z-grain:      0;    /* the fixed body::before texture, behind everything */
  --z-content:    1;    /* main, header and footer sit above the grain */
  --z-raised:     2;    /* tile codes, the retrace beam */
  --z-runhead:    3;    /* the sticky per-movement running head */
  --z-sweep:      4;    /* the sweep-reveal line */
  --z-nav-scrim:  110;
  --z-nav:        120;
  --z-header:     130;
  --z-fab-scrim:  139;
  --z-fab:        140;  /* must stay ABOVE --z-fab-scrim */
  --z-transition: 200;  /* the page wipe covers everything */

  --glow-rule: 0 0 10px rgba(var(--phosphor), .30);
}

body { background: var(--ink); color: var(--cream); }

/* A little grain, fixed and painted once. Static tile, never a per-frame filter. */
/* One quiet grain layer over the whole page. Low enough to read as a signal floor rather
   than as texture — three stacked texture layers is what made the earlier pass feel heavy. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: var(--z-grain); pointer-events: none;
  opacity: .16; mix-blend-mode: screen;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='matrix' values='0 0 0 0 0.30  0 0 0 0 0.75  0 0 0 0 0.06  0 0 0 0.13 0'/></filter><rect width='160' height='160' filter='url(%23n)'/></svg>");
  background-size: 160px 160px;
}
@media (prefers-reduced-motion: reduce) { body::before { display: none; } }

main, .site-header, .site-footer { position: relative; z-index: var(--z-content); }

/* ---------------------------------------------------------------- registers */

.screen { position: relative; }
.screen {
  background-color: var(--ink);
  color: var(--cream);
  /* No graticule by default. It now appears on exactly two elements — the hero and the
     pinned argument — so it reads as an instrument moment rather than as wallpaper. */
}

/* Kept as a section NAME, not a colour scheme — see the header. Identical to .screen by
   design; do not "restore" a light background here without re-reading §4's contrast table. */
.paper { background: var(--ink); color: var(--cream); }

/* ---------------------------------------------------------------- structure */

.wrap { max-width: 1180px; margin: 0 auto; padding-inline: var(--space-5); }
.mv { position: relative; }
.mv__pad { padding-block: var(--space-8) var(--space-8); }

/* Thick structural rules — these are what glow. */

/* Running head. Burns down to a ghost as its movement leaves rather than cutting. */
/* The masthead hides on scroll-down; without this the running head kept sticking at 78px and
   left a dead band of page above it, which is what read as "stuck 20% down the screen".
   Transform, not top, so it rides the same curve as the header and costs no layout. */
.mv__head {
  position: sticky; top: var(--header-h); z-index: var(--z-runhead);
  transition: transform 320ms cubic-bezier(.4, 0, .2, 1);
  display: flex; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-2) var(--space-5);
  border-bottom: 1px solid rgba(var(--phosphor), .35);
  font-family: var(--font-mono); font-size: .6875rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--green);
  background: color-mix(in srgb, var(--ink) 90%, transparent);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}
/* The running head is the only wayfinding again now the rail is gone — so it stays at every
   width. One quiet mono line per movement is a fraction of the rail's weight. */

@supports ((animation-timeline: view()) and (animation-range: exit)) {
  @media (prefers-reduced-motion: no-preference) {
    .mv__head { animation: burnGhost linear both; animation-timeline: view();
                animation-range: exit 0% exit 88%; }
  }
}
html.hdr-hidden .mv__head { transform: translateY(calc(var(--header-h) * -1)); }

@keyframes burnGhost { to { opacity: .2; } }

/* ----------------------------------------------------------------- masthead */

.site-header {
  position: sticky; top: 0; z-index: var(--z-header);
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: color-mix(in srgb, var(--ink) 88%, transparent);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid rgba(var(--phosphor), .35);
  transition: transform 320ms cubic-bezier(.4,0,.2,1);
}
html.hdr-hidden .site-header { transform: translateY(-101%); }

.site-header__mark { display: block; width: 116px; height: 30px; flex: 0 0 auto; }
.site-header__mark .logotype-mark {
  display: block; width: 100%; height: 100%; background: var(--green);
  -webkit-mask: url('/img/logotype.svg') center / contain no-repeat;
  mask: url('/img/logotype.svg') center / contain no-repeat;
}

/* The masthead is the mark and the menu, nothing else. The Vol./page/clock readout that used
   to sit between them is gone (Aug 2026) — it carried the margin-left:auto that pushed the
   toggle right, so the toggle owns that now. */
.nav-toggle { display: flex; flex-direction: column; justify-content: center; gap: 5px;
              width: 44px; height: 44px; padding: 0 8px; flex: 0 0 auto;
              margin-left: auto; margin-right: var(--space-1);
              background: none; border: 0; cursor: pointer; }
.nav-toggle__bar { display: block; height: 2px; width: 100%; background: var(--green); }

/* The fixed green rail is gone. 240px of #7FFF00 at full saturation, present at all times,
   was the heaviest thing on the page — and everything it said (VOL. V, page number, clock)
   the masthead already says in one line. Invisible Creature carries a lot of content and
   still feels light precisely because it has almost no permanent chrome. */

/* ------------------------------------------------------------- P.001 — fold */
/* Compressed deliberately. NN/g: users scroll on only if what is above the fold earns it,
   and 42% of all viewing time lands in the top 20% of a page. A full viewport of wordmark
   spends that budget on a logo. The work now starts inside the first scroll. */

.hero, .pin__stage {
  background-image:
    linear-gradient(to right,  rgba(var(--phosphor), .03) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(var(--phosphor), .03) 1px, transparent 1px);
  background-size: 120px 120px;
  background-position: center top;
}

.hero {
  position: relative;
  /* auto/auto with align-content:center treats the mark and the line beneath it as ONE
     centred group, instead of centring the mark alone and stranding the line at the bottom
     of the fold. row-gap is then the only thing controlling how close they sit. */
  display: grid; grid-template-rows: auto auto;
  align-content: center;
  row-gap: var(--space-3);
  /* Full viewport minus the masthead, minus a sliver — so the services plate below shows only
     as a peek and the fold reads as "there is more", which is what makes someone scroll. */
  min-height: calc(100svh - var(--header-h) - 2.25rem);
}
/* flex:1 so the mark takes the leftover height rather than a fixed cap — §4: the lockup is
   sized by HEIGHT and never cropped. */
.hero__mark svg { overflow: visible; }   /* the halo spreads past the viewBox — never clip it */
.hero__mark { justify-self: center;
              aspect-ratio: 1240/724; width: min(88%, 1080px); max-height: 62svh; }
.hero__mark svg { display: block; width: 100%; height: 100%; }

.beam { stroke: var(--green); stroke-linejoin: round; stroke-linecap: round;
        vector-effect: non-scaling-stroke; fill: none; }
.beam--bloom { stroke-width: 9; }

/* The glow is a BLUR, not a second outline. Without this the bloom layer was just a wider,
   dimmer copy of the letterforms with crisp edges — which is why no amount of opacity made it
   read as glow. The filter is STATIC (only opacity animates), so the browser rasterises the
   blurred layer once and composites it; animating the filter itself would re-run it per frame.
   The radius is in the group's local units, which this SVG scales by 7.53 — so 1.1 here is
   roughly 8px of spread on screen, and it stays proportional at every size. */
/* SAFARI. The filter and the animated opacity are now on SEPARATE elements, and the filter
   does not exist at all until the mark has settled. Both matter, and both are about WebKit
   re-running filters far more eagerly than Blink:

     .beam-glow  — filter only. Static, so it rasterises once and is then just a texture.
     .beam-pulse — opacity only. No filter on it, so the perpetual pulse is a pure composite
                   rather than a full feTurbulence re-render every frame.

   And the filter is withheld until `.is-settled`: during the 2.6s trace GSAP writes drawSVG
   on these eight paths every frame, and with a filter attached that forced a turbulence
   re-render per frame per path. That was the expensive part. */
.beam-glow { }
.hero.is-settled .beam-glow { filter: url(#phosphor); }
.beam-pulse { opacity: .16; }
.beam--trail { stroke-width: 1.4; opacity: .32;
               transition: stroke-width 700ms cubic-bezier(.4,0,.2,1),
                           opacity 700ms cubic-bezier(.4,0,.2,1); }
.beam--head  { stroke-width: 4; opacity: 1; }
/* Under reduced motion settle() fires immediately, so the head must simply not be there —
   no fade to watch. The transition is only offered when motion is welcome. */
@media (prefers-reduced-motion: no-preference) {
  .beam--head { transition: opacity 800ms cubic-bezier(.4,0,.2,1); }
}

.hero.is-settled .beam--trail { opacity: 1; stroke-width: 2.2; }

/* The beam drew the mark; once the mark is lit the beam has no job left, so it goes out.
   crt.js clears its inline opacity at settle so this rule can win. */
.hero.is-settled .beam--head  { opacity: 0; }

/* The only perpetual motion on the page. Opacity on ONE layer — no dashoffset, no filter, no
   re-tessellation — so it is close to free and stays a glow rather than a light show. Kept
   deliberately slow and shallow: it should read as the mark breathing, not blinking. */
@media (prefers-reduced-motion: no-preference) {
  .hero.is-settled .beam-pulse { animation: bloomPulse 4s ease-in-out infinite;
                                 will-change: opacity; }
  .hero.is-offscreen .beam-pulse { animation-play-state: paused; }   /* §7 */
}

/* 0.14 of opacity spread over 6s is about 0.05/second — below the threshold of noticing,
   which is why the first attempt measured as running but could not be seen. Wider swing,
   shorter cycle; it still reads gentle because the layer is diffuse rather than sharp. */
@keyframes bloomPulse {
  0%, 100% { opacity: .16; }
  50%      { opacity: .52; }
}

.hero__foot { display: flex; flex-direction: column; align-items: center;
              gap: var(--space-3); padding: 0 var(--space-4) var(--space-4);
              text-align: center; }
.statement { font-family: var(--font-display); text-transform: uppercase; font-weight: 700;
             font-size: clamp(1.5rem, 3.2vw, 2.75rem); line-height: 1.04; margin: 0;
             color: var(--cream); }
.cue { display: flex; align-items: center; gap: var(--space-2); }
.cue svg { width: 56px; height: 56px; flex: 0 0 auto; }
.cue path { fill: none; stroke: var(--green); stroke-width: 1.4;
            vector-effect: non-scaling-stroke; }
.cue span { font-family: var(--font-text); font-weight: 700; font-size: .7rem;
            letter-spacing: .14em; text-transform: uppercase; color: var(--cream); }

.plate { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline;
         gap: var(--space-1) var(--space-3); list-style: none; margin: 0;
         padding: var(--space-1) var(--space-4);
         background: var(--green); color: var(--ink);
         font-family: var(--font-display); text-transform: uppercase; letter-spacing: .06em;
         font-size: clamp(.9375rem, 1.4vw, 1.25rem); line-height: 1.15; }

/* ------------------------------------------------------- P.002 — work (paper) */

.grid { display: grid; grid-template-columns: 1fr; gap: 1px; background: rgba(var(--phosphor), .28); }
@media (min-width: 768px)  { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) {
  .grid { grid-template-columns: repeat(4, 1fr); }
  .tile--wide { grid-column: span 2; }
  .tile--wide .tile__img { aspect-ratio: 16/10; }
}

.tile { background: var(--ink); display: flex; flex-direction: column; gap: 6px;
        padding: var(--space-3); text-decoration: none; position: relative;
        /* clip, NOT hidden — `hidden` makes an element a scroll container, which freezes
           every view() timeline inside it. */
        overflow: clip; }
.tile__img { width: 100%; height: auto; aspect-ratio: 4/5; object-fit: cover; display: block;
             background: var(--ink); }
.tile__cat { font-family: var(--font-text); font-weight: 700; font-size: .72rem;
             letter-spacing: .08em; text-transform: uppercase; opacity: .6; }
.tile__name { font-family: var(--font-text); font-weight: 700; font-size: 1.05rem;
              letter-spacing: .04em; text-transform: uppercase; }
.tile__code { position: absolute; top: var(--space-3); left: var(--space-3); z-index: var(--z-raised);
              background: var(--green); color: var(--ink);
              font-family: var(--font-mono); font-size: .6875rem; font-weight: 700;
              letter-spacing: .08em; padding: 3px 7px; }
.tile__go { align-self: flex-start; margin-top: 2px; background: var(--green); color: var(--ink);
            font-family: var(--font-text); font-weight: 700; font-size: .6875rem;
            letter-spacing: .08em; text-transform: uppercase; padding: 4px 8px;
            transition: background-color 140ms ease; }
.tile--case:hover .tile__go, .tile--case:focus-visible .tile__go { background: var(--cream); }
.tile--still { cursor: default; }

.more { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3);
        padding: var(--space-3) var(--space-4); text-decoration: none;
        border-top: 2px solid rgba(var(--phosphor), .45);
        font-family: var(--font-text); font-weight: 700; font-size: .75rem;
        letter-spacing: .14em; text-transform: uppercase; color: var(--cream); }
.more:hover, .more:focus-visible { background: var(--green); color: var(--ink); }

/* Partner logos immediately after the work — the credibility beat both Lincoln Design Co and
   Invisible Creature place in exactly this position. */

.partners { overflow: hidden; padding-block: var(--space-4); }
.partners__track { display: flex; width: max-content; }
.partners__grid { display: flex; gap: var(--space-5); list-style: none; margin: 0;
                  padding: 0 calc(var(--space-5)/2); flex: 0 0 auto; }
.partners__logo { flex-shrink: 0; width: 160px; height: 38px;
                  display: flex; align-items: center; justify-content: center; }
.partners__logo img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain;
  /* source files are ink; on the tube they read as the system green */
  filter: brightness(0) saturate(100%) invert(83%) sepia(58%) saturate(2000%) hue-rotate(35deg) brightness(105%); }
@media (prefers-reduced-motion: no-preference) {
  .partners__track { animation: marqueeRun 42s linear infinite; }
  .partners__track.is-paused { animation-play-state: paused; }
}
@keyframes marqueeRun { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* -------------------------------------------------- P.003 — the pinned argument */
/* position: sticky pins natively; a named view-timeline scrubs against it. A pinned scrub for
   zero bytes, where ScrollTrigger would have cost 17.7KB.
   Halved from 300vh: at 300vh it ate 26% of the page before showing any work. */

.pin { position: relative; height: 150vh; view-timeline-name: --sig; view-timeline-axis: block; }
.pin__stage { position: sticky; top: var(--header-h);
              height: calc(100svh - var(--header-h));
              display: grid; grid-template-rows: auto 1fr; overflow: clip; }
.pin__scope { border-bottom: 2px solid rgba(var(--phosphor), .5); }
.pin__scope svg { display: block; width: 100%; height: clamp(110px, 20vh, 180px); }
#tr2 { transform-origin: 50% 50%; }
.trace { fill: none; vector-effect: non-scaling-stroke; }
.trace--1 { stroke: var(--cream); stroke-width: 1.6; }
.trace--2 { stroke: var(--green); stroke-width: 1.4; stroke-dasharray: 7 5; opacity: .9; }

/* The measure has to grow with the type or the phrases break to one word a line. At this size
   28ch is about four words per line, which is what makes it read as a statement rather than
   as a column. */
.pin__copy { align-self: center; padding: var(--space-5) var(--space-5);
             max-width: 28ch; margin-left: auto; margin-right: 6vw; }
.pin__line { display: block; font-family: var(--font-display); text-transform: uppercase;
             font-weight: 700; font-size: clamp(1.3rem, 2.5vw, 2.4rem); line-height: 1.08;
             color: var(--cream); opacity: .1; }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .pin__line { animation: lineLight linear both; animation-timeline: --sig; }
    .pin__line:nth-of-type(1) { animation-range: cover 41% cover 45%; }
    .pin__line:nth-of-type(2) { animation-range: cover 45% cover 49%; }
    .pin__line:nth-of-type(3) { animation-range: cover 49% cover 53%; }
    .pin__line:nth-of-type(4) { animation-range: cover 53% cover 59%; }
    #tr2 { animation: ch2Grow linear both; animation-timeline: --sig;
           animation-range: cover 41% cover 60%; }
  }
}
@keyframes lineLight { from { opacity: .1; } to { opacity: 1; } }
@keyframes ch2Grow   { from { transform: scaleY(.12); } to { transform: scaleY(1.9); } }

@supports not (animation-timeline: view()) {
  .pin { height: auto; } .pin__stage { position: static; height: auto; } .pin__line { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .pin { height: auto; } .pin__stage { position: static; height: auto; } .pin__line { opacity: 1; }
}

/* ---------------------------------------------------- P.004 — studio (paper) */

.title { font-family: var(--font-display); text-transform: uppercase; font-weight: 700;
         font-size: clamp(2.25rem, 6.5vw, 5.5rem); line-height: .92; margin: 0; }
.sub { font-family: var(--font-text); font-size: clamp(1rem, 1.5vw, 1.2rem);
       margin: var(--space-3) 0 0; max-width: 44ch; opacity: .75; }

.stories { display: flex; flex-direction: column; margin-top: var(--space-6); }
.story { display: grid; grid-template-columns: 1fr; gap: var(--space-2);
         padding: var(--space-5) 0; border-top: 1px solid rgba(var(--phosphor), .2); }
@media (min-width: 900px) {
  .story { grid-template-columns: 6rem 1fr; gap: var(--space-5); align-items: start; }
}
.story__no { font-family: var(--font-display); text-transform: uppercase;
             font-size: clamp(2.25rem, 5vw, 4rem); line-height: .8; color: var(--green); opacity: .45; }
.story p { font-family: var(--font-text); font-size: 1.0625rem; line-height: 1.65;
           margin: 0; max-width: 58ch; }

.crew { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
        gap: var(--space-4); margin-top: var(--space-6); }
.crew__member { display: flex; flex-direction: column; gap: 2px; }
.crew__photo { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; }
.crew__name { font-family: var(--font-text); font-weight: 700; font-size: .95rem;
              letter-spacing: .04em; text-transform: uppercase; margin-top: var(--space-2); }
.crew__role { font-family: var(--font-mono); font-size: .6875rem; letter-spacing: .12em;
              text-transform: uppercase; opacity: .65; }

/* ---------------------------------------------------- P.005 — record (screen) */

.quotes { display: flex; gap: 1px; background: rgba(var(--phosphor), .3);
          overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none;
          border-block: 2px solid rgba(var(--phosphor), .5); }
.quotes::-webkit-scrollbar { display: none; }
.quote { margin: 0; background: var(--ink); padding: var(--space-4);
         flex: 0 0 min(88%, 29rem); scroll-snap-align: start;
         display: flex; flex-direction: column; gap: var(--space-3); }
.quote__chip { align-self: flex-start; background: var(--green); color: var(--ink);
               font-family: var(--font-mono); font-size: .6875rem; font-weight: 700;
               letter-spacing: .08em; padding: 3px 7px; }
.quote p { font-family: var(--font-text); font-size: clamp(1rem, 1.5vw, 1.25rem);
           line-height: 1.45; color: var(--cream); margin: 0; }
.quote cite { font-style: normal; font-family: var(--font-text); font-weight: 700;
              font-size: .72rem; letter-spacing: .12em; text-transform: uppercase;
              color: var(--muted); }
.quote cite span { display: block; color: var(--green); margin-top: 2px; }
[data-rail].is-dragging { cursor: grabbing; scroll-behavior: auto; }
[data-rail].is-dragging * { user-select: none; }

.ledger { display: grid; grid-template-columns: 1fr; gap: var(--space-6);
          margin-top: var(--space-6); }
@media (min-width: 768px) { .ledger { grid-template-columns: 1fr 1fr; gap: 0 var(--space-7); } }
.ledger__col { min-width: 0; }
.ledger__title { font-family: var(--font-display); text-transform: uppercase; font-weight: 700;
                 font-size: clamp(1.4rem, 2.4vw, 2rem); line-height: 1;
                 margin: 0 0 var(--space-3); padding-bottom: var(--space-2);
                 border-bottom: 2px solid rgba(var(--phosphor), .5); }
.ledger dl { margin: 0; }
.ledger dl > div { display: grid; grid-template-columns: 7.5rem 1fr; gap: var(--space-3);
                   padding: var(--space-2) 0; border-bottom: 1px solid rgba(241,241,222,.08); }
.ledger dt { font-family: var(--font-mono); font-size: .6875rem; letter-spacing: .16em;
             text-transform: uppercase; color: var(--green); }
.ledger dd { margin: 0; font-family: var(--font-mono); font-size: .8125rem; line-height: 1.5;
             letter-spacing: .04em; }
.ledger dd a { color: var(--cream); text-decoration-color: var(--green); text-underline-offset: 3px; }

/* --------------------------------------------------- P.006 — open channel */

.door { background: var(--green); color: var(--ink);
        padding: var(--space-7) var(--space-4); }
.door .title { color: var(--ink); }
.door .sub { color: var(--ink); opacity: .8; }
.door__no { font-family: var(--font-mono); font-size: .6875rem; letter-spacing: .16em;
            text-transform: uppercase; margin-bottom: var(--space-4); display: block; }

.form-wrap { display: grid; grid-template-columns: 1fr; gap: var(--space-6);
             margin-top: var(--space-5); }
@media (min-width: 1100px) { .form-wrap { grid-template-columns: 1.15fr 1fr; gap: var(--space-7); } }

.field { display: flex; flex-direction: column; gap: var(--space-1); margin-bottom: var(--space-4); }
.field label { font-family: var(--font-mono); font-size: .6875rem; letter-spacing: .16em;
               text-transform: uppercase; color: var(--green); }
.field input, .field select, .field textarea {
  font-family: var(--font-mono); font-size: .875rem; color: var(--cream);
  background: rgba(var(--phosphor), .04); border: 1px solid rgba(var(--phosphor), .4);
  padding: var(--space-2); border-radius: 0;
}
.field textarea { min-height: 8rem; resize: vertical; }
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  outline: 2px solid var(--green); outline-offset: 1px;
}
.field--hp { position: absolute; left: -9999px; }
.submit { font-family: var(--font-display); text-transform: uppercase; font-size: 1.2rem;
          background: var(--green); color: var(--ink); border: 0;
          padding: var(--space-2) var(--space-5); cursor: pointer; letter-spacing: .04em; }
.submit:hover, .submit:focus-visible { background: var(--cream); }
.form-state { font-family: var(--font-mono); font-size: .8125rem; margin-top: var(--space-3);
              padding: var(--space-2); border: 1px solid rgba(var(--phosphor), .5); }

.faq__item { border-bottom: 1px solid rgba(var(--phosphor), .25); }
.faq__q { font-family: var(--font-text); font-weight: 700; font-size: .95rem;
          letter-spacing: .06em; text-transform: uppercase; color: var(--cream);
          padding: var(--space-3) 0; cursor: pointer; list-style: none; }
.faq__q::-webkit-details-marker { display: none; }
.faq__q::before { content: "+ "; color: var(--green); }
.faq__a { font-family: var(--font-text); font-size: .9375rem; line-height: 1.6;
          color: var(--muted); margin: 0 0 var(--space-3); max-width: 52ch; }

/* <details> snaps open by default because height cannot animate to `auto`. interpolate-size
   lifts exactly that restriction, so the panel can ease open natively with no JS and no
   measuring. Guarded: without support it simply snaps, as it does today. */
@supports (interpolate-size: allow-keywords) {
  :root { interpolate-size: allow-keywords; }
  @media (prefers-reduced-motion: no-preference) {
    .faq__item::details-content {
      block-size: 0;
      overflow: hidden;
      transition: block-size 420ms cubic-bezier(.2, .8, .2, 1),
                  content-visibility 420ms allow-discrete;
    }
    .faq__item[open]::details-content { block-size: auto; }
  }
}
.faq__q::before { display: inline-block; transition: transform 300ms cubic-bezier(.2,.8,.2,1); }
.faq__item[open] .faq__q::before { transform: rotate(45deg); }

/* ------------------------------------------------------------------- footer */

.site-footer { border-top: 2px solid rgba(var(--phosphor), .45);
               padding: var(--space-5) var(--space-4) var(--space-6);
               display: flex; flex-wrap: wrap; gap: var(--space-4);
               justify-content: space-between; align-items: flex-end; }
.social { display: flex; gap: var(--space-3); list-style: none; margin: 0; padding: 0; }
.social a { color: var(--green); display: block; width: 24px; height: 24px; }
.social svg { width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.5; }
.social .solid, .social .dot { fill: currentColor; stroke: none; }
.foot-meta { font-family: var(--font-mono); font-size: .6875rem; letter-spacing: .14em;
             text-transform: uppercase; color: var(--green);
             display: flex; flex-direction: column; gap: 4px; }
.foot-meta a { color: var(--green); }

/* -------------------------------------------------------------- sweep reveal */

.sweep-host { position: relative; }
.sweep-line { position: absolute; left: 0; right: 0; top: 0; height: 2px; z-index: var(--z-sweep);
              background: var(--green); box-shadow: 0 0 16px 2px rgba(var(--phosphor), .5);
              opacity: 0; pointer-events: none; will-change: transform; }

/* ------------------------------------------------------------- retrace wipe */

.retrace { position: fixed; inset: 0; z-index: var(--z-transition); pointer-events: none; visibility: hidden; }
.retrace.is-running { visibility: visible; }
.retrace__field { position: absolute; inset: 0; background: var(--ink); opacity: 0; }
.retrace__beam { position: absolute; left: 0; right: 0; top: 0; height: 3px;
                 background: var(--green); box-shadow: 0 0 22px 4px rgba(var(--phosphor), .6);
                 opacity: 0; will-change: transform; }

/* ------------------------------------------------------------------- drawer */

.site-nav { position: fixed; top: 0; right: 0; bottom: 0; width: min(34%, 420px); z-index: var(--z-nav);
            background: var(--green); color: var(--ink);
            display: flex; flex-direction: column; justify-content: center; gap: var(--space-4);
            padding: var(--space-6) var(--space-4);
            transform: translateX(101%); transition: transform 320ms cubic-bezier(.4,0,.2,1); }
.site-nav.is-open { transform: translateX(0); }
.site-nav ul { list-style: none; margin: 0; padding: 0;
               display: flex; flex-direction: column; gap: var(--space-3); }
.site-nav a { font-family: var(--font-display); text-transform: uppercase;
              font-size: clamp(1.5rem, 3vw, 2.25rem); color: var(--ink); text-decoration: none; }
.site-nav a:hover, .site-nav a:focus-visible { text-decoration: underline; text-underline-offset: 6px; }
.site-nav__meta { font-family: var(--font-mono); font-size: .6875rem; letter-spacing: .14em;
                  text-transform: uppercase; display: flex; flex-direction: column; gap: 4px; }
.site-nav__meta a { font-family: var(--font-mono); font-size: .6875rem; text-transform: uppercase; }
.nav-scrim { position: fixed; inset: 0; z-index: var(--z-nav-scrim); background: rgba(14,11,7,.6);
             opacity: 0; visibility: hidden; transition: opacity 220ms ease; }
.nav-scrim.is-open { opacity: 1; visibility: visible; }
body.nav-open { overflow: hidden; }

@media (max-width: 1023px) { .site-nav { width: 100%; } }

/* ==========================================================================
   RADIAL MENU — the persistent door.

   Not decoration and not a second nav: the drawer handles navigation, this
   handles ACTIONS. It exists because contact sits at 81% of the page and only
   17% of viewing time happens below the fold — without it the one door is
   effectively unreachable from where most people actually are.

   Toggle is a registration crosshair that rotates 45° into an ×. Press marks
   are the shop's own language, so the affordance belongs to the brand rather
   than being a generic FAB.
   ========================================================================== */

.fab {
  position: fixed; right: var(--space-4); bottom: var(--space-4);
  z-index: var(--z-fab); width: 62px; height: 62px;
  transition: opacity 260ms ease, visibility 260ms;
}

/* Redundant once the contact form is on screen — it is a shortcut to the thing you are
   already looking at. All widths, unlike the hero rule, which is mobile-only. */
html.contact-near .fab { opacity: 0; visibility: hidden; pointer-events: none; }

/* The pills overlay whatever is behind them, so an open menu needs its own ground or the
   labels fight the page. Ink at 55% is enough to separate without reading as a modal. */
.fab__scrim {
  position: fixed; inset: 0; z-index: var(--z-fab-scrim);
  background: rgba(14, 11, 7, .55);
  opacity: 0; visibility: hidden;
  transition: opacity 260ms ease, visibility 260ms;
}
.fab.is-open ~ .fab__scrim, .fab__scrim.is-open { opacity: 1; visibility: visible; }

.fab__toggle {
  position: absolute; inset: 0; z-index: var(--z-raised);
  display: grid; place-items: center;
  width: 62px; height: 62px; border-radius: 50%;
  background: var(--green); color: var(--ink);
  border: 2px solid var(--ink); cursor: pointer; padding: 0;
  box-shadow: 0 0 18px rgba(var(--phosphor), .35);
}
.fab__toggle svg { width: 30px; height: 30px; display: block; }
.fab__toggle line, .fab__toggle circle { stroke: var(--ink); stroke-width: 1.6; fill: none;
                                          vector-effect: non-scaling-stroke; }
@media (prefers-reduced-motion: no-preference) {
  .fab__toggle svg { transition: transform 320ms cubic-bezier(.2,.8,.2,1); }
}
.fab.is-open .fab__toggle svg { transform: rotate(45deg); }

.fab__items { position: absolute; inset: 0; }

.fab__item {
  position: absolute; left: 0; top: 0;
  display: flex; align-items: center; gap: 0;
  height: 44px; padding: 0 var(--space-3);
  white-space: nowrap; text-decoration: none;
  background: var(--ink); color: var(--green);
  border: 1px solid var(--green);
  font-family: var(--font-mono); font-size: .6875rem;
  letter-spacing: .14em; text-transform: uppercase;
  /* Anchored by its RIGHT edge so the pills stack cleanly against the toggle rather than
     splaying from their own centres. */
  transform-origin: 100% 50%;
  opacity: 0; pointer-events: none;
}
.fab.is-open .fab__item { pointer-events: auto; }
.fab__item:hover, .fab__item:focus-visible { background: var(--green); color: var(--ink); }

/* Closed state without JS: the items are simply not shown and the toggle links to contact. */
.fab:not(.is-open) .fab__item { visibility: hidden; }

@media (max-width: 640px) {
  .fab { right: var(--space-3); bottom: var(--space-3); }
  .fab__item { font-size: .625rem; height: 40px; padding: 0 var(--space-2); }
}

/* ==========================================================================
   TOOLTIP on the radial toggle — the button is an unlabelled crosshair, so
   nothing tells a first-time visitor it is interactive.

   Pure CSS on purpose: a hover state is exactly what CSS is for, and driving
   it from JS meant a tween fighting the stylesheet for the same property.
   The easeReverse idea survives intact — the ENTER curve overshoots
   (cubic-bezier with a >1 control point) and the EXIT curve is flat and
   quicker, so the exit deliberately does not mirror the entrance.
   ========================================================================== */

.fab__tip {
  position: absolute;
  right: calc(100% + 12px);
  top: 50%;
  white-space: nowrap;
  padding: 8px var(--space-3);
  background: var(--green); color: var(--ink);
  border: 1px solid var(--ink);
  font-family: var(--font-mono); font-size: .6875rem;
  letter-spacing: .14em; text-transform: uppercase;
  pointer-events: none;
  opacity: 0;
  transform: translateY(-50%) translateX(10px) scale(.94);
  /* leaving: flat and quick */
  transition: opacity 180ms ease, transform 180ms ease;
}

.fab__tip::after {
  content: ""; position: absolute; left: 100%; top: 50%;
  transform: translateY(-50%);
  border: 6px solid transparent; border-left-color: var(--green);
}

/* Closed menu means the only thing in .fab is the toggle, so hovering the group IS hovering
   the button — and :focus-within covers the keyboard path. */
.fab:not(.is-open):hover .fab__tip,
.fab:not(.is-open):focus-within .fab__tip {
  opacity: 1;
  transform: translateY(-50%) translateX(0) scale(1);
  /* arriving: a little overshoot */
  transition: opacity 420ms cubic-bezier(.34, 1.56, .64, 1),
              transform 420ms cubic-bezier(.34, 1.56, .64, 1);
}

.fab.is-open .fab__tip { display: none; }

@media (prefers-reduced-motion: reduce) {
  .fab__tip { transition: opacity 120ms linear; transform: translateY(-50%); }
  .fab:not(.is-open):hover .fab__tip,
  .fab:not(.is-open):focus-within .fab__tip { transform: translateY(-50%); }
}

/* ==========================================================================
   SUBPAGE FOLD — the opener on /work, the case studies and 404.

   The homepage fold is the lockup taking ink; a subpage cannot repeat that
   without the gesture losing its meaning. So subpages open on TYPE instead:
   the title dithers in over the graticule, with a mono readout underneath.
   Same instrument, different instrument reading.
   ========================================================================== */

.fold {
  position: relative;
  min-height: calc(72svh - var(--header-h));
  display: grid; align-content: center;
  padding: var(--space-7) var(--space-5) var(--space-6);
  background-image:
    linear-gradient(to right,  rgba(var(--phosphor), .03) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(var(--phosphor), .03) 1px, transparent 1px);
  background-size: 120px 120px;
  background-position: center top;
}

.fold__no {
  font-family: var(--font-mono); font-size: .6875rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--green); margin-bottom: var(--space-4);
}

.fold__title {
  font-family: var(--font-display); text-transform: uppercase; font-weight: 700;
  font-size: clamp(2.5rem, 8vw, 6.5rem); line-height: .9; margin: 0;
  color: var(--cream);
}

.fold__sub {
  font-family: var(--font-text); font-size: clamp(1rem, 1.6vw, 1.25rem);
  color: var(--muted); margin: var(--space-4) 0 0; max-width: 46ch;
}

.fold__meta {
  display: flex; flex-wrap: wrap; gap: var(--space-5);
  margin-top: var(--space-6); padding-top: var(--space-3);
  border-top: 1px solid rgba(var(--phosphor), .3);
  font-family: var(--font-mono); font-size: .6875rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--green);
}
.fold__meta b { font-weight: 400; color: var(--cream); }

/* The work index runs the full ten, so it gets its own rhythm: every third tile wide. */
@media (min-width: 1024px) {
  .grid--index .tile:nth-child(6n + 1),
  .grid--index .tile:nth-child(6n + 6) { grid-column: span 2; }
  .grid--index .tile:nth-child(6n + 1) .tile__img,
  .grid--index .tile:nth-child(6n + 6) .tile__img { aspect-ratio: 16/10; }
}

/* ==========================================================================
   404 — one screen. A HEADER band that says what happened and how to leave,
   then the game takes every pixel that is left.
   ========================================================================== */

.err {
  min-height: calc(100svh - var(--header-h));
  display: grid; grid-template-rows: auto 1fr;
}

/* A band, not a fold: it is sized by its content, so the game gets the remainder. */
.err__head {
  display: grid; gap: var(--space-2);
  padding: var(--space-5) var(--space-5) var(--space-4);
  border-bottom: 1px solid rgba(var(--phosphor), .3);
  background-image:
    linear-gradient(to right,  rgba(var(--phosphor), .03) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(var(--phosphor), .03) 1px, transparent 1px);
  background-size: 120px 120px;
  background-position: center top;
}

.err__no {
  font-family: var(--font-mono); font-size: .6875rem;
  letter-spacing: .18em; text-transform: uppercase; color: var(--green);
}

.err__title {
  font-family: var(--font-display); text-transform: uppercase; font-weight: 700;
  font-size: clamp(2.5rem, 6vw, 4.75rem); line-height: .9; margin: 0;
  color: var(--cream);
}

.err__sub {
  font-family: var(--font-text); font-size: clamp(1rem, 1.4vw, 1.125rem);
  color: var(--muted); margin: 0; max-width: 40ch;
}

.err__links {
  display: flex; flex-wrap: wrap; gap: var(--space-4);
  font-family: var(--font-mono); font-size: .6875rem;
  letter-spacing: .16em; text-transform: uppercase;
}
.err__links a {
  color: var(--green); text-decoration: none;
  border-bottom: 1px solid rgba(var(--phosphor), .35);
  padding-bottom: .25em; transition: border-color 160ms ease;
}
.err__links a:hover,
.err__links a:focus-visible { border-bottom-color: var(--green); }

/* Wide: the headline sits on the left and the exits line up on the right, baseline-aligned,
   so the whole thing reads as one masthead rule rather than a stacked hero. */
@media (min-width: 900px) {
  .err__head {
    grid-template-columns: auto 1fr auto;
    column-gap: var(--space-6); align-items: baseline;
  }
  .err__no    { grid-column: 1 / -1; }
  .err__sub   { padding-bottom: .35em; }
  .err__links { justify-self: end; padding-bottom: .35em; }
}

/* ── Chase the signal ─────────────────────────────────────────────────────
   Snake wearing the site's own clothes: the head is the beam, the fading tail
   is the same phosphor persistence used everywhere else, and the field is the
   graticule. Losing is "signal lost", which is also what a 404 is.            */

.game {
  display: grid; grid-template-rows: auto 1fr auto;
  padding: var(--space-4) var(--space-5) var(--space-5);
  min-height: 0;                      /* let the 1fr board shrink instead of pushing the page */
}

.game__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-3);
}

.game__title {
  font-family: var(--font-display); text-transform: uppercase; font-weight: 700;
  font-size: clamp(1.5rem, 2.6vw, 2.25rem); line-height: 1; margin: 0;
  color: var(--cream);
}

.game__readout {
  margin: 0; font-family: var(--font-mono); font-size: .6875rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--green);
}
.game__readout b { font-weight: 400; color: var(--cream); font-size: .875rem;
                   display: inline-block; }   /* scalable: GSAP pops it on every pickup */

/* The board's measuring box. It owns the free space; the stage inside shrink-wraps the
   canvas, so the canvas is sized from THIS element and never from its own parent. */
.game__board {
  min-height: 0; min-width: 0;
  display: flex; align-items: center; justify-content: center;
}

.game__stage {
  position: relative; display: flex; justify-content: center;
  width: max-content; max-width: 100%;
  padding: 12px;                      /* the bezel — js/game.js subtracts this, keep in sync */
  background: var(--ink);
  border: 1px solid rgba(var(--phosphor), .3);
  box-shadow: var(--glow-rule);
}

.game__stage canvas { display: block; max-width: 100%; touch-action: none; outline: none; }
.game__stage canvas:focus-visible { outline: 2px solid var(--green); outline-offset: -3px; }

/* Sits over the board: it is the start button before the first run and the
   restart button after a crash, so it belongs where the eye already is. */
.game__start {
  position: absolute; inset: 0; margin: auto;
  width: max-content; height: max-content;
  background: var(--green); color: var(--ink);   /* ink on green — never cream */
  border: 0; cursor: pointer;
  font-family: var(--font-mono); font-size: .75rem;
  letter-spacing: .16em; text-transform: uppercase;
  padding: .85rem 1.75rem;
}
.game__start:hover { box-shadow: 0 0 18px rgba(var(--phosphor), .45); }
.game__start:focus-visible { outline: 2px solid var(--cream); outline-offset: 3px; }

.game__state {
  margin: var(--space-3) 0 0; font-family: var(--font-mono);
  font-size: .6875rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted); min-height: 1.2em;
}

/* Mobile is its own composition (§8): the band tightens and the board takes the width it
   needs rather than sitting inside desktop gutters. */
@media (max-width: 767px) {
  .err { min-height: 0; }
  .err__head { padding: var(--space-4) var(--space-3) var(--space-3); }
  .err__links { gap: var(--space-3); }
  .game { padding: var(--space-3) var(--space-3) var(--space-5); }
  .game__board { min-height: 58svh; }   /* the portrait field wants the height */
}

/* ==========================================================================
   Case studies. §12b's subpage grammar: a full green fold with ink type and an
   inverted ink plate landing on the fold line, then the reading happens on ink.
   ========================================================================== */

.cs-fold {
  background: var(--green); color: var(--ink);
  padding: var(--space-7) var(--space-5) 0;
  display: grid; align-content: end;
  min-height: calc(78svh - var(--header-h));
}

.cs-fold__no {
  font-family: var(--font-mono); font-size: .6875rem;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink); opacity: .65;
}

.cs-fold__title {
  font-family: var(--font-display); text-transform: uppercase; font-weight: 700;
  font-size: clamp(2.5rem, 7.5vw, 6rem); line-height: .9;
  margin: var(--space-3) 0 0; color: var(--ink);   /* ink on green — never cream (§4) */
}

.cs-fold__client {
  font-family: var(--font-text); font-weight: 700; font-size: .8125rem;
  letter-spacing: .12em; text-transform: uppercase;
  margin: var(--space-3) 0 0; color: var(--ink); opacity: .75;
}

/* The plate is the inverted moment: ink field sitting ON the fold line. */
.cs-plate {
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5);
  list-style: none; margin: var(--space-6) calc(var(--space-5) * -1) 0;
  padding: var(--space-3) var(--space-5);
  background: var(--ink); color: var(--green);
  font-family: var(--font-display); text-transform: uppercase; font-weight: 700;
  font-size: 1.3125rem; line-height: 1;   /* ~21px — the plate exception in §4 */
}

/* ---------------------------------------------------------------- cs body */

.cs-mv { border-top: 1px solid rgba(var(--phosphor), .28); }

.cs-body {
  font-family: var(--font-text); font-size: clamp(1rem, 1.5vw, 1.1875rem);
  line-height: 1.65; color: var(--cream); margin: 0; max-width: 62ch;
  padding-block: var(--space-5) var(--space-6);
}

.cs-figure { margin: 0; padding: 0 var(--space-5) var(--space-6); }
/* Capped so a figure never upscales past its source. The case-study originals top out at
   825px wide; rendered edge-to-edge in a 1290px column they were being blown up ~1.5x (and the
   -t thumbs, which is what was actually being served, ~3x — visibly pixelated). If bigger
   source exports arrive, raise this cap and the srcset widths together. */
.cs-figure img {
  display: block; width: 100%; height: auto;
  max-width: 860px; margin-inline: auto;
  border: 1px solid rgba(var(--phosphor), .28);
}
.cs-figure figcaption, .cs-cell__cap {
  font-family: var(--font-mono); font-size: .6875rem;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted); margin-top: var(--space-2);
}

/* ---------------------------------------------------------------- grids */

.cs-grid {
  display: grid; gap: var(--space-4); list-style: none;
  margin: 0; padding: 0 var(--space-5) var(--space-6);
  grid-template-columns: 1fr;
}
@media (min-width: 640px)  { .cs-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .cs-grid--guide { grid-template-columns: repeat(3, 1fr); } }

.cs-grid--pair { grid-template-columns: 1fr; }
@media (min-width: 640px) { .cs-grid--pair { grid-template-columns: repeat(2, 1fr); } }

.cs-cell { min-width: 0; }
.cs-cell img {
  display: block; width: 100%; height: auto;
  border: 1px solid rgba(var(--phosphor), .28);
  background: var(--ink);
}

/* Zoomable cells are buttons, not images with click handlers — keyboard and AT get it free. */
.cs-zoom {
  display: block; width: 100%; padding: 0; border: 0; background: none;
  cursor: zoom-in; text-align: left; color: inherit;
}
.cs-zoom:focus-visible { outline: 2px solid var(--green); outline-offset: 3px; }
@media (prefers-reduced-motion: no-preference) {
  .cs-zoom img { transition: border-color 220ms ease; }
}
.cs-zoom:hover img, .cs-zoom:focus-visible img { border-color: var(--green); }

/* ---------------------------------------------------------------- next */

.cs-next {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-5); text-decoration: none;
  border-top: 1px solid rgba(var(--phosphor), .28);
  background: var(--ink);
}
.cs-next__label {
  font-family: var(--font-mono); font-size: .6875rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--green);
}
.cs-next__title {
  font-family: var(--font-display); text-transform: uppercase; font-weight: 700;
  font-size: clamp(1.5rem, 4vw, 3rem); line-height: 1; color: var(--cream);
}
.cs-next:hover .cs-next__title, .cs-next:focus-visible .cs-next__title { color: var(--green); }

/* ---------------------------------------------------------------- lightbox */

.lb { border: 0; padding: 0; background: transparent; max-width: 100vw; max-height: 100vh;
      width: 100%; height: 100%; }
.lb::backdrop { background: rgba(14, 11, 7, .93); }
.lb__inner { display: grid; grid-template-rows: 1fr auto; height: 100%;
             padding: var(--space-4); gap: var(--space-3); }
.lb__img { justify-self: center; align-self: center; max-width: 100%; max-height: 100%;
           object-fit: contain; border: 1px solid rgba(var(--phosphor), .3); }
.lb__bar { display: flex; align-items: center; justify-content: space-between;
           gap: var(--space-3); font-family: var(--font-mono); font-size: .6875rem;
           letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
.lb__close { background: var(--green); color: var(--ink); border: 0; cursor: pointer;
             font-family: var(--font-mono); font-size: .6875rem; letter-spacing: .16em;
             text-transform: uppercase; padding: .6rem 1.1rem; }
.lb__nav { display: flex; gap: var(--space-2); }
.lb__nav button { background: none; border: 1px solid rgba(var(--phosphor), .4);
                  color: var(--green); cursor: pointer; padding: .5rem .9rem;
                  font-family: var(--font-mono); font-size: .75rem; }
.lb__nav button:hover { background: var(--green); color: var(--ink); }

@media (max-width: 767px) {
  .cs-fold { padding: var(--space-6) var(--space-3) 0; min-height: calc(62svh - var(--header-h)); }
  .cs-plate { margin-inline: calc(var(--space-3) * -1); padding-inline: var(--space-3); }
  .cs-body, .cs-figure, .cs-grid { padding-inline: var(--space-3); }
  .cs-next { padding: var(--space-4) var(--space-3); }
}

/* ==========================================================================
   Reduced motion — the transform sweep (Phase 5).
   Deliberately NOT a global `* { animation: none }` nuke. prefers-reduced-motion is about
   vestibular triggers: MOVEMENT. Opacity, colour and border transitions are not motion, and
   killing them makes the interface feel broken rather than safer, so those stay.
   What follows is every remaining place something actually MOVES.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  /* The drawer. js/crt.js runs `hasGsap && OK`, so under reduce it falls back to the CSS
     class toggle — which was still sliding the panel 101% across the screen. Now it appears. */
  .site-nav { transition: none; }

  /* The masthead's hide-on-scroll module returns early under reduce, so these never fire
     today. They are pinned anyway: the guard should live with the thing that moves, not
     depend on a JS module elsewhere continuing to bail. */
  .site-header,
  .mv__head { transition: none; }
  html.hdr-hidden .site-header { transform: none; }
  html.hdr-hidden .mv__head { transform: none; }

  /* The FAQ marker rotating + into x. The state still changes, it just stops spinning. */
  .faq__q::before { transition: none; }
}

/* The four inline `style="padding-bottom: …"` declarations became this. Inline styles beat
   every stylesheet rule, so those four elements were quietly immune to any spacing change
   made in CSS — one system, not two. */
.wrap--pad-7 { padding-bottom: var(--space-7); }
.wrap--pad-8 { padding-bottom: var(--space-8); }

/* The highlight the "check the highlighted field" copy has always promised. Border + ring
   rather than colour alone — colour is never the only carrier of meaning. Orange is §4's
   editorial accent and is 5.57:1 on ink, so it passes AA for this size. */
.field input[aria-invalid="true"],
.field select[aria-invalid="true"],
.field textarea[aria-invalid="true"] {
  border-color: var(--orange);
  box-shadow: 0 0 0 2px rgba(250, 70, 22, .28);
}

/* ==========================================================================
   FAB label on touch. The tooltip is revealed by :hover / :focus-within, and a touch device
   has no hover — focus only lands there AFTER a tap, which opens the menu, so the label was
   unreachable on phones. `hover: none` targets the actual capability rather than guessing
   from width, so a touchscreen laptop gets the right treatment too.

   On those devices the label is simply always on: no transition, no transform, and the
   entrance/exit curves above never apply.
   ========================================================================== */

/* Touch reveal. Driven by .is-peek from js/crt.js rather than left permanently on: the label
   was obstructing real content (it covered "Budget range (optional)" on /contact at 390px),
   and a label whose only job is discovery does not need to be there after it has been read. */
@media (hover: none) {
  .fab.is-peek .fab__tip { opacity: 1; transform: translateY(-50%); }
  .fab.is-open .fab__tip { opacity: 0; }   /* menu open — the items say the same thing */
}

/* ==========================================================================
   Mobile fold corrections (Aug 2026), from a rendered 390x844 pass.
   ========================================================================== */

@media (max-width: 767px) {
  /* The plate wraps to TWO rows at this width. The hero subtracts a 2.25rem sliver so the
     plate peeks and the fold reads as "there is more" — but a two-row plate needs a two-row
     peek, or the fifth service (Signage) lands under the fold and looks cut off rather than
     deliberate. Measured: the wrapped plate is ~59px, so the sliver has to clear that. */
  .hero { min-height: calc(100svh - var(--header-h) - 4.25rem); }

  /* The floating action sat on top of that same plate. It is hidden while the fold is on
     screen — nothing has been seen yet, so the shortcut has nothing to shortcut — and fades
     in once the visitor is into the work. Hidden by visibility as well as opacity so it is
     out of the tab order too, not just invisible. */
  html.hero-in-view .fab {
    opacity: 0; visibility: hidden; pointer-events: none;
  }
}

@media (max-width: 767px) and (prefers-reduced-motion: reduce) {
  .fab { transition: none; }
}

/* The one door in the studio stories. Social Church is the only one of the three with a case
   study, so it is the only one that leads anywhere — depth-by-click (§2). It sits AFTER the
   blurb rather than underlining the client name mid-sentence: an inline underline fights the
   reading and the section is deliberately quiet. Same plate as .tile__go, so "green plate with
   an arrow" means the same thing here as it does on a work tile — one signal, learned once. */
.story__go {
  justify-self: start;
  display: inline-flex; align-items: baseline; gap: .5em;
  margin-top: var(--space-2);
  padding: 5px 9px;
  background: var(--green); color: var(--ink);   /* ink on green — §4 */
  font-family: var(--font-text); font-weight: 700; font-size: .6875rem;
  letter-spacing: .08em; text-transform: uppercase; text-decoration: none;
}
@media (prefers-reduced-motion: no-preference) {
  .story__go { transition: background-color 140ms ease; }
}
.story__go:hover, .story__go:focus-visible { background: var(--cream); }

/* The story is a two-column grid at width (number | prose); keep the door under the prose
   rather than letting it fall into the number column on the next row. */
@media (min-width: 900px) {
  .story__go { grid-column: 2; }
}

/* ==========================================================================
   Testimonial rail affordance. `.quotes` hides its scrollbar, so nothing signalled that it
   scrolled at all. Built only from type and rules already in the system — a green fill on an
   ink track, a mono counter in the same voice as the REF chips and the running heads, and two
   ink-on-green plates matching .tile__go.
   ========================================================================== */

.quotes:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; }

.rail__bar {
  height: 3px; background: rgba(var(--phosphor), .16);
  margin: 0 var(--space-4);
  overflow: hidden;
}
.rail__fill {
  display: block; height: 100%; width: 0;
  background: var(--green); box-shadow: var(--glow-rule);
  transform-origin: left center;
}
@media (prefers-reduced-motion: no-preference) {
  /* transform only — the thumb's width changes just on resize, never during a scroll */
  .rail__fill { transition: transform 90ms linear; }
}

.rail__ctl {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); padding: var(--space-2) var(--space-4) 0;
}

.rail__count {
  margin: 0; font-family: var(--font-mono); font-size: .6875rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--muted);
}
.rail__count b { font-weight: 400; color: var(--green); }

.rail__arrows { display: flex; gap: var(--space-1); }

.rail__btn {
  width: 34px; height: 34px; display: grid; place-items: center;
  background: var(--green); color: var(--ink);   /* ink on green — §4 */
  border: 0; cursor: pointer; font-size: .875rem; line-height: 1;
  font-family: var(--font-mono);
}
.rail__btn:hover:not(:disabled) { background: var(--cream); }
.rail__btn:focus-visible { outline: 2px solid var(--green); outline-offset: 3px; }
.rail__btn:disabled {
  background: none; color: var(--muted); cursor: default;
  box-shadow: inset 0 0 0 1px rgba(var(--phosphor), .3);
}
@media (prefers-reduced-motion: no-preference) {
  .rail__btn { transition: background-color 140ms ease, color 140ms ease; }
}

/* Hidden via the [hidden] attribute from crt.js when the rail does not overflow. display is
   set explicitly on .rail__ctl, which would otherwise beat [hidden]. */
.rail__bar[hidden], .rail__ctl[hidden] { display: none; }
