/* =========================================================================
   terminal.css — THE HOUSE LOOK. The fleet's default design language.

   Loaded on every page of every owner, after site.css, after the atmosphere
   layer, after the page's own sheet, and immediately BEFORE the owner's
   optional overlay. See webcore/assemble.py::HOUSE_STYLE_HREF for why it is a
   layer rather than an edit to site.css, and README_FRONTEND.md for the whole
   contract.

   THE METAPHOR: THE SITE IS A CONSOLE INTO THE ISLAND'S REGISTER.
   You are logged in. The page opens on a shell prompt. Every panel is a
   stamped register entry rather than a product card, every heading stands on a
   ruled line, every reading is a mono number with a dot leader running to its
   label, and the whole document sits behind one scan grille instead of each
   surface wearing its own texture. Headings, labels, controls and numbers are
   mono; only running prose is sans. That inversion is the loudest thing here
   and it is what makes a page read as an instrument rather than a landing page.

   ★★★★★ THIS SHEET IS OWNER-NEUTRAL AND MUST STAY THAT WAY.
   It holds no colour literal, no owner name, no owner asset and no owner copy.
   Every value is a role token from webcore/theme.py or a color-mix() of one, so
   the owner's `theme` block supplies the PALETTE and the BRAND and this sheet
   supplies the STRUCTURE. The one string that has to differ per island — the
   shell prompt — arrives as `--prompt`, which assemble.py derives from
   owner.domain. That split is the whole reason this is safe to ship to every
   island at once: without it the house look would be one island's identity
   installed on everybody else's site, which is exactly the failure the fleet's
   donor-literal gate exists to catch. `run_all.py::TOKEN_SHEETS` scans this
   file for colour literals in source AND in every assembled build.

   ★ THIS SHEET NAMES NO ISLAND, INCLUDING IN ITS COMMENTS, AND THAT IS A RULE
   RATHER THAN AN OVERSIGHT. It is copied verbatim to every customer's box as
   plaintext, so a comment mentioning some other island is that island's name
   delivered to a stranger — the same failure the fleet's donor-literal gate and
   the shared-engine scrub exist to catch, just wearing a comment marker.
   THE DESIGN HISTORY, THE PRIOR LAW THIS SUPERSEDES, THE DATE AND THE OWNER'S
   OWN WORDS LIVE IN `webcore/README_FRONTEND.md`, under "The house look".
   That file stays in the repo and never ships. Read it before changing the
   metaphor here: there is a decision behind it, not an accident.
   Nothing is copied from any other tree into this one — no sheet imported, no
   font served that this core does not already vendor, no asset, no global, no
   record-id prefix, no palette. What is carried is the grammar.

   ★ WHAT THIS SHEET DELIBERATELY DOES NOT DO.
   - It adds NO JavaScript. The two motion signatures it does carry (the boot
     type-on and the cursor) are CSS, gated twice. A decode-glyph scramble and a
     live shell would both need JS that mutates text content, which is the
     highest-risk thing that could be shipped to every island at once — it would
     fight i18n.js's re-render and read text to a screen reader mid-scramble.
     OWED, not silently dropped: see README_FRONTEND.md.
   - It NEVER hides content by default. A reveal that starts at opacity 0 and
     waits for JS is a permanently blank section for anyone whose JS did not
     run — and it is exactly what makes a full-page screenshot lie. Every rule
     here has the FINISHED state as its resting state.
   - It never redefines `--glow`. In this core that token is a SHADOW, spread
     with `box-shadow: var(--glow)`. Redefining it as a colour breaks every
     shared sheet that consumes it.

   RAISE THE SELECTOR, NEVER !important. Loading late only wins TIES. The two
   shared rules that out-specify a bare selector are matched at their own shape
   below: backdrop.css's `.night[data-atmos] ~ main` (which is why the register
   ground is `main::before` and not a competing background-image on `main`),
   and cinema.css's `:is(.page-head, .sec-head:has(> .page-h1))`.
   ========================================================================= */

/* -------------------------------------------------------------------------
   1. THE HOUSE CONSTANTS
   Namespaced --tm-* so nothing here can ever collide with a theme.py role name
   or be mistaken for one an owner is allowed to pin.
   ------------------------------------------------------------------------- */
:root {
  --tm-tick: 11px;              /* the registration mark on a panel corner */
  --tm-tick-w: 1px;
  --tm-track: .22em;            /* the micro-label tracking, everywhere */
  --tm-hair: color-mix(in srgb, var(--edge-lit) 22%, transparent);
  --tm-hair-soft: color-mix(in srgb, var(--edge-lit) 12%, transparent);
  --tm-hot: color-mix(in srgb, var(--accent) 62%, transparent);
  --tm-warm: color-mix(in srgb, var(--accent) 26%, transparent);
  --tm-faint: color-mix(in srgb, var(--accent) 9%, transparent);
  /* THE GROUND'S LIGHT. Not a rule strength any more - see section 2, THE
     GROUND CARRIES NO REPEATING GEOMETRY. This is the single soft lift the
     page is lit by, and it is a TONE: a percentage of the lit edge mixed into
     nothing, spent through one large radial with no edges in it. */
  --tm-lift: color-mix(in srgb, var(--edge-lit) 7%, transparent);
  --tm-lift-soft: color-mix(in srgb, var(--edge-lit) 3%, transparent);
  /* the panel face: lit at the top, sinking toward the ground */
  --tm-face: linear-gradient(180deg, var(--surface-2), var(--surface));

  /* ★★★★★ THE INTERIOR HEADING SCALE — FOUR STEPS, AND THAT IS THE WHOLE SET.
     `--fs-1..6` (theme.py) is the PAGE scale: the island's name, a page title,
     a section head. It was never a scale for the headings INSIDE a panel, so
     every page sheet picked its own number for those, and a scan of the nine
     page sheets on 2026-08-13 found NINETEEN distinct sizes:

       10 · 10.5 · 11 · 12 · 12.4 · 12.5 · 13 · 13.5 · 14 · 15.5 · 15.6 ·
       16.5 · 17 · 17.5 · 18 · 19 · 20 · 21 · clamp(19,2.6vw,24)

     Nobody chose nineteen. Each was one author picking a number that looked
     right in one panel, and the drift is the sum. The fix is not to correct
     nineteen numbers - it is to make the number unpickable: a page asks for a
     ROLE and the role has a size, so the next panel head is 12.5px because
     that is what a panel head is, not because somebody measured one.
     `test_house_look.py::NoPageSheetInventsAHeadingSize` refuses a raw
     font-size on a heading in a page sheet. */
  --tm-h-micro: 10.5px;   /* a tracked label over a reading */
  --tm-h-panel: 12.5px;   /* the head of a panel */
  --tm-h-item: 14px;      /* the head of a row, a tile, a list entry */
  --tm-h-sec: clamp(17px, 2.2vw, 23px);   /* a heading leading a whole panel */

  /* site.css's tooled grain is a PAPER texture on a card FACE. It is a
     material on a panel, not a pattern on the page, and it stays - but at the
     grain's own job rather than as a second grille: a vertical lift across the
     face, no repeat, so a panel is lit like a plate instead of ruled like a
     sheet. See section 2 for why nothing on this site repeats any more. */
  --grain: linear-gradient(180deg,
             color-mix(in srgb, var(--edge-lit) 4%, transparent),
             transparent 42%);
  /* The accent is spent as a MARK. A gradient that fades to nothing at label
     width reads as a smudge on a ruled ground. */
  --rule-accent: linear-gradient(90deg, var(--accent), var(--accent));
}

/* -------------------------------------------------------------------------
   2. THE GROUND — a register sheet under a scan grille
   ------------------------------------------------------------------------- */
main { position: relative; }

/* THE REGISTER GROUND. `main::before`, not a background-image on `main`,
   because backdrop.css's `.night[data-atmos] ~ main` is (0,2,1) and would eat a
   bare `main` declaration the moment the atmosphere engine took over. A
   negative z-index keeps it behind every child WITHOUT `isolation`, which would
   create a stacking context and trap the shop / auction / vault dialogs under
   the nav. */
/* ★★★★★ THE GROUND CARRIES NO REPEATING GEOMETRY. THIS IS A RULE, NOT A ROUND
   OF TASTE, AND IT IS PINNED BY A TEST.

   THE HISTORY, because a later reader will otherwise re-add what was removed:
   this ground was a ruled sheet twice. Round one was a single 34px grid at one
   stroke, edge to edge — owner, 2026-08-13: "can we make the background better
   it looks horrible". Round two answered that with ARCHITECTURE: a major rule
   every fourth cell, a minor between, a radial light instead of a linear fade.
   It was a better grid. The owner's second reading of it was
   "for background remove the square and lines thing".

   ★★★★★ THE LESSON, AND IT IS THE ONE WORTH CARRYING: THE FAULT WAS NEVER THE
   GRID'S STRENGTH, IT WAS THAT IT WAS A GRID. Round two treated "it looks
   horrible" as a question about contrast and cadence and answered it with a
   more careful pattern. A reader does not experience a page's background as a
   pattern with parameters; they experience it as a surface or as wallpaper,
   and every value in the parameter space of "ruled sheet" is wallpaper. So the
   second ruling is not "less of it" — there is no amount of graph paper that
   is not graph paper, and softening it a third time is the same mistake a
   third time. `test_house_look.py::TheGroundIsNeverRuled` fails if any shipped
   sheet paints a repeat onto the page ground again.

   ★ AND THE LAYER UNDER IT WAS ALWAYS THE GOOD ONE. backdrop.css paints a
   genuinely deep bed with no JavaScript at all, at z-index -2: a lit reading
   pool at 26%/37%, a floor that closes on the owner's colour, an off-centre
   vignette, the owner's own artwork, and the theme's --backdrop-layers. Plus
   `main`'s scrim ramp, restated in that same sheet. ANY full-bleed pattern at
   z-index -1 sits on top of all of it and averages it out — the most expensive
   layer on the page erased by the cheapest one. Deleting the pattern is
   therefore not a subtraction; it is what lets the bed reach the reader.

   WHAT IS HERE INSTEAD: depth from LIGHT AND TONE. One large radial, anchored
   off-canvas above the left shoulder — the same place backdrop.css puts its
   reading pool, so the two layers agree about where the page is lit rather
   than arguing — and one long vertical settle toward the foot. No edges, no
   period, nothing that can be counted. Every value is a role token, nothing
   animates, and there is no JavaScript. */
main::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    radial-gradient(128% 104% at 18% -12%,
      var(--tm-lift) 0%,
      var(--tm-lift-soft) 42%,
      transparent 78%),
    linear-gradient(180deg,
      transparent 0 34%,
      color-mix(in srgb, var(--bg-deep) 34%, transparent) 100%);
}

