/* ---------- 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 {
  --paper: #ece7db;
  --paper-deep: #e0dac9;
  --ink: #141312;
  --mint: #d9e8cf;
  --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: #57534b;
  --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
     (it grows when the live-position strip shows) 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; }

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; }

/* live GMX position strip — sits between the brand and the buttons; only
   shown (JS drops [hidden]) when there is a real open position on-chain */
.header-live {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 0 14px;
  border: 2px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  text-decoration: none;
  overflow: hidden;
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}
.header-live[hidden] { display: none; }
.header-live:hover { box-shadow: 4px 4px 0 rgba(20, 19, 18, 0.9); transform: translateY(-1px); }
.header-live:focus-visible { outline: 3px solid var(--mint); outline-offset: 2px; }

.header-live-tag {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding-right: 13px;
  border-right: 2px solid var(--ink);
}

/* solid ink tick — a pale outline box at 9px read as a missing glyph */
.header-live-dot {
  width: 9px;
  height: 9px;
  background: var(--ink);
}
@media (prefers-reduced-motion: no-preference) {
  .header-live-dot { animation: hl-pulse 1.8s ease-in-out infinite; }
}
@keyframes hl-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.28; }
}

.header-live-rows {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 0;
  overflow: hidden;
  white-space: nowrap;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
}

.hl-row { display: inline-flex; align-items: center; gap: 8px; }
.hl-row + .hl-row { margin-left: 16px; padding-left: 16px; border-left: 1px solid rgba(20, 19, 18, 0.25); }

/* real "no open positions" state — the book is genuinely empty, not
   unreachable, so it stays visible instead of hiding the whole strip */
.hl-row--flat {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.hl-sym { font-family: var(--display); font-size: 17px; letter-spacing: 0.02em; }

.hl-side {
  font-family: var(--mono);
  font-size: 9px;
  padding: 1px 5px;
  border: 1.5px solid var(--ink);
  letter-spacing: 0.08em;
}
/* up/long = light chip in a mint sticker ring; down/short = ink chip.
   Sign reads by value (light vs dark), mint stays outline-only. */
.hl-side--long { background: var(--paper); box-shadow: 0 0 0 2px var(--mint); }
.hl-side--short { background: var(--ink); color: var(--paper); }

/* direction spark — chip colors mirror the LONG/SHORT chip language:
   green day = mint chip + rising ink line, red day = ink chip + falling
   paper line. The line redraws itself on every 3s refresh. */
.hl-spark {
  flex: none;
  width: 52px;
  height: 18px;
  padding: 2px 4px;
  border: 1.5px solid var(--ink);
}
.hl-spark path {
  fill: none;
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.hl-spark--up { background: var(--paper); box-shadow: 0 0 0 2px var(--mint); }
.hl-spark--up path { stroke: var(--ink); }
.hl-spark--down { background: var(--ink); }
.hl-spark--down path { stroke: var(--paper); }

/* the 3s data refresh rebuilds the row and restarts this, so each poll
   reads as a quick pulse: line draws in ~1s then holds fully visible */
@media (prefers-reduced-motion: no-preference) {
  .hl-spark path {
    stroke-dasharray: 100;
    animation: hl-spark-draw 3s ease-out infinite;
  }
}
@keyframes hl-spark-draw {
  0% { stroke-dashoffset: 100; }
  35%, 100% { stroke-dashoffset: 0; }
}

/* trailing-90-day realized stats — same divider grammar as .hl-row + .hl-row,
   so it reads as one more cell in the same strip rather than a bolted-on badge.
   Empty until the feed answers; :empty keeps the divider from printing a
   dangling rule when there is nothing to show. */
.hl-stats {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-left: 16px;
  padding-left: 16px;
  border-left: 1px solid rgba(20, 19, 18, 0.25);
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  white-space: nowrap;
}

.hl-stats:empty {
  display: none;
}

.hl-stats-label {
  color: var(--muted);
}

/* the counts carry the weight — full ink against the muted window label */
.hl-stats-fig {
  color: var(--ink);
}

.hl-stats-sep {
  color: var(--muted);
  opacity: 0.6;
}

/* 90-day equity curve, plotted from real cumulative closed-trade P&L */
.hl-equity {
  display: block;
  width: 62px;
  height: 18px;
  flex: none;
  overflow: visible;
}

.hl-equity-line {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.5;
  stroke-linejoin: miter;
  stroke-linecap: butt;
  vector-effect: non-scaling-stroke;
}

.hl-equity-zero {
  stroke: var(--ink);
  stroke-width: 1;
  stroke-dasharray: 2 2;
  opacity: 0.35;
  vector-effect: non-scaling-stroke;
}

.hl-equity-tip {
  fill: var(--ink);
  stroke: var(--paper);
  stroke-width: 1;
}

/* Tight headers, in stages. The figures are the point and the curve is the
   flourish, so the curve goes first; then the wording is abbreviated; then the
   whole block goes rather than being clipped by .header-live's overflow:hidden.
   Measured at 390px before this: the stats ran 20px past the strip (so the P&L
   chip was cut in half) and the document gained 37px of horizontal scroll. */
@media (max-width: 1200px) {
  .hl-equity { display: none; }
}

@media (max-width: 900px) {
  .hl-stats { margin-left: 10px; padding-left: 10px; gap: 6px; }
  .hl-stats-label { display: none; }          /* the "90D" window chip */
  /* "35 TRADES · 94% WIN" -> "35T · 94%" */
  .hl-stats-fig[data-short]::after { content: attr(data-short); }
  .hl-stats-fig[data-short] { font-size: 0; }
  .hl-stats-fig[data-short]::after { font-size: 11px; }
}

@media (max-width: 560px) {
  /* below this the live book itself needs the room more than the record does */
  .hl-stats { display: none; }
}

.hl-pnl {
  padding: 1px 6px;
  border: 1.5px solid var(--ink);
}
.hl-pnl--up::before { content: "+"; }
.hl-pnl--up { background: var(--paper); box-shadow: 0 0 0 2px var(--mint); }
.hl-pnl--down { background: var(--ink); color: var(--paper); }

/* narrow: live strip drops to its own full-width row under brand + buttons;
   rows scroll horizontally so PnL stays readable without hiding behind MENU */
@media (max-width: 860px) {
  .site-header {
    height: auto;
    flex-wrap: wrap;
    gap: 8px;
  }
  .brand { order: 1; height: var(--header-h); }
  .header-right { order: 2; margin-left: auto; height: var(--header-h); }
  .header-live {
    order: 3;
    flex: 1 1 100%;
    width: 100%;
    /* it is a link to /book/ — 44px is the floor for a tap target */
    min-height: 44px;
    gap: 10px;
    padding: 0 10px;
  }
  .header-live-tag {
    font-size: 10px;
    letter-spacing: 0.06em;
    padding-right: 10px;
  }
  .header-live-rows {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
  }
  .hl-sym { font-size: 15px; }
  .hl-spark { width: 40px; }
}

.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;
}

.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 live strip, two CTAs
   and the menu button before <main id="top">; 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. */
.skip-link {
  position: fixed;
  z-index: 200;
  left: var(--pad);
  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;
}
.skip-link:focus { top: 14px; }

.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 live-position strip wraps the header to a second row. The
     fallback keeps a sane value if the script never runs. */
  padding-top: var(--header-h-live, var(--header-h));
}

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;
}

