/* ============================================================================
   VOLA - badminton scoreboard. "Paper" edition.
   ----------------------------------------------------------------------------
   The language: warm chalk paper, a light editorial serif doing display, a
   grotesque doing body, handwritten marginalia for labels, a faint court grid,
   hairline rules, and ONE saturated accent.

   Two things this file does deliberately differently from the reference it was
   adapted from, and both are structural rather than decorative:

   1. THE ACCENT IS A PAIR WITH SEPARATED JOBS. The reference runs a single
      electric blue as link colour, as highlighter fill and as emphasis body
      copy - so a link and a highlighted phrase read as the same signal. Here
      --court is INK (links, inverted surfaces, rules) and --tape is FILL (the
      marker, the active tick, the data bar). --tape is never text on paper:
      #F5B02E on #F4F1E8 measures 1.9:1 - still a fill, never text.

   2. THE PAGE MODULATES. The reference is eight screens of one cream, which is
      why its sections stop reading as sections. Here paper carries the
      READING and --court carries the two short image-led bands, so the
      inversion always means "look" and never "read six paragraphs on green".

   No build step, no dependency. Every colour is a token.
   ========================================================================= */

/* ---------------------------------------------------------------- TOKENS -- */
:root{
  /* Surfaces. Chalk, not skin - the reference's cream is warm-pink because it
     is a page about bodies. A court is cooler and greyer. */
  --paper:#F4F1E8;
  --ink:#14140F;
  --ink-2:#4C4A40;          /* body copy */
  --ink-3:#6A6759;          /* captions only, never below 13px */

  /* THE GREEN, AND IT HAS TWO JOBS. --court is the GROUND the inverted band,
     the stat strip and the solid button are painted on; --court-ink is the
     same green used as TEXT. They are equal in the light theme and they are
     NOT in the dark one, which is the whole reason they are two tokens. */
  --court:#161513;
  --court-hover:#2A2823;
  --court-ink:#8A5A00;
  /* Text that sits ON the green ground. Near-white in BOTH themes, because
     the ground it sits on is the same dark green in both. */
  --on-court:#F4F1E8;
  /* ...and on the tape, which is a LIGHT fill in both themes, so its ink is
     dark in both. It was inheriting --ink and measured 1.11:1 once --ink
     flipped. A fill that does not move needs an ink that does not move. */
  --on-tape:#14140F;
  --tape:#F5B02E;           /* app amber. FILL ONLY - never text on paper */

  --line:rgba(20,20,15,.14);       /* hairline on paper */
  --line-2:rgba(20,20,15,.07);     /* the chalk grid on paper */
  --line-on:rgba(244,241,232,.20); /* hairline on green */
  --grid-on:rgba(244,241,232,.06); /* the chalk grid on green */

  /* Type. Newsreader is a light modern serif with an optical-size axis, which
     is what lets the 300 weight hold at 96px AND at 22px without reading as
     two different faces. Manrope does body. Kalam does the marginalia. */
  --serif:'Newsreader',Georgia,'Times New Roman',serif;
  --sans:'Manrope',system-ui,-apple-system,'Segoe UI',sans-serif;
  --hand:'Kalam','Bradley Hand',cursive;

  /* One scale, clamped. Check the middle of a clamp, not just its ends: the
     first pass ran body at 1.02vw, which is 14.7px at 1440 - so it sat on its
     15.5px FLOOR at the single most common desktop width, and the ramp only
     started doing anything past 1716px. Both text scales now reach their
     intended size at 1440.

     The hero is capped at 100 rather than left to run with
     vw: past that the two-line headline breaks to three at 1920 and the
     right-hand phone plate loses its top alignment. */
  --fs-hero:clamp(40px,6.4vw,100px);
  --fs-statement:clamp(25px,3.3vw,50px);
  --fs-section:clamp(29px,3.6vw,58px);
  --fs-feat:clamp(23px,2.2vw,34px);
  --fs-lead:clamp(17.5px,1.45vw,21.5px);
  --fs-body:clamp(16px,1.22vw,18px);
  --fs-micro:12px;

  /* THE MEASURE. The single biggest legibility failure in the reference is
     running copy at the full container - about 110 characters a line at 1440.
     Nothing here reads wider than 66 ACTUAL characters, which is 54ch: the ch
     unit is the width of "0" and Manrope's zero is about 1.31x its average
     character, so a 64ch cap measured 79 characters on the page and 54ch
     still measured 71 - but that metric was counting the SOURCE string, markup
     whitespace and all. Measured properly, off the widest rendered line rect,
     57ch is 66 characters. Measure off the page, never off the string. */
  --measure:57ch;

  /* Rhythm. --sec-x matches nav.css exactly, so the bar's edge and every
     section's edge are one line down the page. */
  --sec-x:clamp(20px,6vw,120px);
  --sec-y:clamp(84px,9.5vw,164px);
  --maxw:1500px;
  --r:18px;
  --r-lg:28px;
  --ease:cubic-bezier(.22,.61,.36,1);

  /* THE SHARED NAVBAR, re-tinted through its own tokens and nothing else.
     Never restyle .nav/.cta/.mnav from here - that is the moment there are two
     navbars. This page's bar is paper with court-green ink. */
  --nav-bg:#F4F1E8;
  --nav-ink:#14140F;
  --nav-muted:#6A6759;
  --nav-faint:#8C8879;
  --nav-line:rgba(20,20,15,.14);
  --nav-fill:rgba(138,90,0,.08);
  --nav-edge:rgba(138,90,0,.34);
  --nav-edge-hi:rgba(138,90,0,.64);
  --nav-hair:rgba(20,20,15,.10);
  --nav-press:rgba(138,90,0,.09);
  --nav-grid:rgba(20,20,15,.045);
  --nav-eye-ring:rgba(20,20,15,.22);
  --nav-accent:#8A5A00;
  --nav-serif:var(--serif);
  --nav-sans:var(--sans);
}