/* THE FRAME. Fixed, inert, and above everything, so the page closes on a
   corner at every scroll position whatever is behind it. One composited layer
   that never repaints.

   ★ THE SCAN GRILLE THAT USED TO RIDE HERE IS GONE, and it went with the ruled
     ground above rather than as a separate decision. It was a 1px/3px repeat
     at ~4% of the lit edge painted over the WHOLE DOCUMENT — a second piece of
     repeating geometry on the page background, differing from the graph paper
     only in having one axis instead of two. The ruling was "remove the square
     and lines thing"; keeping a full-screen line pattern because it is faint
     is the negotiation that rule exists to refuse. What the grille was FOR —
     making a panel read as a lit surface rather than a flat fill — is now done
     by `--grain` on the panel face, where a material belongs, and by the light
     on the ground below. Same job, no pattern.

   WHAT REMAINS IS THE VIGNETTE, and it is light rather than geometry. A frame
   with no corners is a slab whatever is painted inside it. backdrop.css has a
   vignette of its own but it lives at z-index -2 under the artwork and the
   ground, so on a page with either it barely reaches the reader; this one is
   the outermost layer on the site.

   ★ IT IS DELIBERATELY WEAK AND IT STARTS LATE. This layer is ABOVE the
     content, so anything it does, it does to the text as well. Transparent
     through the middle 62% and reaching only ~24% of the deep ground in the
     corners: the gutter is what actually darkens, and no card at any width
     reaches far enough into a corner to lose contrast. Measured after the
     change rather than assumed — the body/ground pair is still 16.43:1, and
     dropping the grille can only have raised it.

   Above --z-skip (200) so it covers the skip link too; pointer-events:none so
   it can never take a click. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 400;
  pointer-events: none;
  background:
    radial-gradient(128% 112% at 50% 34%,
      transparent 0 62%,
      color-mix(in srgb, var(--bg-deep) 24%, transparent) 100%);
}

/* =========================================================================
   2b. THE MEASURE — how much air the page is allowed.
   -------------------------------------------------------------------------
   Owner, 2026-08-13: "so much is missing here ... like too compact. fix."

   TWO DIFFERENT FAULTS WORE THAT ONE SENTENCE and only one of them was
   spacing. The first was literal — three destinations were being cut out of
   the navigation, and "missing" meant missing (cinema.css, THE NAVIGATION MUST
   NEVER BE A SCROLL CONTAINER). This block is the second one.

   ★ THE SHAPE OF THE PROBLEM, MEASURED BEFORE ANY VALUE WAS CHANGED. The shell
   sheet's rhythm is a FIXED 64px between sections at every width from 390 to
   2560, a flat 21px inside every card, and a 26px drop under every section
   head. Fixed values behave in opposite directions as the window grows: 64px
   between two sections is generous on a phone and tight on a 1440 desktop,
   because the amount of PAGE either side of it has tripled while the gap has
   not. So a layout tuned on a laptop reads cramped on a monitor, which is
   exactly where he was looking.

   ★ THE RULE THIS FOLLOWS, and it is the one worth keeping: SEPARATION SCALES,
   PROXIMITY DOES NOT. The distance BETWEEN two unrelated things is what has to
   grow with the viewport; the distance between a label and the number it
   belongs to must never move, or the pair stops reading as a pair. So every
   value below that got a clamp is a separation, and every tight group in this
   sheet was left exactly where it was.

   ★ AND MORE ABOVE A HEADING THAN BELOW IT. A heading belongs to what follows
   it. The shell gives a section head 26px underneath and nothing on top, so
   every heading sat equidistant between the block it introduces and the block
   it has nothing to do with. The section rhythm now carries the space above,
   and the drop below is the smaller of the two.

   ★ WHAT IS NOT HERE, ON PURPOSE: the gutter and the reading measure. Those
   are `--gut` and `--maxw`, they come from the owner's own `theme.geometry`,
   and a sheet that hard-coded them would take that decision away from every
   island at once. Frontier Prime's proposal moves them together — gutter 24 to
   30, measure 1200 to 1140 — because a WIDER gutter with a NARROWER column is
   what makes a page feel roomy. Widening the column instead puts more words on
   every line and reads denser, which is the opposite of what he asked for.
   ========================================================================= */
.section {
  padding-block: clamp(58px, 6.4vw, 96px);
  padding-inline: 0;
}
/* The shell zeroes the top padding on a following section so two adjacent
   sections do not stack their gaps into a canyon. That decision stays; only
   the size of the single remaining gap moves. */
.section + .section { padding-top: 0; }

/* The head, and the air above it. `.section` already carries the space between
   blocks; this is the extra beat a heading needs so it reads as belonging to
   what comes after rather than floating between two things. */
.sec-head { margin-bottom: clamp(26px, 2.4vw, 38px); }
.sec-head p { margin-top: 12px; line-height: 1.7; max-width: 64ch; }

/* THE PANEL. 21px of padding on a card whose corners are 2px reads as a box
   drawn tight around its contents; a register entry wants a margin inside its
   own rule. It scales a little with the window for the same reason the section
   rhythm does, and stops at 30 — past that the card starts to read as empty. */
.card, .feat, .gate, .cta-card, .notice {
  padding: clamp(22px, 1.8vw, 30px);
}
.card h3, .feat h3, .gate h3 { margin-bottom: 12px; }

/* RUNNING PROSE. The shell sets the document at 1.65, which is right for a
   sans at a 62ch measure and slightly tight for the sizes these blocks
   actually run at. 1.72 is the step that opens a paragraph without turning it
   into a list of lines. ONLY running prose: mono labels and readings keep
   their own tight leading, because a tracked micro-label at 1.72 stops
   belonging to the number under it. */
.card p, .feat p, .gate p, .notice p, .steps li, .join-hint {
  line-height: 1.72;
}

/* -------------------------------------------------------------------------
   3. TYPE — mono is the voice, sans is the prose
   ------------------------------------------------------------------------- */
h1, h2, h3, h4,
.page-h1, .page-h2, .page-h3, .sec-head h2, .hero h1,
.card h3, .cta-card h2, .feat h3, .gate h3,
.admin-panel-head, .studio-panel-label, .stage-head,
.cas-card-name, .vp-panel-h {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -.005em;
}

.hero h1, .page-h1 {
  letter-spacing: -.012em;
  line-height: 1.02;
  text-shadow: none;
  text-wrap: balance;
}

/* A mono face runs ~15-20% wider per character than the sans these steps were
   drawn for, so the display scale comes down or a long island name clears the
   gutter on a phone. Measured at 390px, not assumed. */
.hero h1 { font-size: clamp(30px, 5.1vw, calc(var(--fs-6) * .86)); }
.page-h1 { font-size: clamp(24px, 3.6vw, calc(var(--fs-6) * .56)); }
.sec-head h2 { font-size: clamp(20px, 2.6vw, calc(var(--fs-5) * .86)); }
.page-h2 { font-size: clamp(17px, 2.2vw, 23px); }

/* Prose keeps the sans and a real reading measure. */
.hero .lede, .sec-head p, .card p, .feat p, .gate p, .status-note,
.steps li, .join-hint, .admin-hint, .admin-note, .notice {
  font-family: var(--font-ui);
}
/* Opened with the rest of the running prose (section 2b). The MEASURE
   opens with it: 46ch was set when the leading was 1.66, and a line that
   is both short and widely leaded reads as a stack of fragments. */
.hero .lede { font-size: 16.5px; line-height: 1.72; max-width: 52ch; text-shadow: none; }

/* THE MICRO-LABEL FAMILY. One rule owns every small tracked label on the site,
   so a surface added later inherits the voice instead of inventing one. */
.eyebrow, .status-label, .status-src, .fig span, .cas-label, .cas-chip, .cas-card-sub, .cas-card-meta, .iw-bank-k, .iw-state,
.vp-sub, .vp-note, .stage-foot-k, .gr-label, .admin-hint, .foot-legal,
.nav-pts-suffix, .save-note, .au-err, .studio-panel-label, .ro-k {
  font-family: var(--font-mono);
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
  font-size: 10.5px;
  line-height: 1.5;
}

/* Numbers are readings: tabular, mono, and never smaller than their label. */
.fig b, .num, .iw-bank-v, .vp-nums, .vp-price, .nav-pts, .cas-chip-bal,
.gr-readout, .admin-odds, .mult-val, .vp-slider-val, .ro-v, .lb-v {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
  font-weight: 500;
}
.fig b { font-size: 27px; line-height: 1.04; color: var(--fg); }

/* secondary prose: quieter, still sans, still a sentence */
.dim { color: var(--fg-mute); }
.join-hint, .status-note { font-size: 13.5px; color: var(--fg-mute); }

/* -------------------------------------------------------------------------
   4. THE REGISTER LINE — what a heading stands on
   A stamp, a label, and a rule that runs out to the margin.
   ------------------------------------------------------------------------- */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--accent);
  font-weight: 500;
  margin-bottom: 12px;
}
/* THE REGISTRATION MARK: a hollow square turned on its corner. Hollow because
   a register slot is empty until something is entered against it. Drawn with
   borders and not a glyph, so no missing font can break it and no screen
   reader reads it aloud — which is also why site.css's own rule for this
   pseudo-element could not simply be recoloured. */
.eyebrow::before {
  content: "";
  width: 7px;
  height: 7px;
  flex: none;
  border: 1px solid var(--accent);
  border-radius: 0;
  background: none;
  transform: rotate(45deg);
}
.eyebrow::after {
  content: "";
  flex: 1 1 auto;
  min-width: 0;
  height: 1px;
  background: linear-gradient(90deg, var(--tm-hair), transparent);
}

/* THE PAGE HEAD, matched at cinema.css's own selector shape — the one that
   out-specifies a bare `.sec-head` on every page that has an h1. */
:is(.page-head, .sec-head:has(> .page-h1)) {
  border-bottom: 1px solid var(--tm-hair);
  padding-bottom: clamp(12px, 1.3vw, 17px);
}
/* the accent segment becomes a measured tick ladder: a ruled edge, not a bar */
:is(.page-head, .sec-head:has(> .page-h1))::after {
  width: clamp(88px, 12vw, 150px);
  height: 3px;
  border-radius: 0;
  background:
    repeating-linear-gradient(90deg, var(--accent) 0 1px, transparent 1px 7px),
    linear-gradient(90deg, var(--tm-warm), transparent);
}
/* cinema.css pools the head in a soft radial of the owner's accent. On a ruled
   ground that reads as a smudge, so it becomes the sheet's own margin rule: a
   vertical hairline at the gutter, the way a field register is bound. */
:is(.page-head, .sec-head:has(> .page-h1))::before {
  left: 0;
  right: auto;
  width: 1px;
  background: linear-gradient(180deg, transparent, var(--tm-warm) 30%, transparent);
  opacity: .9;
}
.sec-head:not(.page-head):not(:has(> .page-h1)) { border-bottom-color: var(--tm-hair-soft); }

/* -------------------------------------------------------------------------
   5. THE HERO — a session, opened
   ------------------------------------------------------------------------- */
.hero { padding: clamp(38px, 6vh, 66px) 0 clamp(14px, 3vh, 26px); }

/* THE SHELL PROMPT. It rides the kicker and not the h1 deliberately: a
   pseudo-element's text IS exposed to assistive technology, and the h1 is the
   page's first heading — putting the prompt there would make the island's name
   announce as "example.com tilde dollar Example". On the kicker, which is a
   decorative tag either way, the cost is one extra token in a line that is
   already a label. `--prompt` is derived by assemble.py from owner.domain, so
   this sheet names no island. */
.hero .kicker {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 9px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
  color: var(--fg-dim);
  margin-bottom: 15px;
}
/* ★ THIS PSEUDO-ELEMENT ARRIVES WITH FOUR PROPERTIES ALREADY ON IT and every
   one of them has to be undone by name, or the prompt is TEXT DRAWN INSIDE A
   54x2px ACCENT BAR - which is exactly what shipped in the first round and what
   the scrolled screenshot caught. site.css gives it `width:34px; height:2px;
   border-radius; background: var(--rule-accent)` (it was a rule segment, not a
   label) and cinema.css then re-pins `width: clamp(28px, 4vw, 54px)`. A fixed
   width on a text pseudo does not clip - it overflows and paints over the
   flex item beside it. Undo the box, then set the type. */
.hero .kicker::before {
  content: var(--prompt, "~$");
  width: auto;
  height: auto;
  border-radius: 0;
  background: none;
  color: var(--accent);
  letter-spacing: 0;
  text-transform: none;
  /* ★ flex:none is not enough. A flex item's automatic minimum size is
     min-content, and min-content on a hyphen-bearing domain is one SEGMENT of
     it - so `example-site.com:~$` broke after the hyphen, wrapped under itself
     and the kicker text ran up beside the remainder. Seen at 1440, not
     inferred. `flex: 0 0 auto` fixes the sizing and `white-space: nowrap`
     fixes the break; both are needed. */
  flex: 0 0 auto;
  white-space: nowrap;
}
/* and the label after it takes the rest of the row rather than sharing it */
.hero .kicker { row-gap: 4px; }

/* THE CURSOR. `content: ""` — a drawn box, not a glyph, so it announces
   nothing, cannot inherit a missing character, and needs no font to be loaded
   before it is right. */
.hero h1::after {
  content: "";
  display: inline-block;
  vertical-align: baseline;
  width: .52em;
  height: .78em;
  margin-left: .16em;
  background: var(--accent);
  translate: 0 .04em;
}

.hero .lede { margin-top: 19px; }
.hero-cta { margin-top: clamp(22px, 3vh, 32px); gap: 10px; }

/* -------------------------------------------------------------------------
   6. THE RECORD — every panel is a stamped entry
   Two OPPOSED corner ticks, not four: a register is registered top-left and
   bottom-right, and two marks read as a registration where four read as a
   decorative frame.
   ------------------------------------------------------------------------- */
.card, .status, .feat, .notice, .cta-card, .panel, .gate,
.cas-card, .cas-panel, .studio-card, .admin-panel, .au-dialog-card,
.buy-panel, .vp-instrument, .iw-console, .step {
  position: relative;
  border-radius: var(--r);
  background-image: var(--grain), var(--tm-face);
  border-color: var(--tm-hair);
}

/* site.css draws a chiselled highlight inset from each end of the top edge. On
   a screen there is no bevel to catch light, so the top edge becomes the
   record's own rule: full bleed, one weight, accent at the head. */
