/* ---------- self-hosted faces ----------
   These used to come from fonts.googleapis.com behind a render-blocking
   stylesheet, which cost a DNS lookup, a TLS handshake and a round trip to a
   third party before a single glyph could paint — and told Google who was
   reading the page. Same files, same origin, one year immutable.
   Archivo is the VARIABLE cut, so one file covers 500-800 rather than four.
   unicode-range keeps latin-ext off the wire for anyone who never needs it. */
@font-face {
  font-family: "Anton";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/anton-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Anton";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/anton-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/fonts/archivo-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/fonts/archivo-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ============================================================
   IZZY.LA — dev site
   Collage language: aged paper, halftone, torn edges, tape,
   mint sticker-outlines, stacked Anton wordmark. The scroll
   mechanics (fixed 3D cube pair, pinned ticker) stay.
   ============================================================ */

:root {
  /* ============================================================
     SaturnDesign DTCG tokens — compose 2026-08-07 (user-izzydesign)
     NAMED DIRECTION ("Print Roll"):
       mechanism = solved-cube photocopier shell (woodcut filigree +
         Saturn plate + particle band + collage on cream paper;
         mint rationed to one accent — selection, focus swipe, stamps)
       × movement = xerox-zine-collage (craft print, not cream-serif luxury)
       × money-surface = WORK ORDER stays paperwork-calm: straight grid
         fields, mechanism recedes to tape/tear/ghost only
     Logo test: remove IZZY.LA — cube + filigree + photocopier grain still
     name the site. Never clone MUJI/Palace; synthesize one accent ration.
     ============================================================ */
  --sd-color-bg: #ece7db;
  --sd-color-surface: #f5f2ea;
  --sd-color-stock: #e0dac9; /* second stock — slip / chip / paper-deep */
  --sd-color-ink: #141312;
  /* SD base ink-muted was #4a4844; #57534b holds AA on paper + paper-deep */
  --sd-color-ink-muted: #57534b;
  --sd-color-ink-soft: #33302b; /* dense body type on paper (AA body weight) */
  --sd-color-accent: #d9e8cf;
  --sd-color-accent-ink: #141312;
  --sd-color-ink-faint: rgba(20, 19, 18, 0.22);

  /* spacing scale — 8px base (DTCG spacing.*) */
  --sd-space-050: 4px;
  --sd-space-100: 8px;
  --sd-space-150: 12px;
  --sd-space-200: 16px;
  --sd-space-300: 24px;
  --sd-space-400: 32px;
  --sd-space-600: 48px;
  --sd-space-800: 64px;
  --sd-space-1200: 96px;

  /* type scale — base 16, ratio 1.25 (DTCG type-scale.*) */
  --sd-type-xs: 10px;
  --sd-type-sm: 13px;
  --sd-type-base: 16px;
  --sd-type-lg: 20px;
  --sd-type-xl: 25px;
  --sd-type-2xl: 31px;
  --sd-type-3xl: 39px;
  --sd-type-4xl: 49px;

  /* radius roles — vary; one-radius-everywhere is a slop tell */
  --sd-radius-none: 0;
  --sd-radius-sm: 4px;
  --sd-radius-md: 8px;
  --sd-radius-lg: 16px;

  /* --- legacy aliases (load-bearing across shell + JS-written styles) --- */
  --paper: var(--sd-color-bg);
  --paper-lift: var(--sd-color-surface);
  --paper-deep: var(--sd-color-stock);
  --ink: var(--sd-color-ink);
  --ink-soft: var(--sd-color-ink-soft);
  --mint: var(--sd-color-accent);
  --tape: rgba(196, 186, 158, 0.55);
  /* was #6b675f — 4.03:1 on --paper-deep, under AA at the small sizes this
     token carries. #57534b reads 6.20 on paper / 5.48 on paper-deep. */
  --muted: var(--sd-color-ink-muted);
  --line: 1px solid var(--ink);
  --pad: clamp(20px, 4vw, 44px);
  --cube-size: clamp(150px, 24vw, 240px);
  --header-h: 56px;
  /* the header scrolls with the page (not fixed), so it eats real viewport
     height above the hero; main.js measures its true rendered height and
     overwrites this — the fallback covers the no-JS / pre-measure instant */
  --hero-header-offset: calc(var(--header-h) + 10px);
  --font: "Archivo", "Helvetica Neue", Arial, sans-serif;
  --display: "Anton", "Archivo", sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;
}

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

/* The browser's own `[hidden] { display: none }` is a USER-AGENT rule, so any
   author rule that sets `display` silently beats it and `el.hidden = true`
   becomes a no-op. This file already carries four one-off `[hidden]` patches
   written after that bit; this closes the class for good. */
[hidden] { display: none !important; }

html { scroll-behavior: smooth; }

/* Section paging lives in main.js ("section glide") — native CSS snap was
   tried 2026-08-03 and replaced the same day: its snap animation is
   UA-controlled and cannot be made slower or smoother, and izzy wants a
   premium glide. CSS snap must NOT be reintroduced alongside the JS glide —
   the two fight over every landing. */

body {
  font-family: var(--font);
  background: var(--paper);
  color: var(--ink);
  font-weight: 500;
  line-height: 1.35;
  -webkit-font-smoothing: antialiased;
}

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

h1, h2, h3 { text-transform: uppercase; }

a { color: inherit; text-decoration: none; }

/* print grain over everything */
.grain {
  position: fixed;
  inset: 0;
  z-index: 100;
  pointer-events: none;
  opacity: 0.5;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.10 0'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- header ---------- */

.site-header {
  /* scrolls with the page — not sticky/fixed */
  position: relative;
  z-index: 60;
  margin: 10px var(--pad) 0;
  display: flex;
  justify-content: space-between;
  align-items: stretch;
  gap: 20px;
  height: var(--header-h);
}

.brand {
  background: var(--ink);
  color: var(--paper);
  font-family: var(--display);
  font-size: 30px;
  letter-spacing: 0.02em;
  display: flex;
  align-items: center;
  padding: 0 18px;
  flex: 0 1 auto;
  white-space: nowrap;
}

.header-right { display: flex; gap: 10px; flex: none; }

/* narrow: brand + buttons hold one row and wrap only if their widths
   genuinely do not fit */
@media (max-width: 860px) {
  .site-header {
    height: auto;
    flex-wrap: wrap;
    gap: 8px;
  }
  .brand { order: 1; height: var(--header-h); }
  /* The control bar takes the measure beside the masthead — and the WHOLE
     measure on the phones where the wordmark and the buttons cannot share a
     line. MEASURED: brand 96 + WORK ORDER 147 + MENU 95 + gaps = 356 against
     353 available at 393px, so it misses by 3px there, 21px at 375 and 36px
     at 360 — the wrap is the common phone case, not the exception. Before
     this the wrapped row hung right-aligned with a ragged left edge under the
     wordmark, and a 3px near-miss reads as breakage rather than as a second
     row. Now ONE rule composes both states: WORK ORDER absorbs the slack (it
     is the page's one ask) and its arrow parks on the right edge, like the
     ticket stubs elsewhere on the sheet.
     The max-width is what keeps this honest at tablet widths — without it a
     768px viewport grows the slab to ~498px of mostly empty ink. Capped, the
     pair simply flushes right and the composition is the shipped one. */
  .header-right {
    order: 2;
    flex: 1 1 auto;
    min-width: 0;
    justify-content: flex-end;
    height: var(--header-h);
  }
  .header-right .btn-boxed { flex: 1 1 auto; min-width: 0; max-width: 260px; }
  /* the label grows so the arrow sits on the slab's right edge, not adrift
     in the middle of it */
  .header-right .btn-boxed .btn-label { flex: 1 1 auto; }
}

.btn {
  font-family: var(--font);
  font-size: 14px;
  font-weight: 700;
  text-transform: uppercase;
  background: var(--paper);
  color: var(--ink);
  border: 2px solid var(--ink);
  display: inline-flex;
  align-items: stretch;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.btn > span { display: flex; align-items: center; padding: 0 14px; }

/* compact header labels — only exist below 700px
   (.btn > .btn-label-s outranks the .btn > span display rule) */
.btn > .btn-label-s { display: none; }

.btn .btn-arrow { border-left: 2px solid var(--ink); font-size: 16px; }

/* Pointer-gated: on touch, :hover latches on tap and holds until you tap
   somewhere else, so WRITE ME / MENU sat inverted long after the tap. */
@media (hover: hover) and (pointer: fine) {
  .btn:hover { background: var(--ink); color: var(--paper); }
}
.btn:focus-visible { background: var(--ink); color: var(--paper); }

/* Corner LIVE chip — only appears while streaming (never a fake LIVE) */
.live-chip {
  position: fixed;
  z-index: 60;
  right: max(14px, env(safe-area-inset-right));
  bottom: max(14px, env(safe-area-inset-bottom));
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  font-family: var(--font);
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--ink);
  box-shadow: 4px 4px 0 rgba(20, 19, 18, 0.9);
  transform: rotate(1.2deg);
  transition: background 0.15s, color 0.15s, transform 0.15s;
}

.live-chip[hidden] { display: none !important; }

.live-chip-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink);
  animation: kick-pulse 1.6s ease-in-out infinite;
}

/* pointer-gated — a tapped chip on touch would latch this invert */
@media (hover: hover) and (pointer: fine) {
  .live-chip:hover {
    background: var(--ink);
    color: var(--paper);
    transform: rotate(0deg) translate(-1px, -1px);
  }

  .live-chip:hover .live-chip-dot {
    background: var(--mint);
    animation: none;
  }
}