/* ------------------------------------------------------------------ BASE -- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}

body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--sans);
  font-size:var(--fs-body);
  line-height:1.62;
  font-synthesis-weight:none;
  -webkit-font-smoothing:antialiased;
  /* The rule that guarantees no sideways scroll anywhere. Everything that
     bleeds does so inside a section, never past the body. */
  overflow-x:hidden;
}

img,video,svg{display:block;max-width:100%}
a{color:var(--court-ink);text-underline-offset:3px;text-decoration-thickness:1px}
a:hover{text-decoration-thickness:2px}

h1,h2,h3,h4{
  font-family:var(--serif);
  font-weight:300;
  line-height:1.04;
  letter-spacing:-.022em;
  margin:0;
  color:var(--ink);
  text-wrap:balance;
}
/* A 300-weight serif is beautiful at 96px and spindly at 40px - the hairlines
   of a modern face fall below a device pixel. Below 600 the display weight
   steps UP rather than the size stepping down. */
@media (max-width:600px){
  h1,h2,h3{font-weight:400;letter-spacing:-.014em}
}

p{margin:0;color:var(--ink-2);text-wrap:pretty}
p + p{margin-top:1.05em}

::selection{background:var(--tape);color:var(--ink)}

:focus-visible{outline:2.5px solid var(--court-ink);outline-offset:3px;border-radius:5px}
.band :focus-visible{outline-color:var(--tape)}

.skip{
  position:fixed;left:12px;top:-80px;z-index:200;
  background:var(--court);color:var(--on-court);
  padding:12px 18px;border-radius:10px;font-weight:700;
  text-decoration:none;transition:top .18s var(--ease);
}
.skip:focus{top:12px}

/* ------------------------------------------------------------ STRUCTURE -- */
/* ONE-SIDED RHYTHM - the same rule gpay.css landed on. Nothing on paper has a
   background of its own, so a section's bottom pad and the next one's top pad
   are the same empty stretch bought twice. Only .close pays a bottom pad. */
.sec{position:relative;padding:var(--sec-y) var(--sec-x) 0}

/* The inverted band. Full-bleed by construction rather than by a negative
   margin, so it can never be the thing that creates a horizontal scrollbar. */
.band{
  background:var(--court);color:var(--on-court);
  padding:clamp(62px,7vw,116px) var(--sec-x);
  position:relative;overflow:hidden;
}
.band h2,.band h3,.band h4{color:var(--on-court)}
.band p{color:rgba(244,241,232,.84)}
.band a{color:var(--tape)}

/* THE CHALK GRID. Two repeating gradients, no image. It is what makes a plain
   rectangle read as a surface someone chose rather than as a block that failed
   to load - the same argument the NDA tiles on the homepage make. */