.hero-title {
  position: relative;
  z-index: 2;
  font-family: var(--display);
  font-size: clamp(88px, 19vw, 300px);
  line-height: 0.86;
  /* Display type this large has to be TIGHTENED or it reads as a default —
     the loose, template feel you notice before you can name it. Anton is
     already condensed, so a small negative is plenty; em keeps it
     proportional across the whole clamp, mobile included. */
  letter-spacing: -0.015em;
  display: flex;
  flex-direction: column;
  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 */
  --c-x: 0px;
  --c-y: 0px;
  --m-x: 0px;
  --m-y: 0px;
  --y-x: 0px;
  --y-y: 0px;
  --plate-a: 0;
}

@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) {
  .hero-title {
    --c-x: 0px; --c-y: 0px;
    --m-x: 0px; --m-y: 0px;
    --y-x: 0px; --y-y: 0px;
    --plate-a: 0;
  }
}

/* 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 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. */
.lede-note {
  font-family: var(--mono);
  font-size: clamp(12px, 0.95vw, 14px);
  font-weight: 600;
  line-height: 1.7;
  color: var(--muted);
  max-width: 46ch;
  margin-top: clamp(14px, 1.6vw, 22px);
  position: relative;
  z-index: 13;
}

.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;
}


.hero-dice {
  position: absolute;
  top: 14%;
  right: 6%;
  width: clamp(130px, 16vw, 220px);
  color: var(--ink);
  background: transparent;
  --rest-rotate: 4deg;
  transform: translate(var(--drag-x, 0px), var(--drag-y, 0px)) rotate(var(--rest-rotate));
}


