/* ============================================================================
   ericbuildsgames.com

   THE ONE IDEA: the page never touches the art. Every capsule is wrapped in a
   pale mount and a dark keyline, which are COMPLEMENTARY by luminance, so they
   can never both fail — whatever the artwork does.

     --mount   relL must stay ABOVE ~.85   (catches dark art)
     --keyline relL ~.005                  (catches light art)

   Worst-case boundary for ANY art in sRGB: ~4.1:1. Measured on the real
   capsules, 0% of edge pixels fail against both — the darkest capsule fails the
   keyline 59% of the time and the mount 0%.

   THE MOUNT MAY BE TINTED, BUT IT MAY NOT BE MID-TONE. This was measured, not
   assumed: a saturated mid-tone mount (e.g. a jogamedev-style #8B7FF0) drops
   the worst case to 2.40:1, because art around relL .08 is then too dark for
   the mount AND too light for the keyline — both fail at once. Any pale tint
   above relL ~.85 keeps the guarantee (~4.1:1). Tint freely; never darken.
   ========================================================================= */

/* ---- STRUCTURAL. Identical in every theme. DO NOT theme these. ---- */
:root{
  --keyline:#160C1F;
  --mount-w:10px;   /* the mat ring */
  --key-w:3px;
  --col:660px;
  --r-panel:22px;   /* chunky, toy-like */
  --r-art:10px;
  --r-pill:999px;
}

/* ============================ THEMES ====================================
   Every preset defines the identical token set. A fourth theme is one block
   and zero other edits.

     --page     saturated field
     --pattern  the swirl's colour (a near neighbour of --page)
     --mount    PALE tint. Must stay above relL ~.85. Carries the card.
     --ink      dark text ON the mount        --ink-mid  secondary text on mount
     --deep     dark pill/badge ON the mount  --on-deep  its label
     --text-hi  text ON the page              --text-mid secondary on page
     --accent   the bright pop. Lives on the page (the wordmark) — on a pale
                mount a bright accent has nothing to contrast against, so it
                NEVER colours text on the mount. It may act as a FILL behind
                --ink (button hovers): every theme's accent is light enough
                to carry ink text. Keep that true when adding accents.
   ===================================================================== */

/* ---- BAKED DEFAULT (SPEC.md Phase 4; re-baked by eye 2026-07-21):
   SLEEPER + ELECTRIC. The varsity navy pushed down to app-dark ink and the
   teal pushed to full saturation, sampled from the Sleeper scoreboard
   (their field ~#05091E, active teal ~#03FFFF; ours stops short of both).
   Cream cards stay — the one warm note is the signature Sleeper doesn't
   have. The bare :root IS the shipped look — visitors get these tokens
   with zero JS. The lab still overrides everything via html[data-*]. ---- */
:root{
  --page:#0A0F22;   --pattern:#1E2A4C;
  --panel:#EFE6D3;  --mount:#FDF9F0;   /* cream — the warm note on the cold field */
  --ink:#0B1626;    --ink-mid:#41546E;
  --deep:#0A1322;   --on-deep:#FFFFFF;
  --text-hi:#FFFFFF; --text-mid:#9FB2CC;
  --accent:#00E5D8;                     /* electric teal — the winning accent */
  --ring-halo:#FFFFFF; --ring-core:#060A18;
  --shadow:rgba(0,6,18,.65);
}

/* ---- INK — the quiet near-neutral (was the interim default). ---- */
:root[data-theme="ink"]{
  --page:#15151B;   --pattern:#232229;
  --panel:#EDE8DE;  --mount:#FBF8F2;
  --ink:#1C1820;    --ink-mid:#5C5566;
  --deep:#2A2632;   --on-deep:#FFFFFF;
  --text-hi:#F4F2F7; --text-mid:#B9B4C4;
  --accent:#E8B45C;
  --ring-halo:#F4F2F7; --ring-core:#160C1F;
  --shadow:rgba(0,0,0,.6);
}

/* ---- AUBERGINE — the original purple, with coral. ---- */
:root[data-theme="aubergine"]{
  --page:#4A2E9E;   --pattern:#5B3CC4;
  --panel:#C9B8FF;  --mount:#F6F2FF;
  --ink:#241340;    --ink-mid:#52407E;
  --deep:#2E1A52;   --on-deep:#FFFFFF;
  --text-hi:#FFFFFF; --text-mid:#D5C6FA;
  --accent:#FFB07A;
  --ring-halo:#FFFFFF; --ring-core:#160C1F;
  --shadow:rgba(18,6,40,.45);
}

/* ---- NIGHT SHIFT — your navy + teal, the quiet navy. ---- */
:root[data-theme="night-shift"]{
  --page:#0E3D5C;   --pattern:#155379;
  --panel:#A6DCF2;  --mount:#F0FAFF;
  --ink:#06202F;    --ink-mid:#2E5670;
  --deep:#0B2E45;   --on-deep:#FFFFFF;
  --text-hi:#FFFFFF; --text-mid:#B4D9EF;
  --accent:#5CF0DC;
  --ring-halo:#FFFFFF; --ring-core:#0C1420;
  --shadow:rgba(2,18,30,.45);
}