.card::before, .status::before {
  left: 0;
  right: 0;
  top: -1px;
  height: 1px;
  background: linear-gradient(90deg, var(--tm-hot) 0 46px, var(--tm-hair) 46px);
}

.card::after, .status::after, .feat::after, .cas-card::after,
.studio-card::after, .step::after {
  content: "";
  position: absolute;
  right: -1px;
  bottom: -1px;
  width: var(--tm-tick);
  height: var(--tm-tick);
  pointer-events: none;
  border-right: var(--tm-tick-w) solid var(--tm-warm);
  border-bottom: var(--tm-tick-w) solid var(--tm-warm);
  transition: border-color .16s ease;
}
.feat::before {
  content: "";
  position: absolute;
  left: -1px;
  top: -1px;
  width: var(--tm-tick);
  height: var(--tm-tick);
  pointer-events: none;
  border-left: var(--tm-tick-w) solid var(--tm-warm);
  border-top: var(--tm-tick-w) solid var(--tm-warm);
  transition: border-color .16s ease;
}
.card:hover::after, .feat:hover::after, .feat:hover::before,
.cas-card:hover::after, .studio-card:hover::after { border-color: var(--accent); }

/* THE TILE. site.css gives it a filled icon well and a lifting hover; here it
   is a register line with a hairline gutter for its mark. */
.feat {
  border-radius: var(--r);
  padding: 17px 18px;
  gap: 14px;
  transition: border-color .18s ease, background-color .18s ease;
}
.feat:hover { transform: none; border-color: var(--tm-hot); }
.feat-ico {
  border-radius: 0;
  background: none;
  border: 1px solid var(--tm-hair);
  box-shadow: none;
  color: var(--accent);
  width: 32px;
  height: 32px;
}
.feat h3 { font-size: 13.5px; letter-spacing: .01em; }
.feat p { font-size: 13.5px; color: var(--fg-dim); }

/* =========================================================================
   6b. THE SURFACE REGISTER — the one list of what a surface IS.
   -------------------------------------------------------------------------
   Owner, 2026-08-13: "some tabs uis dont match at all ... make them all
   match. then update framework so that never happens again."

   ★★★★★ THE DIAGNOSIS, BECAUSE THE FIX ONLY MAKES SENSE WITH IT.
   Eleven surfaces drifted apart and NOT ONE of them was restyled on purpose.
   They drifted because a page could not CONSUME a record — it could only
   BUILD one. `shop.css` wrote `.item { background; border: 1px solid
   var(--border); border-radius: var(--r); box-shadow: var(--shadow) }`,
   `vault.css` wrote the same four lines for `.slot`, `studio.css` for
   `.glitch-tile`, `livecard.css` for `.lvc-card`, `casino.css` for `.cas-side`
   — five sheets, written months apart, each answering "what does a box look
   like" from scratch. Every one of them was locally reasonable. Together they
   were five design systems.

   ★ SO THE FIX IS NOT FIVE EDITS, IT IS THIS LIST. Section 6 already said
   what a record looks like; it just did not know these classes existed. Every
   card-like surface on the site is named ONCE, here, and takes the record
   grammar from the same place — which means a page sheet no longer HAS a
   reason to describe a surface, and `test_house_look.py` refuses it if one
   tries. Adding a page is adding a name to this list, which is a one-line
   diff that cannot invent a second look.

   ★ WHY A LIST AND NOT A `.card` CLASS IN THE MARKUP. Half of these elements
   are built by JavaScript that belongs to another hand, and a markup change
   that has to land in eleven renderers at once is a change that lands in ten.
   The list is CSS, it is in the sheet that already loads last on every page,
   and it works on markup nobody has to touch.

   ★ AND IT NAMES CLASSES THIS ISLAND MAY NOT HAVE. Every selector below
   belongs to a page an owner can decline to buy; a rule matching nothing
   costs nothing and the sheet stays owner-neutral.
   ========================================================================= */

/* THE RECORDS. Everything in this list is a stamped entry and is dressed by
   section 6: one hairline, one face, the house radius, no shadow. */
.item, .slot, .glitch-tile, .lvc-card, .cas-side, .cas-stage, .cas-panel,
.iw-bank, .iw-award, .deck, .designs, .presets, .channel-list, .stage-wrap,
.buy-picks, .mv-panel, .tbl, .crash-stage, .cardface, .cas-odds, .tbl-head,
.tbl-seat, .mine-tile, .chip-btn, .hand-total, .prize-row, .lvc-well,
.lvc-nest-box, .admin-recent, .admin-list, .admin-fields, .vp-keywell,
.vp-keybind, .cp-swatch, .supgrants-match,
/* The Bans tab (2026-08-17): a match row and the confirm card are stamped
   records like everything else in this list. Registered the day they were
   born, which is what this list is for. */
.bans-match, .bans-confirm {
  border-color: var(--tm-hair);
  box-shadow: none;
}
/* The Bans tab's name DROP-DOWN is the register's one floating overlay: it
   sits over page content, so it keeps an opaque ground and the panel lift -
   both spoken here, so the house look owns them like every plate above. */
.bans-matches {
  border-color: var(--tm-hair);
  box-shadow: var(--shadow-panel);
}
/* The four that carried a raised card's drop shadow. `box-shadow: none` above
   is the whole fix and it is repeated at the hover shape too, because a hover
   that re-adds a shadow re-adds the second design system one pointer at a
   time. */
.item:hover, .slot:hover, .glitch-tile:hover, .lvc-card:hover,
.cas-side:hover, .mine-tile:hover, .tbl-seat:hover {
  box-shadow: none;
  transform: none;
}

/* THE RADIUS. This site's radius is 2px. Anything that shipped its own — 8,
   10, 11, 12, 16, or a 999px stadium — is re-pinned here rather than in the
   sheet that wrote it, so the value lives in ONE place and a new page cannot
   pick a different one without this list noticing.
   ★ `--r-round` is deliberately NOT in this list: an avatar, a coin face and
     the wheel hub are circles because they are circles, not because somebody
     liked round corners. */
.item, .slot, .glitch-tile, .lvc-card, .cas-side, .cas-stage, .cas-stage-felt,
.iw-bank, .iw-award, .tbl, .tbl-head, .tbl-seat, .crash-stage, .cardface,
.mine-tile, .chip-btn, .cas-odds, .lvc-well, .lvc-nest-box, .mv-panel,
.admin-tab, .admin-tabs, .stage-growth, .slot-mut, .lvc-pill, .cas-chip,
.hand-total, .mv-stage, .prize-sw, .lvc-bar, .lvc-bar-fill, .vp-meter-track,
.vp-meter-fill {
  border-radius: var(--r-xs);
}
.item, .slot, .glitch-tile, .lvc-card, .cas-side, .cas-stage, .tbl,
.crash-stage, .mv-panel, .lvc-well { border-radius: var(--r); }

/* THE REST OF THE REGISTER. Sixty more surfaces that draw their own plate and
   that this sheet had never heard of — found by
   `test_house_look.py::EveryCardLikeSurfaceIsRegistered` rather than by
   reading, because "which classes exist" is exactly the question a human
   reviewer answers wrong. Grouped by WHAT THEY ARE, because that is the only
   thing that decides how they are dressed. */

/* records: a plate holding something */
.map-panel, .map-vitals, .vp-setup, .vp-setup-banner, .vp-dialog-head,
.vp-dialog-foot, .vp-meter-plate, .buy-dialog, .price-edit, .shopview,
.shopview-gate, .lvc-growth, .cas-note, .tbl-av, .deck-row, .design-row,
.aik, .coord, .map3d-tip, .tilt-lab, .tilt-wrap, .leaflet-tooltip,
.studio-tabs, .participant-row, .vp-log {
  border-color: var(--tm-hair);
  border-radius: var(--r);
  box-shadow: none;
}

/* ★ THE CARD FACE AND THE AUCTION HOUSE (added 2026-08-14 with
   `css/cards.css`). These surfaces existed for months and this register had
   never heard of them, because their CSS was not in a stylesheet at all - it
   sat in a `<style>` block inside `fragments/cards.html`. So the collection
   page dressed them and the AUCTION page, which calls the very same renderer,
   shipped them naked: on Jurassic Land's live board every lot was an unstyled
   div with a zero-height face. Moving the sheet out is half the fix; naming
   its plates here is the half that stops the next page drifting.
   `.cardt` takes the record grammar; `.cf` is the specimen plate inside it and
   keeps its own tighter radius, because a face cropped at the record's corner
   would lose the animal's own edge. */
.cardt, .au-wallet, .au-payout, .au-hist-row, .au-skel, .cardt-mut,
.au-dialog-card, .cf-stamp {
  border-color: var(--tm-hair);
  box-shadow: none;
}
.cardt, .au-wallet, .au-skel, .au-dialog-card { border-radius: var(--r); }
.cf, .au-payout, .au-hist-row, .cardt-mut { border-radius: var(--r-xs); }
.cardt:hover { box-shadow: none; transform: none; }

/* controls: a hairline, the accent on the active state, never a filled slab
   and never a white outline — section 7's rule, reaching the nineteen
   controls that were written before it existed. */
.mv-toggle, .vp-iconbtn, .vp-switch, .vp-select select, .participant-mute,
.design-load, .design-del, .iw-mute, .admin-pick, .cas-card-play,
.price-input, .corpse-size, .aik-bbtn, .aik-tg, .map-follow, .panel-x,
.leaflet-control-zoom a, .nav-util, .studio-tabs [role="tab"],
.vp-switch-range, .lvc-refresh, .gc-chip, .gc-num {
  border-color: var(--tm-hair);
  border-radius: var(--r-xs);
  box-shadow: none;
  font-family: var(--font-display);
  letter-spacing: .08em;
}
.mv-toggle:hover, .vp-iconbtn:hover, .vp-switch:hover, .design-load:hover,
.iw-mute:hover, .admin-pick:hover, .aik-bbtn:hover, .map-follow:hover,
.lvc-refresh:hover, .nav-util:hover {
  border-color: var(--accent);
  box-shadow: none;
}
.vp-switch[data-active="true"], .participant-mute[data-muted="true"],
.admin-pick[aria-pressed="true"], .aik-tg[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent-hi);
  background: var(--tm-faint);
}

/* ★ THE QUEST LEDGER (2026-08-17). The admin console's quest tab stopped being
   a table - a quest is a named thing with a sentence, a price and a switch, and
   a table gave each of those a column, which is how the row's ID ended up
   printed under its NAME. Its four plates are named here rather than dressed in
   admin.css, which is this register's whole point: the first draft of that
   panel drew stadium chips and a rounded phone-style switch and this file's
   suite refused six selectors by name.
   `.quest-editor` is the drawer that opens under a row; `.quest-empty` is the
   nothing-to-show block; `.qswitch-track` and `.qswitch-thumb` are the switch,
   which is SQUARE for the same reason the lamps are. */
.quest-editor, .quest-empty {
  border-color: var(--tm-hair);
  border-radius: var(--r);
  box-shadow: none;
}
.qswitch-track, .qswitch-thumb {
  border-radius: var(--r-xs);
  box-shadow: none;
}
.qswitch {
  border-radius: var(--r-xs);
  box-shadow: none;
  font-family: var(--font-display);
  letter-spacing: .08em;
}
.qswitch:hover { border-color: var(--accent); box-shadow: none; }

/* pills: section 12's grammar, reaching the nine that were not in its list */
.lvc-pill-live, .lvc-pill-elder, .lvc-pill-prime, .lvc-pill-plain,
.lvc-pill-stale, .lvc-prime, .vp-count, .vp-step-n, .cas-card-tag,
.quest-tag {
  border-radius: var(--r-xs);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
  border-color: var(--tm-hair);
  box-shadow: none;
}

/* THE LAMPS ARE SQUARE. Section 9 says so; `.mv-lamp` and `.lvc-dot` were
   written before that rule existed and were the only round pips left.
   ★ `.participant-avatar` and `.iw-hub` are NOT in this list and must not be
     added to it: an avatar and a wheel's hub are round because they are round
     objects, not because somebody preferred a soft corner. Section 9 says the
     same thing about `--r-round` and it is the same distinction. */
.mv-lamp, .lvc-dot, .lvc-lamp { border-radius: 0; }
.participant-avatar { border-color: var(--tm-hair); box-shadow: none; }
.iw-hub { border-color: var(--tm-hair); }

/* THE MICRO-LABELS. Section 3 owns this voice; these are the labels five
   later sheets declared in the UI sans with their own tracking, which is what
   made the live card and the admin rail read as a different product. */