@keyframes kick-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(20, 19, 18, 0.45); }
  50% { box-shadow: 0 0 0 5px rgba(20, 19, 18, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .live-chip-dot { animation: none; }
}

/* skip link — the header carries a brand mark, the WORK ORDER chip and
   the menu button before <main id="main">; off-screen until it's the
   focused element, then it's the first thing a keyboard user sees.
   z-index clears the grain (100) so nothing prints over it.
   Landmark residual (2026-08-08): #main matches sibling surface skip targets. */
.skip-link {
  position: fixed;
  z-index: 200;
  left: max(var(--pad), env(safe-area-inset-left, 0px));
  top: -60px;
  padding: 10px 16px;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font);
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border: 2px solid var(--paper);
  transition: top 0.2s ease;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
.skip-link:focus {
  top: max(14px, env(safe-area-inset-top, 0px));
  outline: 2px solid var(--paper);
  outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .skip-link { transition: none; }
}

.btn-menu .menu-icon {
  border-left: 2px solid var(--ink);
  font-size: 22px;
  font-weight: 500;
  padding: 0 12px;
  transition: transform 0.25s;
}

.btn-menu[aria-expanded="true"] .menu-icon { transform: rotate(45deg); }

/* ---------- menu overlay ---------- */

.menu-backdrop {
  position: fixed;
  inset: 0;
  z-index: 58;
  background: rgba(20, 19, 18, 0.5);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.3s, visibility 0s 0.3s;
}

.menu-overlay {
  position: fixed;
  z-index: 59;
  top: 10px;
  left: var(--pad);
  right: var(--pad);
  max-height: calc(100dvh - 20px);
  overflow: auto;
  background: var(--paper);
  border: 2px solid var(--ink);
  box-shadow: 6px 6px 0 var(--ink);
  transform: translateY(-8px);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.25s, transform 0.25s, visibility 0s 0.25s;
  /* clears the header, which sits at z-60 ABOVE this panel. Must be the
     MEASURED height (main.js): the static token is 56px and stops being true
     the moment the header wraps to a second row. The fallback keeps a sane
     value if the script never runs. */
  padding-top: var(--header-h-live, var(--header-h));
}

/* the open menu owns the screen: without this, wheel/touch input over the
   overlay fell through and scrolled (then section-glided) the page BEHIND
   the modal — and after enough of it the CLOSE button left the viewport */
body.menu-open { overflow: hidden; }
body.menu-open .menu-backdrop { opacity: 1; visibility: visible; pointer-events: auto; transition: opacity 0.3s; }
body.menu-open .menu-overlay { opacity: 1; visibility: visible; transform: none; pointer-events: auto; transition: opacity 0.25s, transform 0.25s; }

.menu-overlay ul { list-style: none; }

.menu-overlay a {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 18px;
  border-top: 2px solid var(--ink);
  font-size: 18px;
  font-weight: 800;
  text-transform: uppercase;
}

@media (hover: hover) and (pointer: fine) {
  .menu-overlay a:hover { background: var(--ink); color: var(--paper); }
}

/* ---------- cubes ---------- */

.cube-persp {
  position: fixed;
  z-index: 10;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  perspective: 800px;
  pointer-events: none;
}

/* registration-ghost offset stays in misprint range (~6-10% of the cube)
   or the wireframe reads as a second object */
.cube-persp--wire { z-index: 9; margin-top: calc(var(--cube-size) * -0.04); }

/* off-center the cube pair on wide screens so the wordmark's last letter
   isn't buried under a flat cube face at load (portrait overlap is fine —
   organic collage; flat-cube-over-type reads as a bug) */
/* THE CUBE SITS ABOVE HIS HEAD (izzy 2026-08-02: "lets put the cube above my
   head, it goes behind me as we scroll").

   left:50% is not a guess — the portrait is centred on 50% and measured
   536..904 at 1440, i.e. dead centre 720. So the cube shares his axis and
   reads as balanced on his head rather than parked beside it.

   The travel is the point. The cube is viewport-fixed while the portrait
   scrolls, so as soon as you move it descends relative to him and passes
   BEHIND: the portrait is z-12, the cube z-10, and that ordering was already
   in place — it just never mattered while the cube sat off to the side with
   nothing to overlap. Do not raise the cube above z-12 "so it is visible";
   that inverts the whole gesture.

   Was `left: calc(60% + 130px)`, which held the cube clear of a giant display
   wordmark that no longer exists (izzy cut it 07-29).

   🔑 861px, NOT 700px — this is the DESKTOP half of the one mobile boundary.
   The page has a single narrow band (≤860px, where the header wraps the live
   strip onto its own row) and every hero rule has to switch on the same edge.
   It did not: the cube's desktop anchor started at 701px while the ticker
   unpinned at 760px and the mobile hero pass began at 700px, so 701–860px got
   the DESKTOP composition in a phone-shaped window — a viewport-fixed cube
   parked dead centre landed squarely on "BRANDS." and the portrait fell to the
   bottom with ~320px of empty paper above it (izzy, 2026-08-03: "everything
   gets centered, i grow brands is at the top, the cube is underneath it and
   then my picture is under the cube"). If you move ANY of these, move all of
   them together — grep `860px` in this file. */
@media (min-width: 860.02px) {
  .cube-persp--solid { left: 50%; top: 26%; }
  .cube-persp--wire { left: 50%; top: 26%; }
}

/* grab hit-target: flat disc over the cube so pointerdown works despite
   the 0×0 perspective wrapper. Content with .cube-below still covers it. */
.cube-hit {
  display: none;
}

@media (pointer: fine) {
  .cube-hit {
    display: block;
    position: absolute;
    z-index: 2;
    width: calc(var(--cube-size) * 1.15);
    height: calc(var(--cube-size) * 1.15);
    margin: calc(var(--cube-size) * -0.575) 0 0 calc(var(--cube-size) * -0.575);
    border-radius: 18%;
    cursor: grab;
    pointer-events: auto;
    /* invisible — faces still paint underneath */
    background: transparent;
  }
  .cube-persp--solid.is-grabbing .cube-hit { cursor: grabbing; }
  /* The cube grab target is a transparent box sitting on bare paper, so the
     global mint focus ring lands mint-on-paper — 1.04:1, effectively no ring
     at all. Ink is 15:1, and a dashed box is the crop-mark language the rest
     of the page already speaks. */
  .cube-hit:focus-visible {
    outline: 2px dashed var(--ink);
    outline-offset: 4px;
  }
}

@media (pointer: fine) {
  .cube-persp--solid {
    pointer-events: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .cube-hit { display: none !important; }
}

.cube {
  position: absolute;
  transform-style: preserve-3d;
  width: var(--cube-size);
  height: var(--cube-size);
  margin: calc(var(--cube-size) / -2) 0 0 calc(var(--cube-size) / -2);
}

.face {
  position: absolute;
  inset: 0;
  background: var(--paper);
  border: var(--line);
  /* INSET: an outset ring on each face collided with its neighbour's at the
     shared edge, producing the doubled mint line with a black gap. An inset
     ring never crosses a seam and holds constant width under foreshortening. */
  box-shadow: inset 0 0 0 4px var(--mint);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  backface-visibility: hidden;
  overflow: hidden;
}

.face.f1 { transform: translateZ(calc(var(--cube-size) / 2)); }
.face.f2 { transform: rotateY(180deg) translateZ(calc(var(--cube-size) / 2)); }
.face.f3 { transform: rotateY(90deg) translateZ(calc(var(--cube-size) / 2)); }
.face.f4 { transform: rotateY(-90deg) translateZ(calc(var(--cube-size) / 2)); }
.face.f5 { transform: rotateX(90deg) translateZ(calc(var(--cube-size) / 2)); }
.face.f6 { transform: rotateX(-90deg) translateZ(calc(var(--cube-size) / 2)); }

.face svg { width: 72%; height: 72%; }

.face--dark { background: var(--ink); color: var(--paper); }

.face-word {
  font-family: var(--display);
  /* margin inside the face so oblique rotations never slice the lockup */
  font-size: calc(var(--cube-size) * 0.2);
  line-height: 0.95;
  text-transform: uppercase;
  /* ONE LINE (izzy 2026-08-03: "dont format izzy.la like that"). The old
     lockup was two words and broke as IZZY / AWAKE; carrying that break into
     the new name left ".LA" alone on the second line, reading as a stray
     fragment rather than a domain. IZZY.LA is a single token and is set as
     one. nowrap so a narrow face can never re-introduce the break, and the
     size steps down to keep it inside the face's margin at the same width the
     two-line setting used to occupy. */
  font-size: calc(var(--cube-size) * 0.155);
  white-space: nowrap;
}

.face--map { background-color: var(--paper); background-image:
  repeating-linear-gradient(0deg, transparent 0 9px, rgba(20,19,18,0.7) 9px 10px),
  repeating-linear-gradient(90deg, transparent 0 13px, rgba(20,19,18,0.7) 13px 14px),
  repeating-linear-gradient(63deg, transparent 0 29px, rgba(20,19,18,0.9) 29px 31px);
}

.face--seal {
  background: var(--ink);
  color: var(--paper);
}

/* print-register seal (line-art alpha) — no blend or glow needed */
.face--seal img {
  width: 62%;
  height: auto;
  /* The seal was pinned to the LEFT edge of its face, not centred. `.face` is
     a centring flex container, but its flex item is the <picture> wrapper —
     which is display:block and stretches the full 238px face, so there was
     nothing left for justify-content to centre, and the 148px <img> inside it
     simply sat at the wrapper's left edge (gapLeft 0 / gapRight 90).
     `margin-inline:auto` centres the image inside that full-width wrapper;
     `display:block` also drops the inline baseline gap under it, which was
     making the vertical sit 23px top against 28px bottom. */
  display: block;
  margin-inline: auto;
}

.cube-persp--wire .face {
  background: transparent;
  border: 1px solid rgba(20, 19, 18, 0.55);
  box-shadow: none;
  backface-visibility: visible;
}

/* content that must cover the cube */
.cube-below { position: relative; z-index: 20; }

/* ---------- shared chrome ---------- */

.section-heading {
  font-family: var(--display);
  font-size: clamp(32px, 5vw, 64px);
  padding: var(--pad);
  /* was +0.01em — positive tracking on big Anton reads gappy. Tighten it,
     same as the hero wordmark, so the display tier speaks with one voice. */
  letter-spacing: -0.01em;
}

.chip {
  display: inline-block;
  font-size: 12px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border: 2px solid var(--ink);
  padding: 6px 10px;
  background: var(--paper);
}

.chip-row { display: flex; flex-wrap: wrap; gap: 8px; }

/* tape scraps */
.tape {
  position: absolute;
  width: 96px;
  height: 26px;
  background: var(--tape);
  box-shadow: 0 1px 2px rgba(20, 19, 18, 0.15);
}

.tape-tl { top: -12px; left: -28px; transform: rotate(-8deg); }
.tape-tr { top: -12px; right: -28px; transform: rotate(7deg); }

/* torn paper edge */
.tear {
  position: absolute;
  left: 0;
  right: 0;
  height: 18px;
  background: var(--paper);
  clip-path: polygon(0 0, 100% 0, 100% 18%, 96% 68%, 91% 25%, 86% 80%, 80% 30%, 74% 92%,
    68% 38%, 61% 75%, 55% 22%, 48% 88%, 42% 34%, 36% 70%, 30% 18%, 24% 82%, 18% 40%,
    12% 92%, 6% 30%, 0 75%);
}

.tear-top { top: -1px; }

/* ---------- hero ---------- */

/* ---------- the stage ----------
   Every band on this page was full-bleed, but the hero's type is capped in px
   (300px) while its collage pieces are positioned in percentages and the cube
   is fixed at viewport centre. Past about 1600px those three stop agreeing:
   the wordmark huddles left at a fixed size, the cube drifts to the middle,
   the cards fly to the far edge, and the composition that reads as a tight
   collage at 1440 becomes scattered objects on a big empty sheet.
   Capping the stage and centring it means a 2048px window renders the SAME
   composition as a 1600px one, with paper either side. The fixed cube needs no
   change: a centred stage shares its centre with the viewport. Below
   --stage-w nothing here applies at all. */
:root { --stage-w: 1600px; }

.site-header,
.hero,
#about,
#things {
  max-width: var(--stage-w);
  margin-inline: auto;
}

/* .hero / #about / #things carry their own `padding: … var(--pad) …`, so the
   auto margins above cost them nothing. .site-header is the one block whose
   gutter lives in its MARGIN (`margin: 10px var(--pad) 0`) — and
   margin-inline:auto overrides exactly that. On a phone the header therefore
   ran flush to both viewport edges: the wordmark plate started at x=0, the
   MENU plate ended at x=viewport, and both 2px borders were half-eaten by the
   screen while every other block on the page sat --pad in. Bounding the WIDTH
   instead keeps the centring and restores the gutter — same idiom as .footer.

   Deliberately scoped to the phone breakpoint. Above 860px the header has
   been full-bleed since the stage cap landed and that composition is
   approved; widening this rule would move a desktop layout nobody asked to
   change. (Wide screens already inset anyway, once --stage-w bites.) */
@media (max-width: 860px) {
  .site-header {
    max-width: calc(100% - var(--pad) * 2);
  }
}

.hero {
  position: relative;
  /* the in-flow header already consumed --hero-header-offset above us —
     subtract it so header + hero together fill exactly one screen instead
     of overflowing it (was cutting the SCROLL caption off on mobile) */
  min-height: calc(100vh - var(--hero-header-offset));
  /* CAPPED: --stage-w bounds the composition horizontally; this bounds it
     vertically for the same reason. Above the cap the extra viewport height
     becomes clean paper with a little of .intro showing, which also earns
     the caption's "SCROLL." instead of stretching the collage apart. */
  min-height: min(calc(100svh - var(--hero-header-offset)), 1040px);
  padding: calc(var(--header-h) + 44px) var(--pad) var(--pad);
  overflow: hidden;
}


@keyframes hero-rise {
  from { transform: translateY(12%); opacity: 0; }
  to { transform: none; opacity: 1; }
}

/* The CMYK plate stack (.hero-line, .plate-k/c/m/y) was removed 2026-08-02.
   It belonged to the giant IZZY.LA display wordmark izzy cut on 07-29 and
   had had no markup since — grep confirms zero users.

   One of those dead rules was actively harmful: a bare `.plate { display:
   block; white-space: nowrap }` now collides with .plate, which is the class
   the Saturn layers use. It was doing no visible damage yet, but a stale
   selector sitting on a live class name is a bug waiting for the next person
   who adds a rule to it. */

@media (prefers-reduced-motion: reduce) {
}

/* value prop, promoted up under the wordmark so it reads in the first
   viewport — same words as .intro-lede + .about-copy, just moved.
   The portrait (z-index 12) sits directly under this line at most viewport
   sizes and is ~70% opaque there, so this needs the same paper-backed
   "pasted label" treatment .hero-caption already uses to stay legible
   over the collage, not a bare z-index bump. */
/* ---------- hero lead: Anton display claim + Archivo proof ----------
   Three ranked registers, no ties: Anton (claim) -> Archivo 600 (proof) ->
   mono 12px (caption chip). The paper rectangle behind the old lead is GONE:
   painted in the page's own colour it never read as a label on the collage,
   it read as the collage being erased (it was wiping 71% of the Saturn glyph). */

:root { --sw: min(100vw, var(--stage-w)); }

.hero-lede {
  position: relative;
  z-index: 13;
  margin: 0;
}

.lede-claim {
  /* this IS the <h1> now (the proof line was pulled), so zero the UA heading
     margin that would otherwise push the whole hero down */
  margin: 0;
  display: block;
  /* hug the type: as a full-width block its box spanned the whole stage, which
     both made every overlap test a false positive and gave the plate
     pseudo-elements a box far wider than the ink they reprint */
  width: fit-content;
  position: relative;
  font-family: var(--display);
  font-weight: 400;
  text-transform: uppercase;
  /* keyed to the CAPPED stage, not raw vw: above 1600px the composition stops
     growing, so type that kept tracking the viewport would drift out of scale */
  /* the floor is viewport-aware because the lines are `nowrap`: a flat 96px
     floor makes "BRANDS." ~286px, which overflows a 320px phone by a couple
     of px and puts a horizontal scrollbar on the whole page. No effect at
     360px and up. */
  font-size: clamp(min(96px, 27vw), calc(0.104 * var(--sw)), 162px);
  line-height: 0.86;
  letter-spacing: -0.015em;
  color: var(--ink);
  animation: hero-rise 0.9s cubic-bezier(0.2, 0.8, 0.2, 1) both;
  /* CMYK plate offsets — driven by pointer velocity in main.js via #heroTitle */
  --c-x: 0px; --c-y: 0px;
  --m-x: 0px; --m-y: 0px;
  --y-x: 0px; --y-y: 0px;
  --plate-a: 0;
}

.lede-line {
  position: relative;
  display: block;
  width: fit-content;
  white-space: nowrap;
  /* optical: pull the flat-stemmed I onto the --pad line */
  text-indent: -0.04em;
}

/* THE DOUBLE-STRIKE BREATH (ONE MACHINE motion pass, chunk A 2026-08-03).
   NOT the dead CMYK fringe — the comment below records why izzy killed that
   one. This is the same ink struck twice: the gen-chip's own misregistration
   ghost at headline scale. motion.js seats --ds-x/y/a on the photocopier
   clock (stepped, never tweened, never mouse-tracked, never off-palette);
   with JS absent or reduced motion on, --ds-a holds 0 and this renders
   nothing. z -1: a double-strike sits UNDER the clean impression. */
.lede-line::after {
  /* / "" — keep the double-strike out of the H1's accessible name (the
     card-ghost review caught this class; the H1 had the same flaw) */
  content: attr(data-text);
  content: attr(data-text) / "";
  position: absolute;
  inset: 0;
  z-index: -1;
  color: var(--ink);
  opacity: var(--ds-a, 0);
  transform: translate(var(--ds-x, 0px), var(--ds-y, 0px));
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .lede-line::after { opacity: 0; }
}

/* THE CMYK MISREGISTRATION GHOST IS GONE (izzy, 2026-08-02: "the assets look
   unpro and sloppy" -> kill the fringe).

   .lede-line::before/::after reprinted the headline in saturated cyan
   (#00a3e0) and magenta (#e6007e), offset by pointer velocity. Two problems.
   It was the only off-palette colour anywhere on a paper/ink/mint site, and it
   sat on the largest type on the page — so the loudest element was also the
   only one breaking the palette. And a real press misregistration is a
   fraction of a millimetre in process inks; a saturated RGB fringe that tracks
   your mouse reads as a glitch filter, which is the opposite of the printed
   object this page is meant to be.

   main.js still writes --c-x/--m-x on #heroTitle. That is now inert rather
   than wrong: the custom properties simply have no consumer. Left in place
   because the same driver feeds the peel physics. */

/* .lede-proof removed with the proof line (izzy pulled it from the hero). */

/* the sentence lifted out of the deleted intro section. Mono at reading size,
   sitting under the hairline: it explains the claim rather than competing with
   it, which is the job the intro's giant repeat was failing to do. The measure
   is capped in ch so it never runs under the portrait on a wide monitor. */

/* Three-surface doors under the hero note — mono underline only (mono.izzy-dev-page). */
.hero-routes {
  position: relative;
  z-index: 13;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 22px;
  margin-top: clamp(16px, 1.8vw, 24px);
  max-width: 48ch;
}
.hero-routes a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 8px 0;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink);
  border-bottom: 2px solid var(--ink);
}
.hero-routes a span[aria-hidden] {
  color: var(--mint);
  margin-left: 6px;
}
@media (hover: hover) and (pointer: fine) {
  /* :not(.btn) — this is the ink-swipe for the two PLAIN routes: it pays for
     its own padding with negative margin so the swipe bleeds past the text
     without moving it. WRITE IN is now a bordered .btn, and on a box that
     already has padding the same rule is a 108.20 -> 120.20px width jump
     under the cursor (measured 2026-08-23). The swipe belongs to text, not
     to boxes; the button has its own press state. */
  .hero-routes a:not(.btn):hover {
    background: var(--ink);
    color: var(--paper);
    border-bottom-color: transparent;
    padding-inline: 6px;
    margin-inline: -6px;
  }
  .hero-routes a:not(.btn):hover span[aria-hidden] { color: var(--mint); }
}
.hero-routes a:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}


.lede-rule {
  display: block;
  /* a short terminal hairline that gives the two-word statement a "finish"
     now the proof line is gone. Width shortened from the art director's
     0.30*--sw (which ran 172px through the seal scrap) so it clears the
     collage at 1280-2560. */
  width: clamp(120px, calc(0.12 * var(--sw)), 200px);
  max-width: 100%;
  height: 1px;
  margin-top: clamp(20px, 2vw, 30px);
  background: var(--ink);
  opacity: 0.8;
}

/* mint is a STROKE in this language, never a large field — at Anton display
   size a filled highlight became a ~320x130px mint slab, so it underlines.
   text-decoration, NOT a background gradient: with line-height 0.86 the
   inline content box (Anton's full ascent+descent) measured 226px against a
   129px line box, so a gradient's "bottom 0.14em" painted ~60px BELOW the
   line — the mint band floated between GROW and BRANDS instead of underlining
   anything. text-decoration is positioned off the BASELINE, which is the only
   reference that survives a sub-1 line-height. */
.hero-lede .hl {
  background: none;
  text-decoration-line: underline;
  text-decoration-color: var(--mint);
  text-decoration-thickness: 0.085em;
  text-underline-offset: 0.02em;
  text-decoration-skip-ink: none;
}

@media (prefers-reduced-motion: reduce) {
  .lede-claim {
    animation: none;
    --c-x: 0px; --c-y: 0px;
    --m-x: 0px; --m-y: 0px;
    --plate-a: 0;
  }
}

.hero-collage {
  position: absolute;
  inset: 0;
  color: var(--ink);
  pointer-events: none;
}

/* about-section scraps still use .scrap; hero collage is ink-only */
.scrap {
  position: absolute;
  background: var(--paper-deep);
  box-shadow: 3px 4px 0 rgba(20, 19, 18, 0.2);
}

/* seamless hero glyphs — float on paper like saturn + cube, no tape plates */
.hero-saturn {
  position: absolute;
  /* HALO: centred on the portrait's own axis (which sits at left:58%) so the
     ring reads as a halo over izzy's head — his call, and it works now that
     the lead is Anton. It could not sit here before: the old Archivo lead ran
     wide enough to collide, first hidden behind that lead's opaque paper box
     and then printing straight across the word "FOUR". Anton is condensed, so
     the longest line is ~30% of the stage and this lane is clear.
     Sized off the CAPPED stage, not raw vw, or it drifts past 1600px. */
  top: 3%;
  /* Centred with `left`, NOT translateX(-50%): `.peelable` sets `transform`
     globally further down the sheet at equal specificity, so it wins the
     cascade and any transform declared here is silently discarded — the
     portrait's own -50% survives only because it is not .peelable. Half the
     width is subtracted directly instead; clamp/min are monotonic, so halving
     each term is exact. Transform stays owned by the peel physics. */
  left: calc(58% - clamp(65px, min(7vw, 0.072 * var(--stage-w)), 115px));
  right: auto;
  width: clamp(130px, min(14vw, 0.144 * var(--stage-w)), 230px);
  --rest-rotate: 6deg;
}






@media (max-width: 860px) {


}

/* iced seal nested in AWAKE — typography mark locked to the A counter,
   not a collage scrap. Lives only on plate-k so CMYK fringing never
   duplicates it. Asset already has alpha — no black plate, no tape. */

/* keep pavé sparkle, kill rectangular drop-shadow ghosts */

@media (max-width: 860px) {
}

/* pavé glint — borrowed from awake.fashion, toned for paper */
.iced {
  filter:
    contrast(1.1) brightness(1.06) saturate(1.12)
    drop-shadow(-1px 0 0 rgba(76, 243, 255, 0.35))
    drop-shadow(1px 0 0 rgba(255, 61, 207, 0.28))
    drop-shadow(0 0 8px rgba(236, 231, 219, 0.35));
}

/* The photocopy-roughen rule applied to .hero-btc / .hero-dice /
   .hero-filigree — all three props are now cut, so it had no subjects left.
   Kept as a note because the constraint still binds anything added later:
   never put #xerox on a 3D cube face (the filter flattens preserve-3d), and
   never on .about-saturn, whose halftone dot radius main.js rewrites every
   frame — an SVG filter there re-runs feTurbulence over the whole glyph on
   every rAF. */

/* the aces ride their own torn paper offcut — paste story kills the
   die-cut sticker read */


/* peelable glyphs — desktop toys only (no tape / scrap lift language) */
.peelable {
  touch-action: none;
}