.hero-filigree {
  position: absolute;
  bottom: 22%;
  left: 36%;
  width: clamp(160px, 22vw, 320px);
  color: var(--ink);
  opacity: 0.18;
  transform: rotate(-6deg);
  pointer-events: none;
}

.hero-filigree--2 {
  top: 8%;
  bottom: auto;
  left: auto;
  right: 22%;
  width: clamp(120px, 16vw, 240px);
  opacity: 0.14;
  transform: rotate(12deg);
}

@media (max-width: 860px) {

  .hero-dice { width: clamp(110px, 30vw, 150px); top: 12%; right: 2%; }

  .hero-filigree { opacity: 0.12; width: min(55vw, 200px); left: 30%; }
  .hero-filigree--2 { display: none; }
}

/* 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. */
.hero-awake-seal {
  position: absolute;
  /* Anton A: counter is the triangle above the crossbar, centered in A */
  left: 0.28em;
  top: 0.04em;
  height: 0.38em;
  width: auto;
  z-index: 2;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
}

/* keep pavé sparkle, kill rectangular drop-shadow ghosts */
.hero-awake-seal.iced {
  filter:
    contrast(1.1) brightness(1.05) saturate(1.1);
}

@media (max-width: 860px) {
  .hero-awake-seal {
    left: 0.32em;
    top: 0.02em;
    height: 0.44em;
  }
}

/* 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 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 { align-self: flex-start; font-size: 15px; box-shadow: 5px 5px 0 var(--ink); }
.btn-split > span { padding: 14px 18px; }
.btn-split .btn-arrow { padding: 14px 16px; }
.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: #33302b;
}

.about-chips { margin-top: 8px; }

.about-chips .chip {
  font-size: 13px;
  letter-spacing: 0.1em;
}

/* ---------- 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);
  background-image: repeating-linear-gradient(
    45deg,
    var(--ink) 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) */
.ticker-cell--taped { overflow: hidden; }

.site-tape {
  position: absolute;
  z-index: 6;
  top: 44%;
  left: -6%;
  width: 112%;
  padding: 11px 0;
  transform: rotate(-5.5deg);
  background: var(--ink);
  pointer-events: none;
  box-shadow: 0 4px 0 rgba(20, 19, 18, 0.2);
}

.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; }

.site-tape-text {
  display: block;
  text-align: center;
  white-space: nowrap;
  font-family: var(--font);
  font-size: clamp(12px, 1.3vw, 15px);
  font-weight: 800;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--paper);
}

/* 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;
  overflow: hidden;
  background: var(--paper);
}

.ticker-track {
  display: flex;
  gap: clamp(16px, 2vw, 32px);
  padding: 12px 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;
  width: 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 */
.ticker-end {
  flex: none;
  align-self: stretch;
  width: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-left: 2px dashed var(--ink);
  margin-left: 4px;
}
.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);
}

.ticker-name { padding-right: 40px; }

.ticker-arrow {
  position: absolute;
  top: 14px;
  right: 16px;
  font-size: 26px;
}

.ticker-status {
  display: inline-block;
  vertical-align: middle;
  margin-left: 10px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  border: 1px solid var(--ink);
  padding: 3px 7px;
  transform: translateY(-4px);
}

.ticker-status--wip { border-style: dashed; }


/* ---- 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: #4a4640;
  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 */
.ticker-cell--organ {
  width: 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: #33302b;
  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; }
  .ticker-track { width: auto; flex-wrap: wrap; transform: none !important; gap: 14px; }
  .ticker-cell { width: 100%; max-width: none; }
}

/* ---------- hard lines ---------- */

.hardlines { padding-bottom: var(--pad); }

.hardline-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(24px, 3vw, 40px);
  padding: 20px var(--pad) 8px;
}