.lvc-eyebrow, .lvc-sec, .lvc-bar-label, .lvc-specimen-note, .atg-label,
.mv-title, .mv-note, .item-tag, .shop-pill, .slot-facts dt, .tbl-fact,
.crash-tlab, .prize-name, .lvc-ring-label {
  font-family: var(--font-mono);
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
  font-size: 10.5px;
  line-height: 1.5;
}
/* and the readings beside them */
.mv-num, .lvc-num, .lvc-species-sub, .tbl-pot, .tbl-pay, .item-price,
.crash-x, .mult-x {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

/* THE PANEL HEAD. One label at the top of one panel, in one voice. Six sheets
   had six answers — the studio's were sentence-case sans at 19px, which is
   why that page read as a different site in the owner's own screenshot. */
/* ★ THE `h3` VARIANTS ARE LISTED SEPARATELY AND THAT IS NOT REDUNDANT.
   site.css colours every bare `h1,h2,h3` at (0,0,1), which BEATS inheritance
   — so `.stage-head { color: var(--accent) }` dressed the container and the
   heading inside it stayed the page's ink. Measured, not assumed: the
   studio's own three panel heads rendered "SPECIES" in the accent (a
   `.deck h3`, matched directly) and "COLOURS" and "PICK A SPECIES TO START"
   in `--fg` (both `.stage-head h3`, matched only through their parent) — one
   page, two colours, from one rule that looked complete. */
.admin-panel-head, .studio-panel-label, .vp-panel-h, .stage-head,
.lvc-head, .cas-card-name, .deck h3, .designs h3, .presets h3,
.channel-list h3, .mv-title, .iw-bank-k, .tbl-head,
.stage-head h3, .channels .stage-head h3, .admin-panel-head h3,
.vp-panel-h h3, .lvc-head h3 {
  font-family: var(--font-display);
  font-size: var(--tm-h-panel);
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent);
}

/* THE DISPLAY STEP INSIDE A PANEL. `.lvc-species` shipped its own clamp up to
   34px — a second page-title scale, on a card, inside a page that already has
   one. */
.lvc-species, .cas-card-name, .crash-x {
  font-size: clamp(17px, 2.2vw, 23px);
}
.crash-x { font-size: clamp(26px, 4vw, 40px); color: var(--accent-hi); }

/* -------------------------------------------------------------------------
   7. CONTROLS — a hairline and a chevron, never a filled pill
   ------------------------------------------------------------------------- */
.btn, .lva-btn, .gpause-btn, .cas-act, .cas-act-lg, .iw-spin, .vp-act,
.au-dialog-acts button, .buy-actions button {
  border-radius: var(--r-s);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 12.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  background-image: none;
  background-color: color-mix(in srgb, var(--accent) 4%, var(--surface));
  border-color: var(--tm-hair);
  box-shadow: none;
  transition: border-color .16s ease, background-color .16s ease, color .16s ease;
}
.btn::before {
  content: "\203A";
  color: var(--accent);
  font-weight: 700;
  line-height: 1;
  transform: translateY(-.04em);
}
.btn:hover {
  transform: none;
  color: var(--fg);
  border-color: var(--accent);
  background-color: color-mix(in srgb, var(--accent) 10%, var(--surface));
}
/* The loudest ordinary state on the site is a ~17% wash inside a hairline. A
   filled slab would be the only lit rectangle on a void and would pull every
   live reading on the page off its own colour — and on this look every colour
   that is not chrome is a reading. Exactly one control breaks this, on purpose,
   and it is the destructive one. See 7b.

   ★ IT IS BUILT FROM --primary, NOT --accent. Those resolve to the same colour
   for every owner who has not separated them, so nothing moves for them; an
   owner who HAS separated them gets the committing control in the committing
   colour rather than in the brand's. */
.btn-primary {
  background-image: none;
  background-color: color-mix(in srgb, var(--primary) 17%, var(--surface));
  border-color: var(--primary);
  color: var(--fg);
  font-weight: 600;
  box-shadow: none;
}
.btn-primary::before { color: var(--primary); }
.btn-primary:hover {
  background-color: color-mix(in srgb, var(--primary) 26%, var(--surface));
  border-color: var(--primary);
}
.btn-ghost { background-color: transparent; border-color: var(--tm-hair); }
.btn-ghost:hover { background-color: color-mix(in srgb, var(--accent) 7%, transparent); }
.btn[disabled], .btn[aria-disabled="true"] { border-color: var(--tm-hair-soft); }

/* ★ THE ONE COMPACT STEP, AND THE REASON IT HAD TO BE WRITTEN DOWN.
   A render of all eleven surfaces found THREE button sizes on the site:
   site.css's 12px/21px at 12.5px, `shop.css:239 .buy-actions .btn` at
   10px/18px and 13.5px, and `vault.css:87 .slot-act .btn` at 7px/13px. Two of
   them were page sheets solving the same problem — "this button sits in a
   small card and the default is too big" — on their own, months apart, at
   different answers. That is the drift in miniature, on the COMMITTING
   control, which section 7b says is the one control a player uses to spend.
   So the house look owns both sizes and the pages own neither. */
.btn-sm, .slot-act .btn, .buy-actions .btn, .lvc-act .btn {
  padding: 8px 14px;
  font-size: 11.5px;
  letter-spacing: .09em;
}

/* =========================================================================
   7b. TWO REDS — the rule that keeps "do it" apart from "destroy it".
   -------------------------------------------------------------------------
   ★★★★★ DO NOT WEAKEN ANY PART OF THIS WITHOUT READING THE MEASUREMENT.

   THE STAKE. This core ships a control that permanently kills a player's
   animal ("This kills your {species} straight away. It cannot be undone.") and
   another that releases a stored one. There is nothing behind either of them.

   THE PROBLEM. An owner's brand colour may be RED — one on this fleet now is —
   and red is also the universal colour of danger. So the brand and the alarm
   want the same pigment.

   ★★★★★ AND COLOUR CANNOT SOLVE IT. THIS IS ARITHMETIC, NOT TASTE.
   Both reds have to sit above the 3:1 status floor on a near-black ground to
   be legible at all, and that floor puts a hard bracket on how dark either can
   be. Measured through theme.py on the red island: brand #FF4D4D and danger
   #FF1F4B differ by 1.16:1 — relative luminance .2710 against .2275. Any two
   reds that both clear the floor land within roughly 1.5:1 of each other. For
   the ~8% of men with red-green colour blindness the two are indistinguishable
   outright. A "hotter" danger red is worth having and it is NOT a mechanism.

   THE RULE, THEREFORE, IS STRUCTURAL, AND ALL FOUR PARTS ARE LOAD-BEARING:

     1. THE BRAND RED NEVER FILLS A CONTROL. It is a MARK: rules, brackets, the
        current tab, headings' underline, hairlines, tracked micro-labels, icon
        strokes, the shell prompt. Thin, linear, on the ground. Section 7's
        "never a filled pill" is not a style preference any more — it is half
        of this safety rule, and every rule in this sheet obeys it.
     2. THE COMMITTING CONTROL IS --primary, NOT --accent. Buy, bet, sign in,
        park, redeem, travel. On an owner who separates them that is a
        different colour from the brand and from the danger both.
     3. DANGER IS THE ONLY THING ON THE SITE THAT IS A SOLID PLATE. Ink on
        colour, no hairline, no wash — deliberately breaking the one rule every
        other control on the page keeps. That inversion is what the eye catches
        before it reads anything, and it survives any colour blindness because
        it is a difference in SHAPE.
     4. IT ALWAYS CARRIES A MARK AND THE WORDS, AND IT IS ALWAYS SECOND. The
        triangle is drawn by the module that renders the button; the words are
        the owner's own copy ("Yes, kill it"); and it is never the first press —
        the rail is replaced by a written sentence naming the species first.

   ★ WHAT A LATER SESSION MUST NOT DO, and each of these has a reason:
     - give `.btn-primary` a solid fill (then 3 stops being unique);
     - style a destructive control as `.btn-primary` (that is where this whole
       problem came from — `live.js` shipped exactly that until 2026-08-13);
     - drop the mark or the words to "clean up" the button (then only colour is
       left, and colour was measured as 1.16:1);
     - paint `--accent` onto the danger plate, or `--bad` onto anything that
       commits rather than destroys.
   ========================================================================= */
.btn-danger {
  border-radius: var(--r-s);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  background-image: none;
  background-color: var(--bad);
  border-color: var(--bad);
  color: var(--on-bad);
  box-shadow: none;
}
/* Section 7 gives every `.btn` a chevron. The danger plate gets the triangle
   instead — one mark per control, and the mark that means "this is about to
   happen to you" outranks the one that means "onward". */
.btn-danger::before { content: none; }
.btn-danger:hover {
  background-color: color-mix(in srgb, var(--bad) 86%, var(--fg));
  border-color: color-mix(in srgb, var(--bad) 86%, var(--fg));
  color: var(--on-bad);
}
/* The focus ring is drawn in the accent everywhere else. On the one plate that
   is already the alarm colour, an accent ring is a second red touching the
   first — so this ring is the page's own ink, which is the highest-contrast
   thing available against the plate and cannot be confused with either red. */
.btn-danger:focus-visible {
  outline-color: var(--fg);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--bg-deep) 82%, transparent);
}

/* A square, high-contrast focus ring, with a dark halo behind it so it stays
   visible on an accent fill as well as on the void. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 0;
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--bg-deep) 82%, transparent);
}

/* Fields are wells cut into the plate, not raised chips. */
input, select, textarea, .cas-input, .cas-input-slim {
  border-radius: var(--r-xs);
  background-color: var(--surface-sink);
  border-color: var(--tm-hair);
  font-family: var(--font-mono);
  font-size: 13px;
}
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
input::placeholder, textarea::placeholder { color: var(--fg-mute); letter-spacing: .04em; }

/* -------------------------------------------------------------------------
   8. THE NAV — bracket tabs on a scanned band under a lit edge

   ★★★★★ THIS SECTION IS A COPY OF A REFERENCE THE OWNER SENT, NOT A DESIGN
   THIS SHEET CHOSE.  2026-08-13: "design looks terrible at the top dude. make
   it like this for example [screenshot] and then update framework". Four
   previous rounds on this bar were rejected, and every rejection came from
   substituting judgement for the thing he named. So each rule below records
   what was MEASURED off that screenshot, in its own numbers:

     the band     near-black, a scan grille on a 3px pitch (1 lit row in 3,
                  +2/+3 of 255 - it is nearly subliminal and it is the reason
                  the reference bar reads as a surface rather than a hole), and
                  one soft horizontal sheen across it with no edges in it;
     the edge     ONE solid lit hairline along the bottom, brightest in the
                  middle, with a short bloom under it. NOT the irregular dashed
                  "signal band" this sheet used to draw - measured on the
                  reference, row 69 of 72 is a single unbroken line;
     the tabs     EVERY destination inside its own brackets, at rest, not only
                  the current one and not only on hover;
     the lockup   two lines and a block cursor, left gutter;
     the tools    separate square hairline boxes, right gutter (soundchrome.css).

   ★ AND THE ONE THING THAT IS DELIBERATELY *NOT* COPIED IS THE HUE. The
   reference is monochrome in ITS owner's accent at four levels - every glyph
   in its bar is that one colour, including all nine labels. This sheet holds
   no colour of its own and never will, so what is carried across is the
   four-level LADDER and the relationships inside it (bracket dimmer than
   label, sub-line dimmer than name, edge brightest of all), spent through
   whatever `--accent` the island supplies. On an island whose accent is RED
   the labels stay in the ink ladder rather than the accent, because section 7b
   is a safety rule and not a preference: a row of ten labels in the brand red
   would sit one hue away from the danger plate AND would leave the current tab
   with nothing but a shade to distinguish it. The brackets, the wordmark, the
   cursor and the lit edge carry the accent, which is exactly the list 7b
   sanctions - "rules, brackets, the current tab, hairlines, tracked
   micro-labels, the shell prompt".
   ------------------------------------------------------------------------- */