/* ---- VARSITY — the loud navy (SPEC.md 2a), tuned to Michigan
   maize-and-blue: #FFCB05 on #00274C. Maize IS the default accent (the
   "gold" pill maps to it); the others override below. The accent lives
   ONLY on the page (the wordmark), so swapping it is contrast-safe by
   construction — the mount still clears relL .85 like every theme. ---- */
:root[data-theme="varsity"]{
  --page:#00274C;   --pattern:#0A3A66;
  --panel:#ADCBE8;  --mount:#F3F8FF;
  --ink:#0B2340;    --ink-mid:#3D5A7C;
  --deep:#011E3A;   --on-deep:#FFFFFF;
  --text-hi:#FFFFFF; --text-mid:#BCD2E8;
  --accent:#FFCB05;                     /* maize */
  --ring-halo:#FFFFFF; --ring-core:#001730;
  --shadow:rgba(0,16,36,.55);
}
/* ---- SLEEPER — varsity's night game. The navy pushed down to app-dark
   ink and the teal pushed up to electric, sampled from the Sleeper
   scoreboard. Same construction as varsity: accent on the page only. ---- */
:root[data-theme="sleeper"]{
  --page:#0A0F22;   --pattern:#1E2A4C;
  --panel:#B9C9E4;  --mount:#F2F6FD;
  --ink:#0B1626;    --ink-mid:#41546E;
  --deep:#0A1322;   --on-deep:#FFFFFF;
  --text-hi:#FFFFFF; --text-mid:#9FB2CC;
  --accent:#00E5D8;
  --ring-halo:#FFFFFF; --ring-core:#060A18;
  --shadow:rgba(0,6,18,.65);
}

/* ---- CRIMSON — cardinal red, letterman jacket. ---- */
:root[data-theme="crimson"]{
  --page:#8A1E30;   --pattern:#9E2C3E;
  --panel:#F4BFC7;  --mount:#FFF5F6;
  --ink:#3D0A14;    --ink-mid:#7A3644;
  --deep:#4A0D1A;   --on-deep:#FFFFFF;
  --text-hi:#FFFFFF; --text-mid:#F0C4CC;
  --accent:#FFD84D;
  --ring-halo:#FFFFFF; --ring-core:#2A060D;
  --shadow:rgba(40,4,10,.5);
}

/* ---- FOREST — deep green, park ranger. ---- */
:root[data-theme="forest"]{
  --page:#1E5B3C;   --pattern:#2A6E4B;
  --panel:#BCE5CB;  --mount:#F2FBF5;
  --ink:#0B2E1B;    --ink-mid:#3E6B52;
  --deep:#123724;   --on-deep:#FFFFFF;
  --text-hi:#FFFFFF; --text-mid:#C2E2CE;
  --accent:#FFC94D;
  --ring-halo:#FFFFFF; --ring-core:#072415;
  --shadow:rgba(4,26,16,.5);
}

/* ---- TANGERINE — burnt orange, the loudest field in the deck. ---- */
:root[data-theme="tangerine"]{
  --page:#C25517;   --pattern:#D4682A;
  --panel:#FFD9B8;  --mount:#FFF7EE;
  --ink:#4A1E05;    --ink-mid:#8A4E28;
  --deep:#5C2608;   --on-deep:#FFFFFF;
  --text-hi:#FFFFFF; --text-mid:#FFDCC2;
  --accent:#4DE8D0;
  --ring-halo:#FFFFFF; --ring-core:#331303;
  --shadow:rgba(60,22,4,.5);
}

/* ---- RASPBERRY — punchy pink, arcade. ---- */
:root[data-theme="raspberry"]{
  --page:#B03A6E;   --pattern:#C24B7F;
  --panel:#FFD1E3;  --mount:#FFF4F8;
  --ink:#470E2B;    --ink-mid:#8A3F63;
  --deep:#571536;   --on-deep:#FFFFFF;
  --text-hi:#FFFFFF; --text-mid:#F7CCDE;
  --accent:#FFE45C;
  --ring-halo:#FFFFFF; --ring-core:#33081D;
  --shadow:rgba(51,8,29,.5);
}

/* ---- ACCENT OVERRIDES (lab axis, ALL themes) — html[data-accent] -------
   "auto" (no rule here) = the theme's own accent. Every swatch is light
   enough to carry --ink text — the law that keeps hover fills safe. These
   sit AFTER the theme blocks on purpose: equal specificity, later wins. */