@media (pointer: fine) {
  .peelable {
    pointer-events: auto;
    cursor: grab;
  }
  .peelable.is-peeling {
    cursor: grabbing;
    z-index: 8;
    filter: drop-shadow(6px 8px 0 rgba(20, 19, 18, 0.22));
  }
  /* vector glyphs keep their roughen while lifted; raster units
     must not run a displacement filter on every drag frame */
  svg.peelable.is-peeling {
    filter: url(#xerox) drop-shadow(6px 8px 0 rgba(20, 19, 18, 0.22));
  }
}

@media (pointer: coarse), (prefers-reduced-motion: reduce) {
  .peelable {
    pointer-events: none;
    cursor: inherit;
  }
}

.hero-me {
  position: absolute;
  /* above the fixed cube pair (z 9/10): the cube slides behind him, never over his face */
  z-index: 12;
  bottom: 0;
  /* 50%, NOT 58%. This element is invisible whenever the halftone is live
     (html.halftone-on zeroes it), so for every visitor with WebGL the portrait
     on screen has always been the CANVAS — and the canvas was anchored at 50%.
     The 58% here only ever moved the hidden fallback, which meant the page had
     two different hero compositions depending on whether WebGL came up. Now
     that the canvas registers off this box, that 8% became visible: it pushed
     the portrait 115px right at 1440 and slid his face under the cube. Unified
     on the value that has actually been shipping, so the screened and fallback
     versions finally agree and the desktop composition is unchanged. */
  left: 50%;
  transform: translateX(-50%);
  /* width/height:auto is LOAD-BEARING, not a reset. The <img> carries
     width="844" height="1186" attributes (for CLS), and those act as CSS
     width/height — so max-width and max-height below clamp each axis
     INDEPENDENTLY and the intrinsic ratio is lost. Measured at 1920x950:
     the box became 736x549 (aspect 1.34) against a true 0.712, stretching
     izzy 1.88x wider than life; object-fit defaults to `fill`, so the
     pixels really did distort. Restoring auto lets both max-* constraints
     act as a ratio-preserving fit box (736x549 -> 391x549, aspect 0.712). */
  width: auto;
  height: auto;
  max-height: 62%;
  max-width: 46%;
  /* grayscale, contrast and the constant-width mint stroke are BAKED into
     izzy.png (v3) — a CSS drop-shadow ring read as a feathered glow */
}

.hero-caption {
  position: absolute;
  /* pasted-label chip: paper backing + z above the portrait so it stays
     legible where the two overlap on narrow viewports; lifted clear of the
     fixed GEN gutter label */
  z-index: 13;
  /* hero bottom sits ~header-height below the viewport (in-flow header),
     so clearing the viewport-fixed GEN chip needs header + chip + air */
  bottom: calc(var(--pad) + 76px);
  /* optical: the headline's ink starts ~2px inside --pad on a flat stem, so
     matching --pad exactly left this chip's text hanging 12px to its right */
  left: calc(var(--pad) - 12px);
  max-width: 380px;
  padding: 10px 16px 12px 12px;
  /* SECOND STOCK, not --paper. Painted in the page's own colour this chip was
     invisible as an object and instead cut the middle third out of the ₿ coin
     behind it — the coin read as two unexplained arcs of ticks. A different
     stock plus a cast shadow makes it a scrap lying ON the collage. */
  background: var(--paper-deep);
  filter: drop-shadow(4px 5px 0 rgba(20, 19, 18, 0.32));
  /* torn, not cut — a ruler-straight edge through the portrait's arm read
     as a mask artifact on mobile. Amplitude is in px, not %: on a ~60px-tall
     box the old percentage jaggedness was sub-pixel and rendered straight. */
  clip-path: polygon(
    0 2px, 3px 0, 31% 2px, 63% 0, 97% 2px, 100% 7px,
    calc(100% - 2px) 34%, 100% 58%, calc(100% - 3px) 79%, 100% calc(100% - 5px),
    calc(100% - 5px) 100%, 64% calc(100% - 2px), 33% 100%, 6% calc(100% - 2px),
    0 calc(100% - 6px), 2px 61%, 0 31%);
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  line-height: 1.7;
}

/* the copier ready-light (ONE MACHINE chunk A): SCROLL. is the machine's
   status lamp. Solid for the whole 9s cycle, one double-flick right after
   the lamp bar has crossed the page — motion.js phase-locks the cycle to
   the plate clock with a negative animation-delay, so the flick reads as
   the machine recovering from its own sweep. step-end: a lamp switches,
   it does not fade. */
.ready-light {
  animation: ready-blink 9s step-end infinite;
  /* all 9s machine-cycle animations share one phase: motion.js sets
     --mc-delay on the root once (a negative delay = the plate clock) */
  animation-delay: var(--mc-delay, 0s);
}
@keyframes ready-blink {
  0%, 24.4% { opacity: 1; }
  24.5%, 26.3% { opacity: 0.22; }
  26.4%, 27.8% { opacity: 1; }
  27.9%, 29.7% { opacity: 0.22; }
  29.8%, 100% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .ready-light { animation: none; }
}

/* trim marks (ONE MACHINE chunk A): the hero is page one of the roll and
   says so the way a press sheet does — one horizontal + one vertical tick
   per corner, offset from the trim point and never joined (a joined L reads
   as a camera viewfinder, not print furniture). They print in sequence a
   beat after the headline rises, stepped like everything on this machine.
   Half-strength ink: furniture, not content. z 1 keeps them under the
   portrait (12) and the copy (13) wherever they could ever meet. */
.crop-marks {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}
.crop-marks i {
  position: absolute;
  width: 20px;
  height: 20px;
  opacity: 0;
  animation: crop-in 0.4s steps(2, end) forwards;
}
.crop-marks i::before,
.crop-marks i::after {
  content: "";
  position: absolute;
  background: var(--ink);
}
.crop-marks i::before { width: 14px; height: 1.5px; }
.crop-marks i::after { width: 1.5px; height: 14px; }
/* delays all sit past hero-rise's 0.9s: the marks print AFTER the
   headline has settled, as the comment above promises. The bottom-left
   mark rides 42px up — the V1 gen chip (viewport-fixed, plate.css) parks
   in that exact corner at load scroll, and at matching ink weight the
   pair fused into one "|V1" glyph cluster; on a real sheet the mark
   clears the job slug */
.cm-tl { top: 10px; left: 12px; animation-delay: 0.95s; }
.cm-tr { top: 10px; right: 12px; animation-delay: 1.07s; }
.cm-bl { bottom: 42px; left: 12px; animation-delay: 1.19s; }
.cm-br { bottom: 10px; right: 12px; animation-delay: 1.31s; }
.cm-tl::before { top: 0; right: 0; }    .cm-tl::after { left: 0; bottom: 0; }
.cm-tr::before { top: 0; left: 0; }     .cm-tr::after { right: 0; bottom: 0; }
.cm-bl::before { bottom: 0; right: 0; } .cm-bl::after { left: 0; top: 0; }
.cm-br::before { bottom: 0; left: 0; }  .cm-br::after { right: 0; top: 0; }
@keyframes crop-in { to { opacity: 0.5; } }
@media (prefers-reduced-motion: reduce) {
  .crop-marks i { animation: none; opacity: 0.5; }
}


/* the lamp bar (ONE MACHINE chunk B): the plate's 9s sweep carried over
   the DOM. Paper-coloured wash — invisible on paper, it lightens only the
   ink it crosses, which is what a copier lamp does to a print. motion.js
   steps its translateX on the drum clock; there is deliberately NO
   transition here — the movement is quantised like everything else on
   this machine. z 40: over the sheet's content (max 13), under the
   menu/backdrop/header chrome (58-60) — pasted controls sit ON the sheet
   the lamp is scanning. */
.lamp-bar {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  width: 16vw;
  z-index: 40;
  pointer-events: none;
  opacity: 0;
  /* over-WHITE at low alpha, not paper at 0.42: a paper-coloured wash
     cannot lift paper (out = base(1-a) + paper*a), so it read as a
     gradient fill on the ink alone — 42:1 ink-vs-paper lift measured —
     and dropped the lede below AA (2.60:1) at peak. Over-white blooms
     the paper +5 while thinning ink +33: the sheet brightens and the
     print thins together, which is what actually sells 'lamp'. Lede
     contrast at peak: 4.69:1 — AA-clean. */
  background: linear-gradient(
    90deg,
    rgba(255, 253, 247, 0) 0%,
    rgba(255, 253, 247, 0.14) 50%,
    rgba(255, 253, 247, 0) 100%
  );
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  .lamp-bar { display: none; }
}
/* the lamp never reaches the footer: it is a full-bleed INK field — a
   light wash across it read as a searchlight stripe through the wordmark
   (+87 dL measured), with no halftone partner since the opaque footer
   occludes the plate. The sheet the lamp scans ends where the ink slab
   begins. (0,2,0) beats the base .footer rule's z 20. */
.footer.cube-below { z-index: 41; }

/* landing print (ONE MACHINE chunk B): when the section glide lands on a
   station, the arriving heading strikes twice and snaps into register —
   the double-strike again, as an EVENT this time. steps(3): three drum
   frames of settle, no easing. motion.js adds .printed off the glide's
   own html[data-glide] attribute and removes it after the strike. */
@keyframes reg-snap {
  0% { transform: translate(2.2px, 1.1px); text-shadow: -3px -1.4px 0 rgba(20, 19, 18, 0.32); }
  100% { transform: none; text-shadow: 0 0 rgba(20, 19, 18, 0); }
}
/* the compound selector exists to WIN: main.js's reveal rule
   `html.js .section-heading.reveal.is-in` (ink-set, specificity (0,4,1))
   out-specified the bare class and reg-snap never ran — the landing
   print shipped dead behind a green class-presence probe. (0,5,1) wins;
   ink-set is both-filled and finished by landing time, so borrowing the
   animation slot for 0.444s is invisible. 0.444s steps(4) = exactly four
   drum steps; motion.js phase-locks the start with a negative delay. */
html.js .section-heading.reveal.is-in.printed,
.section-heading.printed { animation: reg-snap 0.444s steps(4, end); }
@media (prefers-reduced-motion: reduce) {
  html.js .section-heading.reveal.is-in.printed,
  .section-heading.printed { animation: none; }
}


/* THE ROLL BREATHES (ONE MACHINE chunk C). Card names carry the same
   double-strike as the headline, driven by motion.js one card at a time
   (the stagger IS the volume control). No color on the ghost, on purpose:
   a.ticker-cell:hover inverts the card to ink-ground/paper-type, and a
   hardcoded ink ghost would orphan there — inheriting currentColor flips
   it with the card ([[hover-invert orphans]] class of bug, banked). */
.ticker-cell .cell-name { position: relative; }
.ticker-cell .cell-name::after {
  /* the / "" alt-text hides the reprint from the accessible name — without
     it every card link announced its own title TWICE (measured in the real
     AX tree; opacity does not remove generated content from it). Same
     convention as the print-index counter far below. Plain fallback first:
     engines that can't parse the alt form keep the visual and, there only,
     the doubled name. */
  content: attr(data-text);
  content: attr(data-text) / "";
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: var(--ds-a, 0);
  transform: translate(var(--ds-x, 0px), var(--ds-y, 0px));
  pointer-events: none;
}
/* hover: the card re-prints as it inverts — two drum frames off-register,
   then seated. The animation wins over the ambient var-driven transform
   for its 222ms and hands back cleanly. Fine pointers only: touch latches
   :hover and would leave a phone card permanently mid-flash. */
@media (hover: hover) and (pointer: fine) {
  a.ticker-cell:hover .cell-name::after {
    /* forwards: the strike STAYS seated while the card is lit — without
       it the ghost un-printed itself at 222ms (a third beat nobody
       designed) and the hovered card went back to breathing-tier alpha */
    animation: card-reprint 0.222s steps(2, end) forwards;
  }
}
@keyframes card-reprint {
  0% { opacity: 0.34; transform: translate(2.6px, -1.2px); }
  100% { opacity: 0.3; transform: translate(0.7px, 0.4px); }
}

/* the sprocket strip: film-roll punch holes riding the track's own scrub
   transform (motion.js appends it inside #tickerTrack). Desktop-only —
   under 860px the track wraps into a grid and stops being a roll. */
.roll-sprockets {
  position: absolute;
  left: var(--reel-pad, var(--pad));
  right: var(--reel-pad, var(--pad));
  top: 2px;
  height: 7px;
  pointer-events: none;
  /* a punch hole is an ABSENCE: paper disc + thin ink ring, near-full
     opacity. The first cut printed solid grey dots at 0.42 — over the
     anchored Saturn's halftone they were weaker copies of the grain
     already there and ~60% vanished. A knocked-out hole is light-on-dark
     over the plate and ring-on-light over bare stock: reads everywhere. */
  opacity: 0.85;
  background-image: radial-gradient(circle, var(--paper) 0 1.5px, var(--ink) 1.5px 2.2px, rgba(20, 19, 18, 0) 2.6px);
  background-size: 26px 7px;
  background-repeat: repeat-x;
}
@media (max-width: 860px) {
  .roll-sprockets { display: none; }
}
/* the track also stops being a roll under reduced motion and without JS
   (both wrap it at any width) — the strip goes with it in every case */
@media (prefers-reduced-motion: reduce) {
  .roll-sprockets { display: none; }
}
html:not(.js) .roll-sprockets { display: none; }

/* the impatient scrawl's ghost pass stutters now and then — a copy that
   never quite seated. Group-level opacity, so the per-path 0.34 base
   multiplies rather than fights. */
.doodle-hurry .ghost { animation: ghost-stutter 23s step-end infinite; }
@keyframes ghost-stutter {
  0%, 91.9% { opacity: 1; }
  92%, 93.4% { opacity: 0.15; }
  93.5%, 94.9% { opacity: 1; }
  95%, 96.2% { opacity: 0.3; }
  96.3%, 100% { opacity: 1; }
}

/* END OF ROLL flutters — the tail of a roll never sits still. Stepped
   wobble on the SPAN (it owns no other transform); the dashed tear line
   stays planted. */
/* the tail lifts at the HINGE (the dashed tear line, left edge) and the
   whole tab moves — sheet, not type. 9s period: motion.js phase-locks it
   to the machine clock, so the curl lands as the lamp crosses — the sheet
   reacting to the machine, not a label fidgeting. */
.ticker-end {
  transform-origin: left center;
  animation: roll-flutter 9s step-end infinite;
  animation-delay: var(--mc-delay, 0s);
}
@keyframes roll-flutter {
  0%, 5.9% { transform: none; }
  6%, 8.9% { transform: rotate(1.2deg); }
  9%, 11.9% { transform: rotate(-0.8deg) translateY(-1px); }
  12%, 14.9% { transform: rotate(0.4deg); }
  15%, 100% { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .ticker-cell .cell-name::after { opacity: 0; }
  a.ticker-cell:hover .cell-name::after,
  .doodle-hurry .ghost,
  .ticker-end { animation: none; }
}


/* THE FRAME (ONE MACHINE chunk D). The chrome around the sheet joins the
   machine: the menu feeds in like paper, the wordmarks carry a rare loose-
   plate nudge, and the iced seal catches the lamp as the 9s cycle passes
   its side of the page. */

/* menu: a sheet through the rollers — stepped feed, then the 7 items print
   in drum sequence (line 1 at 0 — it takes focus — last at 354ms). OPEN
   the shipped smooth fade (a sheet feeds IN, it does not un-feed). */
body.menu-open .menu-overlay {
  animation: sheet-feed 0.333s steps(3, end);
}
/* transform ONLY — pinning opacity:1 in these keyframes made a mid-feed
   close HARD-CUT the panel (a removed animation's values are not in the
   before-change style, so no close transition is generated). The dissolve
   belongs to the transition, both directions, matched with the backdrop;
   the stepped structure rides on transform + the item prints. */
@keyframes sheet-feed {
  0% { transform: translateY(-18px) rotate(0.5deg); }
  100% { transform: none; }
}
body.menu-open .menu-overlay li {
  /* steps(1, START): each line pops exactly AT its delay. With end-steps
     every line was invisible for delay+111ms — including line 1, which
     main.js focuses the moment the menu opens; a keyboard user landed on
     a focus ring around nothing. Line 1 prints at 0 for that reason. */
  animation: item-print 0.111s steps(1, start) backwards;
}
body.menu-open .menu-overlay li:nth-child(1) { animation-delay: 0s; }
body.menu-open .menu-overlay li:nth-child(2) { animation-delay: 0.134s; }
body.menu-open .menu-overlay li:nth-child(3) { animation-delay: 0.178s; }
body.menu-open .menu-overlay li:nth-child(4) { animation-delay: 0.222s; }
body.menu-open .menu-overlay li:nth-child(5) { animation-delay: 0.266s; }
body.menu-open .menu-overlay li:nth-child(6) { animation-delay: 0.31s; }
/* the menu has SEVEN items on f3 (STUDIO joined) and may grow — cover two
   spares; an uncovered item prints at delay 0, BEFORE the sheet feeds */
body.menu-open .menu-overlay li:nth-child(7) { animation-delay: 0.354s; }
body.menu-open .menu-overlay li:nth-child(8) { animation-delay: 0.398s; }
@keyframes item-print {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* loose plates, rarely: the boxed wordmarks nudge one hair off-register
   about twice a minute, on different phases so they never twitch together.
   The footer nudge carries the pasted seal with it — one object. */
@keyframes plate-nudge {
  0%, 96% { transform: none; }
  96.1%, 97.3% { transform: translate(0.8px, -0.5px); }
  97.4%, 100% { transform: none; }
}
/* periods a real DWELL can catch: at 31/37s the clocks ticked while the
   elements were off-screen and the nudge was effectively unreachable.
   11/13 are coprime — the two never twitch together. */
.brand { animation: plate-nudge 11s step-end infinite; }
.footer-wordmark { animation: plate-nudge 13s step-end infinite; animation-delay: -6.5s; }

/* the margin heart beats once every ~21s — alive, not animated-looking */
@keyframes heart-beat {
  0%, 94.9% { transform: none; }
  95%, 96.4% { transform: scale(1.14); }
  96.5%, 97.9% { transform: scale(0.98); }
  98%, 100% { transform: none; }
}
.margin-heart { animation: heart-beat 21s step-end infinite; transform-origin: center; transform-box: fill-box; }

/* NO seal glint — tried and DELETED (chunk D review, measured). The iced
   seal is near-white: a white sheen's max luminance lift was 15/255 at the
   strongest seat — physically invisible — and the mask pulled a 124KB PNG
   eagerly on every load. Third white-on-white instance; the seal stays a
   still photographic paste, which the footer reads correctly as. Do not
   re-add a light pass over this asset. */

@media (prefers-reduced-motion: reduce) {
  body.menu-open .menu-overlay,
  body.menu-open .menu-overlay li,
  .brand,
  .footer-wordmark,
  .margin-heart { animation: none; }
}


/* stamp-press (ONE MACHINE chunk E): the boxed controls PRESS. :active
   drops the button flat onto the sheet in one frame (0s duration — a stamp
   does not ease down) and the shadow collapses; release rides the shipped
   transition. Covers the header buttons and the menu box. */
/* .site-header prefix: the header hover rule is (0,3,0) and LATER in
   source — a bare (0,3,0) active tied and lost while both states matched.
   (0,4,0) wins the press outright; same doubled-class pattern for the CTA. */
.site-header .btn.btn-boxed:active,
.site-header .btn.btn-menu:active {
  transition-duration: 0s;
  transform: translate(3px, 3px);
  box-shadow: none;
}
/* (no split-CTA press: .btn-split has no element on this page — a rule
   with no subject is the silently-dead-CSS hazard, so it ships absent) */

/* The INTRO section was deleted 2026-08-02 — it restated the page's own <h1>
   one screen below the hero. Its rules went with it; the one sentence worth
   keeping now prints as .lede-note in the hero. */

.btn-split:hover { box-shadow: 2px 2px 0 var(--ink); transform: translate(3px, 3px); }

/* ---------- about ---------- */

/* no z-index on the section itself: children stack against the traveling
   cube individually — body copy above it, collage matter beneath it */
.about {
  position: relative;
  background: var(--paper);
  padding: calc(var(--pad) * 1.5) var(--pad) calc(var(--pad) * 2);
}

.about-body {
  position: relative;
  z-index: 20;
}

.about .section-heading { margin-bottom: 28px; }

.about-body {
  max-width: 680px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.about-lead {
  font-family: var(--display);
  font-size: clamp(30px, 5vw, 58px);
  line-height: 0.95;
  letter-spacing: -0.01em; /* display tier stays tight and consistent */
  text-transform: uppercase;
}

.about-copy {
  font-size: clamp(16px, 1.8vw, 20px);
  font-weight: 600;
  line-height: 1.5;
  color: var(--ink-soft);
}

.about-chips { margin-top: 8px; }

.about-chips .chip {
  font-size: 13px;
  letter-spacing: 0.1em;
}

/* ---- the one chip in that row that is a door ----
   It stays on the same paper stock as its neighbours, because the row has to
   read as one row. What marks it as pressable is the motif the whole page is
   built on: a second impression printed 3px off-register behind the plate.
   Pressing pulls the two impressions into register and inks the plate, so the
   click is the page's own idea rather than a borrowed button. Mint is
   deliberately not used — that accent is reserved for LIVE status chips. */

.go-mark { margin-left: 7px; }

/* 44px pointer target without changing a printed pixel (the chip prints 34px
   tall). Transparent and inside the anchor, so it only grows the hit box; it
   keeps 3px of slack above-left, which is what stops the pressed translate from
   sliding out from under the cursor and flickering.
   -7px, not -6px: the pseudo is laid out against the PADDING box, so the 2px
   border eats 2px of the inset — MEASURED, -6px was a 42px target, one short of
   the 44 this comment has always claimed. */
.chip--go::after {
  content: "";
  position: absolute;
  inset: -7px -5px;
}

@media (prefers-reduced-motion: no-preference) {
}

/* A little extra air after the discipline group, so the door reads as a second
   object rather than a fourth label — dropped on the narrow side, where the row
   wraps and this chip starts a line of its own: there a left margin stops
   reading as separation and starts reading as a chip that failed to line up
   with the one above it. Gated on 860.02, the site's ONE breakpoint (paired
   with the 860 max-width rules) — a fresh 961 here is exactly the drift that
   once split this layout into an in-between band. */
@media (min-width: 860.02px) {
}

/* Pressed = in register + inked. :focus-visible is NOT pointer-gated (that is
   the keyboard path and has to work at every width); :hover is, because mobile
   browsers latch :hover on tap and would leave the chip stuck pressed. */
.chip--go:focus-visible {
  transform: translate(3px, 3px);
  box-shadow: 0 0 0 var(--paper), 0 0 0 2px var(--ink);
  background: var(--ink);
  color: var(--paper);
}

@media (hover: hover) and (pointer: fine) {
  .chip--go:hover {
    transform: translate(3px, 3px);
    box-shadow: 0 0 0 var(--paper), 0 0 0 2px var(--ink);
    background: var(--ink);
    color: var(--paper);
  }
}

/* ---- chip glossary (izzy 2026-08-06) ----
   The gloss is a margin note printed above the chip, in the slip's mono
   fine-print register. Parked, it is CLIPPED — not display:none — so the
   text never leaves the accessibility tree; a screen reader always hears
   label + gloss as one run. Reveal paths: :hover (pointer-gated — touch
   latches :hover), :focus-visible (never gated: the keyboard path), and
   .gloss-open (set by the tap module for coarse pointers). */
.chip-gloss { position: relative; }
@media (hover: hover) and (pointer: fine) {
  .chip-gloss { cursor: help; }
}
.chip-tip {
  /* PARKED = a true 1px sr-box. clip-path alone hides PAINT, not the BOX —
     the parked note's full-width box extended page layout to 437px at
     393vw, mobile Chrome zoom-fit the wider layout, and every touch
     coordinate landed ~10% off target (measured: taps hit the section).
     1px + overflow hidden keeps the text in the accessibility tree with
     zero layout footprint. */
  position: absolute;
  left: -2px;
  bottom: calc(100% + 9px);
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 400;
  letter-spacing: 0.02em;
  line-height: 1.45;
  text-transform: none;
  z-index: 30;
  opacity: 0;
  clip-path: inset(50%);
  pointer-events: none;
}
.chip-gloss:focus-visible .chip-tip,
.chip-gloss.gloss-open .chip-tip {
  width: max-content;
  height: auto;
  max-width: min(36ch, 76vw);
  padding: 8px 10px;
  border: 2px solid var(--ink);
  /* off-register second impression, the page's own pressable/printed mark */
  box-shadow: 3px 3px 0 var(--paper), 3px 3px 0 2px var(--ink);
  overflow: visible;
  opacity: 1;
  clip-path: none;
}
@media (hover: hover) and (pointer: fine) {
  .chip-gloss:hover .chip-tip {
    width: max-content;
    height: auto;
    max-width: min(36ch, 76vw);
    padding: 8px 10px;
    border: 2px solid var(--ink);
    box-shadow: 3px 3px 0 var(--paper), 3px 3px 0 2px var(--ink);
    overflow: visible;
    opacity: 1;
    clip-path: none;
  }
}
@media (prefers-reduced-motion: no-preference) {
  .chip-gloss:hover .chip-tip,
  .chip-gloss:focus-visible .chip-tip,
  .chip-gloss.gloss-open .chip-tip {
    /* two drum steps, same 111ms machine clock as every other print-in */
    animation: tip-print 0.222s steps(2) both;
  }
}
@keyframes tip-print {
  from { opacity: 0.35; }
  to { opacity: 1; }
}
/* 44px touch target on the discipline chips without changing a printed pixel.
   Same device as .chip--go above: a transparent inset pseudo INSIDE the
   element, so only the hit box grows and the labels stay the small stamped
   marks they are meant to be.
   The insets look 2px too big because they are laid out against the PADDING
   box: an absolutely positioned pseudo resolves against its originating
   element's padding box, so the chip's 2px border eats 2px of every side.
   THE NUMBER THIS ROW ACTUALLY PRINTS AT: 33.55px, not the 34 an earlier pass
   here assumed — these chips are font-size 13px (line-height 17.55px), while
   .chip--go inherits the base .chip size and does print 34. That half pixel is
   the whole bug: padding box = 33.55 - 4 = 29.55, so the old -7px reached
   29.55 + 14 = 43.55, and MEASURED at 393x852 in real Chrome the four chips
   came back 43.3 / 43.3 / 44.05 / 44.05 — under the floor, not "exactly 44".
   -7.25px is still not enough (MEASURED: 43.3 on the upper line, because the
   lower line's hit box truncates it). -7.5px gives 29.55 + 15 = 44.55, and all
   four MEASURE 45.05.
   Row-gap follows from the overhang, it is not a free number: the box now
   grows 7.5 - 2 = 5.5px past the printed edge, so two wrapped lines need
   >= 11px between them or the bands overlap and the tap silently goes to
   whichever chip is later in the DOM. 12px leaves 0.5px of clearance
   (MEASURED). It costs 2px of row height (77.09 -> 79.09) on a phone and
   NOTHING anywhere else: at 1440 the row is a single line (MEASURED), and
   row-gap is inert wherever the row does not wrap. Not gated on
   (pointer: coarse) for that reason, and because headless emulation does not
   flip that feature — a coarse gate would ship unproven.
   The horizontal -4px leaves 4px of clean gap between neighbours in a row:
   MEASURED, the midpoint of every horizontal gap still belongs to the ROW,
   not to either chip, so no chip steals its neighbour's tap. */
.about-chips { row-gap: 12px; }
.about-chips .chip-gloss::after {
  content: "";
  position: absolute;
  inset: -7.5px -4px;
}

/* Coarse pointers: a floating note under a finger overflows the viewport
   from right-edge chips (the third chip at 393px measured 470px of tip) and
   hover doesn't exist — so the tap UNFOLDS the chip instead: the note
   goes static inside the chip's own border, the row reflows below it.
   User-initiated, so the layout shift is the point, not jank. */
@media (max-width: 860px), (pointer: coarse) {
  /* :focus-visible included: a floating note from a right-edge chip
     overflows the narrow viewport, so a Bluetooth-keyboard focus gets the
     same in-flow unfold the tap gets. */
  .chip-gloss.gloss-open .chip-tip,
  .chip-gloss:focus-visible .chip-tip {
    position: static;
    display: block;
    margin-top: 6px;
    width: auto;
    height: auto;
    max-width: 32ch;
    box-shadow: none;
    border: 0;
    border-top: 1.5px dashed var(--ink);
    padding: 6px 0 0;
  }
}

/* ---------- ticker ---------- */

.ticker-section { height: 380vh; }

/* status chips — LIVE vs BUILDING must read unmistakably apart within the
   monochrome brand, and each status must look IDENTICAL everywhere it
   shows up (linked card or static card alike). Mint = the one live-state
   accent, so it's reserved for LIVE; BUILDING stays ink-only (hatched +
   dashed, never filled) so the two can't be mistaken at a glance. */
.chip--live {
  background: var(--mint);
  border-color: var(--ink);
  color: var(--ink);
}

.chip--building {
  background-color: var(--paper);
  /* SCREENED, not solid (measured 2026-08-23). The hatch printed at full
     --ink: 1.5px of near-black laid directly under an 11px mono label, so
     every glyph that crossed a stripe sat at 1.00:1 and came apart — and the
     chips carrying it are the four that say NOT READY YET (BUILDING x2,
     PRE-DROP, WAITLIST), i.e. exactly the ones a stranger has to read. Same
     plate, same 45deg rhythm as .site-tape, just less ink on the roller: at
     38% the worst pixel behind the label is #9a968f and the ink label reads
     6.30:1 (AA small text wants 4.5; the solid stripe read 1.00). Screened,
     never deleted — the hatch is the only thing separating BUILDING from the
     solid-mint LIVE chip, and the dashed border is the second signal. */
  background-image: repeating-linear-gradient(
    45deg,
    rgba(20, 19, 18, 0.38) 0 1.5px,
    transparent 1.5px 7px
  );
  border: 2px dashed var(--ink);
  color: var(--ink);
}

/* industrial band slapped across a card — hazard stripes + quoted label,
   kept in the page's ink/paper palette (no safety yellow) */


.site-tape::before,
.site-tape::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 6px;
  background: repeating-linear-gradient(
    45deg,
    var(--paper) 0 7px,
    var(--ink) 7px 14px
  );
}

.site-tape::before { top: 0; }
.site-tape::after { bottom: 0; }


/* the card inverts to ink when pressed, so the band inverts too or it
   vanishes. Hover is pointer-gated for the reason spelled out at the card's
   own invert block below — touch latches :hover and never releases it. */
a.ticker-cell--taped:focus-visible .site-tape {
  background: var(--paper);
  box-shadow: 0 4px 0 rgba(236, 231, 219, 0.2);
}

a.ticker-cell--taped:focus-visible .site-tape-text { color: var(--ink); }

@media (hover: hover) and (pointer: fine) {
  a.ticker-cell--taped:hover .site-tape {
    background: var(--paper);
    box-shadow: 0 4px 0 rgba(236, 231, 219, 0.2);
  }
  a.ticker-cell--taped:hover .site-tape-text { color: var(--ink); }
}

.ticker-sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  padding-top: calc(var(--header-h) + 14px);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  /* clip, not hidden: hidden leaves the box a programmatic scroller, and
     Chrome's focus/find-in-page scroll-into-view writes scrollLeft on it
     (measured 1578px on Tab to the END slot) — the reel scrubs by
     transform, so nothing ever resets that and every rest sits
     de-registered for the session. clip forbids all scrolling; hidden
     stays first for engines without clip. */
  overflow: hidden;
  overflow: clip;
  background: var(--paper);
}

.ticker-track {
  display: flex;
  gap: clamp(16px, 2vw, 32px);
  /* side padding doubles as the frame's registration margin: main.js's
     reel fit sets --reel-pad to gap-8 so the cover frame (translate 0)
     and the END rest (track right-aligned) sit on the same grid as every
     interior stop. Without JS the --pad gutter returns with the old
     clamp() card widths — the pair ships together. */
  padding: 12px var(--reel-pad, var(--pad)) calc(var(--pad) + 10px);
  width: max-content;
  will-change: transform;
  /* cards ride above the cube; the section no longer forces everything up */
  position: relative;
  z-index: 20;
}

.ticker-sticky .section-heading {
  position: relative;
  z-index: 20;
}

.ticker-cell {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 14px;
  /* --frame-w = main.js reel fit: cards flex so an INTEGER number close
     each frame exactly (izzy 2026-08-05: no card ever rests cut off). The
     clamp() is the no-JS/reduced/wrapped fallback, not a second design. */
  width: var(--frame-w, clamp(240px, 28vw, 380px));
  flex-shrink: 0;
  min-height: 170px;
  padding: 18px;
  border: 2px solid var(--ink);
  background: var(--paper);
  box-shadow: 5px 5px 0 rgba(20, 19, 18, 0.9);
  font-family: var(--display);
  font-size: clamp(20px, 2vw, 30px);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.ticker-cell:nth-child(odd) { transform: rotate(-1deg); }
.ticker-cell:nth-child(even) { transform: rotate(0.8deg); }

/* print end-mark — a real cue for "nothing more to scrub to" instead of
   leaving it to guesswork whether more cards exist past the pin.
   On the fitted reel it occupies a FULL frame slot (--frame-w): the last
   rest right-aligns the track, and only a card-width end slot keeps that
   rest on the same grid as every other frame — a 56px tab would land the
   closing frame 300px off-register and slice the cards under it. */
.ticker-end {
  flex: none;
  align-self: stretch;
  width: var(--frame-w, 56px);
  display: flex;
  align-items: center;
  /* the caption sits AGAINST the dashed tear line, not centred in the
     slot — a full frame slot of leader with a floating label read as
     unmoored (Mars, 2026-08-05). No margin: the slot must sit on the
     same (width+gap) pitch as every card or the END rest de-registers. */
  justify-content: flex-start;
  padding-left: 14px;
  border-left: 2px dashed var(--ink);
}
.ticker-end span {
  writing-mode: vertical-rl;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}






/* ---- the card inverts to ink when pressed or focused ----
   Every child that carries its OWN colour has to be flipped with it, or it
   keeps its paper-stock ink and disappears on the slab. Four were already
   handled (.ticker-status, ::after, .site-tape, .site-tape-text); three were
   not, and on a phone they are most of the card:
     .cell-body   #33302b on #141312 = 1.43:1  — the description
     .cell-organ  #57534b on #141312 = 2.29:1  — the subtitle
     .chip        has a background but no colour, so it INHERITS the flip and
                  printed paper text on its own paper plate = 1.0:1. The URL
                  was rendering as an empty box.
   The hover half is pointer-gated. A touch screen has no hover, but mobile
   browsers latch :hover on tap and hold it until you tap elsewhere — so a
   tapped card stayed inverted after a back-navigation, with no way to clear
   it. :focus-visible is deliberately NOT gated: that is the keyboard path and
   it has to invert at every width. */

a.ticker-cell:focus-visible { background: var(--ink); color: var(--paper); }
a.ticker-cell:focus-visible .ticker-status { border-color: var(--paper); }
a.ticker-cell:focus-visible .cell-body { color: rgba(236, 231, 219, 0.92); }
a.ticker-cell:focus-visible .cell-organ { color: rgba(236, 231, 219, 0.62); }
a.ticker-cell:focus-visible .chip:not(.chip--live) {
  background: transparent;
  border-color: var(--paper);
  color: var(--paper);
}

@media (hover: hover) and (pointer: fine) {
  a.ticker-cell:hover { background: var(--ink); color: var(--paper); }
  a.ticker-cell:hover .ticker-status { border-color: var(--paper); }
  a.ticker-cell:hover .cell-body { color: rgba(236, 231, 219, 0.92); }
  a.ticker-cell:hover .cell-organ { color: rgba(236, 231, 219, 0.62); }
  a.ticker-cell:hover .chip:not(.chip--live) {
    background: transparent;
    border-color: var(--paper);
    color: var(--paper);
  }
}


.ticker-cell--static .ticker-status,


/* ...except the status chip itself: LIVE/BUILDING stay full-strength so the
   state reads the same on a static (private-repo) card as on a live link */
.ticker-cell--static .chip--live,


/* linked but not live yet — same physical card as its siblings (a lighter
   border read as a missed style, not intent); the dashed BUILDING chip
   carries the status by itself */
a.ticker-cell--building .cell-body {
  color: var(--ink-soft);
  font-size: 14px;
}

/* LIVE links stay the loud interactive objects */
a.ticker-cell--organ:not(.ticker-cell--building) {
  border-width: 2.5px;
}

/* anatomy cells: bigger cargo than the old project cells.
   THIS is the width authority for every current card (all five are
   organs, and this later rule outguns .ticker-cell at equal
   specificity) — so the reel fit's --frame-w must ride here too, or
   the integer-fit frames silently never happen. */
.ticker-cell--organ {
  width: var(--frame-w, clamp(300px, 38vw, 540px));
  min-height: 272px;
  gap: 10px;
}

.cell-name { font-family: var(--display); font-size: clamp(26px, 2.6vw, 40px); }

.cell-organ {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--muted);
}

.cell-body {
  /* typewriter body — a clean neutral sans next to the grunge read as a
     template pack; the mono matches the hero caption's register */
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.65;
  letter-spacing: 0.01em;
  text-transform: none;
  color: var(--ink-soft);
  padding-top: 6px;
}

.cell-status { margin-top: auto; }
.cell-status .chip { font-family: var(--mono); font-size: 11px; padding: 4px 8px; }

/* reduced motion: unpin and wrap the ticker so every cell is reachable */
@media (prefers-reduced-motion: reduce) {
  .ticker-section { height: auto; }
  .ticker-sticky { position: static; height: auto; overflow: visible; }
  .ticker-track { width: auto; flex-wrap: wrap; transform: none !important; }
}

/* no-JS fallback: html.js only lands once the reveal IIFE runs, so
   :not(.js) means the scroll-scrub script never executed (blocked,
   ad-blocker false-positive, CSP hiccup) — the pin still applies from
   pure CSS with nothing left to drive the horizontal transform, so cards
   past the first screen would be sealed behind overflow:hidden forever.
   Same unpin-and-wrap rescue as reduced motion, not gated on a media query. */
html:not(.js) .ticker-section { height: auto; }
html:not(.js) .ticker-sticky { position: static; height: auto; overflow: visible; }
html:not(.js) .ticker-track { width: auto; flex-wrap: wrap; transform: none !important; }

/* Phones get the same unpinned, wrapped treatment. The scroll-scrubbed
   horizontal track is a desktop pleasure and a mobile trap: a thumb swiping
   the card row does nothing (only vertical page scroll pans it), and if the
   JS never runs the cards past the first screen are unreachable behind
   overflow:hidden. The projects section is the part of this page that wins
   work — it must not depend on a gesture people will not guess. */
@media (max-width: 860px) {
  .ticker-section { height: auto; }
  .ticker-sticky {
    position: static;
    height: auto;
    overflow: visible;
    /* --page-lead-in: solved by main.js so the PREVIOUS stop (#about) can
       never rest with the top of card 01 sliced under its fold. 0 on most
       phones — it only pays out where the geometry actually collides. */
    padding-top: calc(var(--header-h) + 14px + var(--page-lead-in, 0px));
  }
  .ticker-track {
    width: auto;
    flex-wrap: wrap;
    transform: none !important;
    gap: 14px;
    /* --page-tail = the paper the CLOSING frame needs beneath it, solved
       by main.js: without it the WORK ORDER section's edge prints inside
       the last page's frame. 0 when the solver is not running. */
    padding-bottom: calc(var(--pad) + 10px + var(--page-tail, 0px));
  }
  .ticker-cell { width: 100%; max-width: none; }
}

/* ---------- hard lines ---------- */




.hardline:nth-child(odd) { transform: rotate(-0.6deg); }
.hardline:nth-child(even) { transform: rotate(0.5deg); }





/* ---------- receipts ---------- */

.receipts { padding-bottom: calc(var(--pad) * 2); }


.receipt { display: flex; flex-direction: column; align-items: flex-start; gap: 18px; }

.receipt h3 {
  font-family: var(--display);
  font-size: clamp(20px, 2vw, 30px);
  letter-spacing: 0.01em;
}

.receipt p { max-width: 520px; font-size: 15px; font-weight: 600; line-height: 1.55; color: var(--ink-soft); }




/* ---------- the work order (commission slip) ----------
   SaturnDesign money-surface rule (Print Roll): mechanism (cube/filigree/
   collage) stays PRESENT above the fold; at WORK ORDER it calms to
   paperwork chrome only — tape, tear, off-register ghost. Fields stay
   dead-square on the 8px grid. Mint remains the single accent (focus swipe
   + stamp hover), never a status palette.
   The ruled fill-in slip gets RUBBER-STAMPED — baselines not input boxes. */

.order {
  position: relative;
  background: var(--paper);
  padding: calc(var(--pad) * 1.5) var(--pad) calc(var(--pad) * 2);
}

.order .section-heading {
  margin-bottom: var(--sd-space-300);
  max-width: 14ch; /* forces the break after "BUILD YOU" — Anton wants the stack */
}

.order-slip {
  position: relative;
  max-width: 720px;
  /* real second stock. Printing the slip in --paper on --paper is the
     invisible-eraser mistake already logged for .hero-caption: same colour
     reads as the sheet being wiped, not as a form pasted onto it. */
  background: var(--paper-deep);
  border: 2px solid var(--ink);
  padding: var(--sd-space-400) var(--pad) var(--pad);
}

/* the slip is torn off the sheet above it, so the tear prints in PAPER
   (the sheet's colour) biting down into the deeper stock */
.order-slip > .tear-top { top: -1px; height: 14px; }

/* .tape's own -28px offsets hang outside the slip; on a phone that is real
   horizontal overflow, so this placement pulls both pieces inboard */
.order-slip > .tape-tl { top: -13px; left: 16px; width: 78px; }
.order-slip > .tape-tr { top: -13px; right: 16px; width: 78px; }

.order-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  border-bottom: 2px solid var(--ink);
  padding-bottom: 10px;
}

.order-doc {
  position: relative;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  transform: rotate(-1.2deg);
}

/* the page's copy-of-a-copy motif: a second impression 2px off register.
   aria-hidden by construction — it is a pseudo-element, so a screen reader
   never hears "WORK ORDER WORK ORDER". */
.order-doc::after {
  content: attr(data-text);
  position: absolute;
  left: 2px;
  top: 2px;
  color: var(--sd-color-ink-faint);
  white-space: nowrap; /* same shrink-to-fit trap the GEN chip hit */
  pointer-events: none;
}

.order-no {
  font-family: var(--mono);
  font-size: var(--sd-type-sm);
  letter-spacing: 0.08em;
  color: var(--muted);
}

.order-no b { color: var(--ink); letter-spacing: 0.14em; }

.order-lede {
  margin: var(--sd-space-200) 0 var(--sd-space-300);
  font-size: clamp(var(--sd-type-base), 1.7vw, 19px);
  font-weight: 600;
  line-height: 1.5;
  max-width: 48ch;
  color: var(--ink-soft);
}

.order-form { display: flex; flex-direction: column; gap: var(--sd-space-300); }

.order-field { display: flex; flex-direction: column; gap: var(--sd-space-100); border: 0; padding: 0; margin: 0; }

.order-label {
  display: flex;
  align-items: center;
  gap: var(--sd-space-100);
  padding: 0;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

/* the field number is a struck box, not a bullet — same register as the
   page's other stamps */
.order-num {
  display: inline-block;
  min-width: 22px;
  padding: 2px 4px;
  background: var(--ink);
  color: var(--paper);
  text-align: center;
  letter-spacing: 0.06em;
}

.order-opt { letter-spacing: 0.04em; text-transform: none; font-weight: 600; }

/* THE fill-in rule. A paper form has a line to write on, never a box with a
   radius — a single border-radius anywhere here breaks the whole premise. */
.order-input {
  width: 100%;
  border: 0;
  border-bottom: 2px solid var(--ink);
  border-radius: 0;
  background: transparent;
  padding: 8px 2px;
  /* 44px floor on the target, not on the ink. These print 43.19px at desktop
     and 35.39px under the <=860 density block below (which retunes font-size
     and padding and so silently invalidated the height) — MEASURED, and the
     only two sub-44 targets left on the page. Unlike the chips, a text field
     cannot take an inset ::after: the pseudo would sit over the text and eat
     the caret, so the box itself has to grow. box-sizing is border-box
     globally (line 70), so this is 44px INCLUDING the 2px rule and padding.
     min-height, not height: the textarea shares this class and must stay
     free to grow. */
  min-height: 44px;
  /* 18px: above the 16px floor, so iOS never zooms the viewport on focus */
  font-family: var(--font);
  font-size: 18px;
  font-weight: 600;
  line-height: 1.4;
  color: var(--ink);
  -webkit-appearance: none;
  appearance: none;
}

.order-input::placeholder { color: var(--muted); font-weight: 500; opacity: 1; }

/* focus = a highlighter swipe along the rule. Mint is the page's one accent
   and it is used here exactly as a stamp colour, never as a second palette. */
.order-input:focus-visible {
  outline: none;
  border-bottom-width: 3px;
  box-shadow: inset 0 -14px 0 var(--mint);
}

.order-textarea { resize: vertical; min-height: 108px; }

.order-stamps { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 2px; }

/* the stamp IS the control: the radio stays in the a11y tree and keeps its
   arrow-key group, the label carries the printed look */
.order-stamp { position: relative; display: inline-flex; }

.order-stamp input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  /* 16px floor: radios never summon the iOS keyboard-zoom, but holding every
     input at >=16px keeps the audit floor mechanical instead of argued */
  font-size: 16px;
}

.order-stamp span {
  display: flex;
  align-items: center;
  /* 44px floor — the SaturnDesign tap-target law, stricter than WCAG's 24 */
  min-height: 44px;
  padding: 6px 14px;
  border: 2px solid var(--ink);
  background: var(--paper);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  transition: background 0.12s steps(2, end), color 0.12s steps(2, end);
}

@media (hover: hover) and (pointer: fine) {
  .order-stamp:hover span { background: var(--mint); }
}

.order-stamp input:focus-visible + span { box-shadow: 3px 3px 0 var(--ink); }

/* struck = inked. The chosen stamp inverts, the way a rubber stamp leaves
   solid ink rather than a tint. */
.order-stamp input:checked + span { background: var(--ink); color: var(--paper); }

/* off-screen, not display:none — a bot reading the DOM still fills it */
.order-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
/* same 16px floor as the visible fields — it can never be tapped (offscreen,
   tabindex -1) but the floor stays uniform across every input on the page */
.order-hp input { font-size: 16px; }

.order-send-row {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  margin-top: 4px;
}

.order-send {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  min-height: 52px;
  padding: 14px 24px;
  border: 2px solid var(--ink);
  border-radius: 0;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font);
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  /* hard offset, not a blur: collage depth is a cut line, never a soft shadow */
  box-shadow: 5px 5px 0 rgba(20, 19, 18, 0.28);
  transition: transform 0.12s steps(2, end), box-shadow 0.12s steps(2, end);
}