.hardline {
  position: relative;
  border: 2px solid var(--ink);
  background: var(--paper);
  box-shadow: 5px 5px 0 rgba(20, 19, 18, 0.9);
  padding: 26px 24px;
  min-height: 220px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.hardline:nth-child(odd) { transform: rotate(-0.6deg); }
.hardline:nth-child(even) { transform: rotate(0.5deg); }

.hardline h3 {
  font-family: var(--display);
  font-size: clamp(22px, 2.2vw, 34px);
  line-height: 1.05;
  letter-spacing: 0.01em;
}

.hardline p { font-size: 15px; font-weight: 600; line-height: 1.5; color: #33302b; }

.hardline-chips { padding: 18px var(--pad) 0; }

.tape-card { top: -12px; left: 50%; transform: translateX(-50%) rotate(-4deg); }

/* ---------- receipts ---------- */

.receipts { padding-bottom: calc(var(--pad) * 2); }

.receipt-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(24px, 3vw, 48px);
  padding: 8px var(--pad) 0;
}

.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: #33302b; }

.receipt-diary {
  background: var(--paper-deep);
  border: 1px dashed var(--ink);
  padding: 18px;
  max-width: 520px;
}

.receipt-diary p {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  line-height: 1.8;
  color: var(--ink);
}

.receipt-chip { font-family: var(--mono); font-size: 11px; }

/* ---------- footer ---------- */

.footer {
  position: relative;
  background: var(--ink);
  color: var(--paper);
  margin-top: 60px;
}

.footer .tear-top { background: var(--ink); transform: scaleY(-1); top: -17px; }

.footer a:hover { text-decoration: underline; text-underline-offset: 4px; }

.footer-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-bottom: 1px solid var(--paper);
}

.footer-col {
  padding: var(--pad) var(--pad) 26px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-size: 14px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.footer-col + .footer-col { border-left: 1px solid var(--paper); }

.footer-label { color: #a9a49a; margin-bottom: 8px; }

/* 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 {
  margin-top: 6px;
  align-self: flex-start;
  min-height: 24px;
  padding: 3px 10px;
  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); }

.footer-cta {
  margin: var(--pad);
  border: 2px solid var(--paper);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: 18px;
  font-size: clamp(15px, 1.6vw, 20px);
  font-weight: 800;
  text-transform: uppercase;
  transition: background 0.15s, color 0.15s;
}

@media (hover: hover) and (pointer: fine) {
  .footer-cta:hover { background: var(--paper); color: var(--ink); text-decoration: none !important; }
}
.footer-cta:focus-visible { background: var(--paper); color: var(--ink); text-decoration: none !important; }

/* 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; }
}

.footer-mark {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  padding: 0 var(--pad);
  flex-wrap: wrap;
}

.footer-seal {
  width: clamp(56px, 8vw, 96px);
  height: auto;
  margin: 0 auto 18px;
  mix-blend-mode: screen;
  flex-shrink: 0;
  opacity: 0.95;
}

.footer-wordmark {
  font-family: var(--display);
  font-size: clamp(48px, 13vw, 210px);
  line-height: 0.86;
  text-transform: uppercase;
}

/* the positioning line carries the close — display weight, paper ink,
   sitting under the SELF-FUNDED stamp while the seal owns the void */
.footer-desc {
  padding: 18px var(--pad) 0;
  font-family: var(--display);
  font-size: clamp(24px, 2.8vw, 40px);
  line-height: 1.05;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--paper);
}

.footer-fund {
  padding: 34px var(--pad) 0;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: #a9a49a;
}

.footer-legal {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  padding: 40px var(--pad) 24px;
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  color: #a9a49a;
  font-family: var(--mono);
}

/* legal row sits right, clear of the fixed GEN chip in the corner */
.footer-legal { justify-content: flex-end; }

/* ---------- responsive ---------- */

@media (max-width: 1100px) {
  .hardline-grid { grid-template-columns: 1fr; }
  .receipt-grid { grid-template-columns: 1fr; }
}

@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 */
  .btn-account { display: none; }

  .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;
  }
  .footer-cols { grid-template-columns: 1fr; }
  .footer-col + .footer-col { border-left: none; border-top: 1px solid var(--paper); }
  .hero-me { max-width: 70%; }
  .hero-caption { max-width: 260px; bottom: calc(var(--pad) + 96px); }

  .ticker-cell--organ { width: min(86vw, 400px); }
}