.grid-bg::before{
  content:'';position:absolute;inset:0;pointer-events:none;z-index:0;
  background:
    repeating-linear-gradient(90deg,var(--gc,var(--line-2)) 0 1px,transparent 1px var(--gs,84px)),
    repeating-linear-gradient(0deg, var(--gc,var(--line-2)) 0 1px,transparent 1px var(--gs,84px));
  /* Faded before the edges so it reads as a drawn field rather than as
     wallpaper that got cropped. */
  -webkit-mask-image:radial-gradient(112% 82% at 50% 26%,#000 28%,transparent 96%);
          mask-image:radial-gradient(112% 82% at 50% 26%,#000 28%,transparent 96%);
}
.band.grid-bg::before{--gc:var(--grid-on)}
.grid-bg > *{position:relative;z-index:1}
@media (max-width:600px){.grid-bg::before{--gs:56px}}

/* --------------------------------------------------------- MARGINALIA -- */
/* The handwritten label and its drawn underline. This is the reference's whole
   personality and the one thing worth taking wholesale - but set in Kalam
   rather than the reference's face, and the rule is a real SVG path rather
   than a second line of text pretending to be one. */
.marg{
  display:inline-flex;flex-direction:column;gap:1px;
  font-family:var(--hand);font-weight:400;
  font-size:clamp(19px,1.45vw,23px);
  line-height:1.2;color:var(--court-ink);letter-spacing:0;
}
.band .marg{color:var(--tape)}
.marg svg{width:100%;height:9px;overflow:visible}
.marg svg path{fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;opacity:.7}

/* THE MARKER. --tape as a fill behind ink, never as ink itself.
   box-decoration-break is what puts the block on every line of a wrap rather
   than one long rectangle behind the whole phrase. */
mark{
  background:var(--tape);color:var(--ink);
  padding:.02em .16em .08em;border-radius:3px;
  -webkit-box-decoration-break:clone;box-decoration-break:clone;
}


.lead{font-size:var(--fs-lead);line-height:1.55;color:var(--ink-2);max-width:var(--measure)}

/* ------------------------------------------------------------- BUTTONS -- */
.btn{
  display:inline-flex;align-items:center;gap:10px;
  min-height:48px;padding:13px 22px;      /* >=44px touch target everywhere */
  border-radius:999px;border:1.5px solid transparent;
  font-family:var(--sans);font-weight:700;font-size:15px;letter-spacing:-.01em;
  text-decoration:none;cursor:pointer;
  transition:background .2s var(--ease),color .2s var(--ease),
             border-color .2s var(--ease),transform .2s var(--ease);
}
.btn-solid{background:var(--court);color:var(--on-court)}
.btn-solid:hover{background:var(--court-hover)}
.btn-ghost{background:transparent;color:var(--ink);border-color:var(--line)}
.btn-ghost:hover{border-color:var(--ink);background:rgba(20,20,15,.04)}
.band .btn-solid{background:var(--tape);color:var(--on-tape)}
.band .btn-solid:hover{background:#E4FA6B}
.band .btn-ghost{color:var(--on-court);border-color:var(--line-on)}
.band .btn-ghost:hover{border-color:var(--on-court);background:rgba(244,241,232,.08)}

/* Position-based, never class-based, so it cannot be misapplied: a LEADING
   arrow nudges the way it points (left), a TRAILING one goes up and right. */
.btn svg{flex:none;transition:transform .22s var(--ease)}
.btn:hover svg:first-child{transform:translateX(-3px)}
.btn:hover svg:last-child{transform:translate(3px,-2px)}

/* ============================================================================
   THE CLIP
   ----------------------------------------------------------------------------
   Every screen on this page is a clip: a still <img> with a muted <video>
   layered over it that has NO src until vola.js gives it one. Nine files,
   shown in twelve slots.

   Three things carried over from build-clips.js and load-bearing here:

   - THE SILHOUETTE IS A border-radius, NOT AN ALPHA CHANNEL. Video has no
     alpha, so the device was cropped out of its transparent padding at build
     time and the corners are cut in CSS. Both halves of 16.5%/8% matter:
     percentages resolve per axis, so one number gives an elliptical corner on
     a 600x1238 box.
   - .clip MUST STAY position:relative. The media inside is absolutely
     positioned; with no positioning context it escapes to the nearest ancestor
     and the phone renders cropped on all four sides.
   - THE POSTER IS THE LAST FRAME. Frame one is the held opening the app's
     entrance animates out of, so a poster of it is a near-empty screen. The
     last frame is the assembled screen, and it is also where the loop returns
     from - so the hand back to the still is the one place nothing moves.
   ========================================================================= */
.clip{
  position:relative;
  aspect-ratio:600/1238;
  height:100%;width:auto;
  margin-inline:auto;
  border-radius:16.5% / 8%;
  overflow:hidden;
  background:#0B0B08;
  /* A real shadow, because the phone floats on a plate rather than sitting in
     a frame. Two layers: a tight contact shadow and a long soft one. */
  box-shadow:0 2px 6px rgba(11,11,8,.18),0 26px 60px -18px rgba(11,11,8,.42);
}
.clip-still,.clip-v{
  position:absolute;inset:0;
  width:100%;height:100%;
  object-fit:cover;
}
.clip-v{opacity:0;transition:opacity .32s var(--ease)}
.clip.is-playing .clip-v{opacity:1}

/* NO PLAY BUTTON, by the owner's call (2026-09-24). A clip plays once when it
   reaches the fold and then rests on its still for good. Where autoplay is
   refused (iOS Low Power Mode) or reduced motion is on, the still IS the
   screen - a full-resolution crop of the owner's export, not a stand-in. */

/* A media stage needs a DEFINITE SIZE, however it gets one. The clip is
   height-driven (width:auto), so every stage below sets a height or a
   max-height and the clip fits itself to it. Capping by HEIGHT rather than
   width is what keeps a phone and its caption in one look on a short laptop. */

/* =============================================================== 01 HERO -- */
.hero{
  position:relative;
  padding:calc(var(--nav-h) + clamp(46px,6vw,92px)) var(--sec-x) 0;
}
/* THREE AREAS, AND THE DOM IS IN THE SAME ORDER AS ALL THREE OF THEM.
   The obvious build is copy-then-plate with `order:-1` on the plate below the
   fold, so the picture leads on a phone. It renders correctly and it is wrong:
   `order` moves the box and not the tab stop, so the plate's play button would
   be visually first and focused last, and a screen reader would read the meta
   grid before the screens it describes. Three named areas re-flow to
   copy / plate / meta at every width with no `order` anywhere, so the visual,
   DOM and focus orders are one order. */
.hero-in{
  max-width:var(--maxw);margin:0 auto;
  display:grid;
  grid-template-columns:minmax(0,1.02fr) minmax(0,.98fr);
  grid-template-areas:"copy plate" "meta meta";
  grid-template-rows:auto auto;
  gap:clamp(26px,2.8vw,44px) clamp(36px,4.5vw,84px);
  align-items:start;
}
 .hero-copy{grid-area:copy;padding-top:clamp(4px,1vw,18px)}
.hero-plate{grid-area:plate}
.hero-meta{grid-area:meta}

/* CENTRED HERO (no side phone). Copy caps at a reading measure and centres;
   the spec grid spans the same column so the whole header reads as one block. */
.hero-centre .hero-in{
  grid-template-columns:minmax(0,1fr);
  grid-template-areas:"copy" "meta";
  max-width:min(880px,var(--maxw));margin-inline:auto;
}
.hero-centre .hero-copy{padding-top:0;text-align:center}
.hero-centre .hero-copy .lead{margin-inline:auto}
.hero-centre .hero-cta{justify-content:center}

/* PRODUCT FILM FOLD. One 16:9 frame, full measure, poster first. The frame's
   size, radius and shadow are this page's; the player inside it - controls,
   click-to-toggle, resume - is assets/css/film.css + assets/js/film.js. */
.intro-film{max-width:var(--maxw);margin:0 auto}
.intro-film-frame{
  position:relative;overflow:hidden;border-radius:var(--r-lg);
  aspect-ratio:16/9;background:#0B0B08;
  box-shadow:0 2px 6px rgba(11,11,8,.18),0 26px 60px -18px rgba(11,11,8,.42);
  /* Never taller than the screen under the nav, so the controls are always
     in the same look as the picture. Does not bind at 1440x900. */
  max-width:max(320px,calc((100vh - 120px) * 16 / 9));margin-inline:auto;
}

.hero h1{
  font-size:var(--fs-hero);
  margin:18px 0 0;
  max-width:15ch;      /* two lines at every width from 360 to 1920 */
}
.hero .lead{margin-top:clamp(20px,2vw,30px)}

/* THE META GRID. The reference puts Role / Timeline / Live / Recognition in a
   2x2 under the intro with hand-lettered labels, and it is the best thing on
   that page: it answers every "what is this, who made it, when" question in
   one glance without a single sentence. Kept, tightened, and given a real
   surface so it reads as a spec block rather than as four stray captions. */
.meta{
  margin-top:clamp(24px,2.6vw,40px);
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:1px;background:var(--line);
  border:1px solid var(--line);border-radius:var(--r);
  overflow:hidden;
}
.meta-cell{background:var(--paper);padding:clamp(16px,1.5vw,22px)}
.meta-cell .marg{margin-bottom:6px}
.meta-cell p{font-size:15px;line-height:1.5;color:var(--ink)}
.meta-cell p span{display:block;color:var(--ink-3)}

/* ------------------------------------------------ THE HERO CYCLER ------- */
/* ONE PHONE, THREE SCREENS, ONE AT A TIME.

   This replaced a composed trio of three phones. The trio showed everything at
   once and needed no controls, which is a real virtue, but it bought that by
   making each phone 228px wide at 1440 and clipping two of them behind the
   third - on a page whose entire argument is that these screens are legible.
   Alone, the phone is 300px+ and every screen gets seen whole.

   THE HANDOVER IS DRIVEN BY `ended`, NOT BY A TIMER. A timer has to guess a
   duration, and these nine recordings are not the same length; guess short and
   you cut the app's entrance in half, guess long and the phone sits dead on
   its last frame. Ending is the only moment that is correct for every clip,
   and it is also the one moment nothing is moving - the poster underneath is
   that same last frame - so the crossfade lands on a still picture. */
.hero-plate{
  position:relative;
  background:var(--court);
  border-radius:var(--r-lg);
  padding:clamp(28px,3.2vw,48px) clamp(16px,2vw,34px) clamp(62px,5vw,80px);
  display:flex;align-items:center;justify-content:center;
  overflow:hidden;
}

/* The stage. A DEFINITE height, because the clips inside are absolutely
   positioned and height-driven - with an auto height they resolve to nothing. */
.cyc{
  position:relative;
  width:100%;
  height:min(560px,60vh);
}
.cyc-item{
  position:absolute;top:50%;left:50%;
  height:100%;
  transform:translate(-50%,-50%) scale(.985);
  opacity:0;visibility:hidden;
  transition:opacity .5s var(--ease),transform .5s var(--ease),visibility 0s .5s;
}
.cyc-item.is-on{
  opacity:1;visibility:visible;
  transform:translate(-50%,-50%) scale(1);
  transition:opacity .5s var(--ease),transform .5s var(--ease),visibility 0s 0s;
}
/* visibility, not display:none - a display swap would drop the video element
   out of layout and some engines discard the decode with it. */

@media (prefers-reduced-motion:reduce){
  .cyc-item,.cyc-item.is-on{transition:none;transform:translate(-50%,-50%)}
}

/* ---- the plate's footer: label left, controls right ---- */
.plate-foot{
  position:absolute;left:clamp(16px,2vw,26px);right:clamp(16px,2vw,26px);
  bottom:clamp(14px,1.6vw,22px);z-index:3;
  display:flex;align-items:center;justify-content:space-between;gap:12px;
}
.plate-tag{
  display:flex;align-items:center;gap:8px;margin:0;
  font-family:var(--sans);font-weight:700;font-size:11.5px;
  letter-spacing:.14em;text-transform:uppercase;line-height:1.25;
  color:rgba(244,241,232,.82);   /* 11.5px bold is not WCAG large text */
}
.plate-tag i{width:7px;height:7px;border-radius:50%;background:var(--tape);flex:none}
.plate-tag span{color:var(--tape)}

.cyc-nav{display:flex;align-items:center;gap:4px}

/* The controls are 32px of ink inside a 44px hit area. Anything that advances
   on its own has to be stoppable (WCAG 2.2.2), and a 12px dot is not a target
   - the padding is what makes it one without making the dots look like
   buttons. */
.cyc-hold,.cyc-dot{
  -webkit-appearance:none;appearance:none;
  background:none;border:0;cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;
  min-width:34px;min-height:34px;padding:0;
  border-radius:50%;
  color:rgba(244,241,232,.72);
}
@media (pointer:coarse){.cyc-hold,.cyc-dot{min-width:44px;min-height:44px}}
.cyc-hold:hover,.cyc-dot:hover{color:var(--on-court)}
.cyc-hold svg{width:11px;height:11px;fill:currentColor}
.cyc-hold.is-held svg{display:none}
.cyc-hold.is-held::after{
  content:'';width:0;height:0;
  border-left:10px solid currentColor;
  border-top:6px solid transparent;border-bottom:6px solid transparent;
  margin-left:2px;
}

.cyc-dots{display:flex;align-items:center}
.cyc-dot i{
  width:7px;height:7px;border-radius:50%;
  background:rgba(244,241,232,.34);
  transition:background .25s var(--ease),transform .25s var(--ease);
}
.cyc-dot[aria-selected="true"] i{background:var(--tape);transform:scale(1.3)}

/* ========================================================== 02 PROBLEM -- */
/* ONE SECTION OPENING, USED EVERYWHERE. This shipped as four near-identical
   rules - .statement-in, .feat-head, .sheet-head, .proc-head - of which two
   were two-column and two were not, so half the page's sections opened with a
   headline against an empty right half and half did not. A page should have
   one idea about how a section starts. Question left, answer right, both
   sitting on the same baseline. */
.sec-head{
  max-width:var(--maxw);margin:0 auto clamp(42px,4.6vw,78px);
  display:grid;grid-template-columns:minmax(0,1.12fr) minmax(0,1fr);
  gap:clamp(24px,3.4vw,72px);
  align-items:end;
}
.sec-head h2{font-size:var(--fs-section);max-width:16ch;margin:18px 0 0}
.sec-head p{max-width:44ch}
.statement .sec-head{margin-bottom:0}
.statement .sec-head h2{font-size:var(--fs-statement);max-width:20ch;line-height:1.14}
.statement .sec-head .lead{max-width:46ch}
/* STACKED: the headline takes the full measure on one line, as far as it will
   go, and the standfirst sits under it rather than beside it. "The rest of
   it" band, where a two-line headline beside a short paragraph left a hole in
   the middle of the fold. */
.sec-head--stack{grid-template-columns:minmax(0,1fr);align-items:start;gap:clamp(16px,1.8vw,26px)}
.sec-head--stack h2{max-width:none}
.sec-head--stack p{max-width:62ch}

/* THE STAT STRIP. A green band this early is what stops three paper sections
   running together the way the reference's eight do. Four facts, only ones
   that are true of the shipped thing. */
.stats{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  max-width:var(--maxw);margin:0 auto;
}
/* NO BOX. The band is already the container, so the four facts sit straight on
   it, centred in their columns and split by a short hairline rather than a
   second bordered card inside the first. */
.stat{padding:4px clamp(12px,2vw,28px);text-align:center}
.stat + .stat{border-left:1px solid var(--line-on)}
.stat b{
  display:block;font-family:var(--serif);font-weight:300;
  font-size:clamp(34px,3.8vw,56px);line-height:1;letter-spacing:-.03em;
  color:var(--tape);
}
.stat span{
  display:block;margin-top:10px;
  font-family:var(--sans);font-weight:600;font-size:13px;line-height:1.35;
  color:rgba(244,241,232,.78);
}
/* The band itself goes compact for this strip - the default band pad is sized
   for a heading and a paragraph, not a single row of numbers. */
.band-stats{padding-block:clamp(40px,4.6vw,68px)}

/* ========================================================= 03 FEATURES -- */
.feat-list{max-width:var(--maxw);margin:0 auto}

/* SIX ROWS ON A SPINE. Screen left, argument right, the two centred on each
   other, separated by a full-width hairline.

   THERE IS NO STICKY HERE, AND THAT IS THE SECOND ANSWER RATHER THAN THE
   FIRST. This shipped once as a sticky handoff: every row's phone cell pinned
   under the nav, every row sharing one `top` and one stage height, so phone N
   would unpin exactly where phone N+1 pinned and six screens would appear to
   swap through one position. Measured, it does nothing. Sticky can only travel
   through the difference between its own height and its containing block's,
   and the containing block here is a row whose height IS the phone - 594px of
   phone against about 350px of copy. There was nothing to travel through, so
   the phone simply scrolled, and the row's bottom padding showed up as a seam
   where the media column went empty between features.

   A mechanic that does nothing but leave a seam is worse than no mechanic. The
   hairline does the job it was supposed to do - it makes six pairs read as one
   numbered sequence - and it costs nothing at any width. */
.feat-item{
  display:grid;
  grid-template-columns:minmax(0,.56fr) minmax(0,1fr);
  gap:clamp(28px,3.6vw,72px);
  align-items:center;
  padding:clamp(40px,4.4vw,70px) 0;
  border-top:1px solid var(--line);
}
.feat-item:first-child{border-top:0;padding-top:0}
.feat-media{
  /* A DEFINITE height, so the clip inside has something to resolve against. */
  height:min(600px,66vh);
  display:flex;align-items:center;justify-content:center;
}
.feat-media .clip{height:100%}

.feat-copy{max-width:var(--measure)}
/* THE COUNTER. The reference gives a six-item sequence no position indicator
   at all, so there is no way to know whether you are two screens or five from
   the end of it. */
.feat-n{
  display:flex;align-items:center;gap:12px;
  font-family:var(--sans);font-weight:700;font-size:var(--fs-micro);
  letter-spacing:.15em;text-transform:uppercase;line-height:1.25;
  color:var(--ink-3);margin:0 0 16px;
}
.feat-n em{font-style:normal;color:var(--court-ink)}
.feat-n i{flex:1;max-width:80px;height:1px;background:var(--line)}
.feat-copy h3{font-size:var(--fs-feat);margin:0 0 16px;max-width:18ch}
.feat-copy p{max-width:var(--measure)}

/* The consequence line: one fact per feature, on a rule, in the page's own
   micro-label voice. Its padding-top is the row's own rhythm, never a
   hard-coded 16 - a label that hangs closer to its own hairline than to the
   space below it is the single most common card-footer bug. */
.feat-foot{
  margin-top:clamp(20px,2vw,30px);
  padding-top:clamp(16px,1.6vw,22px);
  border-top:1px solid var(--line);
  font-family:var(--sans);font-weight:700;font-size:var(--fs-micro);
  letter-spacing:.13em;text-transform:uppercase;line-height:1.35;
  color:var(--ink);
  /* It is ALLOWED to wrap to two lines - the column is not wide enough at
     every width for the longest of them - so it is balanced, which is what
     makes two lines read as deliberate rather than as an overflow. */
  text-wrap:balance;max-width:52ch;
}
.feat-foot em{font-style:normal;color:var(--ink-3);font-weight:600}

/* ===================================================== 04 CONTACT SHEET -- */
/* The remaining three screens as an index rather than as three more features.
   Flush, gutter-less, on green - a contact sheet. It says "here is the rest,
   at a glance", which is a different job from the six above and therefore gets
   a different treatment. The reference uses one treatment three times. */
.sheet{
  max-width:var(--maxw);margin:0 auto;
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(22px,2.6vw,46px);
}
.sheet-item{display:flex;flex-direction:column}
.sheet-stage{
  height:min(500px,54vh);
  display:flex;align-items:center;justify-content:center;
}
.sheet-stage .clip{height:100%;box-shadow:0 2px 6px rgba(4,20,13,.3),0 30px 64px -20px rgba(4,20,13,.7)}
.sheet-cap{
  margin-top:clamp(20px,2vw,28px);
  padding-top:clamp(14px,1.4vw,18px);
  border-top:1px solid var(--line-on);
}
.sheet-cap h3{font-size:clamp(20px,1.6vw,26px);margin:0 0 8px}
.sheet-cap p{font-size:15px;line-height:1.5}

/* =========================================================== 05 PROCESS -- */
.steps{max-width:var(--maxw);margin:0 auto;border-top:1px solid var(--line)}
.step{
  display:grid;
  /* The heading column is sized to the heading. At .9fr it was 455px holding
     a 16ch title that needs 250, so every row had a 370px hole in the middle
     of it - the kind of gap that reads as a missing element rather than as
     air, because there is a hairline under it pointing straight at it. */
  grid-template-columns:clamp(60px,6vw,104px) minmax(0,.5fr) minmax(0,1fr);
  gap:clamp(18px,3vw,56px);
  align-items:start;
  padding:clamp(26px,3vw,46px) 0;
  border-bottom:1px solid var(--line);
}
.step-n{
  font-family:var(--hand);font-weight:400;
  font-size:clamp(30px,3.2vw,50px);line-height:1;
  color:var(--court-ink);
}
.step h3{font-size:clamp(20px,1.75vw,28px);margin:0;max-width:18ch}
.step p{max-width:52ch}

/* ============================================================= 06 CLOSE -- */
.close{padding-bottom:clamp(56px,6vw,96px)}
.close-in{
  max-width:var(--maxw);margin:0 auto;
  display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr);
  /* CENTRED, not end-aligned: with the next-project card on the right the two
     columns are different heights, and bottom-aligning them left the copy
     hanging low beside a card that started higher. */
  gap:clamp(30px,4vw,80px);align-items:center;
}
.close h2{font-size:var(--fs-section);max-width:15ch;margin:20px 0 0}
.close-acts{display:flex;flex-wrap:wrap;gap:12px;margin-top:clamp(16px,1.6vw,24px)}   /* close to the line it answers */

/* THE NEXT PROJECT - the shape is assets/css/next.css, shared by all four
   case studies. This page only tints it and says where it sits. */
.close{
  --next-ink:var(--on-court);
  --next-muted:rgba(244,241,232,.80);
  --next-line:var(--line-on);
  --next-line-hi:rgba(244,241,232,.42);
  --next-accent:var(--tape);
  --next-name-font:var(--serif);
  --next-label-font:var(--sans);
  --next-r:var(--r);
  --next-ease:var(--ease);
}
.close .next-proj{justify-self:end}
.colophon{
  max-width:var(--maxw);margin:clamp(50px,5vw,84px) auto 0;
  padding-top:clamp(20px,2vw,30px);
  border-top:1px solid var(--line-on);
  display:flex;flex-wrap:wrap;gap:10px 28px;align-items:baseline;
  font-size:13.5px;color:rgba(244,241,232,.80);   /* .62 measured 3.77:1 on --court */
}
.colophon b{
  font-family:var(--serif);font-weight:300;font-size:22px;
  letter-spacing:.02em;color:var(--on-court);
}

/* ============================================================== REVEALS -- */
/* Guarded by html.js, so with scripting off nothing is ever hidden. The
   failsafe in the page head covers the OTHER case - the script 404s or throws
   - which the class alone has never protected against. */
html.js .rise{opacity:0;transform:translateY(18px)}
html.js .rise.in{
  opacity:1;transform:none;
  transition:opacity .7s var(--ease),transform .7s var(--ease);
}
@media (prefers-reduced-motion:reduce){
  html.js .rise{opacity:1;transform:none;transition:none}
}

/* ============================================================================
   RESPONSIVE
   ----------------------------------------------------------------------------
   Not a set of things that shrink. Four of these breakpoints change what a
   piece of the page IS, because the desktop idea stops being the better idea
   at that width rather than merely getting tight:

   - 1080  the hero stops being side-by-side. A 100px headline and a phone
           cannot share a row below this without the headline going to four
           lines.
   -  980  the sticky handoff is switched OFF. Sticky needs the copy column to
           be taller than the phone to have anything to travel through; once
           they are stacked the phone would pin over its own heading.
   -  760  the hero phone takes back the height the two-column layout cost it.
   -  560  the four-up stat strip and the three-up sheet go to two-up and
           one-up. A 90px stat cell wraps "Scoring tests pass" to four lines.
   ========================================================================= */

/* ------------------------------------------------------------ <= 1180px -- */
@media (max-width:1180px){
  .sheet{gap:clamp(18px,2.2vw,32px)}

}

/* ------------------------------------------------------------ <= 1080px -- */
@media (max-width:1080px){
  /* One column, and the three areas simply stack in the order they are
     written: title, then the screens it names, then the reference material.
     No `order` - see the note on .hero-in. */
  .hero-in{
    grid-template-columns:minmax(0,1fr);
    grid-template-areas:"copy" "plate" "meta";
    gap:clamp(30px,4.6vw,50px);
  }
  .hero-plate{min-height:0}
  .hero h1{max-width:18ch}
  .sec-head,.close-in{grid-template-columns:minmax(0,1fr);gap:clamp(16px,2.6vw,26px);align-items:start}
  .close .next-proj{justify-self:start;margin-top:clamp(12px,3vw,24px)}
  .sec-head h2{max-width:20ch}
  .sec-head--stack h2{max-width:none}
  .statement .sec-head h2{max-width:24ch}
}

/* ------------------------------------------------------------ <= 1200px -- */
@media (max-width:1200px){
  /* The bar goes two-up HERE and not at some rounder number, because this is
     where a cell stops being able to carry its longest line - measured, not
     guessed: at 1080 the cell is 233px and "Eight people who play weekly"
     wraps; at 980 "Solo - research, design, build" goes with it. Four tiles
     in a row is only worth having while all four read as one line each. */
  .meta{grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* ------------------------------------------------------------- <= 980px -- */
@media (max-width:980px){
  /* The row stacks. The phone keeps a generous height because it is now the
     full width of the column rather than two thirds of it, and a screenshot
     the reader has to squint at is the one thing this whole page is about
     avoiding. */
  .feat-item{
    grid-template-columns:minmax(0,1fr);
    gap:clamp(22px,3.4vw,36px);
    padding:clamp(40px,6vw,64px) 0;
  }
  .feat-media{height:min(460px,54vh)}
  .feat-copy{max-width:var(--measure)}
  .close h2{max-width:20ch}

  /* The step's three columns become two: the number joins its own heading
     rather than sitting alone in a column 56px wide. */
  .step{
    grid-template-columns:clamp(48px,6vw,64px) minmax(0,1fr);
    gap:clamp(14px,2.4vw,26px) clamp(16px,3vw,28px);
    align-items:baseline;
  }
  .step p{grid-column:2;max-width:52ch}
}

/* ------------------------------------------------------------- <= 760px -- */
@media (max-width:760px){
  /* The cycler is one phone at every width already, so there is nothing to
     hide here any more - only a height to hand it now that the plate is the
     full column rather than half of it. */
  .cyc{height:min(520px,58vh)}
  .hero-plate{padding:clamp(26px,5vw,40px) clamp(18px,4vw,30px)}

  /* THE CONTACT SHEET GOES ONE-UP. Two-up with three items leaves an orphan
     in a section whose whole argument is that it is a tidy index. */
  .sheet{grid-template-columns:minmax(0,1fr);gap:clamp(44px,8vw,64px)}
  .sheet-stage{height:min(460px,56vh)}
  .sheet-item{max-width:520px;margin-inline:auto;width:100%}

  .meta{grid-template-columns:minmax(0,1fr);max-width:520px}
}

/* ------------------------------------------------------------- <= 560px -- */
@media (max-width:560px){
  :root{
    --sec-y:clamp(64px,14vw,92px);
    /* A 64ch measure on a 360px screen is not the constraint any more - the
       viewport is. Let it go to 100% and let padding do the work. */
    --measure:100%;
  }
  .stats{grid-template-columns:repeat(2,minmax(0,1fr));row-gap:28px}
  .stat:nth-child(odd){border-left:0}
  .feat-media{height:min(430px,52vh)}
  .sheet-stage{height:min(430px,52vh)}
  .cyc{height:min(470px,54vh)}

  /* Buttons go full width and stack. Two 48px pills side by side at 360px are
     two 160px pills with the label wrapping inside them. */
  .hero-cta,.close-acts{flex-direction:column;align-items:stretch}
  .hero-cta .btn,.close-acts .btn{justify-content:center;width:100%}

  .colophon{gap:8px 20px;font-size:13px}
  .feat-foot,.feat-n{letter-spacing:.11em}
}

/* ------------------------------------------------------------- <= 400px -- */
@media (max-width:400px){
  /* THE STAGES DO NOT SHRINK HERE, AND THAT IS A CORRECTION.
     This block used to drop all three stages to ~340px, on the reasoning that
     "below this the phone renders at a scale where the app's own type is
     illegible". Measured, that is backwards: it produced a 165px-wide phone at
     390px, which is the single most common viewport width there is, while 430
     kept a 223px one. Shrinking the screenshot is what makes it illegible.

     The phone is width-bound here, not height-bound, and there is room: at
     390px the plate has 291px of usable width and a 55vh phone is 223 of it.
     So the stages keep the sizes set at 560, and only the step grid - which
     genuinely does run out of room for a three-column layout - changes. */
  .step{grid-template-columns:minmax(0,1fr)}
  .step p{grid-column:1}
  .step-n{font-size:26px}
}

/* --------------------------------------------------- SHORT AND LANDSCAPE -- */
/* A phone held sideways is 380px tall. Every stage on this page is capped in
   vh precisely so this case resolves itself, but the section rhythm still has
   to come in or one heading fills the screen. */
@media (max-height:560px) and (orientation:landscape){
  :root{--sec-y:clamp(48px,8vw,72px)}
  .hero{padding-top:calc(var(--nav-h) + 28px)}
  .cyc,.feat-media,.sheet-stage{height:78vh}
}

/* -------------------------------------------------------------- PRINT -- */
/* A case study is a thing people put in a PDF and send to a hiring manager.
   Green bands eat a cartridge and sticky positioning prints as overlap. */
@media print{
  /* THE ONE PLACE THIS PAGE TOUCHES .nav DIRECTLY, and it is allowed because
     it does not restyle the bar - it removes it from a medium that has no
     scrolling. .nav is position:fixed; printed, it lands on top of the first
     paragraph of every page. Re-tinting still happens only through --nav-*. */
  .nav,.mnav,.skip,.hero-cta,.close-acts{display:none !important}
  body{background:#fff;color:#000}
  .band{background:#fff;color:#000;border-top:1px solid #999;border-bottom:1px solid #999}
  .band h2,.band h3,.band p,.colophon,.colophon b{color:#000}
  .stat b{color:#000}
  .feat-item,.step,.sheet-item{break-inside:avoid;page-break-inside:avoid}

  /* THE STAGE TAKES THE HEIGHT, NOT THE CLIP. Written as `.clip{height:320px}`
     this collapsed every phone to ZERO in print: `.feat-media .clip{height:100%}`
     is (0,2,0) and outranks it, and 100% of an auto-height parent resolves to
     auto, which on a width:auto aspect-ratio box is nothing at all. Measured -
     the printed PDF had twelve invisible screens in it. Same trap the repo
     already documents: a media stage needs a DEFINITE size, however it gets one. */
  .cyc,.feat-media,.sheet-stage{height:340px}
  .clip{box-shadow:none}
  /* The cycler prints as the ONE screen it is currently showing. Making the
     items static instead would stack three 340px phones inside a 340px plate
     that clips - and the other two screens are both printed further down the
     page anyway, in the feature rows. */
  .cyc-nav{display:none}
  .grid-bg::before{display:none}
  html.js .rise{opacity:1 !important;transform:none !important}
}

/* ------------------------------------------------- FORCED COLOURS / HC -- */
/* Windows High Contrast throws every author colour away, so the two things
   carrying meaning by colour alone need a border to survive it. */
@media (forced-colors:active){
  .clip{border:1px solid CanvasText}
  .meta,.stats{border:1px solid CanvasText}
  .btn{border:1px solid ButtonText}
  mark{background:Highlight;color:HighlightText}
}

/* ==========================================================================
   THE DARK THEME
   Added 2026-09-11, when the switcher went site-wide.

   THIS IS A TOKEN BLOCK AND NOTHING ELSE. Every colour on this page was
   already a token, which is the only reason a second theme is 30 lines rather
   than a second stylesheet - the audit that preceded this found exactly one
   literal that had to move (.btn-solid's hover green) and two that correctly
   stay put: the phone bezel behind the screen recordings, which is black in
   any theme, and the @media print block, which is always ink on white.

   --court AND --tape SWAP JOBS. In the light theme the green is the ink and
   the tape is a fill that may never be text. On a dark ground the green is
   too dark to read, so the ink role moves to a lifted green and the tape -
   already a light chartreuse - becomes safe as text for the first time.

   WHAT DOES NOT CHANGE is the green band. .band paints --court as a GROUND in
   both themes, and paper-coloured ink on it was already correct, so every
   rgba(244,241,232,...) inside a band is left exactly as it is. A dark theme
   that also inverted the bands would have flattened the page's one structural
   contrast into a single tone.
   ========================================================================== */
html.dark{
  --paper:#12120E;               /* the cream, inverted and kept warm */
  --ink:#F4F1E8;
  --ink-2:rgba(244,241,232,.72);
  --ink-3:rgba(244,241,232,.58); /* .58, not .40 - see the note on --ink-3 */

  /* THE GROUND DOES NOT MOVE. .band, .stat, .skip and .btn-solid are painted
     on this green in both themes and carry near-white text; lifting it turned
     six grounds pale under text that stayed light. Only the INK lifts. */
  --court-ink:#F5B02E;
  --tape:#F5B02E;                /* unchanged: still a fill, now also legible */

  --line:rgba(244,241,232,.15);
  --line-2:rgba(244,241,232,.07);

  /* The bar follows the page. nav.css owns the bar; this only re-tints it. */
  --nav-bg:#12120E;
  --nav-ink:#F4F1E8;
  --nav-muted:rgba(244,241,232,.62);
  --nav-faint:rgba(244,241,232,.42);
  --nav-line:rgba(244,241,232,.15);
  --nav-fill:rgba(245,176,46,.10);
  --nav-edge:rgba(245,176,46,.38);
  --nav-edge-hi:rgba(245,176,46,.70);
  --nav-hair:rgba(244,241,232,.10);
  --nav-press:rgba(245,176,46,.12);
  --nav-grid:rgba(244,241,232,.05);
  --nav-eye-ring:rgba(244,241,232,.24);
  --nav-accent:#F5B02E;
}

/* --ink ALONE IS NEVER ENOUGH. `color` inherits as a COMPUTED value, so an
   element that resolved it against the light --ink keeps that literal even
   after the token flips underneath it. Re-stating it on the elements that
   declare their own colour is what actually moves them. Learned the hard way
   on the Marketplace page, where two headings went to 1.00:1 - invisible -
   because only the token was changed. */
html.dark body{color:var(--ink)}
html.dark .btn-ghost{color:var(--ink)}
/* NO html.dark RULE FOR .btn-solid. There was one, and it was both redundant
   and harmful: --on-court does not move between themes, so the base rule
   already covers it - and at (0,2,1) the dark selector outranked
   `.band .btn-solid` at (0,2,0), overriding the tape's dark ink with a
   near-white one and putting the footer button at 1.11:1. A theme override
   that restates a theme-independent value can only ever break something. */
html.dark .btn-ghost:hover{background:rgba(244,241,232,.06)}

/* The green band's own hover, which brightened a chartreuse that is already
   at the top of its range. On a dark page the plain tape is bright enough. */
html.dark .band .btn-solid:hover{background:#C7E23A}

/* The phone plate sat on a near-white wash that becomes a hole in the page. */
html.dark .plate{background:rgba(244,241,232,.04)}