@media (hover: hover) and (pointer: fine) {
  .order-send:hover { transform: translate(2px, 2px); box-shadow: 2px 2px 0 rgba(20, 19, 18, 0.28); }
}
.order-send:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
.order-send[disabled] { cursor: default; opacity: 0.55; box-shadow: none; transform: none; }

.order-fine {
  margin: 0;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.05em;
  line-height: 1.5;
  color: var(--muted);
}

.order-msg {
  margin: 0;
  font-size: 14px;
  font-weight: 700;
  line-height: 1.5;
  color: var(--ink);
}
.order-msg:empty { display: none; }
.order-msg a { color: var(--ink); }

/* an error is marked by an ink rule on the left, not by red — this sheet has
   one accent and it is not a status palette */
.order-msg.is-err { border-left: 4px solid var(--ink); padding-left: 10px; }
.order-msg.is-ok { border-left: 4px solid var(--mint); padding-left: 10px; }

/* the field that failed gets the same left-rule treatment on its rule line */
.order-input.is-bad { border-bottom-color: var(--ink); border-bottom-width: 4px; }

/* ---- the stamp ----
   Struck across the slip on a successful send. Outline type, hard rotation,
   ink only: a real rubber stamp is a single colour and it lands crooked. */
.order-filed {
  position: absolute;
  right: 6%;
  bottom: 14%;
  font-family: var(--display);
  font-size: clamp(56px, 11vw, 104px);
  line-height: 0.9;
  letter-spacing: 0.04em;
  color: transparent;
  -webkit-text-stroke: 3px var(--ink);
  border: 5px solid var(--ink);
  padding: 6px 18px;
  transform: rotate(-13deg);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  z-index: 3;
}