:root[data-accent="gold"]   { --accent:#FFCB05; }   /* maize */
:root[data-accent="coral"]  { --accent:#FF9E6E; }
:root[data-accent="magenta"]{ --accent:#FF5EA8; }
:root[data-accent="mint"]   { --accent:#5BF2A2; }   /* the jogamedev counterfactual */
:root[data-accent="teal"]   { --accent:#5CF0DC; }
:root[data-accent="electric"]{ --accent:#00E5D8; }  /* sleeper teal */
:root[data-accent="lilac"]  { --accent:#B39DFF; }
:root[data-accent="sky"]    { --accent:#7CC8FF; }
:root[data-accent="lime"]   { --accent:#C8F05C; }

/* ---- PANEL OVERRIDES (lab axis) — html[data-panel] ---------------------
   The card colour, independent of theme ("blue" = the theme's own panel).
   Each value re-pairs the MOUNT to match temperature; every mount stays
   above relL ~.85 — that law is not up for negotiation. ---- */
:root[data-panel="cream"] { --panel:#EFE6D3; --mount:#FDF9F0; }
:root[data-panel="butter"]{ --panel:#F2DFA8; --mount:#FEFAEC; }
:root[data-panel="mint"]  { --panel:#BCE8DD; --mount:#F1FCF9; }
:root[data-panel="bone"]  { --panel:#E2DFD6; --mount:#FCFAF5; }
/* the reversal: white card, cream mat ring. The mat cream is deliberately
   PALER than the cream panel (#EFE6D3 sits at relL ~.80 — too dark to be
   the mat); #F5EEDF clears the relL ~.85 law. */
:root[data-panel="white"] { --panel:#FDF9F0; --mount:#F5EEDF; }

/* ---- SULLY — teal fur, purple spots. Fixed accent (no lab axis): bright
   lavender on the deep teal page, with the purple carried by the pills
   and badges (--deep), the way the spots sit on the fur. ---- */
:root[data-theme="sully"]{
  --page:#0F6E80;   --pattern:#1B8B9E;
  --panel:#A8E1EA;  --mount:#F0FBFD;
  --ink:#093036;    --ink-mid:#33646E;
  --deep:#43317A;   --on-deep:#FFFFFF;
  --text-hi:#FFFFFF; --text-mid:#BFE4EC;
  --accent:#B39DFF;
  --ring-halo:#FFFFFF; --ring-core:#062A30;
  --shadow:rgba(3,30,36,.5);
}

/* ============================ TYPE ====================================== */

@font-face{
  font-family:"Archivo";
  src:url("/assets/fonts/archivo-latin-var.woff2") format("woff2-variations");
  font-weight:400 800;
  font-display:swap;
  font-style:normal;
}
/* Display face for the WORDMARK ONLY (SPEC.md 2d): Alfa Slab One, latin
   subset, 15KB. Declared 400 800 so the wordmark's weight lands on it with
   no synthetic bolding; if the file ever 404s the stack falls through to
   Archivo 800 and nothing breaks. Source ttf: tools/AlfaSlabOne-Regular.ttf */
@font-face{
  font-family:"Slab";
  src:url("/assets/fonts/alfa-slab-latin.woff2") format("woff2");
  font-weight:400 800;
  font-display:swap;
  font-style:normal;
}
:root{
  --font:"Archivo", system-ui, -apple-system, "Segoe UI", Roboto,
         "Helvetica Neue", Arial, sans-serif;
}

/* ============================ BASE ====================================== */

*,*::before,*::after{ box-sizing:border-box; }
html{ -webkit-text-size-adjust:100%; background-color:var(--page); }

body{
  position:relative;
  margin:0;
  padding:64px 20px 72px;   /* wordmark sits centered between viewport top and first card */
  font:400 16px/1.5 var(--font);
  color:var(--text-hi);
  -webkit-font-smoothing:antialiased;
}

/* ---- THE BACKGROUND PATTERN ------------------------------------------
   Driven as a MASK, not a background-image: an external SVG used as
   background-image is an isolated document, so its currentColor resolves to
   black and never inherits the theme. As a mask only the alpha matters and the
   colour is just this element's background — so ONE file per pattern serves
   every theme, and pattern and theme stay independent.

   Swap with html[data-pattern="..."]. Regenerate via tools/make-patterns.py.
   -------------------------------------------------------------------- */
:root{
  --pat-img:url("/assets/img/pat-diamond.svg");   /* wallpaper-recipe defaults (lab axis) */
  --pat-size:170px;
  --pat-fade:.85;    /* per-pattern trim, for the ones that cover a lot of area */
  --drift-dur:21s;   /* seconds per ONE TILE of travel — see DRIFT below.
                        Set per pattern so the perceived speed (~8px/s on each
                        axis) stays constant however big the tile is. */
}
:root[data-pattern="swirl"]    { --pat-img:url("/assets/img/pat-swirl.svg");     --pat-size:760px; --pat-fade:1; --drift-dur:95s; }
:root[data-pattern="dots"]     { --pat-img:url("/assets/img/pat-dots.svg");      --pat-size:34px;  --pat-fade:1; --drift-dur:4.5s; }
:root[data-pattern="grid"]     { --pat-img:url("/assets/img/pat-grid.svg");      --pat-size:44px;  --pat-fade:1; --drift-dur:5.5s; }
:root[data-pattern="checker"]  { --pat-img:url("/assets/img/pat-checker.svg");   --pat-size:120px; --pat-fade:.55; --drift-dur:15s; }
:root[data-pattern="stripes"]  { --pat-img:url("/assets/img/pat-stripes.svg");   --pat-size:40px;  --pat-fade:.75; --drift-dur:5s; }
:root[data-pattern="scanlines"]{ --pat-img:url("/assets/img/pat-scanlines.svg"); --pat-size:6px;   --pat-fade:.8; --drift-dur:1.2s; }
:root[data-pattern="cross"]    { --pat-img:url("/assets/img/pat-cross.svg");     --pat-size:48px;  --pat-fade:1; --drift-dur:6s; }
:root[data-pattern="blocks"]   { --pat-img:url("/assets/img/pat-blocks.svg");    --pat-size:96px;  --pat-fade:1; --drift-dur:12s; }
:root[data-pattern="sprinkle"] { --pat-img:url("/assets/img/pat-sprinkle.svg");  --pat-size:160px; --pat-fade:.9; --drift-dur:20s; }
:root[data-pattern="bolt"]     { --pat-img:url("/assets/img/pat-bolt.svg");      --pat-size:234px; --pat-fade:.55;  --drift-dur:29s; }
:root[data-pattern="diamond"]  { --pat-img:url("/assets/img/pat-diamond.svg");   --pat-size:170px; --pat-fade:.85; --drift-dur:21s; }
:root[data-pattern="none"]     { --pat-img:none; }

/* BAKED (Phase 4; pattern + motion re-baked 2026-08-19): the HOMEPAGE ships
   the DIAMOND wallpaper (the --pat-* defaults above: 170px tile, fade .85)
   DRIFTING down and to the right. It is deliberately the loudest mask on the
   site: bigger AND stronger than the checker, because fewer large
   shapes read as architecture while more small ones read as noise. The value
   gap to --page still stays narrow, so the game art keeps the top of the
   page's contrast range. Press pages override this base with the
   giant broken checker — see press.css, which also stops the drift. Either
   arrives with zero JS; the lab recipes below override everything locally.

   The negative inset is the drift's one-tile bleed — see DRIFT below. It is
   harmless when the drift is off: the mask repeats, so shifting the layer's
   origin by a whole tile changes nothing. */
body::before{
  content:"";
  position:fixed; z-index:-1;
  inset:calc(-1 * var(--pat-size));
  animation:pat-drift-down var(--drift-dur) linear infinite;
  will-change:transform;
  background:var(--pattern);
  opacity:var(--pat-fade);
  -webkit-mask-image:var(--pat-img);
  mask-image:var(--pat-img);
  -webkit-mask-size:var(--pat-size) var(--pat-size);
  mask-size:var(--pat-size) var(--pat-size);
  -webkit-mask-repeat:repeat;
  mask-repeat:repeat;
  pointer-events:none;
}

/* ---- BACKGROUND RECIPES (SPEC.md 2b) ---------------------------------
   html[data-bg] swaps the whole recipe (lab axis; the baked default above
   equals data-bg="checker"). "wallpaper" is the uniform tile system — the
   only recipe the pattern axis applies to. Every recipe is STATIC, and
   every decorative layer dies under prefers-contrast / forced-colors —
   the guards at the bottom kill both pseudo-elements at once.
   -------------------------------------------------------------------- */

/* WALLPAPER — the uniform tile, driven by the --pat-* vars. */
:root[data-bg="wallpaper"] body::before{
  opacity:var(--pat-fade);
  -webkit-mask-image:var(--pat-img);
  mask-image:var(--pat-img);
  -webkit-mask-size:var(--pat-size) var(--pat-size);
  mask-size:var(--pat-size) var(--pat-size);
}

/* DRIFT (lab axis, wallpaper only) — the whole tiled layer creeps diagonally,
   forever. Two facts make it free and seamless:

   • one period of travel is exactly ONE TILE on each axis, so the frame the
     animation ends on is pixel-identical to the frame it started on — the
     loop has no seam and needs no crossfade;
   • the layer is grown by one tile on every side (inset), so the corner it
     drifts away from is still covered. Nothing else changes.

   It is a transform on a fixed, composited layer: no repaint, no layout, and
   nothing for the main thread to do. prefers-reduced-motion kills it via the
   global rule at the bottom (duration .01ms → it lands on the identical end
   frame and stops).

   DOWN — down AND to the right, the way the eye reads — is BAKED onto
   body::before above, so visitors get it with zero JS. The rules here only
   mirror it, stop it, or keep it off the recipes it would break. */
@keyframes pat-drift-down{
  from{ transform:translate3d(0,0,0); }
  to  { transform:translate3d(var(--pat-size), var(--pat-size), 0); }
}
@keyframes pat-drift-up{
  from{ transform:translate3d(0,0,0); }
  to  { transform:translate3d(calc(-1 * var(--pat-size)), calc(-1 * var(--pat-size)), 0); }
}
:root[data-bg="wallpaper"][data-drift="up"] body::before{
  animation-name:pat-drift-up;          /* the mirror; duration is inherited */
}
/* Drift belongs to the tiled wallpaper only. Every other recipe paints
   gradients positioned against the layer's own box, so the bleed would move
   them and the travel would not be a whole tile of anything — both die here,
   together with the lab's explicit "off". */
:root[data-drift="off"] body::before,
:root[data-bg="glow"] body::before,
:root[data-bg="checker"] body::before,
:root[data-bg="halftone"] body::before,
:root[data-bg="watermark"] body::before,
:root[data-bg="scene"] body::before{
  inset:0;
  animation:none;
}
/* recipes without an overlay of their own must kill the baked one */
:root[data-bg="wallpaper"] body::after,
:root[data-bg="glow"] body::after,
:root[data-bg="watermark"] body::after,
:root[data-bg="scene"] body::after{ content:none; }
:root{
  /* shared film grain: inline feTurbulence tile, ~5% strength */
  --grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.05'/%3E%3C/svg%3E");
}

/* BLUEPRINT: built, judged, eliminated by eye 2026-07-20. Do not rebuild. */

/* GLOW — no motif at all: a spotlight behind the masthead, vignetted
   corners, film grain. Stage lighting on the games. */
:root[data-bg="glow"] body::before{
  -webkit-mask-image:none; mask-image:none;
  opacity:1;
  background:
    var(--grain),
    radial-gradient(1100px 700px at 50% 8%,
      color-mix(in srgb, var(--pattern) 85%, #fff) 0%, transparent 62%),
    radial-gradient(140% 120% at 50% 40%, transparent 60%, rgba(0,0,0,.28) 100%);
}

/* CHECKER, BROKEN — the checkerboard saved from wallpaper-hood: giant
   scale, strongest at the edges, faded to nothing behind the content
   column. The ::after overlay (painted above ::before) does the fading. */
:root[data-bg="checker"] body::before{
  -webkit-mask-image:url("/assets/img/pat-checker.svg");
  mask-image:url("/assets/img/pat-checker.svg");
  -webkit-mask-size:340px 340px;
  mask-size:340px 340px;
  opacity:.5;
}
:root[data-bg="checker"] body::after{
  content:"";
  position:fixed; inset:0; z-index:-1;
  background:radial-gradient(120% 90% at 50% 40%, var(--page) 38%, transparent 82%);
  pointer-events:none;
}

/* HALFTONE — chunky print dots that dissolve to nothing behind the
   content column, same fade trick as the broken checker. Print-shop, not
   wallpaper: the motif only lives at the edges. */
:root[data-bg="halftone"] body::before{
  -webkit-mask-image:url("/assets/img/pat-dots.svg");
  mask-image:url("/assets/img/pat-dots.svg");
  -webkit-mask-size:52px 52px;
  mask-size:52px 52px;
  opacity:.85;
}
:root[data-bg="halftone"] body::after{
  content:"";
  position:fixed; inset:0; z-index:-1;
  background:radial-gradient(110% 85% at 50% 38%, var(--page) 30%, transparent 78%);
  pointer-events:none;
}

/* WATERMARK — one giant tilted "EBG" in the slab face, cropped off the
   canvas edges (baked into the SVG; cover keeps the composition at every
   viewport). Editorial poster move: no repetition at all, just one huge
   quiet mark. Regenerate via tools/make-watermark.py. */
:root[data-bg="watermark"] body::before{
  -webkit-mask-image:url("/assets/img/wm-ebg.svg");
  mask-image:url("/assets/img/wm-ebg.svg");
  -webkit-mask-size:cover;
  mask-size:cover;
  -webkit-mask-repeat:no-repeat;
  mask-repeat:no-repeat;
  -webkit-mask-position:center;
  mask-position:center;
  opacity:.9;
}

/* SCENE-LITE — the background is a place: a horizon band, a warm lamp
   low-left (tinted by the accent), vignette, grain. CSS-only on purpose:
   real scene art gets made only if this recipe wins (SPEC.md Phase 2b). */
:root[data-bg="scene"] body::before{
  -webkit-mask-image:none; mask-image:none;
  opacity:1;
  background:
    var(--grain),
    radial-gradient(700px 520px at 12% 88%,
      color-mix(in srgb, var(--accent) 22%, transparent) 0%, transparent 65%),
    linear-gradient(to top, rgba(0,0,0,.38) 0%, transparent 18%),
    radial-gradient(140% 120% at 50% 35%, transparent 62%, rgba(0,0,0,.25) 100%);
}

h1,h2,p{ margin:0; }
img{ max-width:100%; }

.skip{
  position:absolute; left:-9999px;
  padding:12px 18px; border-radius:var(--r-pill);
  background:var(--text-hi); color:var(--page);
  font-weight:700; text-decoration:none;
}
.skip:focus{ left:20px; top:20px; z-index:10; }

/* ============================ MASTHEAD ================================== */

.masthead{ max-width:var(--col); margin:0 auto 46px; text-align:center; }

/* The one deliberate idle animation on the site: the wordmark's float.
   ±6px at 2.6s/leg — visible without being bouncy; ±3px at 3.6s was
   measured moving ~1.5px/s, i.e. invisible. Killed automatically for
   prefers-reduced-motion by the global rule below. */
@keyframes wordmark-float{
  from{ transform:translateY(-6px); }
  to  { transform:translateY(6px); }
}

.wordmark{
  font-family:"Slab", var(--font);   /* Alfa Slab One; falls through to Archivo 800 */
  font-size:clamp(2.1rem,7.5vw,3.25rem);   /* sized so the slab stays one line at --col */
  animation:wordmark-float 1.45s ease-in-out infinite alternate;
  font-weight:800;
  letter-spacing:.02em;
  text-transform:uppercase;
  line-height:1;
  color:var(--accent);
  -webkit-text-stroke:.11em var(--keyline);
  paint-order:stroke fill;       /* stroke behind the glyph, not through it */
  text-shadow:.07em .08em 0 var(--keyline);
}
.wordmark .m{ color:var(--mount); }

/* ---- WORDMARK POSES (SPEC.md 2c) — html[data-wordmark] -----------------
   "classic" is the rules above, untouched: the control. The .l1/.l2 spans
   in the markup exist only so "twoline" can stack without a DOM change. */

/* PLATE — the wordmark on a nameplate speaking the cards' language: mount,
   keyline, hard toy shadow. "Builds" swaps to the page colour — a bright
   accent has nothing to push against on a pale mount. The toy shadow is
   ACCENT-coloured instead: it sits on the page, where the accent belongs,
   so the accent axis stays judgeable in this pose. */
:root[data-wordmark="plate"] .wordmark{
  display:inline-block;
  padding:.32em .55em .28em;
  background:var(--mount); color:var(--ink);
  border:3px solid var(--keyline);
  border-radius:var(--r-panel);
  -webkit-text-stroke:0; text-shadow:none;
  box-shadow:0 8px 0 -2px var(--accent), 0 18px 40px -12px var(--shadow);
}
:root[data-wordmark="plate"] .wordmark .m{ color:var(--page); }

/* TWO-LINE — left-aligned stack: the single biggest silhouette break. */
:root[data-wordmark="twoline"] .masthead{ text-align:left; }
:root[data-wordmark="twoline"] .wordmark{ font-size:clamp(2.6rem,9vw,4.4rem); }
:root[data-wordmark="twoline"] .wordmark .l1,
:root[data-wordmark="twoline"] .wordmark .l2{ display:block; }

/* ============================ CARDS =====================================
   The card is ONE chunky object: the pale mount holds the art AND the info.
   The info does not float on the page — that was the whole miss.
   ===================================================================== */

main{ max-width:var(--col); margin:0 auto; }

.card{ margin-bottom:34px; }
.card:last-of-type{ margin-bottom:0; }

/* ---- CARD RHYTHM (SPEC.md 2e) — html[data-cards="sticker"]: laid by
   hand, not templated. The transform sits on .card, NOT .panel, so the
   panel's hover lift composes on top untouched. Static transforms — no
   motion, so no reduced-motion concern. Collapses to straight where
   there's no room to lean. */
@media (min-width:700px){
  :root[data-cards="sticker"] .card:nth-of-type(odd){
    transform:translateX(-26px) rotate(-.5deg);
  }
  :root[data-cards="sticker"] .card:nth-of-type(even){
    transform:translateX(26px) rotate(.6deg);
  }
}

/* The card: a saturated frame. It carries the theme's colour and holds
   everything — art AND info. It is NOT the mount. */
.panel{
  background:var(--panel);
  border-radius:var(--r-panel);
  padding:14px;
  box-shadow:0 10px 0 -2px var(--keyline),         /* hard toy shadow */
             0 18px 40px -12px var(--shadow);
  transition:transform .2s ease, box-shadow .2s ease;
}
.card:hover .panel,
.card:focus-within .panel{
  transform:translateY(-3px);
  box-shadow:0 13px 0 -2px var(--keyline),
             0 24px 50px -12px var(--shadow),
             0 0 44px -6px rgba(var(--glow, 0,0,0), .45);
}

/* THE MAT. This pale ring — not the coloured panel — is what rescues dark art.
   It must abut the keyline, and it must stay above relL ~.85. Do not tint it
   down to match the panel: that is exactly the mid-tone failure the header
   describes (4.16:1 becomes 2.40:1 and dark art's edge starts dissolving). */
.frame{
  margin:0;
  background:var(--mount);
  padding:var(--mount-w);
  border-radius:calc(var(--r-art) + var(--mount-w) - 2px);
}

.capsule{
  position:relative;
  aspect-ratio:616/353;        /* all three assets, exactly */
  border-radius:var(--r-art);
  overflow:hidden;
  isolation:isolate;
}

/* ***** THE KEYLINE *****
   MUST be ::after with z-index — NOT an inset box-shadow on .capsule itself.
   A parent's inset shadow paints BENEATH its <img> children, so the keyline
   would silently vanish, taking the contrast guarantee with it, and nothing
   would look wrong on the light capsules you'd happen to test with.
   DO NOT "simplify" this. DO NOT soften --key-w below 2px.                */
.capsule::after{
  content:"";
  position:absolute; inset:0; z-index:3;
  border-radius:inherit; pointer-events:none;
  box-shadow:inset 0 0 0 var(--key-w) var(--keyline);
}

.art,.shot{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;            /* absorbs 16:9 real screenshots (~2% crop) */
  display:block;
}
.art{ z-index:1; }

/* THE DIP. Shots hand off THROUGH this, never directly to each other: one
   fades out to black, then the next comes up out of it (site.js, hide/show).
   Cross-fading them put two plays' worth of players on one field — and let the
   key art show through the 25% gap that two passing opacities always leave.

   Same z-index as .art but later in the DOM, so it covers the art while the
   reel runs without ever being able to sit above a shot. Created by site.js:
   with JS off there is no reel and no need for it. --fade comes from site.js
   (FADE); the 220ms fallback below must match it. */
.blackout{ position:absolute; inset:0; z-index:1; background:#000; }
.shot{ z-index:2; }
.shot,.blackout{ opacity:0; transition:opacity var(--fade,220ms) ease-in-out; }
.shot.is-on,.blackout.is-on{ opacity:1; }
/* The black SNAPS on, under a shot that is still fully opaque. Fading it in
   alongside the outgoing shot left the same 25% gap, and the art flashed
   through it. It still fades out, when the reel stops, back to the art. */
.blackout.is-on{ transition:none !important; }

/* There is deliberately no no-JS hover cross-fade: the .shot images are created
   by JS on first interaction (shipping them inline cost every mobile visitor
   ~1MB before they touched anything), so with JS off there is nothing to fade —
   and the capsule, which is the work, renders perfectly on its own. */

/* ============================ CARD META =================================
   Inside the panel. Info left, pills right — the reference's anatomy.
   Everything here sits on the PALE mount, so everything here is DARK.
   ===================================================================== */

.meta{
  display:flex; align-items:center; justify-content:space-between;
  gap:14px 18px; flex-wrap:wrap;
  padding:16px 6px 4px;
}
.info{ min-width:0; flex:1 1 260px; }

.meta h2{
  font-size:1.4rem; font-weight:800; letter-spacing:-.015em;
  color:var(--ink); line-height:1.15;
}
/* Sentence case, normal tracking, on purpose: uppercase microlabels read
   as template filler no matter how tight the letter-spacing is. */
.status{
  margin-top:5px;
  font-size:13px; font-weight:700; color:var(--ink-mid);
}
.status .out{
  display:inline-block;
  background:var(--deep); color:var(--on-deep);
  padding:3px 10px; border-radius:var(--r-pill);
}
.tag{
  margin-top:9px; max-width:46ch;
  font-size:.9rem; line-height:1.45; color:var(--ink-mid);
}
.cta{ display:flex; gap:10px; flex-wrap:wrap; align-items:center; }

/* ============================ PILLS =====================================
   SOLID = the strong action (buy / contact). OUTLINE = the soft one (follow).
   Differentiate by KIND, not by loudness: the outline pills are the same size
   and full contrast — different, not quieter.

   There is NO [disabled] rule in this stylesheet, so a dead button is
   structurally unrepresentable.
   ===================================================================== */

.btn{
  font:700 14px/1 var(--font); letter-spacing:.01em;
  padding:13px 20px; border-radius:var(--r-pill);
  display:inline-flex; align-items:center; justify-content:center; gap:.5em;
  text-decoration:none; cursor:pointer; white-space:nowrap;
  border:2px solid var(--deep);
  transition:transform .14s ease, background-color .14s ease, color .14s ease;
}
/* Hover = the accent moment: both pills light up in the theme's full accent
   (ink text — every accent is light by rule). A partial wash over cream reads
   as a washed-out mint, not the accent. This is what makes the accent axis
   matter beyond the wordmark. */
.btn--solid{ background:var(--deep); color:var(--on-deep); }
.btn--solid:hover{
  transform:translateY(-2px);
  background:var(--accent); color:var(--ink);
}
.btn--ghost{ background:transparent; color:var(--ink); }
.btn--ghost:hover{
  transform:translateY(-2px);
  background:var(--accent);
}
.btn:active{ transform:none; }

/* ============================ CONTACT ===================================
   One more card, not a hero slab: the section reuses .panel and the .meta
   anatomy wholesale, so it can never drift out of the cards' language.
   Only the padding differs (no art above the meta row). */

.contact{ max-width:var(--col); margin:58px auto 0; }
.contact .meta{ padding:6px 6px 2px; }
.contact .micro{ margin:10px 6px 0; font-size:12px; color:var(--ink-mid); }
.contact .micro a{ color:var(--ink); }

/* ============================ SOCIAL ==================================== */

/* Proximity: the socials and the copyright are one footer unit — a big
   breath separates them from the content above, a small gap binds them
   to each other. */
.social{
  max-width:var(--col); margin:52px auto 0;
  display:flex; gap:8px; flex-wrap:wrap; justify-content:center;
}
/* Bare icons, no rings: big marks straight on the page, glyphs sized to
   what the old circles were. Box > glyph keeps a generous tap target. */
.social a{
  width:56px; height:56px;
  display:inline-flex; align-items:center; justify-content:center;
  border-radius:var(--r-pill);           /* keeps the focus ring pill-shaped */
  color:var(--text-hi);
  transition:color .16s ease, transform .14s ease;
}
.social a:hover{
  color:var(--accent);                   /* on the page: accent-safe */
  transform:translateY(-2px);
}
.social svg{ width:44px; height:44px; fill:currentColor; }

/* Placeholder for a network whose account doesn't exist yet: visibly
   parked (dimmed, no hover, not a link) so it can never be a dead control. */
.social .social__soon{
  width:56px; height:56px;
  display:inline-flex; align-items:center; justify-content:center;
  color:var(--text-mid); opacity:.45;
}

/* ============================ FOOTER ==================================== */

footer{
  max-width:var(--col); margin:18px auto 0;
  text-align:center;
  font-size:13px; font-weight:500; color:var(--text-mid);
}
footer a{ color:var(--text-hi); }
footer a:hover{ color:var(--accent); }

/* ============================ FOCUS =====================================
   TWO-TONE, AND IT IS MANDATORY. The page is saturated and the mount is
   near-white, so a single-colour ring is invisible on one or the other in
   every theme. Same complementary trick as the capsule frame.
   ===================================================================== */

:where(a,button):focus-visible{
  outline:none;
  box-shadow:0 0 0 3px var(--ring-core),   /* reads on the pale mount */
             0 0 0 6px var(--ring-halo);   /* reads on the saturated page */
}

/* ============================ A11Y / MOTION ============================= */

@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.01ms !important;
    scroll-behavior:auto !important;
  }
  /* Opacity is not movement, and nothing here parallaxes. Keep the fade. */
  .shot,.blackout{ transition-duration:150ms !important; }
  .card:hover .panel,
  .card:focus-within .panel,
  .btn:hover,
  .social a:hover{ transform:none !important; }
}

/* :root[data-theme="ink"] (0,2,0) outranks a bare :root (0,1,0), and the JS
   always sets data-theme — so this must match the theme selectors' specificity
   or it is silently dead. */
@media (prefers-contrast: more){
  :root,
  :root[data-theme]{ --key-w:4px; --ink-mid:var(--ink); }
  body::before,
  body::after{ display:none; }   /* every bg recipe is decoration; drop the noise */
}

/* Forced colours (Windows High Contrast) suppress box-shadow outright and
   repaint backgrounds. That would kill BOTH halves of the mount/keyline pair at
   once — the single state the contrast guarantee says is impossible. Images are
   already exempt from colour forcing, so the frame around them has to be too. */
@media (forced-colors: active){
  .frame,
  .capsule::after{ forced-color-adjust:none; }
  .frame{ background:var(--mount); }
  .capsule::after{ box-shadow:inset 0 0 0 var(--key-w) var(--keyline); }
  body::before,
  body::after{ display:none; }
  :where(a,button):focus-visible{ outline:3px solid Highlight; outline-offset:3px; }
}

/* ============================ RESPONSIVE ================================
   Single column at every width. Never a grid: three capsules this tonally
   different, side by side in one eye-span, read as a mistake.
   ===================================================================== */

@media (max-width:560px){
  .meta{ padding:14px 4px 2px; }
  .cta{ width:100%; }
  .cta .btn{ flex:1 1 100%; }
}

@media (max-width:480px){
  :root{ --mount-w:10px; --r-panel:18px; }
  body{ padding-top:48px; }
  .wordmark{ -webkit-text-stroke-width:.09em; }
}