.nav {
  background-image:
    /* THE SHEEN. One wide sweep, no stops the eye can find, brightest just
       past the middle - what a curved glass front does to a light above it. */
    linear-gradient(90deg,
      transparent,
      color-mix(in srgb, var(--edge-lit) 4%, transparent) 34%,
      color-mix(in srgb, var(--edge-lit) 7%, transparent) 54%,
      color-mix(in srgb, var(--edge-lit) 3%, transparent) 74%,
      transparent),
    /* THE SCAN GRILLE, on the reference's own 3px pitch. This is an OBJECT,
       not the page ground - section 2's "no repeating geometry" rule is about
       the ground under the whole document, and the ground is still bare.
       ★ 3.5%, MEASURED AGAINST THE REFERENCE RATHER THAN CHOSEN. Its lit row
       runs +2/+3 of 255 above its dark ones; the first build of this ran +8 and
       read as corduroy across the masthead of every page. A grille you can
       count the lines of is a texture; one you cannot is a surface, and a
       surface is the whole point. */
    repeating-linear-gradient(180deg,
      color-mix(in srgb, var(--edge-lit) 3.5%, transparent) 0 1px,
      transparent 1px 3px),
    linear-gradient(180deg,
      color-mix(in srgb, var(--bg-deep) 94%, transparent),
      color-mix(in srgb, var(--bg-deep) 78%, transparent));
  border-bottom-color: var(--tm-hair);
  box-shadow: none;
}
/* ★ THE BAR GETS ITS AIR BACK. site.css sizes the row at a 62px minimum, which
   was right for a one-line lockup and is 6px short of the three-line one the
   reference draws (name, tagline, cursor - 46px of content in a ~70px bar).
   Measured off the reference: 8px above the lockup and the same below. This is
   the house look's own row height and it is the only size this sheet sets on
   the bar; every element inside it is still sized where it was. */
.nav-in { min-height: 68px; }
/* THE LIT EDGE. The bar's own bottom border is a dark hairline; this sits on
   top of it, so what a reader sees is one line, lit, with the accent's bloom
   spilling a few pixels into the page below. Inset ends rather than a hard
   full-bleed stop: the reference's line fades at both gutters.

   ★★★★★ IT IS `::before`, AND THE REASON IS A CONFLICT THAT HAD ALREADY EATEN
   THE MARK THAT WAS HERE. `.nav::after` BELONGS TO motion.css - it is the
   SCROLL PROGRESS RULE, and motion.js drives it with
   `transform: scaleX(var(--mo-progress, 0))`. This sheet loads later, so its
   colour and its opacity won the cascade and its TRANSFORM did not exist, which
   left the element scaled to zero width at the top of every page. The dashed
   "signal band" this replaces had exactly the same fault and had it silently:
   it was invisible at scroll 0 - where a reader meets the bar - and only grew
   in as the page scrolled, wearing the progress rule's geometry. Nobody had
   looked, because a mark that is missing looks like a mark that was never
   designed. Two different statements ("the bar ends here" and "you are 40%
   down the page") cannot share one box, so they no longer do.

   ★ AND THE BLOOM IS ON THIS ELEMENT, NOT ON `.nav`. motion.css also owns the
   bar's own `box-shadow` (it hardens it on scroll), so a glow written there
   would be dropped the moment a reader scrolled. */
.nav::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 1px;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(90deg,
    transparent,
    var(--tm-hot) 8%,
    var(--accent) 34%,
    var(--accent) 66%,
    var(--tm-hot) 92%,
    transparent);
  box-shadow: 0 1px 14px -3px var(--accent-glow);
}

.nav-links a {
  border-radius: var(--r-xs);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 12px;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--fg-dim);
}
.nav-links a:hover { color: var(--accent-hi); background: var(--tm-faint); }
/* ★★★★ THE CURRENT TAB IS MARKED IN WEIGHT AS WELL AS IN COLOUR, AND THE
   WEIGHT IS THE PART THAT MATTERS. site.css marks it with a 2px rule drawn out
   of `::before` — and this sheet takes `::before` for the opening bracket, so
   that rule has not existed on a bracketed bar since the day the brackets
   landed. Colour alone is not a signal this core is allowed to rely on (see
   section 7b's measurement on two reds, and the same argument holds for one
   accent against its own bright step), so the mark is a real difference in
   SHAPE: 700 against 500.
   ★ AND IT COSTS THE ROW NOTHING, WHICH IS WHY IT CAN BE THE MARK HERE. The
   nav is set in the mono face, where every weight has the same advance, so the
   current tab does not reflow the row when a reader changes page. A bold step
   in a proportional face would, and would have to be paid for in width. */
.nav-links a[aria-current="page"],
.nav-links.grouped a[aria-current="page"] {
  color: var(--accent-hi);
  background: none;
  font-weight: 700;
}

/* BRACKET TABS.
   ★★★★★ THEY ARE ON AT REST NOW, ON EVERY DESTINATION. (owner reference,
   2026-08-13.) They used to be `opacity: 0` until a pointer or the current
   page lit them, which meant the bar a visitor actually looks at carried ONE
   bracketed word and nine bare ones — and the bracketed row is the single
   loudest thing in the reference he sent. Measured off it: nine items, nine
   pairs of brackets, all the same weight, and the brackets sit DIMMER than
   their label (bracket rgb 36,109,197 against label 76,179,252 — about 60%).
   That ratio is what stops ten pairs of brackets from becoming ten boxes, and
   it is why the rest state below is `--tm-hot` (the accent at 62%) rather than
   the accent itself.

   ★ THE CURRENT TAB IS STILL THE BRIGHTEST THING IN THE ROW, and it has to be:
   once every item is bracketed, the brackets have stopped being the signal.
   It keeps three marks that a rest-state tab does not have — the accent at
   full strength in the brackets, `--accent-hi` ink, and a 700 WEIGHT, which is
   a difference in shape and therefore survives both colour blindness and
   forced colours. (site.css's 2px underline is not one of the three: it is
   drawn out of this same `::before` and the bracket has had that box since the
   day brackets landed. See the weight rule above.)

   ★ IN FLOW, AND THAT IS THE SECOND ANSWER TO THIS. The first build put them
   out of flow (absolute, in the link's own padding) to stop the row growing.
   That worked on width and broke on rendering: site.css has a DENSITY TIER that
   cuts `.nav-links a` padding to 3px, an absolute bracket at `left: 3px` then
   sits ON TOP of the first letter, and the current tab rendered as `[ASINO]`
   with the brackets overprinting the label. Caught in a real capture of the
   casino page; invisible to every DOM assertion.

   In flow they cannot collide with anything, and the width they add is now safe
   because `.nav-links` is a real scroller (see below) - so the row shrinks or
   scrolls instead of pushing the page. Space is reserved on EVERY link, not
   only the current one, so moving between pages never shifts the row.

   site.css marks the current page with a BAR in `::before`, and
   `.nav-links.grouped a[aria-current="page"]::before` (0,3,1) plus the
   language-picker variant (0,6,1) both out-specify a bare rule - so the bracket
   is written at all three shapes. Drop one and that owner's tab bar silently
   returns to the bar with nothing failing. */
.nav-links a::before,
.nav-links.grouped a::before,
.nav-links a::after,
.nav-links.grouped a::after {
  position: static;
  width: auto;
  height: auto;
  background: none;
  border-radius: 0;
  color: var(--tm-hot);
  opacity: 1;
  transition: color .16s ease;
}
/* ★★★★★ RAISED PAST site.css's CURRENT-PAGE RULE, AND THIS IS A REAL DEFECT
   BEING CLOSED, NOT BELT AND BRACES. site.css draws the current tab's 2px
   underline out of the SAME pseudo-element - `.nav-links a[aria-current=
   "page"]::before { content: "" }` at (0,2,1) - so a bare `.nav-links a::before`
   at (0,1,1) loses to it and the current tab renders with NO OPENING BRACKET.
   On a row the owner declared families in, `.nav-links.grouped a::before` ties
   at (0,2,1) and this sheet loads later, so it won and nobody saw it; on a flat
   row - an owner with no `sep` and no `style` in his manifest, which is most of
   a new island's life - the bar read `HOME ]  [ SHOP ]`. Found in the
   "fewer nav items" leg of this wave's edge pass, on a rendered bar.
   `.nav` in front puts both shapes at (0,3,1)/(0,4,1), which no rule in
   site.css reaches, so the brackets are the brackets at every row shape. */
.nav .nav-links a::before, .nav .nav-links.grouped a::before,
.nav-links a::before, .nav-links.grouped a::before { content: "["; margin-right: 4px; }
.nav .nav-links a::after, .nav .nav-links.grouped a::after,
.nav-links a::after,  .nav-links.grouped a::after  { content: "]"; margin-left: 4px; }
/* And the underline's GEOMETRY has to go with its content, or the bracket is
   drawn as a 2px bar 3px off the bottom of the link. */
.nav .nav-links a[aria-current="page"]::before,
.nav .nav-links.grouped a[aria-current="page"]::before {
  content: "[";
  position: static;
  left: auto;
  right: auto;
  bottom: auto;
  width: auto;
  height: auto;
  background: none;
  border-radius: 0;
  margin-right: 4px;
}

.nav-links a:hover::before, .nav-links a:hover::after,
.nav-links a[aria-current="page"]::before,
.nav-links a[aria-current="page"]::after,
.nav-links.grouped a[aria-current="page"]::before,
.nav-links.grouped a[aria-current="page"]::after,
.nav .nav-in:has(.nav-lang:not([hidden]):not(:empty)) .nav-links.grouped a[aria-current="page"]::before {
  opacity: 1;
  color: var(--accent);
  position: static;
  width: auto;
  height: auto;
  background: none;
}
.nav .nav-in:has(.nav-lang:not([hidden]):not(:empty)) .nav-links.grouped a[aria-current="page"]::before {
  content: "[";
  margin-right: 4px;
}
/* The density tier can take the link padding to 3px, which is too tight for a
   bracket AND a label to share. A floor here costs a little width the scroller
   already absorbs, and it is what keeps the tabs readable at every tier. */
.nav-links a, .nav-links.grouped a { padding-inline: 9px; }

/* The group rule drops to the SOFT hairline now that every destination beside
   it carries brackets. At `--tm-hair` it was reading brighter than the marks
   it was dividing, which makes a divider the loudest thing in a row of ten
   labels — the reference has no dividers at all, and this is as close to that
   as an owner who declared groups in his manifest should be taken. */
.nav-links .sep { background: var(--tm-hair-soft); }

/* ★★★★★ THE NAV CAN NEVER PUSH THE PAGE, AND IT CAN NEVER SWALLOW A TAB.
   The first half of that sentence used to be the whole law here, and buying it
   with a scroller is what cost this look three destinations on the owner's own
   screen. The structural answer now lives in ONE place - cinema.css, under
   "THE NAVIGATION MUST NEVER BE A SCROLL CONTAINER" - and this sheet no longer
   has an opinion about the row's overflow. Read that block before touching
   this one; it carries the measurements and the reason.

   WHAT IS STILL TRUE AND STILL WORTH THE NEXT READER'S TIME: this look sets
   the nav in uppercase mono at .13em, which runs ~116px wider across eleven
   items than the sans it replaced (759px vs 643px on the widest page). That is
   real and it is the reason THIS sheet is the one that made the fault visible
   first. It is no longer a fault, because a row that cannot fit now takes a
   line of its own instead of eating its own tail.

   ★ TWO THINGS THAT WERE HERE ARE DELIBERATELY GONE.
   - The `overflow-x: auto` block. Superseded, see above.
   - The 769-1180px edge FADE. A mask that dissolves the first and last tab is
     a way of drawing "there is more over here", and there is never more over
     here any more. Worse, it was scoped to a band the fault did not live in:
     the owner's clipping happened at 1536 and above, where no fade was ever
     painted. A hint at the wrong widths for a problem that no longer exists is
     two reasons to delete it.

   ★ AND THE BREAKPOINT WAS WRONG BY 51 PIXELS. This block said 769px; site.css
   turns `.nav-links` into the absolutely-positioned phone DRAWER at 820px and
   below. So from 769 to 820 the drawer was being handed desktop row rules -
   a horizontal scroller and an edge fade on a vertical menu. Everything that
   remains here is at site.css's own 821px, and nothing in this sheet may set
   the desktop row's flex or overflow again. */
@media (min-width: 821px) {
  .nav-in { min-width: 0; }
}

/* ★ THE SIGNPOSTED LINK JOINS THE BRACKET FAMILY. It used to be the one BOXED
   entry in the row, which was right while the brackets were invisible and is
   wrong now that every entry has a pair: a bordered word beside ten bracketed
   ones is the "some tabs don't match at all" complaint in miniature, and there
   is no boxed destination anywhere in the reference. It keeps the signpost the
   owner asked for by being the only entry in the ACCENT at rest - brighter
   than its neighbours, in the row's own grammar. */
.nav-links a.nav-sup, .nav-links.grouped a.nav-sup {
  border: 0;
  border-radius: var(--r-xs);
  color: var(--accent-hi);
  background: none;
}
.nav-cta, .nav-auth-btn, .nav-chip {
  border-radius: var(--r-xs);
  font-family: var(--font-display);
  font-size: 11.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  border-color: var(--tm-hair);
}