.order-slip.is-filed .order-filed { opacity: 0.9; visibility: visible; }

/* hard cut, not an ease — a photocopy does not fade into existence */
@media (prefers-reduced-motion: no-preference) {
  .order-slip.is-filed .order-filed { animation: filed-strike 200ms steps(3, end); }
}
@keyframes filed-strike {
  from { transform: rotate(-13deg) scale(1.5); opacity: 0; }
  to { transform: rotate(-13deg) scale(1); opacity: 0.9; }
}

/* the filed slip stops being a form: the fields grey back and the stamp owns it */
.order-slip.is-filed .order-form { opacity: 0.45; }
.order-slip.is-filed .order-msg { opacity: 1; }

@media (max-width: 860px) {
  .order { padding: var(--pad) var(--pad) calc(var(--pad) * 1.5); }
  /* the fixed cube parks at right:2%/top:63% below this boundary and prints
     straight through anything full-width — the copy gutter and the cube are
     one mechanism (see the .about-copy/.intro-note gutters) */
  .order .section-heading { max-width: 74vw; }
  .order-filed { font-size: clamp(44px, 15vw, 80px); right: 4%; bottom: 10%; }
}

@media (max-width: 560px) {
  .order-send { width: 100%; justify-content: space-between; }
  .order-send-row { gap: 12px; }
}

/* ---------- footer: the back cover ---------- */

/* The last sheet flips to ink: a full-bleed band, torn edge biting up into
   the paper above, interior on the same measure as every other band. It was
   previously contained into a centered 1100px "stamp block" card — that
   containment existed to keep the footer off the full-page Saturn plate,
   which has since moved up-page to PROJECTS, and the leftover card read as
   a floating modal at the end of the scroll (izzy 2026-08-03). */
.footer {
  position: relative;
  background: var(--ink);
  color: var(--paper);
  margin-top: clamp(64px, 9vw, 120px);
}

.footer .tear-top { background: var(--ink); transform: scaleY(-1); top: -17px; }

.footer-sheet {
  max-width: var(--stage-w);
  margin-inline: auto;
}

@media (hover: hover) and (pointer: fine) {
  .footer a:hover { text-decoration: underline; text-underline-offset: 4px; }
}

/* the routes line: reference info in one thin row — the WORK ORDER slip
   directly above owns conversion, so the old two-column contact grid and
   full-width WRITE ME bar are gone (izzy's routes ruling, 2026-08-03:
   Kick + Snapchat only; IG permanently OFF this brand) */