/* ---------- reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .hero-title { animation: none; }
  .btn, .footer-cta, .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,
.footer-cta: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 */
.ticker-track { counter-reset: cell; }
.ticker-cell { counter-increment: cell; }
.ticker-cell--organ .cell-name { padding-right: 40px; }
.ticker-cell::after {
  content: counter(cell, decimal-leading-zero);
  /* alt-text syntax hides the index from screen readers where supported */
  content: counter(cell, decimal-leading-zero) / "";
  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 */
.footer a:hover {
  text-decoration-color: var(--mint);
  text-decoration-thickness: 2px;
}

/* the CTA arrow picks up mint until the whole block inverts */
.footer-cta span:last-child { color: var(--mint); }
.footer-cta:hover span:last-child { color: inherit; }

/* SELF-FUNDED reads as a rubber stamp, not a caption */
.footer-fund {
  display: inline-block;
  margin: 34px var(--pad) 0;
  padding: 7px 13px;
  border: 2px solid #6b675f;
  color: #cfc9bd;
  letter-spacing: 0.14em;
  transform: rotate(-2.2deg);
}

/* ---------- 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;
  color: var(--muted);
  pointer-events: none;
}
.gen-chip[hidden] { display: none; }

/* teleprinter strike — fires from main.js ONLY on a real data change */
@media (prefers-reduced-motion: no-preference) {
  .hl-pnl.hl-strike { animation: hl-strike 0.38s steps(4, end); }
}
@keyframes hl-strike {
  0% { filter: contrast(2.4) brightness(0.4); transform: translateY(-1px); }
  55% { filter: contrast(1.5); transform: translateY(1px); }
  100% { filter: none; transform: none; }
}

/* a position leaving the book gets a beat on screen before the row set
   rebuilds under it, instead of just vanishing — the ::after label carries
   the state change in plain text so reduced-motion still reads it */
.hl-row--closing::after {
  content: " CLOSED";
  font-size: 9px;
  color: var(--muted);
  margin-left: 4px;
}
@media (prefers-reduced-motion: no-preference) {
  .hl-row--closing { animation: hl-row-close 0.4s ease-in both; }
}
@keyframes hl-row-close {
  to { opacity: 0.15; }
}

/* 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-heart-hero { left: 27%; bottom: 9%; width: 38px; transform: rotate(-9deg); }


.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; }

.doodle-heart-footer { left: 8%; bottom: 118px; width: 30px; transform: rotate(-11deg); }
.doodle-heart-footer path { stroke: rgba(236, 231, 219, 0.55); }

@media (max-width: 860px) {
  .doodle-heart-hero { left: 60%; bottom: 42%; width: 30px; }
  .doodle-hurry { left: 6%; top: 13%; width: 96px; }
}

/* ============================================================
   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; }

@media (max-width: 860px) {
  .doodle-heart-footer { left: 7%; bottom: 96px; }
}

/* 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;
}
.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 */
.gen-chip {
  opacity: calc(0.38 + 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 */
.plate-ghost {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 1;
  pointer-events: none;
  color: transparent;
  -webkit-text-stroke: 1.6px var(--ink);
  transform: translate(7px, 7px);
  opacity: 0.32;
}
.ghost-pad { visibility: hidden; }
@media (max-width: 699px) {
  .plate-ghost { display: none; }
}
/* 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. */
  :root { --cube-size: clamp(84px, 23vw, 120px); }
  .cube-persp--solid,
  .cube-persp--wire {
    left: auto;
    /* Keep real air here. The cube SPINS with scroll, so its projected width
       is not constant — at the face-diagonal it is ~1.4x its edge, and a
       right% tuned against one sampled rotation clips the lede at another
       (4% swept clean at 40px steps and still clipped by 3px at 30px steps).
       This value clears the widest projection, not the average one. */
    right: 2%;
    top: 63%;
  }
  /* .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. */


/* ---------- the footer becomes a stamp block ---------- */

/* It used to be a full-bleed ink band across the bottom of the sheet — the
   single largest opaque object on the page, and against the plate it read as
   the print stopping dead. Contained, bordered and offset it reads as what it
   always was: the stamp block at the foot of the proof. */
.footer {
  background: var(--ink);
  border: 2px solid var(--ink);
  box-shadow: 0 0 0 4px var(--mint), 8px 8px 0 var(--ink);
  max-width: min(1100px, calc(100% - var(--pad) * 2));
  margin: clamp(60px, 9vw, 120px) auto 0;
}
/* the torn top edge belonged to a full-bleed band; on a bordered slab it
   printed a paper notch floating outside the box */
.footer .tear-top { display: none; }

/* 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.
   ============================================================ */

/* The three contact links in the ink footer measured 19px tall — they are
   inline text in a flex column, so their box was the line box and nothing
   more. On a phone that is a third of a fingertip. Padding rather than a
   min-height on the anchor alone, so the hit area grows around the ink
   instead of leaving the text floating at the top of a taller box.
   The negative inline margin keeps the text visually flush with the column's
   left rule — the target grows, the layout does not move. */
.footer-col a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 4px 6px;
  margin-inline: -6px;
}

/* 24px -> 44px. A copy button is the one control people jab at repeatedly
   when the first tap misses, so it is worth the height. */
.copy-email-btn {
  min-height: 44px;
  padding: 8px 14px;
}

/* 42px was within rounding of the floor but still under it, and the skip link
   is the first thing a keyboard or screen-reader user lands on. */
.skip-link { min-height: 44px; display: inline-flex; align-items: center; }