/* ---- THE LOCKUP — two lines and a cursor, measured off the reference ----
   Its brand block reads, top to bottom: the name in the accent at a 10px cap
   height; the tagline directly under it in the same accent at ~60%, tracked
   right out, at an 8px cap; and a block cursor on a third line, accent, left
   edge flush with both. The whole stack is 46px tall inside a 69px bar.

   ★ THE NAME IS THE ACCENT, WHICH IS THE ONE PLACE THIS SHEET IS ALLOWED TO
   SPEND IT ON A WORD. It is a wordmark, it is thin ink on the ground rather
   than a fill, and it is the island's own name in the island's own colour -
   section 7b's list of what the brand colour is FOR opens with exactly this
   kind of mark. Nothing else in the bar takes it at full strength except the
   current tab and the lit edge. */
.brand-txt b {
  font-family: var(--font-display);
  font-weight: 700;
  /* ★ 13 -> 14px, and the old note is kept below because its reason still
     stands and only its arithmetic changed. Uppercase mono at 15px/.06em did
     overrun this lockup - but that was before .brand carried `overflow:
     hidden` and an ellipsis (cinema.css) and before the bar could grow a row,
     so an overrun is now a clip and not a broken corner. 14px is the
     reference's own step (a 10px cap height / .73 em-to-cap for this face) and
     it is the largest this slot has ever safely held.
     ORIGINAL NOTE: mono is ~18% wider per character than the sans this slot
     was sized for and uppercase adds more. */
  font-size: 14px;
  letter-spacing: .05em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--accent);
}
.brand-txt span {
  font-family: var(--font-mono);
  /* 11px, not 9.5: the reference's tagline is 80% of its name's size, and at
     9.5 against a 14px name the second line read as a caption under a heading
     rather than as the other half of one lockup. */
  font-size: 11px;
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
  color: var(--tm-hot);
  margin-top: 2px;
}
/* THE CURSOR UNDER THE LOCKUP. The same block the hero's headline carries
   (section 5), at the lockup's own scale, so the bar opens on the same "you
   are logged in" statement the page does. Purely decorative: it is an empty
   `aria-hidden` span, it is never the brand link's accessible name, and it
   costs the row no width because it sits on its own line.
   ★ SIZED IN `em` OF ITS OWN 11px CONTEXT so it tracks the tagline it follows
   rather than needing a second number when either step changes. */
.brand-cursor {
  display: block;
  width: .58em;
  height: 1.15em;
  margin-top: 3px;
  font-size: 11px;
  background: var(--accent);
  flex: none;
}
.nav-menu { border-radius: var(--r-s); border-color: var(--tm-hair); }

/* ---- THE IDENTITY BLOCK — a square plate, a square face, two lines ----
   The reference's right-hand corner is a 225x40 hairline box holding a square
   avatar tile, the player's name over a small tracked status line, and a solid
   caret. Ours was a 220px STADIUM holding a round avatar and one line, which
   is the fleet default pill wearing a mono font — the one control in the bar
   still built out of the old grammar.

   ★ THE FACE IS SQUARE HERE AND THAT DOES NOT CONTRADICT SECTION 6b. That
   register keeps `--r-round` off its list because an avatar, a coin face and
   the wheel hub are circles for a reason of their own. This is the reason of
   its own: in a console the identity is a REGISTERED PLATE, not a social
   profile picture, and the reference draws it as one. It is stated here, at
   the one element it applies to, rather than by deleting the register's note.

   ★ NO SIZE IS SET ON THE PLATE. site.css owns the chip's box; this only
   re-voices it. A page sheet may not size a button (test_house_look) and the
   house look does not need to. */
.nav-chip {
  border-radius: var(--r-xs);
  background: var(--surface-sink);
  align-items: center;
  gap: 10px;
}
.nav-chip:hover { border-color: var(--tm-hot); }
.nav-chip[aria-expanded="true"] { border-color: var(--accent); }
.nav-avatar, .nav-menu-avatar {
  border-radius: var(--r-xs);
  border-color: var(--tm-hair);
  background: var(--surface-sink);
}
/* Line one: the player's own name, in the page's brightest ink — the reference
   sets it above its accent, not in it, and it is the only string in this block
   a reader is actually identifying themselves by. */
.nav-who-name {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: none;
  color: var(--fg);
}
/* Line two: the tracked micro-label, at section 1's own tracking. It carries
   the balance when the balance could be read and the session's state when it
   could not — see site.js, renderAuthSlot, and the note there about why a
   missing balance is never a zero. */
.nav-who-sub {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
  color: var(--tm-hot);
  white-space: nowrap;
}
/* The caret is a MARK, so it takes the accent like every other mark in the
   bar; site.css draws it out of borders, so the accent has to reach the one
   border that is not transparent. */
.nav-caret { border-top-color: var(--accent); }

/* The ambient-sound control sits in the nav on every page and was the one
   chip still wearing the fleet default - a rounded sans pill beside eleven
   mono bracket tabs. It is authored by ambient.js, so it is dressed by class
   here rather than in markup. */
.wc-aud-btn, .wc-aud-say {
  border-radius: var(--r-xs);
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: .1em;
  text-transform: uppercase;
  border-color: var(--tm-hair);
}
.wc-aud[data-on="1"] .wc-aud-btn { color: var(--accent-hi); border-color: var(--accent); }
.wc-aud-rail { border-radius: var(--r-xs); border-color: var(--tm-hair); }

/* ---- THE TOOL BOXES — three squares standing on the bar ----
   The reference draws each captionless control as a square hairline box with
   an accent glyph in it and NOTHING behind it: no fill, no gradient, no
   shadow. Ours had the Discord control wearing site.css's `--grad-cta` and a
   drop shadow (it was the bar's one FILLED call to action back when it still
   spelled its own name) and the sound switch wearing a `--surface-2` plate, so
   the corner held two different materials beside a third.

   ★ SELECTORS ARE RAISED, NOT !important, AND THAT IS THE WHOLE REASON THEY
   LOOK LIKE THIS. soundchrome.css loads AFTER this sheet by design (see
   assemble.py, CHROME_STYLE_HREF) and owns these two controls' GEOMETRY at
   (0,2,0) and (0,3,0). Adding `.nav` in front puts these at (0,3,0)/(0,4,0),
   so the house look wins the colour and soundchrome keeps the box - which is
   the split those two sheets are supposed to have.

   ★ AND AN UNFILLED DISCORD ICON DOES NOT WEAKEN SECTION 7b. 7b's rule 2 is
   about the COMMITTING control being `--primary`; this is a link to a chat
   server. What 7b does forbid - the brand colour used as a FILL - is exactly
   what this removes. */
.nav .nav-util .nav-cta[data-cta-icon],
.nav .nav-util .wc-aud-btn {
  background-image: none;
  background-color: transparent;
  border: 1px solid var(--tm-hair);
  border-radius: var(--r-xs);
  box-shadow: none;
  color: var(--accent);
}
.nav .nav-util .nav-cta[data-cta-icon]:hover,
.nav .nav-util .wc-aud-btn:hover {
  border-color: var(--accent);
  background-color: var(--tm-faint);
  color: var(--accent-hi);
  transform: none;
}
/* ON still reads as ON without a caption, and it does it with the HAIRLINE
   alone. The wash came off after the first render: a filled box beside an
   unfilled one is two different materials in a corner the reference draws as
   three of the same square, and "sound is on" is the resting state of most
   visits — so the loudest thing in the bar would have been the switch nobody
   came for. The lit border is the state; the wash is reserved for hover, which
   is transient and is the one moment a second signal helps. */
.nav .nav-util .wc-aud[data-on="1"] .wc-aud-btn {
  border-color: var(--accent);
  background-color: transparent;
  color: var(--accent);
}
/* The signed-out gate is the one control in the corner that keeps its words,
   so it keeps a plate to sit them on - it is what a first-time visitor is
   looking for and it must not read as a third icon. */
.nav .nav-util .nav-auth-btn {
  background-color: var(--surface-sink);
  border-color: var(--tm-hair);
  color: var(--fg);
}
.nav .nav-util .nav-auth-btn:hover { border-color: var(--accent); color: var(--accent-hi); }

/* -------------------------------------------------------------------------
   9. READINGS — the status card as a register line
   ------------------------------------------------------------------------- */
.status { padding: 20px 22px; }
.status-top { border-bottom: 1px solid var(--tm-hair-soft); padding-bottom: 12px; }
.status-label { color: var(--fg); font-size: 11px; }
.status-src { color: var(--fg-mute); }

/* THE DOT LEADER. A reading and its label belong to each other across a gap,
   and a ruled leader is how a register says so without a box around each pair. */
.fig {
  position: relative;
  padding-top: 14px;
  border-top: 1px solid var(--tm-hair-soft);
}
.fig span {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--fg-mute);
  margin-top: 5px;
}
.fig span::after {
  content: "";
  flex: 1 1 auto;
  min-width: 0;
  height: 1px;
  background: repeating-linear-gradient(90deg, var(--tm-hair) 0 1px, transparent 1px 5px);
}

/* Status lamps are square. A round pip is a product's dot; a square one is a
   panel lamp, and this chrome is a panel. `--r-round` is left alone, so an
   avatar, a coin face and a wheel hub stay circular. */
.dot, .conn-lamp, .vp-lamp { border-radius: 0; width: 8px; height: 8px; }
.dot.on { box-shadow: 0 0 9px var(--ok-glow); }
.dot.off { box-shadow: 0 0 9px var(--bad-glow); }
.dot.wait { box-shadow: 0 0 9px var(--warn-glow); }

/* -------------------------------------------------------------------------
   10. THE STAMPED ORDINAL — steps, without the counter bubble
   ------------------------------------------------------------------------- */
.steps li { padding-left: 44px; font-size: 14px; }
.steps li::before {
  width: auto;
  height: auto;
  min-width: 30px;
  top: 0;
  border-radius: 0;
  border: 0;
  background: none;
  box-shadow: none;
  display: block;
  text-align: left;
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: var(--tm-track);
  color: var(--accent);
  content: "0" counter(s);
}
/* site.css runs a connector between steps; on a ruled ground it doubles a grid
   line, so it thins into the ordinal gutter instead. */
.steps li:not(:last-child)::after { left: 5px; background: var(--tm-hair-soft); }

/* -------------------------------------------------------------------------
   11. THE GATE — a signed-out surface is a closed register, not an error
   On a fully-bought island most gated pages are what a first-time visitor
   actually sees, so this state gets a real design and not a dashed box.
   ------------------------------------------------------------------------- */
.gate, .stage-gate {
  border-style: solid;
  /* ★ THE WIDTH, AND IT IS NOT DECORATION. This rule sets a border STYLE on
     two classes and only one of them had a width: site.css gives `.gate` 1px
     (it used to be a dashed box), and `.stage-gate` had none - so `medium`
     applied and the studio's stage gate was the only empty state on the site
     inside a 3px frame. Caught by the side-by-side, which is exactly the kind
     of thing only a side-by-side catches: a 3px border looks deliberate until
     it is beside seven 1px ones. */
  border-width: 1px;
  border-color: var(--tm-hair);
  background-color: var(--surface-sink);
  background-image: var(--grain);
  padding: clamp(34px, 6vh, 52px) 24px;
  gap: 10px;
}
.gate::before, .gate::after {
  content: "";
  position: absolute;
  width: 16px;
  height: 16px;
  pointer-events: none;
  border-color: var(--tm-warm);
  border-style: solid;
}
.gate::before { left: -1px; top: -1px; border-width: 1px 0 0 1px; }
.gate::after  { right: -1px; bottom: -1px; border-width: 0 1px 1px 0; }
/* ★ `.stage-gate h3` IS IN THIS RULE and used to be at 19px in studio.css.
   The studio's stage gate ("Sign in to design") is the same event as the
   vault's, the shop's and the staff console's - a closed register - and it
   was the only one saying so in sentence case at a page-heading size. */