.footer-routes {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: clamp(16px, 2.4vw, 36px);
  padding: 12px var(--pad);
  border-bottom: 1px solid var(--paper);
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.footer-label { color: #a9a49a; }
.footer-label-direct { margin-left: auto; }

/* inline text in a flex row is a 19px line box; padding grows the target to
   44px around the ink, the negative inline margin keeps the text flush */
.footer-routes a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 4px 6px;
  margin-inline: -6px;
}

/* outbound arrows pick up mint — the accent stays an accent */
.footer-routes a span[aria-hidden] { color: var(--mint); margin-left: 6px; }

/* pastes the exact string already printed on the page — no invented
   content, just a faster path than copying it by hand off the mailto: */
.copy-email-btn {
  min-height: 44px;
  padding: 8px 14px;
  background: transparent;
  color: var(--paper);
  border: 1.5px solid var(--paper);
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {
  .copy-email-btn:hover { background: var(--paper); color: var(--ink); }
}
.copy-email-btn:focus-visible { background: var(--paper); color: var(--ink); }
/* Inverted, not mint — a clipboard write is not a live or risk state. It
   inverts to PAPER because this button sits on the ink footer: filling it
   with ink would erase it into the background, which is the same mistake as
   a paper label on paper. */
.copy-email-btn.is-copied { background: var(--paper); color: var(--ink); border-color: var(--paper); }

/* WRITE ME confirmation stamp. The wording is deliberately an attempt, not
   an outcome: a page cannot observe whether a mail client exists, launched,
   or sent anything, so "MAIL OPENED" was asserting a fact about the
   visitor's machine that may never have happened. The ellipsis is the whole
   claim — the browser was asked to hand this off, and that much is true. */
.is-stamped { position: relative; }
.is-stamped::after {
  content: "OPENING MAIL…";
  position: absolute;
  top: -10px;
  right: 8px;
  padding: 2px 8px;
  /* mint is the live/risk colour — a mail hand-off is neither. A real rubber
     stamp is ink anyway. */
  background: var(--ink);
  border: 1.5px solid var(--ink);
  color: var(--paper);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  transform: rotate(-6deg);
  pointer-events: none;
}
/* two of the three placements sit on the ink footer, where an ink stamp is
   an invisible box around floating paper text */
.footer .is-stamped::after {
  background: var(--paper);
  border-color: var(--paper);
  color: var(--ink);
}
@media (prefers-reduced-motion: no-preference) {
  .is-stamped::after { animation: stamp-in 180ms steps(3, end); }
}
@keyframes stamp-in {
  from { transform: rotate(-6deg) scale(0.7); opacity: 0; }
  to { transform: rotate(-6deg) scale(1); opacity: 1; }
}

/* -- the masthead: the address is the object -- */
.footer-mark {
  padding: clamp(32px, 5.5vh, 64px) var(--pad) 0;
}

/* Sized to SPAN the measure: (--sw − gutters) ÷ 2.605, the MEASURED advance
   ratio of "IZZY.LA" in Anton at 512px (wordmark box ÷ font-size, Chrome).
   letter-spacing 0 is load-bearing — the ratio bakes it in; tracking would
   push the span past the measure. On phones the fluid size still fills. */
.footer-wordmark {
  position: relative;
  display: inline-block;
  font-family: var(--display);
  font-size: clamp(96px, calc((var(--sw) - var(--pad) * 2) / 2.605), 580px);
  line-height: 0.86;
  letter-spacing: 0;
  text-transform: uppercase;
}

/* the iced seal pastes over the wordmark's shoulder — PHOTOGRAPHIC (standing
   ruling, ruled twice). Blend stays NORMAL: the asset is a true-alpha cutout,
   and screen-blend goes invisible wherever the paste crosses the cream
   letterforms (white-on-white) — it only ever worked in the old layout
   because the seal sat in pure ink. Em units keep the paste riding the type
   at every size. */
.footer-seal-paste {
  position: absolute;
  top: -0.08em;
  right: 0.03em;
  width: 0.4em;
  transform: rotate(5.5deg);
  /* decorative paste — must never eat clicks (its box grazes the COPY
     button's row) or block text selection on the wordmark */
  pointer-events: none;
}
.footer-seal {
  display: block;
  width: 100%;
  height: auto;
  opacity: 0.95;
}

/* -- the close: sign-off line + rubber stamp -- */
.footer-close {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px 40px;
  flex-wrap: wrap;
  padding: clamp(28px, 4vh, 44px) var(--pad);
}

/* the positioning line carries the close — display weight, paper ink.
   Scaled against the 580px masthead above it: at the old 40px cap the close
   read as a whisper under the giant address (measured side by side). */
.footer-desc {
  font-family: var(--display);
  font-size: clamp(28px, 3.2vw, 60px);
  line-height: 1.05;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--paper);
}

.footer-stampline { position: relative; }

/* SELF-FUNDED reads as a rubber stamp, not a caption */
.footer-fund {
  display: inline-block;
  padding: 9px 16px;
  border: 2px solid #6b675f;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: #cfc9bd;
  transform: rotate(-2.2deg);
}

/* print-margin data line, ON the plate: geometry comes from plate.css's
   .margin-bar (incl. iOS safe-area insets); recolored for ink here. The
   fixed V1 chip owns the bottom-left corner — the row stays right-aligned. */
.footer .margin-bar { border-top: 1px solid #3f3b35; }
.footer .margin-note { color: #a9a49a; }
.footer .margin-heart { fill: var(--paper); }
.footer .margin-links a { color: var(--paper); }
/* mono.w2-footer margin-links tap — residual 44px hit on ink footer route strip */
.footer .margin-links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-block: 10px;
}
@media (hover: hover) and (pointer: fine) {
  .footer .margin-links a:hover { color: #a9a49a; }
}

/* ---------- responsive ---------- */

@media (max-width: 1100px) {
}

@media (max-width: 860px) {
  .brand { font-size: 22px; }
  .btn { font-size: 13px; }

  /* premium bar = fewer, heavier. ACCOUNT lives in the menu on mobile;
     WRITE ME prints as a solid ink slab, MENU as one seamless plate —
     full words, no internal border seams, no ticket-stub chips */

  .header-right .btn-boxed .btn-label {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    padding: 0 4px 0 14px;
    overflow: visible;
    clip: auto;
    white-space: nowrap;
  }
  .header-right .btn-boxed .btn-label-s { display: none; }

  .header-right .btn-boxed {
    background: var(--ink);
    color: var(--paper);
    border-color: var(--ink);
    font-weight: 800;
    letter-spacing: 0.06em;
  }
  .header-right .btn-boxed .btn-arrow {
    border-left: none;
    padding: 0 13px 0 2px;
    font-size: 16px;
    color: var(--mint);
  }

  .btn-menu { font-weight: 800; letter-spacing: 0.06em; }
  .btn-menu .menu-label { padding: 0 4px 0 14px; }
  .btn-menu .menu-icon {
    border-left: none;
    padding: 0 13px 0 2px;
    font-size: 20px;
  }
  /* routes become a column of 44px rows; DIRECT reads as a section header */
  .footer-routes { flex-direction: column; align-items: flex-start; padding: 16px var(--pad) 20px; }
  .footer-label-direct { margin-left: 0; margin-top: 12px; }
  /* Reclaiming the dead band above (see .hero padding-top) clears the copy on
     a normal phone, but not on a short one: the copy block ends a stable
     ~285px into the hero at every phone width, while the portrait is
     bottom-anchored at 62% — so on a 602px hero its top lands at 229 and his
     hair prints through "The rest stays private" again.
     Clamping the height against the hero's own box pins his top edge at a
     constant 300px instead of a constant percentage. It only binds when the
     hero is short: 62% still wins above a ~790px hero, so the tall-viewport
     and desktop compositions are byte-identical to what izzy approved. */
  /* ...and 300 is now short by the ROUTES ROW, which did not exist when it
     was measured. .hero-routes (SATURNSHOP / PROJECTS / WRITE IN) hangs 44px
     of tap target off the bottom of the lede, so the copy block ends 416px
     into the hero at 860 and 468px on a phone (there the header plate wraps
     to two rows and the claim sets 96px Anton over two lines) — against the
     ~285px that 300 was built on. That gap is the collision: measured 390x844
     on izzy.la, the row boxed y440-484 with the portrait starting at y450, so
     34px of him printed through it — the links' underlines cutting across his
     hair, two of the three labels sitting on his halo. Taps still landed (the
     img takes no pointer events), so it reads as a legibility defect, not a
     broken control.
     480 is the one pin that clears every narrow width, so this stays a single
     rule instead of a phone tier below it (a second tier would also have to
     be placed AFTER this block to win the cascade, which reads as a trap).
     Measured on this tree with fonts loaded, 2026-08-23: 390x844 -> 234px
     tall, 12px under the row; 560x900 -> 354px, 38px; 860x1180 -> 560px,
     64px. It costs the portrait ~7% at the tablet end — the cheaper half of
     the trade. The 30% floor is the honest limit at the short end: a 375x667
     hero is 537px and the lede alone is 444, so he clamps to 161px and still
     crowds the row there (92px of overlap, down from 168). That one is a
     copy-side fix, not a portrait-side one. */
  .hero-me {
    max-width: 70%;
    max-height: min(62%, max(30%, calc(100% - 480px)));
  }
  .hero-caption { max-width: 260px; bottom: calc(var(--pad) + 96px); }

  .ticker-cell--organ { width: min(86vw, 400px); }

  /* THE CONTACT SHEET, part 1 — the cards carry no dead air.
     min-height:272 is a DESKTOP evenness rule (five frames on one reel
     line read as a set). Wrapped on a phone it padded the one-line
     CLOTHING card with ~115px of nothing and pushed every frame past the
     fold; sized to their own copy instead, two cards close a screen. */
  .ticker-cell--organ {
    min-height: 0;
    padding: 13px;
    gap: 6px;
  }

  /* izzy 2026-08-06, ruling on saturn-vs-cards: "just change the card
     size/font to make it work with saturn fully in view." Each page is
     centred, so every pixel off page 1's cards buys half a pixel of clear
     paper above the heading — which is where the planet reads. The BODY
     SIZE IS THE FLOOR AND DOES NOT MOVE (13px): those five receipts are
     the page's whole argument, and type that is technically uncut but
     practically unreadable would be a worse answer than a covered planet.
     Only leading and the box come down. */
  .cell-name { font-size: 23px; }
  .cell-organ { font-size: 11px; letter-spacing: 0.1em; }
  .cell-body { line-height: 1.5; padding-top: 3px; }
  .cell-status .chip { padding: 3px 7px; }

  /* the roll's door, printed ACROSS the sheet. The 56px vertical tab is a
     reel object — it stands in a frame slot beside the cards. Wrapped, it
     became a lone 56px column with a whole screen to itself; here it is
     the tear line the last frame closes on, on the cards' own width so it
     registers under them. */
  .ticker-end {
    width: min(86vw, 400px);
    /* --end-fill: solved in main.js from the closing frame's leftover, so
       the strip closes the sheet instead of leaving it half printed. Unset
       (0) wherever the frame is already full — the object is the same,
       it just breathes. */
    min-height: var(--end-fill, 0px);
    align-self: auto;
    align-items: center;
    justify-content: space-between;
    padding: 18px 40px;
    border-left: none;
    border-top: 2px dashed var(--ink);
    border-bottom: 2px dashed var(--ink);
    /* sprocket perforations down both margins — the roll's own furniture,
       and the thing that makes a tall strip read as film leader rather
       than as empty paper. KNOCKED OUT (paper disc, ink ring): printed
       grey dots camouflage over the Saturn plate's dot field, which is
       exactly how the desktop sprockets failed review in chunk C. */
    background:
      radial-gradient(circle at 15px 17px, var(--paper) 0 5px, var(--ink) 5px 6.3px, transparent 6.4px) 0 0 / 30px 34px repeat-y,
      radial-gradient(circle at 15px 17px, var(--paper) 0 5px, var(--ink) 5px 6.3px, transparent 6.4px) 100% 0 / 30px 34px repeat-y;
  }
  .ticker-end span {
    writing-mode: horizontal-tb;
    font-size: 13px;
    letter-spacing: 0.18em;
    color: var(--ink);
  }
  /* THE ROLL RUNS OUT — izzy 2026-08-06: "make the end of roll animated /
     more intentional". Once per 9s machine cycle, as the lamp crosses, the
     strip FEEDS: the perforations advance exactly one sprocket pitch (34px,
     so the loop closes seamlessly) in four 111ms drum steps, and the arrow
     is pushed toward the door and settles. Both ride --mc-delay, the root
     var the whole page's 9s furniture is phase-locked to (chunk D) — this
     is the machine pulling the last of the roll through, not a widget
     animating on its own clock. Stepped, never eased: this page prints.
     The flutter drops to a third of its throw — a 1.2° tip is right for a
     56px tab and reads as a crooked sheet on a strip this wide. */
  @media (prefers-reduced-motion: no-preference) {
    .ticker-end {
      animation: roll-flutter-plate 9s step-end infinite,
                 roll-feed 9s step-end infinite;
      animation-delay: var(--mc-delay, 0s);
    }
    .ticker-end::after {
      animation: roll-arrow 9s step-end infinite;
      animation-delay: var(--mc-delay, 0s);
    }
  }
  @keyframes roll-flutter-plate {
    0%, 5.9% { transform: none; }
    6%, 8.9% { transform: rotate(0.32deg); }
    9%, 11.9% { transform: rotate(-0.22deg) translateY(-1px); }
    12%, 14.9% { transform: rotate(0.1deg); }
    15%, 100% { transform: none; }
  }
  @keyframes roll-feed {
    0%, 7.7% { background-position: 0 0, 100% 0; }
    7.8%, 9.0% { background-position: 0 8.5px, 100% 8.5px; }
    9.1%, 10.3% { background-position: 0 17px, 100% 17px; }
    10.4%, 11.6% { background-position: 0 25.5px, 100% 25.5px; }
    11.7%, 100% { background-position: 0 34px, 100% 34px; }
  }
  @keyframes roll-arrow {
    0%, 7.7% { transform: none; }
    7.8%, 9.0% { transform: translateX(3px); }
    9.1%, 10.3% { transform: translateX(7px); }
    10.4%, 11.6% { transform: translateX(3px); }
    11.7%, 100% { transform: none; }
  }
  .ticker-end::after {
    content: "\2192";
    font-family: var(--display);
    font-size: 30px;
    line-height: 0.9;
    color: var(--ink);
  }
}

/* ---------- reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .btn, .btn-menu .menu-icon { transition: none; }
}

/* ============================================================
   VISUAL DETAIL PASS — print crop marks, collage carry-through,
   section-heading accent, branded focus + scrollbar. Additive
   only; nothing here changes copy or layout structure.
   ============================================================ */

/* (print crop marks retired 2026-08-02 — the full-bleed Saturn plate frames
   the sheet now; extra registration furniture read as clutter against it) */

/* ---------- section-heading highlighter ---------- */
/* mint marker swipe sitting behind the lower third of the Anton glyphs */
.section-heading { position: relative; z-index: 0; }
.section-heading::after {
  content: "";
  position: absolute;
  z-index: -1;
  left: var(--pad);
  bottom: calc(var(--pad) + 0.14em);
  width: clamp(46px, 7vw, 104px);
  height: 0.2em;
  background: var(--mint);
  transform: rotate(-1.2deg);
}

/* ---------- ABOUT collage carry-through ---------- */
.about-collage {
  position: absolute;
  z-index: 4;
  top: 50%;
  right: clamp(24px, 6vw, 96px);
  transform: translateY(-42%);
  width: clamp(220px, 24vw, 340px);
  pointer-events: none;
  z-index: 0;
}


.about-saturn { width: 88%; height: auto; }

/* second paper offcut peeking behind for collage depth */


@media (max-width: 960px) {
  .about-collage { display: none; }
}

/* ---------- branded focus + scrollbar ---------- */
:focus-visible {
  outline: 3px solid var(--mint);
  outline-offset: 2px;
}
/* keep the loud inverting elements readable when focused on ink */
a.ticker-cell:focus-visible,
.brand:focus-visible {
  outline-offset: 3px;
}

html {
  scrollbar-width: thin;
  scrollbar-color: var(--ink) var(--paper-deep);
}
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: var(--paper-deep); }
::-webkit-scrollbar-thumb {
  background: var(--ink);
  border: 3px solid var(--paper-deep);
}
::-webkit-scrollbar-thumb:hover { background: #000; }

/* ---------- micro-details ---------- */

/* ticker cells: print-index in the corner, set by CSS counters.
   The denominator is a DENOMINATOR, not decoration: at card 03 of a
   pinned carousel with no arrows and no dots, "03" alone cannot tell you
   whether two cards remain or five, so a fast scroller leaves believing
   the roll is shorter than it is. --cell-total is the single place that
   count lives; CSS counters cannot compute it, so when a ninth cell ships
   change it HERE and nowhere else. Quoted because content: concatenates
   strings, not numbers. */
.ticker-track { counter-reset: cell; --cell-total: "08"; }
.ticker-cell { counter-increment: cell; }
.ticker-cell--organ .cell-name { padding-right: 40px; }
.ticker-cell::after {
  content: counter(cell, decimal-leading-zero) "/" var(--cell-total);
  /* alt-text syntax hides the index from screen readers where supported */
  content: counter(cell, decimal-leading-zero) "/" var(--cell-total) / "";
  position: absolute;
  top: 16px;
  right: 16px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--muted);
}
a.ticker-cell:focus-visible::after { color: rgba(236, 231, 219, 0.7); }
@media (hover: hover) and (pointer: fine) {
  a.ticker-cell:hover::after { color: rgba(236, 231, 219, 0.7); }
}


/* tape: torn angled ends that fade out slightly, like real tape */
.tape {
  clip-path: polygon(2.5% 0, 100% 5%, 97.5% 100%, 0 93%);
  background: linear-gradient(
    90deg,
    rgba(196, 186, 158, 0.35),
    var(--tape) 14%,
    var(--tape) 86%,
    rgba(196, 186, 158, 0.35)
  );
}

/* (caption tape removed — a tone-on-tone horizontal strip above the label
   read as a broken marker highlight; the torn clip already sells the paste) */

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

/* mint marker underline instead of a default text-decoration on ink */
@media (hover: hover) and (pointer: fine) {
  .footer a:hover {
    text-decoration-color: var(--mint);
    text-decoration-thickness: 2px;
  }
}

/* (the WRITE ME footer CTA and its mint arrow are gone — the WORK ORDER
   slip owns conversion; the SELF-FUNDED stamp is fully defined in the
   footer section above) */

/* ---------- ticker over the cube ---------- */

/* the ticker section is cube-below (z 20) so cards occlude the cube, but the
   sticky stays transparent so the cube still shows through the gaps */
.ticker-sticky { background: transparent; }

/* ---------- mobile: cube wordmark ---------- */

@media (max-width: 560px) {
  /* behind the portrait's hair the wordmark grinds to orphan letters —
     a clean black panel reads as intent, two random letters read as a bug */
  .face-word { visibility: hidden; }
}

/* ---------- paste-down reveals ---------- */
/* Scroll-triggered "cut-and-paste" entrance: elements start nudged down and
   faded, then settle. Uses the `translate` longhand (not `transform`) so it
   layers cleanly on top of each element's existing rotate() tilt. Only active
   with JS + motion allowed; main.js sets html.js and toggles .is-in. */
@media (prefers-reduced-motion: no-preference) {
  html.js .reveal {
    opacity: 0;
    translate: 0 18px;
    transition:
      opacity 0.55s cubic-bezier(0.2, 0.8, 0.2, 1),
      translate 0.55s cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  html.js .reveal.is-in {
    opacity: 1;
    translate: 0 0;
  }
  /* ink-set — section headings resolve like wet ink coming into focus on
     paper as they land, on top of the paste-down. Filter + scale are both
     compositor properties (no reflow) and `scale` composes with the
     independent `translate` above and any element rotate() tilt. Headings
     only: the hero wordmark keeps its CMYK-misregistration signature, which
     a blur would flatten. */
  html.js .section-heading.reveal.is-in {
    animation: ink-set 0.72s cubic-bezier(0.22, 0.8, 0.2, 1) both;
  }
}
@keyframes ink-set {
  0%   { filter: blur(5px);   scale: 1.022; }
  55%  { filter: blur(1.1px); }
  100% { filter: blur(0);     scale: 1; }
}


/* a reachable but unreleased property: hollow the dot back out and print
   the qualifier, so "REACHED" can never read as an open business */


/* ---------- registration click (section headings) ---------- */
.section-heading.reg-armed {
  --reg: 0;
}

.section-heading.reg-armed::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: calc(var(--pad) * 0.35) calc(var(--pad) * 0.45);
  pointer-events: none;
  --tick: rgba(107, 103, 95, 0.85);
  --tl: 14px;
  --tt: 1.5px;
  opacity: var(--reg, 0);
  background:
    linear-gradient(var(--tick), var(--tick)) left  top    / var(--tl) var(--tt) no-repeat,
    linear-gradient(var(--tick), var(--tick)) left  top    / var(--tt) var(--tl) no-repeat,
    linear-gradient(var(--tick), var(--tick)) right top    / var(--tl) var(--tt) no-repeat,
    linear-gradient(var(--tick), var(--tick)) right top    / var(--tt) var(--tl) no-repeat,
    linear-gradient(var(--tick), var(--tick)) left  bottom / var(--tl) var(--tt) no-repeat,
    linear-gradient(var(--tick), var(--tick)) left  bottom / var(--tt) var(--tl) no-repeat,
    linear-gradient(var(--tick), var(--tick)) right bottom / var(--tl) var(--tt) no-repeat,
    linear-gradient(var(--tick), var(--tick)) right bottom / var(--tt) var(--tl) no-repeat;
  transition: opacity 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
}

@media (prefers-reduced-motion: reduce) {
  .section-heading.reg-armed::before { display: none; }
}

/* ============================================================
   COPY OF A COPY — carried by the full-bleed Saturn plate now
   (/plate.js, shared with /saturnshop/). The old copy-engine
   toner overlay retired 2026-08-02: two full-screen GL layers
   would fight, and the plate runs the same photocopier cadence,
   light-sweep and GEN dwell-aging itself. The GEN chip below is
   driven by plate.js with the same data-label/--gen contract.
   ============================================================ */

/* generation counter — prints the real shader uniform, never a fake */
.gen-chip {
  position: fixed;
  left: 17px;
  bottom: 15px;
  z-index: 91;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  /* --ink, NOT --muted. --muted is an AA token (6.20:1 on paper) but this
     chip prints through its own opacity ramp below, and the ramp ate it:
     #57534b at V1's 0.465 composites to #a7a298 = 2.06:1, the page's one
     Lighthouse contrast failure. Full ink under a lifted ramp lands the same
     quiet mid-grey (#63605b at V1) while clearing AA. */
  color: var(--ink);
  pointer-events: none;
}
.gen-chip[hidden] { display: none; }

/* peel physics — lift + torque; --drag-rot is driven by main.js */
.peelable {
  transform: translate(var(--drag-x, 0px), var(--drag-y, 0px))
    rotate(var(--rest-rotate, 0deg)) rotate(var(--drag-rot, 0deg));
}
@media (pointer: fine) {
  .peelable.is-peeling { scale: 1.045; }
}

/* page-to-page = another copy: white-hot in from the copier flash.
   Pages sharing this stylesheet opt in together; /design and /account
   have their own CSS and stay no-op until they add this block.
   Unsupported browsers no-op; reduced motion never opts in at all. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }

  ::view-transition-old(root) { animation: vt-flash-out 0.16s ease-in both; }
  ::view-transition-new(root) { animation: vt-flash-in 0.34s cubic-bezier(0.2, 0.8, 0.2, 1) both; }
}
@keyframes vt-flash-out {
  to { filter: brightness(2.6) contrast(0.7); }
}
@keyframes vt-flash-in {
  from { filter: brightness(2.6) contrast(0.7); }
  to { filter: none; }
}


/* ============================================================
   SEAL OFFCUT (restored) + SCATTERED CASINO MATTER
   The seal rides a black torn offcut on paper (never on the
   wordmark's letterforms); the dealt hand recurs down the sheet
   like a photocopied paste-up — smaller, tilted, degrading with
   the page's generations.
   ============================================================ */


/* ---------- scattered glyphs ---------- */
/* printed ink, one motif per section, never the same asset twice */

.scatter {
  position: absolute;
  pointer-events: none;
  color: var(--ink);
  filter: url(#xerox);
}


.scatter-cross {
  right: -4%;
  bottom: -14%;
  width: clamp(46px, 5vw, 72px);
  transform: rotate(10deg);
}

/* ---------- margin doodles ---------- */
/* pencil scrawl — izzy's handwriting in the margins; always under the cube */

.doodle {
  position: absolute;
  pointer-events: none;
  filter: url(#xerox);
  z-index: 4;
}
.doodle path {
  /* ballpoint, not marker: thin dark pen strokes (the '17' diary register) */
  fill: none;
  stroke: rgba(20, 19, 18, 0.62);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}



.doodle-hurry { left: 27%; top: 17%; width: 132px; transform: rotate(-4deg); z-index: 0; }
.doodle-hurry path { stroke: rgba(20, 19, 18, 0.74); stroke-width: 1.5; }

/* pinned to the stamp cluster now (inside .footer-stampline), not to the
   plate — it hangs off the SELF-FUNDED stamp's corner like a margin scribble */
.doodle-heart-footer { left: -42px; top: -24px; width: 46px; transform: rotate(-11deg); }
.doodle-heart-footer path { stroke: rgba(236, 231, 219, 0.55); }

@media (max-width: 860px) {
  /* the HURRY UP scrawl was measured fully hidden behind the first project
     card at every mobile width — its % offsets are meaningless once the
     ticker unpins and stacks. It was invisible there anyway; stop painting
     it rather than invent a new mobile position nobody has approved. */
  .doodle-hurry { display: none; }
}

/* ============================================================
   FINAL DETAILS — director pass
   ============================================================ */

/* body prose joins the typewriter register (the grotesk paragraphs were
   the last block reading as template, not zine) */
.about-copy {
  font-family: var(--mono);
  font-size: 15px;
  line-height: 1.72;
  letter-spacing: 0.01em;
}
.about-copy { max-width: 46ch; }

/* pen pressure: the gone-over-twice ghost pass + real underline strokes */
.doodle .ghost path { stroke-width: 1.05; opacity: 0.34; }
.doodle-hurry path.u1 { stroke-width: 2.1; }
.doodle-hurry path.u2 { stroke-width: 1.2; opacity: 0.4; }

/* (mobile heart override removed — the heart rides the stamp cluster now
   and scales with it at every width) */

/* NOTE: the portrait's mint rim is BAKED into izzy.png (a 5-6px band on the
   alpha edge). The 4x drop-shadow ring that used to live here stacked a
   third keyline on top of that band in the no-WebGL fallback, so it is
   deliberately gone. The shader prints the baked band as the single edge. */

/* live GPU halftone screen (halftone-portrait.js). Registers over the
   portrait's own box; the mint edge is drawn in-shader, so no CSS
   drop-shadow here — it would fringe every dot. Hidden until the shader
   compiles and the texture decodes; the <img> is the fallback. */
.hero-me-halftone {
  position: absolute;
  z-index: 12;
  /* left/top are written by halftone-portrait.js from the portrait's MEASURED
     box every layout. Do not restore a percentage anchor here: this rule used
     to say `left: 50%` while .hero-me says `left: 58%`, and the screened plate
     printed 31px to the left of the man on a 393px phone. One object, one
     source of position. */
  left: 0;
  top: 0;
  pointer-events: none;
}
html.halftone-on .hero-me {
  /* keep it in the layout (the canvas measures its box) but yield the
     paint to the screened version */
  opacity: 0;
}

/* nav plates get the same hard print offset as the cards */
.site-header .btn {
  box-shadow: 3px 3px 0 var(--ink);
  margin: -1px 0 0 -1px;
}
/* pointer-gated — on touch this pressed look latched after every tap */
@media (hover: hover) and (pointer: fine) {
  .site-header .btn:hover {
    box-shadow: 1px 1px 0 var(--ink);
    transform: translate(2px, 2px);
  }
}

/* the GEN chip demonstrates the mechanism: it degrades with the count —
   more ink laid down per generation, same 0.085 step izzy approved. Only the
   FLOOR moved (0.38 -> 0.55, measured 2026-08-23): plate.js stamps V1-V3
   only, so V1 is the state most visitors ever see and it was printing at
   0.465 = 2.06:1. Against --ink the ramp now reads 5.07 / 6.73 / 8.91:1
   across V1/V2/V3 — still a subordinate corner stamp (aria-hidden, 10px),
   still visibly ageing, no longer the one AA failure on the page. */
.gen-chip {
  opacity: calc(0.55 + var(--gen, 1) * 0.085);
}
.gen-chip::after {
  content: attr(data-label);
  position: absolute;
  left: calc(var(--gen, 1) * 0.45px);
  top: calc(var(--gen, 1) * 0.4px);
  opacity: 0.3;
  /* The ghost is a 2px misregistration overprint. Absolutely positioned, it
     shrink-to-fits against the chip's own 42.6px box, so "GEN 05" wrapped to
     two lines and printed a phantom "05" a full line-height BELOW the chip —
     read as a duplicated label, not a misprint. Measured 27px tall against a
     13.5px line box. */
  white-space: nowrap;
}

/* the swallowed terminal E prints as a misregistered ghost behind the
   portrait — total loss read as a bug; a ghost reads as the mechanism */
@media (max-width: 699px) {
}
/* the shared <filter> host — was an inline style attribute, which the page's
   style-src CSP refuses; a class costs nothing and lets the policy stay tight */
.svg-defs { position: absolute; }

/* ============================================================
   MOBILE COMPOSITION PASS (2026-08-01)
   The desktop collage was never re-composed for a phone: the same
   percentage anchors that spread eight objects over a 1440x1040 stage
   stack them on top of each other on a 393x736 one. Measured before this
   block: hero props + headline + portrait + caption covered 84% of the
   hero sheet, and the fixed cube printed straight through the intro and
   about copy. The desktop pass answered exactly this by SUBTRACTING
   (props 8 -> 4, collisions 4 -> 0); this is the same move one screen
   size down. Nothing here touches the desktop composition.
   ============================================================ */

@media (max-width: 860px) {
  /* --- the traveling cube becomes a margin mark ---
     .cube-persp is position:fixed at left/top 50%, so on a phone it parks
     dead centre of the viewport — which is where the reading happens. It
     was redacting the middle of every line of the intro paragraph ("Stores,
     soft[ware...] If it's live, it's [linked...] private till it sh[ips]")
     and striking through the about copy, because neither section has an
     opaque backing to occlude it the way the ticker's project cards do.
     Desktop solves this by horizontal separation (cube pinned right of a
     capped text column). A phone's copy is full-bleed, so the gutter has to
     be cut explicitly — hence the paired max-width below. The two must move
     together: widen the copy without moving the cube and the redaction
     comes straight back. */
  /* --- reclaim the dead band above the headline ---
     .hero's top padding is `--header-h + 44px` = 100px, a value from when the
     header overlapped the hero. The header is in FLOW now, so on a phone that
     is 100px of blank paper between the header plate and "I GROW" — and it is
     not free. The copy block is laid out from the top of the hero, while the
     portrait is bottom-anchored at 62% of it, so the shorter the viewport the
     further izzy rides UP into the text. Measured: the lede's bottom sits
     361px into the hero and the portrait's top at 40% of it, which collide
     for any hero under ~900px — i.e. every phone (430x755 gave 85px of
     overlap, printing his hair through "and the market. If it's").
     Reclaiming the dead band moves the copy up instead of shrinking him. */
  .hero { padding-top: 24px; }

  /* ...and the SHORT-viewport half of that same collision, which reclaiming
     the top band alone cannot reach. .hero grows via min-height, but the
     portrait is absolutely positioned at bottom:0, so flow content never
     reserves space for it: on a <=760px-tall screen the routes ran 91px INTO
     the face, printing "PROJECTS" across the forehead and the offer line
     through the hair (measured 375x667, 2026-08-23, after the hero note
     landed). bottom:0 resolves against the PADDING box, so bottom padding
     pushes the portrait down while the routes stay put — it buys clearance
     without shrinking him, which is why this is padding and not another
     max-height floor. 390x844 already had the slack and is untouched. */
  /* SMALLEST phones. The hero note is his offer and must not be cut, but at
     320px it sets 5 lines at 15/25.8 = 129px and pushes the WRITE IN button
     77px BELOW the fold — the primary CTA off the first screen is a worse
     failure than the overlap this block was opened to fix. Tightening the
     note's own type is the lever that costs nothing else on the page: the
     sentence still reads, the routes come back up. Measured 320x568 and
     360x640, 2026-08-23. */
  @media (max-width: 400px) {
    .lede-note { font-size: 13.25px; line-height: 1.5; max-width: 82vw; }
    /* ...and the rest comes out of whitespace, not out of the type. Measured
       320x568: h1 149 + rule 20 + caption 22/42/30 + note 80 + routes 16/92
       = 452 into a 424px slot. The routes wrap to two rows at this width, so
       the row itself is not the fat — the 72px of stacked margin above it is.
       Trimming that lands the button on the first screen; the headline keeps
       its size, which is the one thing on this page that cannot shrink. */
    .lede-rule { margin-top: 12px; }
    .hero-caption { margin-top: 16px; margin-bottom: 14px; }
    .hero-routes { margin-top: 10px; }
    /* last 13px: leading, not size. The two display lines sit at 0.86 of the
       face; 0.82 is still open enough for ANTON's flat sidebearings at this
       size and buys the button its final row. Cap height is untouched, so
       "I GROW BRANDS." reads exactly as loud as it did. */
    .lede-claim { line-height: 0.82; }
  }

  @media (max-height: 760px) {
    .hero { padding-bottom: calc(var(--pad) + 104px); }
    /* and the cap has to leave the hero's own height behind. max-height:%
       is a share of .hero, so the padding above grew the portrait almost as
       fast as it lowered him — 91px of overlap only came back to 53px.
       Anchoring to vh breaks that feedback loop: the box he may occupy is
       now the SCREEN's, not the section's he is pushing taller. */
    .hero-me { max-height: 16vh; }
  }

  /* FULLY ONSCREEN (izzy 2026-08-03: "the cube is cut off on mobile").
     The old right:2% centred the cube ON the edge — a half-bleed margin
     mark, 42% of it offscreen at every phone width. Rebuilt from a
     rest-state sweep (320/360/393/430 x hero + ABOUT rests, glyph-level
     collision testing): the one lane that is clear at every width is the
     right gutter BETWEEN the claim's ink and the portrait's top. The top
     anchors to --hero-header-offset (the JS-published live header bottom)
     because the header's height varies by width and a viewport % cannot
     serve both 668 and 932. The inset derives from the cube's own size so
     the widest spin projection (~1.42x edge) clears the edge at rest.
     Mid-glide the title still sweeps past behind it — transient animation
     frames, same as every fixed mark on the page. */
  :root { --cube-size: clamp(52px, 15vw, 78px); }
  .cube-persp--solid,
  .cube-persp--wire {
    left: auto;
    right: calc(var(--cube-size) * 0.71 + 2px);
    top: calc(var(--hero-header-offset) + 258px);
  }
  /* .lede-note is the sentence that moved up into the hero from the deleted
     intro; it needs the same mobile gutter the intro paragraph did, or the
     fixed cube redacts the middle of every line. */
  .lede-note,
  .about-lead,
  .about-copy,
  /* the chip row belongs in the gutter too — at 360px it reached to within
     7px of the cube's lane, which passes today and stops passing the moment a
     font loads differently. Constrained, it wraps to two rows instead. */
  .about-chips { max-width: 74vw; }
  /* the intro lede is the one display line wide enough to reach the cube's
     lane, and being display type it cannot just be given a max-width without
     wrapping "I GROW BRANDS." — which would duplicate the hero's two-line
     setting a screen earlier. Trimming the floor instead keeps it on one line
     and clears the gutter; the vw term is raised so larger phones still grow
     it. Verified by sweeping every scroll position through both sections. */


  /* --- hero: give the portrait its sheet back ---
     .hero-seal is the one prop whose solid black field lands under the
     display type: it sits at top 56%/left 21%, which on a phone puts it
     directly beneath "BRANDS." — 85x68px of black paper under black Anton,
     so the B and R lose their counters entirely. It is also the one prop
     that is not unique to the hero (the same AWAKE mark prints on the cube
     face and again as the footer jewel), so dropping it here costs the page
     nothing it doesn't say twice more. Saturn, the coin and the aces all
     land on paper or on the portrait, which is the intended layering. */

}

/* ============================================================
   THE PLATE — 2026-08-02 (izzy: "upgrade the rest of izzy.la
   to look like /saturnshop/", + "still implement the cube and
   assets we have").

   The whole domain now prints on ONE ground: the procedural WebGL
   halftone Saturn from /plate.js. The layer itself, the margin bar
   and the .plate-card object live in /plate.css, which every
   surface links — see that file for why it is shared.

   Everything the collage already owned — the traveling cube, the
   halftone portrait, the coin/cards/seal/spade/cross glyphs, the
   doodles, the tape and torn offcuts — stays exactly where it was
   and now floats ON the plate instead of on flat cream.

   Below: only the homepage-specific consequences of that move.
   ============================================================ */

/* ---------- the erasers, removed ---------- */

/* .about carried `background: var(--paper)` so its copy sat on flat stock.
   Against a live plate that is a rectangle of nothing punched through the
   print. The plate's calm zone (data-calm tracks .about-body) dims the dots
   to a whisper behind the text instead — legibility without the hole. */
.about { background: transparent; }

/* Same for the status board's section. The paper band was there to stop the
   traveling cube passing through the telex lines (black face under thin ink
   = unreadable). That job moves to the board itself, one line down, which
   becomes a real dispatch slip rather than an invisible wash. */


/* ---------- (footer containment removed 2026-08-03) ----------
   The "stamp block" card that used to live here existed to keep the footer
   off the full-page Saturn plate; the plate now ends at PROJECTS, and the
   leftover card read as a floating modal at the end of the scroll. The
   footer is the full-bleed back cover again — defined in the footer
   section above, torn top edge included. */

/* The status note and heading sit directly on the plate (the board below them
   is a card, they are not). At 52% ink they were competing with the halftone
   dots rather than reading over them — the plate's calm zone only follows the
   tracked boxes, and this note is not one of them. Lifted to a weight that
   survives texture; the tone is still clearly secondary to the board. */


/* The plate is anchored to the PAGE now (izzy 2026-08-02: "have the animated
   saturn stay down the page, doesnt move with scrolling"), so it needs a band
   to occupy rather than the viewport. Two screens tall, starting at the top:
   the rings reach up into the hero, the planet body sits just below the fold —
   "down the page" — and the whole print scrolls away like ink on paper instead
   of following the reader.
   ROWS is raised to match: the shader's ruling locks to the SHORT axis of the
   plate, which used to be the 900px viewport height and is now the full page
   width, so the default 78 would print dots ~1.6x coarser than /saturnshop/. */
:root {
  /* the Saturn band: one screen and a half, pinned by data-anchor to #things
     (PROJECTS) rather than to a pixel offset, so it stays on that section no
     matter how much the copy above it grows */
  --plate-h: 150vh;
}

/* ============================================================
   MOBILE PASS — 2026-08-03 (izzy: "now lets fully optimize the
   mobile site").

   Measured at 320 / 393 / 430 with a real headless Chrome before
   touching anything: no horizontal scroll at any width, every
   input already clears the iOS 16px zoom floor. What did fail was
   touch ergonomics — five controls under the 44px minimum the
   rest of the site holds to.
   ============================================================ */

/* (footer link + copy-button touch targets live in the footer section now —
   the 44px floors are baked into .footer-routes a and .copy-email-btn) */
/* skip-link 44px + safe-area: see block above main chrome (residual 20260808-g23) */

/* On phones the stacked full-width project cards bury the anchored Saturn
   print — lift it into the open paper at the section's head, so the disc
   and rings print behind PROJECTS. and above card 01 (izzy 2026-08-03,
   picked from a 200/320/440 sweep). plate.js subtracts this from the
   anchor position; unset elsewhere = 0. */

/* ============================================================
   TOUCH SNAP — phones get the NATIVE engine (2026-08-03, canonical v2)
   iOS runs paging in its compositor with its own spring — zero JS in the
   loop (three JS glide iterations were harness-green and still buggy on
   the real iPhone). Scoped to coarse pointers at phone widths; main.js's
   snapOwned check stands the JS glide down on these devices — this block
   and that check are a PAIR, sync them together.
   PROJECTS: a blanket align on the over-tall section makes its area COVER
   the snapport, which (spec, WebKit-verified) turns the interior into free
   scroll and makes inner snap points INERT — so .ticker-section stays
   align:none and the stops live inside it. THE STOP IS NOW THE PAGE, NOT
   THE CARD (izzy 2026-08-06: "2 or 3 cards per screen so it doesnt look
   empty" — the one-card film frame kept his standing never-cut-off law at
   a cost of half a screen of paper per card). main.js's reel page fit
   packs the cards into pages that close the SMALL viewport whole and
   solves the leader between pages against the LARGE one; only a page's
   first card carries .is-page-lead, so it is the only card holding a snap
   point and a rest can only ever show whole cards. The heading rides page
   1 as a prefix on that card's scroll-margin, which puts "PROJECTS." on
   screen AT the rest instead of in a leader nobody stops in. No JS (or
   reduced motion) = no page leads = no card-level stop at all: the
   section free-scrolls, uncuttable by construction. stop:normal lets a
   hard fling pass several pages; the first card is stop:always so the
   section can never fly by unseen.
   CONTACT: id-based stops (the .footer.cube-below entry stop died
   to class churn and swipes sailed 599px past the form); the slip is its
   own stop; focus anywhere in the footer suspends snapping so the iOS
   keyboard's scroll-to-field is never re-snapped away — the form owns the
   screen while you type. Reduced-motion gets no snap.
   ============================================================ */
@media (max-width: 860px) and (pointer: coarse) {
  @media (prefers-reduced-motion: no-preference) {
    html { scroll-snap-type: y mandatory; }
    /* the leader between PAGES is solved per page by main.js and written
       as margin-top on each page-leading card, so the track keeps the
       plain 14px feed pitch (base ≤860 rule): a page's own cards sit
       tight together and the paper lives between frames, where snapping
       jumps over it and it costs nothing on screen. */
  }
  .site-header { scroll-snap-align: start; }
  .about, .footer.cube-below {
    scroll-snap-align: start;
    scroll-snap-stop: always;
  }
  .ticker-section { scroll-snap-align: none; }
  .ticker-cell { scroll-snap-align: none; }
  .ticker-cell.is-page-lead,
  .ticker-end.is-page-lead {
    scroll-snap-align: start;
    /* solved per page: the frame's top margin, plus the heading's own
       height on page 1 */
    scroll-margin-top: var(--page-m, 14px);
  }
  .ticker-cell:first-child { scroll-snap-stop: always; }
  #contact { scroll-snap-align: start; scroll-snap-stop: always; }
  .order-slip { scroll-snap-align: start; scroll-margin-top: 20px; }
  #orderForm input, #orderForm textarea { scroll-margin: 120px 0; }
  .margin-bar { scroll-snap-align: end; }
}
@media (max-width: 860px) and (pointer: coarse) {
  html:has(#contact :focus) { scroll-snap-type: none; }
}

@media (max-width: 860px) {
  /* the drawn planet's CENTRE sits 40px below the PROJECTS section top —
     plate.js place() solves the plate position from live measured geometry
     (immune to plate-h/cy retuning, which staled two fixed constants). */
  .plate--saturn { --plate-planet-at: 40px; }
}

/* ---------- THE COLLAGE — the WORK ORDER section's own print ----------
   Band + canvas are created by collage.js (like motion.js's lamp bar).
   The section paints opaque paper at z-index:20, so the print lives INSIDE
   it: first child, z -1 — above the section's paper, below slip/heading. */
.order-collage{position:absolute;inset:0;z-index:-1;pointer-events:none;overflow:hidden;contain:strict}
.order-collage canvas{position:absolute;inset:0;width:100%;height:100%;display:block}

/* ============================================================
   WORK ORDER ONE-SCREEN (izzy, 2026-08-06): on phones the whole slip
   compresses to land in a single viewport — same copy, denser scale.
   Inputs hold the 16px iOS floor; chip tap targets stay 44px. */
@media (max-width: 860px) {
  #contact .section-heading { font-size: 26px; margin-bottom: 10px; }
  #contact .order-slip { padding: 16px 16px 12px; }
  #contact .order-lede { font-size: 14px; line-height: 1.4; margin: 10px 0 12px; }
  #contact .order-lede.order-terms { font-size: 13px; line-height: 1.42; margin-bottom: 12px; }
  #contact .order-label { font-size: 10px; }
  #contact .order-input { font-size: 16px; padding: 5px 0 6px; }
  #contact .order-stamps { gap: 8px; }
  #contact .order-stamp span { padding: 9px 11px; font-size: 11px; }
  #contact #orderForm textarea { min-height: 64px; }
  #contact .order-send { padding: 11px 16px; }
  #contact .order-head { font-size: 11px; }
  /* -8px, not the exact-44 -7.5px: this chip's target is PINNED to its
     font-size — any future size shift in this block drops an exact-44
     under the floor silently. 1px of margin is the alarm gap. */
  #contact .chip--go::after { inset: -8px -5px; }
}

/* ============================================================
   THE ORDER SECTION'S OWN SPECK PASS (2026-08-06, izzy: "add the animated
   background particles to the entire site, and make sure its seamless").
   .order paints opaque paper at z:20 on purpose — it covers the cube's
   flight path — which buries the viewport-fixed .plate--specks field and
   left this section the one bare stretch of sheet on the page. The band
   re-prints the same field INSIDE the section: first static child, z:-1
   (above the section's paper, below every control), painting OVER the
   collage band that collage.js prepends at boot — specks are the sheet's
   last pass, same order the Saturn plate composes in. Geometry only:
   the renderer, cadence, sweep and smear all come from plate.js, and
   data-rows-fit on the canvas keeps the dot the exact size the viewport
   field prints, so the ground reads as ONE sheet across the section edge. */
.plate--order {
  position: absolute;
  inset: 0;
  height: auto;
  z-index: -1;
  contain: strict;
}

/* ============================================================
   THE ABOUT ROW'S DOOR — A LINE, NOT A LABEL
   (2026-08-06, izzy: "remove the saturnshop box and make it like a link thing
   below the boxes to fill out the form".)

   SATURNSHOP was the fourth chip in .about-chips. The other three name
   DISCIPLINES; SATURNSHOP is a product with a form behind it, so the row was
   spending one mark — the boxed label — on two meanings, and the door was
   camouflaged as a label. Pulled out and set below the row it reads as the
   only pressable thing in the section, and the three chips finally read as
   one class of thing. Supersedes the 08-05 "nothing here navigates" ruling.

   DIRECTION — MISREGISTERED PRESS. .chip--go carries the signature as an
   offset outline behind the plate; a line has no plate to outline, so the
   same mechanism runs on the rule underneath: one impression in register, a
   second 3px off, pulled flush and absorbed on press. Both rules ride
   currentColor, so the keyboard's ink slab flips them to paper for free.

   THE TARGET IS REAL PADDING, not an inset ::after. 11 + 23 + 11 = 45px, a
   full pixel over the floor, and it measures with getBoundingClientRect()
   like any painted box — the pseudo route resolves against the padding box
   and has silently shipped a short target on this page twice. The negative
   margins hand the padding back to the layout, so the section is exactly as
   tall as the printed line would make it. margin-top -6px against
   .about-body's 20px gap leaves 14px between border boxes — 6.5px of
   clearance under the chips' own grown hit boxes, which end 7.5px below the
   row. If that gap or .about-chips' inset ever moves, re-measure BOTH. */
.about-go {
  align-self: flex-start;
  display: inline-flex;
  /* flex-end, not flex-start: on one line the two items are the same 23px box
     so this is byte-identical, but when the label takes two lines (≤320px) it
     keeps the arrow trailing the LAST line instead of stranding it beside the
     first. .go-mark gets the label's 5px bottom padding so "end" means the
     same baseline, not the bottom of the rule. */
  align-items: flex-end;
  padding: 11px 3px;
  /* the -3px left cancels the 3px of side padding, so the printed rule starts
     on the same hard left as .about-lead and .about-copy — the target grows
     outward, the ink does not move */
  margin: -6px 0 -11px -3px;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  line-height: 18px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink);
}
/* Second three-surface door (studio + saturnshop stack) */
.about-go + .about-go {
  margin-top: 2px;
}


.about-go-label {
  position: relative;
  padding-bottom: 5px;
}

.about-go .go-mark { padding-bottom: 5px; }

/* first impression, in register — then the second, 3px off the plate */
.about-go-label::before,
.about-go-label::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: currentColor;
}

.about-go-label::after {
  opacity: 0.3;
  transform: translate(3px, 3px);
}

@media (prefers-reduced-motion: no-preference) {
  .about-go-label { transition: background 0.13s, color 0.13s, box-shadow 0.13s; }
  .about-go-label::after { transition: transform 0.13s, opacity 0.13s; }
  .about-go .go-mark { transition: transform 0.13s; }
}

/* Pressed = the two impressions pulled into register and the arrow stepping
   forward. :hover is pointer-gated (touch latches :hover and would leave the
   line permanently in register); :focus-visible never is — that is the
   keyboard path and has to work at every width. */
@media (hover: hover) and (pointer: fine) {
  .about-go:hover .about-go-label::after { transform: translate(0, 0); opacity: 0; }
  .about-go:hover .go-mark { transform: translateX(4px); }
}

.about-go:focus-visible .about-go-label::after { transform: translate(0, 0); opacity: 0; }
.about-go:focus-visible .go-mark { transform: translateX(4px); }

/* The keyboard also inks the plate — the move every other door on this page
   makes (.btn, .chip--go, .ticker-cell). box-shadow, not padding, so the slab
   grows outward and the line never reflows on focus. */
.about-go:focus-visible .about-go-label {
  background: var(--ink);
  color: var(--paper);
  box-shadow: 0 0 0 3px var(--ink);
}

/* ============================================================
   THE SECOND PULL — the WORK ORDER section's Saturn pass (2026-08-06).
   .order paints opaque paper at z:20, so the document Saturn can never
   show behind it; the planet is struck again INSIDE the section.
   z:-2, not -1: the collage band and the speck band are both z:-1 and
   resolve by DOM order, which collage.js rewrites at boot — a third pass
   that must sit under BOTH needs a different number, not an earlier
   sibling. Still above the section's own paper.
   NOTE for whoever tunes this: the canvas deliberately carries
   data-rows-match and NOT the data-rows-fit its sibling .plate--order
   carries. They register against different things — the specks match the
   viewport-fixed ground, this matches the document Saturn's own cell.
   Copying rows-fit across prints a 7.2px cell here, i.e. a different
   plate, at the exact seam this pass exists to erase.
   ============================================================ */
.plate--order-saturn {
  position: absolute;
  inset: 0;
  height: auto;
  z-index: -2;
  contain: strict;
}

/* Visibility is CSS's job, not the media-attribute's. data-mq keeps a phone
   from ever BUILDING the WebGL context, but it fires once and has no teardown:
   a desktop window narrowed (or zoomed to 200%, which shrinks the CSS viewport
   the same way) would otherwise keep printing a full-width Saturn over a phone-
   width WORK ORDER. This rule is true at every instant regardless of init
   history, and it also parks the plate — a display:none band has clientWidth 0
   and never intersects, so its draw loop stops. 860 / 860.02 is the site's one
   breakpoint pair; both halves move together. */
@media (max-width: 860px) {
  .plate--order-saturn { display: none }
}

/* ============================================================
   WAVE 2 — TRUE PHONE CRAFT (2026-08-07)
   Homepage exclusive CSS only (this file). Scoped ≤430px so the
   861+ desktop composition and the 431–860 tablet pass stay
   untouched. §4 art lock holds: no token restyle, no new typeface,
   no desktop hero redesign — only notch safety, thumb feedback, and
   44px floors that the one-screen density retune can silently drop.
   ============================================================ */

/* Horizontal safety on the narrowest viewports — ticket stubs with
   hard offsets can paint 2–3px past the gutter; clip, not hide, so
   we never create a programmatic scroller (same reason ticker uses
   overflow:clip). */
@media (max-width: 430px) {
  html, body { overflow-x: clip; }

  /* Notches / Dynamic Island: the in-flow header’s 10px top margin was
     written for a rectangular phone; without the inset the ink brand
     plate sits under the status bar. Safe-area bottom later keeps the
     menu scroll clear of the home indicator. */
  .site-header {
    margin-top: max(10px, env(safe-area-inset-top, 0px));
  }
  .menu-overlay {
    top: max(10px, env(safe-area-inset-top, 0px));
    max-height: calc(
      100dvh
      - 20px
      - env(safe-area-inset-top, 0px)
      - env(safe-area-inset-bottom, 0px)
    );
    padding-bottom: max(12px, env(safe-area-inset-bottom, 0px));
  }

  /* Control bar: at 320 the prior 10px gap still wastes 4px the label could
     use. Keep the measured two-row wrap composition; only tighten the seam. */
  .header-right { gap: 6px; }

  /* PROJECTS cards: 5+5 hard offset on a 320 stage eats too much of the
     frame. Same ink language, quieter throw. Desktop 5px offset is untouched. */
  .ticker-cell {
    box-shadow: 3px 3px 0 rgba(20, 19, 18, 0.9);
  }

  /* SATURNSHOP door lives under the discipline chips; keep it in the same
     cube-gutter lane so the fixed cube cannot redact the label on the
     narrowest widths (chips already take 74vw above). */
  .about-go { max-width: 74vw; }

  /* One-screen density (≤860) retunes stamp/input padding. Re-assert the
     44px floor HERE where it can be measured without tablet reflow. */
  #contact .order-input,
  #contact .order-stamp span,
  #contact .order-send {
    min-height: 44px;
  }
  #contact .order-stamp span {
    padding: 10px 12px;
  }

  /* Footer routes are a column of 44px rows at ≤860; on true phones the
     left-align needs a thumb-width hit strip so edge taps don't miss. */
  .footer-routes a {
    min-width: 44px;
    padding-inline: 8px;
    margin-inline: -8px;
  }
}

/* Coarse press feedback that does NOT latch. Hover is pointer-gated for a
   reason (touch latches :hover); :active lifts with the finger. Scoped
   with max-width so desktop coarse tablets (surface) keep their own hover
   path when they have fine + hover. */
@media (max-width: 430px) and (pointer: coarse) {
  .site-header .btn:active {
    box-shadow: 1px 1px 0 var(--ink);
    transform: translate(2px, 2px);
  }
  .menu-overlay a:active {
    background: var(--ink);
    color: var(--paper);
  }
  .order-stamp:active span {
    background: var(--mint);
  }
  .order-stamp input:checked + span {
    /* checked already inverted — don't flash mint on press */
    background: var(--ink);
    color: var(--paper);
  }
  a.ticker-cell:active {
    background: var(--ink);
    color: var(--paper);
  }
  a.ticker-cell:active .cell-name,
  a.ticker-cell:active .cell-body {
    color: inherit;
  }
  .about-go:active .about-go-label::after {
    transform: translate(0, 0);
    opacity: 0;
  }
  .about-go:active .go-mark {
    transform: translateX(4px);
  }
  .about-go:active .about-go-label {
    background: var(--ink);
    color: var(--paper);
    box-shadow: 0 0 0 3px var(--ink);
  }
}

/* Prefer reduced-motion: kill press transforms that only exist for feedback. */
@media (max-width: 430px) and (pointer: coarse) and (prefers-reduced-motion: reduce) {
  .site-header .btn:active {
    transform: none;
  }
  .about-go:active .go-mark {
    transform: none;
  }
}

/* THE LEDE TAG (izzy 2026-08-08): the hero's pasted label moved up out of
   the portrait corner into the lede slot, one line only. Static in the lede
   flow; inline-block so the chip shrink-wraps its line the way the absolute
   seat did. bottom/left neutralized so the phone seat override (max-430
   block) cannot re-anchor it. */
.hero-caption--lede {
  position: static;
  display: inline-block;
  margin: 22px 0 30px;
  bottom: auto;
  left: auto;
}