.gate h3, .stage-gate h3 {
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.stage-gate p { color: var(--fg-dim); max-width: 46ch; }
.gate p { color: var(--fg-dim); max-width: 46ch; }
.gate-ico {
  color: var(--accent);
  border: 1px solid var(--tm-hair);
  border-radius: 0;
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  background: none;
}
.gate-note, .save-note { color: var(--fg-mute); }

/* =========================================================================
   11b. THE EMPTY STATE, PROVEN AT ITS EDGES.
   -------------------------------------------------------------------------
   Section 11 designed the gate. This block is what a RENDER of all eleven
   surfaces found wrong with it, and every rule is a defect that was on the
   screen rather than a preference.

   ★★★★★ (1) A GATE WITH NO WORDS IN IT.
   The vault and the auction floor rendered as A BARE ICON IN A 52px BOX and
   nothing else — no heading, no sentence, at 1440 and at 390. The markup was
   perfect and the CSS was perfect: `vault.js` calls `setGate(V.empty, ...)`
   and `auction.js` calls `setGate(A.empty_title, A.empty_body)`, both of
   which do `textContent = title || ''`, and NEITHER KEY EXISTS in that
   island's copy. So an owner who is missing one string gets a large empty
   plate on the state a player hits most — an empty vault and an empty floor
   are the FIRST thing a new island has, not an edge case.
   The copy is the real fix and it belongs to the manifest. What belongs HERE
   is that a missing string can never again paint a 200px box around silence:
   an empty heading or paragraph takes no space, and a gate reduced to its
   icon closes up to the size of the icon. The failure becomes small and
   obviously a gap, instead of large and obviously broken.

   ★★★★ (2) `.card` AND `.gate` ON THE SAME ELEMENT PAINT TWO MARKS.
   The staff console is the one surface that carries `class="card gate"`.
   Section 6 gives `.card::before` a full-bleed top rule; section 11 gives
   `.gate::before` a 16px corner bracket. Both matched, the later declaration
   won the box, and what reached the screen was AN ORPHAN ACCENT SQUARE
   floating in the top-left corner with no rule attached to it — visible in
   the render, invisible to every DOM assertion. A gate is a gate: when both
   classes are present the gate's registration wins and the card's rule stands
   down.

   ★★★ (3) ONE STATE, ONE SHAPE. The wheel wrote its "could not be read" state
   as a bare red mono sentence with no box at all, while seven other pages
   wrote theirs as a bordered plate with an icon, a heading and a line of
   prose. Same event, same page-level meaning, two unrelated presentations.
   `.iw-state[data-err]` and the console's error line now take the gate's
   voice, so the eleventh surface says "nothing here" the way the other ten do.
   ========================================================================= */

/* (1) an empty string occupies nothing */
.gate h3:empty, .gate p:empty,
.gate-act:empty, .gate-extra:empty, .gate-note:empty { display: none; }
/* a gate with nothing but its icon closes up rather than framing silence */
.gate:not(:has(h3:not(:empty))):not(:has(p:not(:empty))) {
  padding: clamp(20px, 3vh, 28px) 24px;
}

/* (2) the gate's registration wins on an element that is also a card */
.card.gate::before {
  left: -1px;
  right: auto;
  top: -1px;
  width: 16px;
  height: 16px;
  background: none;
  border-color: var(--tm-warm);
  border-style: solid;
  border-width: 1px 0 0 1px;
}

/* (3) the wheel's state line joins the same family.
   ★ `.iw-state.bad` IS THE REAL SELECTOR AND THE FIRST DRAFT GUESSED
     `[data-err]`. `wheel.js:851` writes `className = "iw-state" + " " + tone`
     and passes `"bad"` on the failure path, so the attribute form matched
     nothing and the wheel kept its bare red sentence through one whole render
     round. Read the renderer, do not infer the hook. The attribute form is
     kept beside it because it costs nothing and a later renderer may use it.
   ★ AND ONLY THE FAILURE STATE. `.iw-state` with no tone is an ordinary line
     ("Reading the wheel...") and must not be plated - an error frame around a
     routine message is its own defect. */
.iw-state.bad, .iw-state[data-err],
.iw-err, .admin-error, .cas-err, .au-err, .buy-error {
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
  color: var(--fg-dim);
  border: 1px solid var(--tm-hair);
  border-radius: var(--r-xs);
  background: var(--surface-sink);
  padding: 12px 14px;
}
.iw-state.bad b, .iw-state[data-err] b, .iw-err b, .admin-error b { color: var(--fg); }

/* ★★★ (4) THE LIVE CARD'S EMPTY STATE WAS A SECOND EMPTY STATE.
   `.lvc-empty` is what a player sees on the page the owner links first, and
   it was the one that agreed with nothing: a bare 34px glyph at 50% opacity
   where every other surface frames its icon in a bordered well, and a heading
   at clamp(19px, 2.6vw, 24px) where the shared gate heading is 15px. Side by
   side with the vault and the shop it read as a different product's page. It
   joins the family here rather than being re-tuned in its own sheet, which is
   how it came to differ in the first place. */
.lvc-empty { gap: 10px; padding: clamp(34px, 6vh, 52px) 24px; }
.lvc-empty h3 {
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--fg);
}
.lvc-empty p { color: var(--fg-dim); max-width: 46ch; }
.lvc-empty > svg {
  width: 46px;
  height: 46px;
  padding: 11px;
  opacity: 1;
  color: var(--accent);
  border: 1px solid var(--tm-hair);
  box-sizing: border-box;
}

/* ★★★★ (5) THE VOICE ROOM OPENED LIKE A SECOND LANDING PAGE.
   Measured across all eleven surfaces: nine interior pages open on
   `.page-h1` at 34.16px in the page's own ink; home opens on `.hero h1` at
   52.46px in the accent, with the typing cursor after it. THE VOICE ROOM WAS
   ON THE HOME PAGE'S SIDE — 52.46px, accent, cursor — because its fragment is
   built from `.hero`. It is the loudest single reason a visitor moving from
   the vault to the voice room feels they changed sites.

   ★ AND THE MARKUP CANNOT SIMPLY CHANGE. That comment above the element is
   load-bearing: `voice.js` hides exactly `.hero` when the room is re-hosted
   inside the site-wide dock's 1x1 iframe, so renaming the class silently
   leaves a headline in a dock that has no room for one. The behaviour belongs
   to another hand this session either way.
   So the ELEMENT stays and only its DRESS changes, matched on something only
   this room has — the connection row inside its own hero. One session opens
   per site, on the landing page, and the interior pages are interior. */
.hero:has(.vp-conn-row) { padding: clamp(26px, 4vh, 44px) 0 0; }
.hero:has(.vp-conn-row) h1 {
  font-size: clamp(24px, 3.6vw, calc(var(--fs-6) * .56));
  color: var(--fg);
  letter-spacing: -.012em;
}
/* the cursor is the session opening. It opens once. */
.hero:has(.vp-conn-row) h1::after { content: none; }
.hero:has(.vp-conn-row) .kicker {
  font-size: 10.5px;
  letter-spacing: var(--tm-track);
  color: var(--accent);
  margin-bottom: 12px;
}
.hero:has(.vp-conn-row) .lede { font-size: 15px; max-width: 62ch; }
@media (max-width: 430px) {
  .hero:has(.vp-conn-row) h1 { font-size: clamp(20px, 6vw, 24px); }
}

/* -------------------------------------------------------------------------
   12. TABLES, LISTS, CHIPS — hairlines, and wide data in its own scroller
   ------------------------------------------------------------------------- */
.admin-table, .cas-table, .au-board {
  font-family: var(--font-mono);
  font-size: 12.5px;
  border-collapse: collapse;
}
.admin-table th, .cas-table th {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
  color: var(--fg-mute);
  border-bottom: 1px solid var(--tm-hair);
  text-align: left;
}
.admin-table td, .cas-table td { border-bottom: 1px solid var(--tm-hair-soft); }
.admin-table tbody tr:hover td { background: var(--tm-faint); }
/* Wide data scrolls inside its own container. The PAGE never scrolls sideways;
   see section 17. */
.admin-table-wrap, .au-board, .cas-table { max-width: 100%; overflow-x: auto; }

.pill, .shop-pill, .item-tag, .cas-chip, .conn-pill, .gpause-chip {
  border-radius: var(--r-xs);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
  border: 1px solid var(--tm-hair);
  background: none;
}

.notice, .admin-note, .au-err, .buy-error, .vp-merr, .admin-error {
  border-radius: var(--r-s);
  border-left-width: 1px;
}
.notice-ico { color: var(--accent); }

/* -------------------------------------------------------------------------
   13. TABS
   ------------------------------------------------------------------------- */
.admin-tabs button, .tabs button, [role="tab"] {
  border-radius: var(--r-xs) var(--r-xs) 0 0;
  font-family: var(--font-display);
  font-size: 11.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  border-bottom: 2px solid transparent;
  background: none;
}
.admin-tabs button[aria-selected="true"], .tabs button[aria-selected="true"],
[role="tab"][aria-selected="true"] {
  color: var(--accent-hi);
  border-bottom-color: var(--accent);
}
.admin-tabs button[aria-selected="true"]::before,
[role="tab"][aria-selected="true"]::before { content: "\00BB\0020"; color: var(--accent); }

/* -------------------------------------------------------------------------
   14. THE LIVE BAR — the session's status line
   ------------------------------------------------------------------------- */
#wc-livebar {
  border-top: 1px solid var(--tm-hair);
  background-color: color-mix(in srgb, var(--bg-deep) 94%, transparent);
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
}
.lb-k { color: var(--fg-mute); }
.lb-v { color: var(--fg); }
.lb-sep { background: var(--tm-hair); }

/* -------------------------------------------------------------------------
   15. THE FOOTER — the session closes on a rule
   ------------------------------------------------------------------------- */
.foot {
  border-top: 1px solid var(--tm-hair);
  background-image: none;
  background-color: var(--bg-deep);
  box-shadow: none;
  margin-top: clamp(48px, 8vh, 84px);
}
.foot-brand b {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  font-size: 14px;
}
.foot-brand span span, .foot-legal {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .1em;
  color: var(--fg-mute);
  text-transform: none;
}
.foot-links a {
  font-family: var(--font-display);
  font-size: 11.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--fg-dim);
}
.foot-links a:hover { color: var(--accent-hi); }

/* -------------------------------------------------------------------------
   16. THE BOUGHT SURFACES. Each inherits sections 3-13 through the shared
   classes; what is left here is only what those pages own outright. Every
   selector below belongs to a page an owner may not have bought, so each rule
   is inert on an island that did not buy it.
   ------------------------------------------------------------------------- */

/* casino */
.cas-stage, .cas-stage-felt { border-radius: var(--r); border-color: var(--tm-hair); }
.cas-card-ico { color: var(--accent); }
.cas-strip { border-bottom: 1px solid var(--tm-hair); }
.mult-well {
  border: 1px solid var(--tm-hair);
  border-radius: var(--r-s);
  background: var(--surface-sink);
}
.mult-val { font-size: 26px; color: var(--accent-hi); }

/* wheel */
.iw-console, .iw-bank { border-radius: var(--r); border-color: var(--tm-hair); }
.iw-state { color: var(--fg-dim); border-top: 1px solid var(--tm-hair-soft); padding-top: 10px; }
.iw-bank-v { font-size: 22px; color: var(--accent-hi); }

/* vault */
.vp-track {
  border-radius: 0;
  background: var(--surface-sink);
  border: 1px solid var(--tm-hair);
}
.vp-meter { border-radius: 0; }
.vp-modal, .vp-mi { border-radius: var(--r-s); border-color: var(--tm-hair); }
.vp-price { color: var(--accent-hi); font-size: 19px; }

/* auctions */
.au-dialog-card, .au-sell-form { border-radius: var(--r); border-color: var(--tm-hair); }
.au-dialog-lines { font-family: var(--font-mono); font-size: 12.5px; }

/* skin studio */
.deck, .stage, .stage-wrap, .designs, .channel-list, .presets {
  border-radius: var(--r);
  border-color: var(--tm-hair);
}
.stage-foot, .code-row { font-family: var(--font-mono); font-size: 11.5px; }
.stage-head, .studio-panel-label { color: var(--accent); }

/* shop */
.buy-panel, .buy-picks { border-radius: var(--r); border-color: var(--tm-hair); }
.buy-lines { font-family: var(--font-mono); font-size: 12.5px; }

/* the staff console */
.admin-bar, .admin-filter, .admin-recent, .admin-fields, .admin-list {
  border-radius: var(--r-s);
  border-color: var(--tm-hair);
}
.admin-panel-head {
  font-size: 12.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent);
  border-bottom: 1px solid var(--tm-hair-soft);
  padding-bottom: 9px;
}
.admin-odd { background: color-mix(in srgb, var(--edge-lit) 3%, transparent); }

/* the voice room. It is a page of this site and wears the same look; its own
   sheet loads BEFORE this one (assemble.py), so these win on equal specificity. */
.vp-instrument, .vp-panel, .vp-keywell, .vp-dialog-section {
  border-radius: var(--r);
  border-color: var(--tm-hair);
}
.vp-panel-h {
  font-size: 12px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent);
}
.vp-key, .vp-key-cap {
  font-family: var(--font-mono);
  border-radius: var(--r-xs);
  border: 1px solid var(--tm-hair);
  font-size: 11px;
}
.vp-emblem-mark { color: var(--accent); }
.conn-label {
  font-family: var(--font-display);
  letter-spacing: .1em;
  text-transform: uppercase;
  font-size: 11px;
}

/* -------------------------------------------------------------------------
   16b. THE FINISH BATCH - eight things a fresh reviewer found in the render
   that neither the DOM, the detector nor the suites can see. Each is named
   with what it WAS, because "why is this rule here" is the question that gets
   a fix like this deleted six months from now.
   ------------------------------------------------------------------------- */

/* (1) GRADIENT TEXT, INHERITED AND NEVER OVERRIDDEN.
   cinema.css:1115 puts a gradient behind every display heading with
   `background-clip: text` + `-webkit-text-fill-color: transparent`, so EVERY
   h1 rendered near-white at the cap line falling to accent at the baseline.
   Nothing here authored it and nothing here was overriding it. Straight
   refusal on the craft floor: emphasis comes from weight and size, and in a
   one-phosphor world the display type is ONE colour - the screen character
   comes from the grille that already exists. BOTH properties must be reset:
   clearing the background alone leaves the fill transparent and the heading
   vanishes entirely. */
.hero h1, .page-h1, .sec-head h2, .status .fig b, .fig b {
  background-image: none;
  -webkit-background-clip: border-box;
          background-clip: border-box;
  color: var(--fg);
  -webkit-text-fill-color: currentColor;
  filter: none;
}
.hero h1 { color: var(--accent-hi); }

/* (2) THE REJECTED PAGE'S THREE-UP CARD GRID SURVIVED THE REDESIGN.
   Twelve tiles, three columns, icon-left / heading / paragraph - the same
   block, in the same place, in the same order as the design the owner
   rejected, with only the radius, the border colour and the heading face
   changed. The direction contract's own thesis refuses "rounded cards in a
   three-up grid", and a reviewer with no history of this build set it beside
   the old capture and called it the strongest evidence for a re-skin.
   So the tiles stop being cards: two columns, no box, a top hairline, and the
   registration ticks doing the work - which is what the rest of this sheet
   already says a record looks like. `:has()` scopes it to the feature grid so
   no other three-up on the site moves. */
.grid-3:has(> .feat) {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 clamp(20px, 3vw, 44px);
}
.feat {
  background-image: none;
  background-color: transparent;
  border: 0;
  border-top: 1px solid var(--tm-hair-soft);
  border-radius: 0;
  padding: 16px 2px 18px;
  box-shadow: none;
}
.feat:hover { background-color: var(--tm-faint); border-top-color: var(--accent); }
/* the ticks were invisible anyway: a full four-side border in the same colour
   gives a corner registration mark nothing to register against */
.feat::before, .feat::after { display: none; }
.feat-ico { border-color: var(--tm-hair-soft); }

/* (3) THE SHELL PROMPT REACHED 2 PAGES OUT OF 11.
   Home and the voice room opened on `domain:~$`; the other nine opened on a
   plain page head - which is exactly why the interior pages read closer to the
   rejected design than the landing page did. The prompt is the strongest
   "you are logged in" signal in the system, so every PAGE head carries it now.
   Section heads inside a page keep the register diamond: a session opens once,
   not once per section. */
:is(.page-head, .sec-head:has(> .page-h1)) .eyebrow::before {
  content: var(--prompt, "~$");
  width: auto;
  height: auto;
  border: 0;
  border-radius: 0;
  background: none;
  transform: none;
  color: var(--accent);
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
  flex: 0 0 auto;
}

/* (4) TWO PANEL-BORDER LANGUAGES ON ONE SITE.
   Home's records carried the warm hairline; the studio, shop, admin, casino
   and voice panels carried the shared sheets' own `--border`, which on a
   carbon void reads as the loudest line on the page and out-shouts the accent
   everywhere it appears. One hairline, everywhere. */
.deck, .stage, .stage-wrap, .designs, .channel-list, .presets, .channels,
.channel, .chip, .studio-card, .cas-panel, .cas-card, .admin-panel, .panel,
.buy-panel, .buy-picks, .au-dialog-card, .au-sell-form, .vp-panel,
.vp-instrument, .vp-keywell, .iw-console, .iw-bank, .notice, .gpause-card {
  border-color: var(--tm-hair);
}

/* (5) THE SKIN STUDIO WAS HALF WEARING THE FLEET DEFAULT.
   The most Operate-mode page on the site, and the first one anyone judging a
   design FRAMEWORK opens: seven colour-channel rows as rounded grey pills with
   sans labels, rounded swatches and hex fields, a rounded segmented control, a
   rounded filter input, and a white-outlined RANDOMISE sitting two inches from
   an accent-tinted SIGN IN. One page, two button languages. */
.channel { border-radius: var(--r-xs); background: var(--surface); }
/* `.channel label` is a one-word channel name and takes the micro-label
   voice. `.studio-size-note` is NOT in this rule and must not be added to it -
   it is a 52ch SENTENCE, and this sheet has now made the same mistake twice
   (see `.dim`): a tracked-uppercase sentence at 11px is unreadable. */
.channel label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: var(--tm-track);
  text-transform: uppercase;
  color: var(--fg-dim);
}
.studio-size-note, .stage-note { font-family: var(--font-ui); color: var(--fg-mute); }
/* The paste's name box and the layout picker beside an owned skin (2026-08-14)
   are the same KIND of surface as the code box they sit with - a typed field
   sunk into the card - so they are registered here rather than left to draw
   their own plate in the page sheet, which is what the house look's surface
   register exists to prevent. */
.channel .hex, #code-input, #code-name, .skin-pattern {
  font-family: var(--font-mono);
  border-radius: var(--r-xs);
  background: var(--surface-sink);
  border-color: var(--tm-hair);
  letter-spacing: .02em;
}
.channel input[type="color"] { border-radius: 0; border: 1px solid var(--tm-hair); }
.channel input[type="color"]::-webkit-color-swatch { border-radius: 0; border: 0; }
.channel input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.chip, .btn-sm, .deck-search input, .stage-foot, .preset-row {
  border-radius: var(--r-xs);
  font-family: var(--font-display);
  letter-spacing: .08em;
}
.chip { text-transform: uppercase; font-size: 11px; }
/* one control language site-wide: a hairline, the accent on the active state,
   never a filled slab and never a white outline */
.chip[aria-pressed="true"], .chip.on, .chip[data-on="1"] {
  border-color: var(--accent);
  color: var(--accent-hi);
  background: var(--tm-faint);
}
.deck-search input { background: var(--surface-sink); font-family: var(--font-mono); }
.stage-wrap { border-radius: var(--r-s); border-color: var(--tm-hair); }

/* (6) A FILLED BOX NESTED INSIDE A BORDERED PANEL AT 390, and the feed stamp
   losing its shape. `.status-top` is a lit strip inside `.status`, which
   becomes a nested card the moment the row wraps; and `.status-src` - a ~60px
   right-aligned stamp on desktop - stretched into a full-width outlined bar
   that reads as an empty error button rather than a status mark. */
.status-top { background: none; }
.status-src {
  flex: 0 0 auto;
  width: auto;
  align-self: flex-start;
  white-space: nowrap;
}

/* (7) THE EMBER BLOOM LANDED ON THE HEADLINE AT PHONE WIDTH.
   The atmosphere layer is scattered and reads as depth at 1440; at 390 the
   same blob sits behind the one piece of type that has to be crisp and reads
   as a smudge on the letterforms. The layer stays - it is quietened, and only
   where the collision happens. */
@media (max-width: 720px) {
  .night::after { opacity: .35; }
  .hero h1, .page-h1 { position: relative; z-index: 1; }
}

/* (8) An orphan accent fragment floated above the footer on every page, with
   nothing attached to it - a rule end with no rule. */
.foot::before { display: none; }

/* -------------------------------------------------------------------------
   17. NOTHING SCROLLS SIDEWAYS BUT THE THING THAT HAS TO
   ------------------------------------------------------------------------- */
main img, main svg, main video, main canvas, main iframe { max-width: 100%; }
main table { max-width: 100%; }
.copyrow code, .feat-cmd code, .code-row {
  overflow-x: auto;
  max-width: 100%;
  font-family: var(--font-mono);
}

/* -------------------------------------------------------------------------
   18. THE PHONE — the same register, one column wide
   ------------------------------------------------------------------------- */
@media (max-width: 720px) {
  .hero .kicker { font-size: 11px; gap: 7px; }
  .fig b { font-size: 23px; }
  .steps li { padding-left: 36px; }
  .steps li::before { min-width: 26px; }
}
@media (max-width: 430px) {
  /* a long island name in mono clears the gutter below this width unless the
     display step comes down again — measured at 390, not assumed */
  .hero h1 { font-size: clamp(24px, 7.1vw, 30px); }
  .page-h1 { font-size: clamp(20px, 6vw, 24px); }
}

/* -------------------------------------------------------------------------
   19. MOTION — one authored moment: the session opening
   The prompt types, then the island's name wipes in behind it. Gated TWICE: on
   the reader's own preference, and on the engine flag motion.js sets, so a page
   whose JS never ran shows finished text.
   ★ NO animation-fill-mode. The resting state IS the finished state, so a
   reader who never receives the animation is never left with a hidden line —
   which is the failure a reveal that hides by default ships to exactly the
   people who cannot see it move, and the reason a full-page screenshot of this
   stack lies (docs/EYEBALL_HARNESS.md).
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes tm-type { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
  @keyframes tm-blink { 0%, 46% { opacity: 1; } 47%, 100% { opacity: 0; } }

  html[data-wc-motion="on"] .hero .kicker { animation: tm-type .62s steps(22, end) backwards; }
  html[data-wc-motion="on"] .hero h1 { animation: tm-type .42s steps(16, end) .5s backwards; }
  /* The donor tree leaves its cursor blinking under reduced motion. This one
     stops, because a ~1 Hz flash is precisely what that preference asks about. */
  .hero h1::after { animation: tm-blink 1.06s steps(1, end) infinite; }
}

/* ★★★ THE LOCKUP'S CURSOR DOES NOT BLINK, AND THAT IS A DECISION WITH A
   PRECEDENT. The obvious move was to hang it off `tm-blink` beside the
   headline's, which is what the first build of this wave did. Two reasons it
   came back out, and the first is the one that generalises:

   1. THE HEADLINE APPEARS ONCE, ON ONE PAGE. This bar is sticky, it is on
      every page, and it never leaves. soundchrome.css already settled exactly
      this argument when it stopped the speaker pulsing — "a control that
      animates while nothing is happening is noise in the other channel" — and
      a ~1 Hz flash in the corner of every screen of the site is a bigger
      version of the same thing.
   2. IT MADE THE MARK UNPHOTOGRAPHABLE. A `steps(1, end)` blink is off half
      the time, so half of every screenshot set showed a lockup with no cursor
      in it and no way to tell that from a bug. Found exactly that way, in the
      eyeball pass for this wave.

   The resting state is the finished state, which is this sheet's own rule for
   every reveal it ships. */

@media (prefers-reduced-motion: reduce) {
  .hero .kicker, .hero h1 { animation: none; clip-path: none; }
  .hero h1::after { animation: none; opacity: 1; }
}

/* -------------------------------------------------------------------------
   20. THE EDGES
   ------------------------------------------------------------------------- */
/* A reader in forced-colours has asked the OS to decide every colour. The
   grille and the ruled ground are texture with no information in them, so they
   go; the marks stay, drawn in the system's own ink. */
@media (forced-colors: active) {
  body::after, main::before, .nav::after { display: none; }
  /* The lit edge is a real boundary, not texture, so it survives - redrawn in
     the system's own ink rather than in a colour the OS did not choose. */
  .nav::before { background: CanvasText; box-shadow: none; }
  .eyebrow::before, .card::after, .feat::before, .feat::after { border-color: CanvasText; }
  .hero h1::after { background: CanvasText; }
  /* The grille and the sheen are texture with no information in them and the
     bar has to fall back to the system's own ground; the two MARKS in the
     lockup — the cursor and the brackets — are information and are redrawn in
     the system's ink. A bracket painted in a colour the OS did not choose is
     the one thing that can vanish entirely against a forced ground. */
  .nav { background-image: none; }
  .brand-cursor { background: CanvasText; }
  .nav-links a::before, .nav-links a::after,
  .nav-links.grouped a::before, .nav-links.grouped a::after { color: CanvasText; }
}

@media print {
  body::after, main::before, .nav::after, .night { display: none; }
  .hero h1::after { display: none; }
  .brand-cursor { display: none; }
  .nav { background-image: none; }
  .nav::before { box-shadow: none; }
  .card, .status, .feat, .gate { background: none; border-color: currentColor; }
}
