/* ============================================================
   Tapeclerk — shared page scaffold ("Bioluminescent Deep")
   Extracted verbatim from design/preview.html v3.5 so the design
   specimen and the shipped pages style themselves from ONE sheet:
   two copies of these rules would drift, and the contrast numbers
   measured against one copy would stop describing the other.

   Every colour, size, radius, duration and easing here is a token
   from tokens.css. A raw hex outside tokens.css is a lint failure.
   Landing-only rules live in landing.css and import nothing new.
   ============================================================ */
/* Preview scaffold. Every value comes from tokens.css. */
*, *::before, *::after { box-sizing: border-box; }
html { background: var(--tc-bg); -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: var(--tc-font-ui);
  font-size: var(--tc-text-base);
  line-height: var(--tc-leading-body);
  letter-spacing: var(--tc-tracking-body);
  color: var(--tc-fg);
  background: var(--tc-bg);
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* No overflow-x: hidden. That property does not "fix" horizontal overflow,
     it hides it — and it silently made a nav that ran 70px off-screen at
     320px look fine. Overflow here is a bug to measure, not to mask. */
}
/* Suppress transitions only during a theme swap, so the glass does not
   cross-fade through muddy middles. Motion-gated: under reduce there is
   nothing to suppress. */
html.tc-swapping *, html.tc-swapping *::before, html.tc-swapping *::after {
  transition: none !important;
}

main { max-width: 980px; margin: 0 auto; padding: var(--tc-space-xl) var(--tc-space-md) var(--tc-space-3xl); }
section + section { margin-block-start: var(--tc-space-2xl); }
h1, h2 {
  font-family: var(--tc-font-display);
  font-weight: var(--tc-weight-heavy);
  letter-spacing: var(--tc-tracking-display);
  line-height: var(--tc-leading-tight);
  text-wrap: balance;
  overflow-wrap: break-word;
}
h1 { font-size: var(--tc-text-4xl); }
h2 { font-size: var(--tc-text-2xl); margin-block-end: var(--tc-space-md); }
p { margin: 0; }
p.sub { color: var(--tc-fg-muted); max-width: var(--tc-measure-lead); text-wrap: pretty; }
/* Label voice. Sentence case, no letter-spacing, no container — the three
   habits that make generated UI look generated are tracked-out CAPS, the
   outlined pill, and the coloured left rule. Two of those were fixed by
   removing containers; this one is fixed by not shouting. Uppercase survives
   in exactly two places, where the string genuinely IS uppercase: tape
   counter readouts, and the wire codes the API actually returns. */
.micro {
  font-family: var(--tc-font-ui); font-weight: var(--tc-weight-medium);
  font-size: var(--tc-text-xs); letter-spacing: var(--tc-tracking-body);
  color: var(--tc-fg-muted);
}
/* Numerals that count, time, or change shape need tabular figures, or the
   row jitters every time a digit rolls. */
.tc, .counter, .cue-flag, .live-readout, code { font-variant-numeric: tabular-nums; }
code, .mono { font-family: var(--tc-font-mono); font-size: var(--tc-text-xs); }

:focus-visible {
  outline: var(--tc-focus-width) solid var(--tc-focus);
  outline-offset: var(--tc-focus-offset);
}
.btn-primary:focus-visible, .play-btn:focus-visible { outline-color: var(--tc-focus-on-accent); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip {
  position: absolute; inset-inline-start: var(--tc-space-md); inset-block-start: var(--tc-space-md);
  transform: translateY(-200%); z-index: 10;
  background: var(--tc-accent); color: var(--tc-accent-on-fill);
  padding: var(--tc-space-xs) var(--tc-space-md); border-radius: var(--tc-radius-pill);
  font-weight: var(--tc-weight-medium); text-decoration: none;
}
.skip:focus { transform: none; }

/* ---- aurora field ---- */
.aurora { position: fixed; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; }
.aurora i { position: absolute; border-radius: 50%; filter: blur(90px); }
/* Placed where a lens can be pointed at them. Parked off the edge of a 1400px
   viewport and blurred 90px, an aurora is invisible and every glass panel on
   the page has nothing to be glass OVER — which is how a transparent UI ends up
   reading as flat grey cards (the first light-theme render proved it). They
   reach inward, sit behind the panel columns, and keep a dense core so the
   blur has structure left to smear. */
.aurora .a1 { width: 68vw; height: 68vw; inset-inline-start: -8vw; inset-block-start: -14vw; opacity: var(--tc-aurora-1); background: radial-gradient(circle, var(--tc-violet), transparent 72%); }
.aurora .a2 { width: 58vw; height: 58vw; inset-inline-end: -6vw; inset-block-start: 2vw; opacity: var(--tc-aurora-2); background: radial-gradient(circle, var(--tc-accent), transparent 72%); }
.aurora .a3 { width: 54vw; height: 54vw; inset-inline-start: 12vw; inset-block-end: -18vw; opacity: var(--tc-aurora-3); background: radial-gradient(circle, var(--tc-coral), transparent 72%); }
/* The warm bloom. Three cool blooms and no warm one is why the first field
   looked like every other generated aurora: a hue sweep with no light source.
   Oxide sits low-right, where the hero's form is, so the glass panel has
   something warm to actually lens. */
.aurora .a4 { width: 62vw; height: 62vw; inset-inline-end: 4vw; inset-block-end: -20vw; opacity: var(--tc-aurora-4); background: radial-gradient(circle, var(--tc-oxide), transparent 68%); }

/* Film grain. Fixed, behind the content, above the field: it turns the abyss
   from a fill into a surface and stops the big dark areas from banding.
   Deliberately below the panels, not above everything — grain over 14px copy
   eats contrast, and the mint bloom is already spending most of its margin. */
.grain { position: fixed; inset: 0; z-index: -1; pointer-events: none; background-image: var(--tc-grain); opacity: var(--tc-grain-opacity); }

/* ---- glass material ----
   One property does the legibility work here: backdrop-filter. The filter
   chain in tokens.css bounds the luminance of whatever a panel is looking at
   (brightness 0.62 down in dark, 1.28 up in light), so transparency and AA
   stop being a trade-off. `background-color` is a 4% tint — a highlight on
   the glass, not a veil over it. */
.glass {
  background-color: var(--tc-glass-bg);
  -webkit-backdrop-filter: var(--tc-glass-filter);
  backdrop-filter: var(--tc-glass-filter);
  border: var(--tc-rule-width) solid var(--tc-glass-stroke);
  border-radius: var(--tc-radius-lg);
  box-shadow: var(--tc-shadow-depth), var(--tc-inner-sheen);
}
.glass-strong { background-color: var(--tc-glass-bg-strong); -webkit-backdrop-filter: var(--tc-glass-filter-strong); backdrop-filter: var(--tc-glass-filter-strong); }
.lift { transition: transform var(--tc-dur-med) var(--tc-ease-out-soft), box-shadow var(--tc-dur-med) var(--tc-ease-out-soft); }

/* ---- nav ---- */
nav {
  display: flex; align-items: center; gap: var(--tc-space-sm) var(--tc-space-md);
  padding: var(--tc-space-sm) var(--tc-space-md); border-radius: var(--tc-radius-pill);
  margin-block-end: var(--tc-space-2xl); flex-wrap: wrap; min-height: var(--tc-hit-target);
}
.logo { font-family: var(--tc-font-display); font-weight: var(--tc-weight-bold); font-size: var(--tc-text-lg); letter-spacing: var(--tc-tracking-display); }
.logo b { color: var(--tc-accent-text); font-weight: var(--tc-weight-heavy); }
nav a { color: var(--tc-fg-muted); text-decoration: none; font-size: var(--tc-text-sm); padding-block: var(--tc-space-2xs); }
nav .spacer { flex: 1; }
.toggle {
  display: inline-flex; align-items: center; gap: var(--tc-space-2xs); cursor: pointer;
  border: 0; background: var(--tc-chip-fill);
  color: var(--tc-fg); border-radius: var(--tc-radius-pill);
  padding: var(--tc-space-2xs) var(--tc-space-sm); min-height: var(--tc-hit-target);
  font: inherit; font-size: var(--tc-text-xs);
}

/* ---- hero ---- */
.hero { position: relative; text-align: left; padding: var(--tc-space-xl) 0 var(--tc-space-2xl); }

/* ---- the tape band: the one graphic gesture on the page ----
   Before this, the hero was text on a gradient — nothing in it said "audio"
   until you read the words. A tape strip with a spool on one end, a speech
   envelope in the middle and a counter on the other says it before the copy
   does, and the glass form in front of it finally has something to be glass
   OVER. The strip is tilted 1.2deg because a rectangle exactly aligned to the
   grid reads as a table cell; a slight rake reads as an object lying there. */
.tape {
  position: relative; display: flex; align-items: center; gap: var(--tc-space-md);
  margin-block: 0 var(--tc-space-2xl); padding: var(--tc-space-md) var(--tc-space-lg);
  border-radius: var(--tc-radius-lg); transform: rotate(-1.2deg);
  background-image: var(--tc-tex-speckle), linear-gradient(180deg, rgba(16, 32, 46, 0.86), rgba(4, 8, 15, 0.94));
  background-size: 240px 240px, auto;
  background-blend-mode: overlay, normal;
  border: var(--tc-rule-width) solid var(--tc-glass-border);
  box-shadow: var(--tc-shadow-hero), var(--tc-inner-sheen);
}
/* It used to hang 3.2rem into the copy below it. That looked like depth in a
   screenshot and was a collision in a browser: the counter line landed on the
   spool and its underline cut across the bars. An overlap only reads as depth
   when the thing behind is unmistakably behind — here both were the same
   weight, so the eye read one mess instead of two layers. */
[data-theme="light"] .tape {
  background-image: var(--tc-tex-speckle), linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(233, 241, 246, 0.86));
  background-size: 240px 240px, auto;
  background-blend-mode: overlay, normal;
}
/* Spool: three blades off a conic sweep, hole punched with a mask. Turns once
   every 14s — slow enough to be ambient, mechanical enough to read as a motor. */
.spool {
  flex: none; inline-size: 74px; aspect-ratio: 1; border-radius: 50%; opacity: 0.85;
  background: conic-gradient(from 0deg, var(--tc-oxide), transparent 22%, var(--tc-oxide) 44%, transparent 66%, var(--tc-oxide) 88%, transparent);
  -webkit-mask: radial-gradient(circle, transparent 21%, #000 22%);
          mask: radial-gradient(circle, transparent 21%, #000 22%);
  filter: drop-shadow(0 0 12px rgba(255, 122, 69, 0.35));
}
.tape-strip {
  position: relative; flex: 1; min-width: 0; display: flex; align-items: center; gap: 2px;
  block-size: 76px; overflow: hidden;
}
.tape-strip i { flex: 1; min-width: 2px; border-radius: 1px; background: var(--tc-fg-faint); opacity: 0.55; }
.tape-strip i.on { background: var(--tc-accent); opacity: 1; box-shadow: var(--tc-glow-accent); }
.tape-strip .playhead {
  position: absolute; inset-block: 0; inset-inline-start: 0; inline-size: 2px; border-radius: 1px;
  background: var(--tc-cue); box-shadow: var(--tc-glow-cue);
}
.tape-time {
  flex: none; font-family: var(--tc-font-mono); font-stretch: 75%;
  font-size: clamp(20px, 2.4vw + 10px, 34px); font-weight: var(--tc-weight-medium);
  letter-spacing: 0.02em; color: var(--tc-tape-ink); font-variant-numeric: tabular-nums;
}
.tape-time .sep { opacity: 0.5; }   /* the colon of a VFD, not a typographic colon */
.hero > h1, .hero > .live-readout, .hero > .sub, .hero > .field { position: relative; z-index: 1; }

/* ---- the transport (design/transport.js) ----
   The band above is the CSS drawing of the same object: two reels, a strip, a
   counter. When the WebGL scene is available it takes over the drawing and the
   band hands over its own paint, so the page shows one machine rather than two
   illustrations of it. The counter stays DOM — real text stays real text.

   `html.gl` is set by the script and by nothing else, so with reduced motion,
   Save-Data, 2g, no WebGL, forced colours or a narrow viewport, everything below
   is inert and the band looks exactly as it always did. */
/* Its own name, not `.transport`: that class is the band's flex row in the
   specimen sheet, and a canvas inheriting `margin-block-start` from a rule written
   for a flex row sits 12px away from every coordinate the shader was given. */
.hero-scene {
  position: absolute; inset: 0; z-index: 0; display: block; margin: 0;
  inline-size: 100%; block-size: 100%; pointer-events: none;
}
/* The scene draws a straight run of tape; the band's rake existed to stop the
   CSS rectangle reading as a table cell, and an object drawn in perspective does
   not need it. */
@media (forced-colors: none) {
  html.gl .tape {
    transform: none;
    background-image: none; background-color: transparent;
    background-blend-mode: normal;
    box-shadow: none;
    border-color: var(--tc-rule);
  }
  /* Reels, envelope and playhead are drawn by the scene from here on; the
     counter, the spool-free flex layout and the border stay in the band. */
  html.gl .tape-strip i, html.gl .tape-strip .playhead, html.gl .spool { display: none; }
  html.gl .tape-strip { background: none; }
  /* The scene has to know what it is not allowed to brighten, so the copy is
     lifted above it explicitly rather than by document order. The scene itself
     is excluded by name: it is the one child that must stay out of the flow, and
     a blanket `> *` here turns the canvas into page content - which then feeds
     its own height back into the hero and the page grows without limit. */
  html.gl .hero > :not(.hero-scene) { position: relative; z-index: 1; }

  /* The counter keeps its own recess, and it is a CSS background rather than
     something the scene draws: a bezel painted under DOM text by a canvas makes
     the digits' contrast depend on what happens to show through it, and a
     decoration has no business deciding that. `--tc-bg-sunk` is the token for a
     surface pushed into the page, which is precisely what a counter window is. */
  html.gl .tape-time {
    background: var(--tc-bg-sunk);
    border-radius: var(--tc-radius-xs);
    padding-inline: var(--tc-space-sm);
    /* At least as much recess above and below as the contrast measurement looks
       outside the glyphs, so the backdrop an audit samples is the recess and not
       the oxide eight pixels away. */
    padding-block: var(--tc-space-xs);
  }
  html.gl .hero-scene { z-index: 0; }
}
/* The live count is a COUNTER, so it gets the counter language: bare type,
   hairline underneath, no container. It used to be an outlined pill, which is
   the shape every generated landing page puts its fake social proof in — and
   the pill was also costing contrast, since its own hairline is neither
   surface nor boundary. */
.live-readout {
  display: inline-flex; align-items: center; gap: var(--tc-space-xs);
  font-family: var(--tc-font-mono); font-size: var(--tc-text-2xs);
  letter-spacing: 0.04em; color: var(--tc-accent-text);
  border-block-end: var(--tc-rule-width) solid var(--tc-accent-deep);
  padding-block-end: var(--tc-space-3xs); margin-block-end: var(--tc-space-md);
}
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--tc-dot); box-shadow: var(--tc-glow-accent); flex: none; }
.hero h1 {
  font-size: var(--tc-text-display); letter-spacing: var(--tc-tracking-hero);
  font-stretch: var(--tc-stretch-display); font-weight: var(--tc-weight-heavy);
  margin-block-start: 0;   /* the band above already carries the space */
}
.hero .live-readout { margin-block-start: 0; }
/* Weight, not colour, does the emphasis. The accent line drops to the bottom
   of the weight range at the top of the size range — that 200-vs-800 jump on
   one line is where the drama comes from, and it survives a theme switch, a
   colour-blind user and a printer with no ink far better than a coloured word
   does. The tint is kept because this IS a product about light. */
.hero h1 em { font-style: normal; font-weight: var(--tc-weight-thin); color: var(--tc-accent-text); text-shadow: var(--tc-glow-accent); }

/* ---- the form: a real one, because this control is the product ---- */
/* Label, control row, note — the three parts of one field, stacked. The row
   itself is only [control][button][button], so align-items: center puts the
   buttons on the INPUT's axis. It used to include the label and the note inside
   the same flex item, which left the buttons 22px above the thing they submit.
   A button that does not sit on its field's line reads as carelessness. */
.cta-row { display: flex; gap: var(--tc-space-sm); flex-wrap: wrap; align-items: center; }
.field { min-width: 0; }
.cta-row .field-control { flex: 1 1 16rem; min-width: 0; }
.hero .field { margin-block-start: var(--tc-space-lg); }
.field label { display: block; margin-block-end: var(--tc-space-2xs); }
.field-control {
  display: flex; align-items: center; gap: var(--tc-space-sm);
  padding: var(--tc-space-sm) var(--tc-space-md);
  border-radius: var(--tc-radius-pill); min-height: var(--tc-hit-target);
  background-color: var(--tc-glass-bg);
  -webkit-backdrop-filter: var(--tc-glass-filter); backdrop-filter: var(--tc-glass-filter);
  border: var(--tc-rule-width) solid var(--tc-glass-stroke);
  box-shadow: var(--tc-inner-sheen);
  transition: border-color var(--tc-dur-fast) var(--tc-ease-out-soft);
}
/* A field already inside a glass card gets a recessed fill instead of a second
   lens. Stacking two backdrop filters is both a contrast double-charge and,
   visually, a pill inside a pill — which is what the first pass looked like. */
.outcome .field-control { background-color: var(--tc-field-fill); -webkit-backdrop-filter: none; backdrop-filter: none; }
.field-control:focus-within { border-color: var(--tc-accent-deep); }
.field-control .ico { color: var(--tc-fg-faint); flex: none; }
.field-control input {
  flex: 1; min-width: 0; /* without this the input's default size blows the row */
  border: 0; background: none; color: var(--tc-fg);
  font: inherit; font-size: var(--tc-text-base); /* 16px: below that iOS zooms on focus */
  padding: 0; border-radius: 0; appearance: none;
}
.field-control input::placeholder { color: var(--tc-fg-faint); }
.field-hint { margin-block-start: var(--tc-space-2xs); color: var(--tc-fg-faint); font-size: var(--tc-text-xs); }
.field[data-state="invalid"] .field-control { border-color: var(--tc-hazard); }
.field[data-state="invalid"] .field-control .ico { color: var(--tc-hazard); }
.field-error {
  display: flex; align-items: center; gap: var(--tc-space-2xs);
  margin-block-start: var(--tc-space-xs); padding: var(--tc-space-xs) var(--tc-space-sm);
  border-radius: var(--tc-radius-sm);
  color: var(--tc-hazard); background: var(--tc-hazard-fill); font-size: var(--tc-text-sm);
  /* Full measure, same edges as the field above it. A message that hugs its
     own text width makes a ragged second edge under the input, and the eye
     reads that as a second object rather than as belonging to the field. */
}

/* ---- buttons ---- */
.btn {
  display: inline-flex; align-items: center; gap: var(--tc-space-2xs);
  border-radius: var(--tc-radius-pill); padding: var(--tc-space-sm) var(--tc-space-lg);
  min-height: var(--tc-hit-target); font: inherit; font-size: var(--tc-text-sm);
  font-weight: var(--tc-weight-medium); cursor: pointer;
  border: var(--tc-rule-width) solid transparent; text-decoration: none;
  transition: transform var(--tc-dur-fast) var(--tc-ease-out-soft), box-shadow var(--tc-dur-fast) var(--tc-ease-out-soft);
}
.btn { position: relative; overflow: hidden; }
.btn-glass { background-color: var(--tc-glass-bg); border-color: var(--tc-glass-stroke); color: var(--tc-fg); -webkit-backdrop-filter: var(--tc-glass-filter); backdrop-filter: var(--tc-glass-filter); }
.btn-primary { background: var(--tc-accent); color: var(--tc-accent-on-fill); box-shadow: var(--tc-glow-accent); }
.btn-ghost { background: transparent; border-color: var(--tc-rule-strong); color: var(--tc-fg-muted); }
.btn:active { transform: scale(var(--tc-scale-press)); }

/* ---- three signals, each one in the place it actually appears ----
   live    = the hero readout (bare mono, hairline under, mint dot)
   cue     = the cyan bar on the waveform, with its timecode next to it
   counter = the tape counter in the transport row
   hazard  = the clipped corner on the card that is locked

   There used to be a fourth thing here: a row of four differently-coloured
   little labels, shown together because they were a design system. Sticker
   sheets are what a generator produces when it has components but no product
   — a player does not display "live", "cue" and "drm_locked" side by side,
   so neither does this. Each signal now sits where a user would meet it. */
.tag {
  display: inline-flex; align-items: center; gap: var(--tc-space-2xs);
  font-family: var(--tc-font-ui); font-weight: var(--tc-weight-medium);
  font-size: var(--tc-text-xs); letter-spacing: var(--tc-tracking-body);
}
/* The clipped corner is the hazard geometry — that notch is why this tag
   looks cut rather than framed. A 3px coloured bar down the left edge would
   have been the shortcut, and it is the most recognisable tell of
   machine-generated UI there is. */
.tag-hazard {
  color: var(--tc-hazard); background: var(--tc-hazard-fill);
  clip-path: polygon(0 0, calc(100% - 9px) 0, 100% 9px, 100% 100%, 0 100%);
  padding: var(--tc-space-2xs) var(--tc-space-sm) var(--tc-space-2xs) var(--tc-space-xs);
}
.tag-hazard .ico { flex: none; }

/* ---- transport: what a player really shows ---- */
.wave-wrap { position: relative; }
.cue-flag {
  position: absolute; inset-block-end: var(--tc-space-2xs); transform: translateX(-50%);
  font-family: var(--tc-font-mono); font-size: var(--tc-text-2xs); letter-spacing: 0.04em;
  color: var(--tc-cue-text); font-variant-numeric: tabular-nums;
}
div.transport { display: flex; align-items: center; gap: var(--tc-space-sm); margin-block-start: var(--tc-space-sm); flex-wrap: wrap; }
.play-btn {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  inline-size: var(--tc-hit-target); block-size: var(--tc-hit-target); border-radius: 50%;
  border: 0; cursor: pointer; background: var(--tc-accent); color: var(--tc-accent-on-fill);
  box-shadow: var(--tc-glow-accent);
  transition: transform var(--tc-dur-fast) var(--tc-ease-out-soft);
}
.counter {
  font-family: var(--tc-font-mono); font-size: var(--tc-text-xs); line-height: var(--tc-leading-mono);
  letter-spacing: 0.04em; color: var(--tc-fg-muted); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.counter b { color: var(--tc-fg); font-weight: var(--tc-weight-medium); }
/* The wire code, shown as-is. This is a developer product; the FE state and
   the API code should be the same string in the screenshot. */
.code-chip {
  justify-self: start;
  display: inline-block; font-family: var(--tc-font-mono); font-size: var(--tc-text-2xs);
  color: var(--tc-fg-muted); background: var(--tc-chip-fill);
  border-radius: var(--tc-radius-xs);
  padding: var(--tc-space-3xs) var(--tc-space-2xs); letter-spacing: 0.02em;
}
.code-chip[data-tone="hazard"] { color: var(--tc-hazard); background: var(--tc-hazard-fill); }

/* ---- transcript card ---- */
.transcript { padding: var(--tc-space-lg); }
.line {
  display: grid; grid-template-columns: auto auto minmax(0, 1fr);
  gap: var(--tc-space-xs) var(--tc-space-sm);
  padding: var(--tc-space-xs) var(--tc-space-sm); border-radius: var(--tc-radius-sm);
  font-size: var(--tc-text-sm);
}
/* Timecodes are Martian Mono — that is the third voice in the system, and a
   transcript that sets them in the UI font throws away the alignment the
   mono exists for. `min-width: 0` + the `auto` tracks keep a long timestamp
   from eating the text column. */
.line .tc {
  font-family: var(--tc-font-mono); font-size: var(--tc-text-xs);
  line-height: var(--tc-leading-mono); color: var(--tc-cue-text); white-space: nowrap;
}
.line .who {
  font-family: var(--tc-font-label); font-stretch: var(--tc-label-stretch);
  font-weight: var(--tc-weight-bold); font-size: var(--tc-text-2xs);
  text-transform: uppercase; letter-spacing: var(--tc-tracking-label);
  color: var(--tc-fg-muted); align-self: center; white-space: nowrap;
}
.line .txt { color: var(--tc-fg-muted); min-width: 0; max-width: var(--tc-measure); }
.line.active { background: var(--tc-glass-bg-strong); box-shadow: var(--tc-inner-sheen); }
.line.active .txt { color: var(--tc-fg); }
.transcript[data-state="empty"] .empty { display: grid; gap: var(--tc-space-xs); justify-items: start; padding: var(--tc-space-lg) 0; }
.empty { display: none; }

/* ---- waveform ---- */
.wave { display: flex; align-items: center; gap: 3px; height: 84px; padding: var(--tc-space-md); }
.wave i { flex: 1; border-radius: var(--tc-radius-pill); background: var(--tc-wave-bar); min-height: 6px; }
.wave i.played { background: var(--tc-wave-bar-played); box-shadow: var(--tc-glow-accent); }
.wave i.cuebar { background: var(--tc-cue); box-shadow: var(--tc-glow-cue); }

/* ---- outcome states (the §3 contract, rendered) ----
   These were three cards in a row, and the row was chaos: a form, a radio
   list and a paragraph are three different anatomies, and forcing them to the
   same width left each one breaking on its own axis — the picker wrapped to
   three lines per row, the error chip ended mid-sentence, the columns bottomed
   out at random heights. They are not three cards, they are three STATES OF
   ONE STEP, so they are one stack with a shared two-column skeleton: the code
   lives on a fixed left rail, everything else on a single content edge. Now
   every heading, body and button in the section starts at the same x. */
.states { display: grid; gap: var(--tc-space-md); }
.outcome {
  display: grid; gap: var(--tc-space-sm); align-content: start;
  grid-template-columns: minmax(0, 1fr);
  padding: var(--tc-space-lg);
}
@media (min-width: 46em) {
  .outcome {
    grid-template-columns: 13rem minmax(0, 1fr);
    column-gap: var(--tc-space-xl); align-items: start;
  }
  .outcome > .code-chip { grid-column: 1; }
  .outcome > :not(.code-chip) { grid-column: 2; }
}
.outcome h3 { font-family: var(--tc-font-display); font-size: var(--tc-text-lg); font-weight: var(--tc-weight-bold); letter-spacing: var(--tc-tracking-display); line-height: var(--tc-leading-snug); }
.outcome p { font-size: var(--tc-text-sm); color: var(--tc-fg-muted); text-wrap: pretty; max-inline-size: 58ch; }
.outcome .actions { display: flex; gap: var(--tc-space-sm); flex-wrap: wrap; }
/* Severity without a stripe: the code chip goes hazard, the heading goes
   hazard, and the card itself keeps its glass. Nothing draws a rule down the
   inside edge of a panel. */
.outcome[data-severity="hazard"] h3 { color: var(--tc-hazard); }
.picker { display: grid; gap: var(--tc-space-xs); border: 0; padding: 0; margin: 0; }
.picker legend { padding: 0; }
.pick {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center;
  gap: var(--tc-space-sm);
  padding: var(--tc-space-xs) var(--tc-space-sm); border-radius: var(--tc-radius-sm);
  background-color: var(--tc-chip-fill); border: var(--tc-rule-width) solid var(--tc-rule);
  font-size: var(--tc-text-sm); min-height: var(--tc-hit-target); cursor: pointer;
}
.pick input { accent-color: var(--tc-accent); width: 16px; height: 16px; margin: 0; }
.pick .title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick .meta { color: var(--tc-fg-faint); font-family: var(--tc-font-mono); font-size: var(--tc-text-2xs); white-space: nowrap; }
.code-table { width: 100%; border-collapse: collapse; font-size: var(--tc-text-sm); }
.code-table th { text-align: start; }
.code-table td { padding: var(--tc-space-2xs) var(--tc-space-xs) var(--tc-space-2xs) 0; vertical-align: top; color: var(--tc-fg-muted); }
.code-table caption { text-align: start; }

/* ---- the pointer light ----
   Frosted glass is invisible when what sits behind it is a flat gradient: the
   filter has nothing to work on. A light that follows the pointer gives every
   panel something to bend, which is the cheapest way to make transparency
   READABLE rather than merely present. Its ceiling lives in tokens.css, because
   a bloom that travels has to clear the contrast floor everywhere it can reach. */
.aurora .spot {
  width: auto; height: auto; inset: 0; border-radius: 0; filter: blur(60px);
  background: radial-gradient(520px circle at var(--tc-px, 50%) var(--tc-py, 10%), var(--tc-accent), transparent 62%);
  opacity: var(--tc-spot-opacity);
}

/* ---- section numbers, in the machine's own voice ----
   Tracked-out CAPS eyebrows were the AI tell. The opposite habit is the one this
   product is built on: position on a reel. CSS counters, so the numbering cannot
   drift out of sync when a section is added or moved. */
main { counter-reset: sec; }
section > h2 { counter-increment: sec; }
section > h2::before {
  content: counter(sec, decimal-leading-zero) " /";
  display: block; font-family: var(--tc-font-mono); font-stretch: 75%;
  font-size: var(--tc-text-sm); font-weight: 400; letter-spacing: 0.02em;
  color: var(--tc-oxide-text); margin-block-end: var(--tc-space-3xs);
}

/* ---- the console: the page doing the product ----
   A preview that describes a live transcript with a static screenshot is a
   promise. This one runs the sequence instead, because the sequence is the
   design: resolve, then first lines, then fill as it writes. */
.console { display: grid; gap: var(--tc-space-md); padding: var(--tc-space-lg); }
.console-head { display: flex; align-items: center; gap: var(--tc-space-xs) var(--tc-space-md); flex-wrap: wrap; }
.console-url {
  flex: 1 1 12rem; min-width: 0; font-family: var(--tc-font-mono); font-size: var(--tc-text-xs);
  color: var(--tc-fg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.console-meta { color: var(--tc-fg-faint); }
.console-lines { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--tc-space-3xs); }
/* Without script the console shows its finished state. Hiding the lines and
   then revealing them is an enhancement, so the default has to be readable. */
html.js .console-lines li { opacity: 0; transform: translateY(8px); }
html.js .console-lines li.on { opacity: 1; transform: none; }
.console .wave { margin-block: 0; height: 64px; padding: 0; }
.console .wave i { min-height: 4px; }

/* Speaker turns carry the hue. The caps stay — a transcript set in all-caps
   speaker labels is a real convention from the script room, and hue alone would
   not survive a colour-blind reader, so it is hue AND case AND weight. */
.line[data-sp="1"] .who { color: var(--tc-who-1); }
.line[data-sp="2"] .who { color: var(--tc-who-2); }
.line[data-sp="3"] .who { color: var(--tc-who-3); }
.code-chip[data-tone="ok"] { color: var(--tc-accent-text); background: var(--tc-chip-fill); }
.code-chip[data-tone="warn"] { color: var(--tc-oxide-text); background: var(--tc-chip-fill); }
.probe-note { display: flex; align-items: center; gap: var(--tc-space-xs); min-block-size: 1.6em; }
.probe-note .code-chip { flex: none; }
/* The `hidden` attribute loses to `.code-chip { display: inline-flex }` on the
   specificity ladder, so an empty field would wear a RESOLVED chip it never
   earned. Any class that sets display has to honour hidden explicitly. */
.code-chip[hidden] { display: none; }

/* ---- the leader ----
   The strip of coloured tape at the head of a reel. One warm, loud, full-bleed
   block at the end of a page that has been cool and restrained the whole way is
   the reason to have introduced a warm hue at all — and the cut corner is the
   splice, not a decorative clip-path nobody would choose twice. */
/* Named wrong is the same as absent: a mint alias that does not exist resolves to
   nothing, which invalidates the whole declaration and leaves the section with no
   background at all — measured, not guessed. The aurora primitives are
   `--tc-aurora-*`; the theme-facing aliases are `--tc-accent`, `--tc-violet`,
   `--tc-coral`, `--tc-oxide`. The leader deliberately uses the RAW mint, not
   `--tc-accent`, because in light mode `--tc-accent` is deep teal and the splice
   would disappear. */
/* One colour, like the real thing. A gradient that travels all the way to mint
   interpolates through khaki and reads as generated wallpaper; leader tape is
   oxide, and the mint enters as a SPLICE — a narrow diagonal joint where the
   leader meets the tape, not a wash. Every ink pixel therefore sits on solid
   oxide: the harness samples the band directly (a declared surface patch in
   the top padding — the bottom bleeds over the footer, so a patch there would
   measure the footer's near-black, not oxide) and reads 9.80:1 under the h2
   and 7.13:1 under the 0.82-opacity paragraph, in BOTH themes. The darkest
   stop was #E4652F at 4.65 — legal and one hairline from not being; #EF7540
   buys the margin the texture's mottle actually spends. A gradient blend
   between two hues could not make any of these claims, because between the
   stops the colour is neither. */
.leader {
  margin-block: var(--tc-space-xl) calc(-1 * var(--tc-space-3xl));
  padding: var(--tc-space-2xl) var(--tc-space-xl) var(--tc-space-3xl);
  background-image:
    var(--tc-tex-speckle), var(--tc-tex-mottle),
    linear-gradient(104deg, transparent 0 80%, rgba(61, 255, 197, 0.55) 86%, transparent 93%),
    linear-gradient(174deg, #FF8B55, var(--tc-aurora-oxide) 46%, #EF7540);
  background-size: auto, 240px 240px, 480px 240px, auto;
  background-blend-mode: normal, overlay, soft-light, normal;
  color: var(--tc-accent-on-fill);
  clip-path: polygon(0 14px, 100% 0, 100% calc(100% - 14px), 0 100%);
}
.leader > h2 { counter-increment: none; color: inherit; max-inline-size: 22ch; }
.leader > h2::before { content: none; }
.leader p { color: var(--tc-accent-on-fill); opacity: 0.82; max-inline-size: var(--tc-measure-lead); }
/* A near-black button, in both themes: on rust-orange, a mint pill would be the
   same hue family wearing a hat, and a white one would be a hole in the tape. */
.leader .btn { background: var(--tc-abyss); color: var(--tc-aurora-mint); border: 0; box-shadow: none; }
.leader .btn::after { display: none; }
.leader .code-chip { color: var(--tc-accent-on-fill); background: rgba(4, 17, 12, 0.14); }

/* ---- the tape path: the pipeline drawn the way the deck it runs on would be ----
   A screenshot of a UI explains nothing; a schematic does. The link enters as a
   tag, threads three heads — resolve, fetch, transcribe — and leaves as a
   transcript stack, over a ruler that carries the episode's real chapter marks.
   It is line art: strokes here are DRAWING, the register of a technical
   diagram, not chrome pretending a label needs a border. */
.diagram { margin-block-start: var(--tc-space-lg); }
.diagram svg { display: block; inline-size: 100%; block-size: auto; }
.diagram .rb-fill { fill: var(--tc-ribbon); }
.diagram .rb-edge { stroke: var(--tc-fg-faint); stroke-opacity: 0.55; }
.diagram .feed { stroke: var(--tc-accent); stroke-opacity: 0.6; }
.diagram .head-fill { fill: var(--tc-bg); stroke: var(--tc-fg-faint); stroke-opacity: 0.7; }
.diagram .drop { stroke: var(--tc-fg-faint); stroke-opacity: 0.4; stroke-dasharray: 2 6; }
.diagram .st-num { font-family: var(--tc-font-mono); font-weight: var(--tc-weight-bold); font-size: 11px; fill: var(--tc-oxide-text); }
.diagram .st-name { font-family: var(--tc-font-ui); font-weight: var(--tc-weight-medium); font-size: 13.5px; fill: var(--tc-fg); }
.diagram .st-desc { font-family: var(--tc-font-ui); font-size: 12px; fill: var(--tc-fg-muted); }
.diagram .st-detail, .diagram .tick-label { font-family: var(--tc-font-mono); font-size: 10.5px; fill: var(--tc-fg-faint); letter-spacing: 0.02em; }
.diagram .tick { stroke: var(--tc-fg-faint); stroke-opacity: 0.4; }
.diagram .tick-maj { stroke-opacity: 0.7; }
.diagram .chap { fill: var(--tc-accent); }
.diagram .page-fill { fill: var(--tc-chip-fill); }
.diagram .page-line { fill: var(--tc-fg-faint); opacity: 0.55; }
.diagram .tag-fill { fill: var(--tc-chip-fill); }
.diagram figcaption { margin-block-start: var(--tc-space-sm); color: var(--tc-fg-faint); font-size: var(--tc-text-sm); max-inline-size: 58ch; }

/* The clerk's stamp. FILED is what a clerk does to a transcript; the oxide ink
   and the rough displacement are what make it a stamp instead of a badge. The
   outline box is the OBJECT — a rubber stamp is an outline by nature — and the
   turbulence is what keeps it from reading as UI chrome. Without JS or with
   reduced motion it simply sits there, already filed. */
.console-end { display: flex; align-items: center; justify-content: space-between; gap: var(--tc-space-md); flex-wrap: wrap; margin-block-start: var(--tc-space-sm); }
.console-end .console-meta { margin-block: 0; }
.stamp { color: var(--tc-oxide-text); inline-size: 106px; block-size: auto; flex: none; margin-inline-start: auto; transform: rotate(-6deg); opacity: 0.92; }
.stamp text { font-family: var(--tc-font-mono); fill: currentColor; }
.stamp .stamp-word { font-size: 17px; font-weight: var(--tc-weight-bold); letter-spacing: 0.22em; }
.stamp .stamp-sub { font-size: 7.5px; letter-spacing: 0.14em; }
[data-theme="light"] .stamp { mix-blend-mode: multiply; }
html.js #console:not(.done) .stamp { opacity: 0; transform: rotate(-6deg) scale(1.7); }

/* ---- motion demos ---- */
.demo-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); gap: var(--tc-space-md); }
.demo { padding: var(--tc-space-lg); }
.shimmer { position: relative; overflow: hidden; padding: var(--tc-space-lg); }
.shimmer::after { content: none; }
.float { animation: none; }

footer { margin-block-start: var(--tc-space-3xl); text-align: center; position: relative; padding-block-start: var(--tc-space-lg); }
footer::before { content: ""; position: absolute; inset-block-start: 0; inset-inline: 10%; height: 1px; background: linear-gradient(90deg, transparent, var(--tc-rule-strong) 30%, var(--tc-accent-deep) 50%, var(--tc-rule-strong) 70%, transparent); opacity: .6; }

/* ---- hover, gated ----
   These were un-gated, which on a touch device leaves a card stuck 4px off
   the surface after a tap, because "tap" fires :hover and nothing clears it. */
@media (hover: hover) and (pointer: fine) {
  nav a:hover { color: var(--tc-fg); }
  .lift:hover { transform: translateY(-4px); box-shadow: var(--tc-shadow-lift), var(--tc-inner-sheen); }
  .btn-primary:hover { transform: translateY(-2px); }
  .pick:hover { border-color: var(--tc-rule-strong); }
}

/* ---- motion, opt-in ----
   Written as no-preference rather than a reduce override, so anything added
   later is gated by default. The page claims "all motion respects
   reduced-motion"; this is what makes that claim true instead of aspirational.
   Static fallbacks: the equalizer keeps played bars solid mint, the shimmer
   keeps a still sheen, the buoyant card just sits at depth. */
@media (prefers-reduced-motion: no-preference) {
  .aurora .a1 { animation: drift var(--tc-dur-drift) var(--tc-ease-in-out-soft) infinite alternate; }
  .aurora .a2 { animation: drift calc(var(--tc-dur-drift) * 1.6) var(--tc-ease-in-out-soft) infinite alternate-reverse; }
  .aurora .a3 { animation: drift calc(var(--tc-dur-drift) * 2.2) var(--tc-ease-in-out-soft) infinite alternate; }
  .dot { animation: pulse 1.6s var(--tc-ease-in-out-soft) infinite; }
  .console-lines li { transition: opacity var(--tc-dur-med) var(--tc-ease-out-soft), transform var(--tc-dur-med) var(--tc-ease-out-soft); }
  .console .wave i { transition: background-color var(--tc-dur-fast) linear, box-shadow var(--tc-dur-fast) linear; }
  .demo .wave i { animation: eq 1.4s var(--tc-ease-in-out-soft) infinite; transform-origin: bottom; }
  .demo .wave i:nth-child(2n) { animation-delay: .12s; }
  .demo .wave i:nth-child(3n) { animation-delay: .27s; }
  .demo .wave i:nth-child(5n) { animation-delay: .41s; }
  .shimmer::after {
    content: ""; position: absolute; inset: 0; transform: translateX(-100%);
    background: linear-gradient(105deg, transparent 40%, var(--tc-sheen) 50%, transparent 60%);
    animation: sweep 2.8s var(--tc-ease-in-out-soft) infinite;
  }
  .float { animation: floaty 6s var(--tc-ease-in-out-soft) infinite alternate; }
  /* The transport: playhead across the strip, spool turning, the VFD colon
     blinking. Three loops at three unrelated periods (26s / 14s / 1.8s) so the
     hero never visibly repeats — a page whose animations tick in lockstep is
     the difference between an ambient product and a screensaver. */
  .tape-strip .playhead { animation: play 26s linear infinite; }
  .spool { animation: spin 14s linear infinite; }
  .tape-time .sep { animation: blink 1.8s steps(1, end) infinite; }
  .btn-primary::after {
    content: ""; position: absolute; inset-block: 0; inset-inline-start: -45%; inline-size: 45%;
    background: linear-gradient(100deg, transparent, var(--tc-glass-edge), transparent);
    transform: translateX(-140%); animation: sweep 7s var(--tc-ease-in-out-soft) infinite;
  }
  /* The diagram's tape actually moves, at 17s — a fourth unrelated period, so
     even with the hero in view nothing on the page ticks together. */
  .diagram .feed { animation: feed 17s linear infinite; }
  html.js #console.done .stamp { animation: stamp-in 0.28s cubic-bezier(0.2, 1.3, 0.4, 1) both; }
}
@keyframes drift { from { transform: translate3d(0,0,0) scale(1); } to { transform: translate3d(6vw, 4vh, 0) scale(1.15); } }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }
@keyframes eq { 0%,100% { transform: scaleY(.45); } 50% { transform: scaleY(1); } }
@keyframes sweep { to { transform: translateX(100%); } }
@keyframes floaty { from { transform: translateY(0); } to { transform: translateY(-10px); } }
@keyframes play { from { inset-inline-start: 0; } to { inset-inline-start: 100%; } }
@keyframes spin { to { transform: rotate(1turn); } }
@keyframes blink { 0%, 55% { opacity: 0.5; } 56%, 100% { opacity: 0.12; } }
@keyframes feed { to { stroke-dashoffset: -200; } }
@keyframes stamp-in {
  /* `from` must be explicit: the moment .done lands, the base rule already says
     opacity .92, and a to-only keyframe would animate from the settled state —
     no stamp, just a blink. */
  from { opacity: 0; transform: rotate(-6deg) scale(1.7); }
  to { opacity: 0.92; transform: rotate(-6deg) scale(1); }
}

/* ---- narrow: nothing may need more width than it is given ---- */
@media (max-width: 34em) {
  .hero { padding-block: var(--tc-space-lg) var(--tc-space-xl); }
  /* The old grid was `72px 84px 1fr`, which at 320px left the text column
     59px wide and every line clipped. The speaker row and the text get their
     own rows instead of competing for a fixed track. */
  .line { grid-template-columns: auto auto; }
  .line .txt { grid-column: 1 / -1; }
  .cta-row > .btn { flex: 1 1 9rem; justify-content: center; }
  .wave { height: 64px; padding: var(--tc-space-sm); }
  /* The spool is decoration at a size where decoration costs space. The strip
     and the counter stay — at 320px those two still say what this product is. */
  .spool { display: none; }
  .tape { margin-inline: calc(-1 * var(--tc-space-sm)); padding: var(--tc-space-sm) var(--tc-space-md); }
  nav { border-radius: var(--tc-radius-xl); }
  main { padding-inline: var(--tc-space-sm); }
}

@media (forced-colors: active) {
  .glass, .btn, .pick, .field-control, .tape { border: var(--tc-rule-width) solid CanvasText; }
  .code-chip, .tag-hazard { forced-color-adjust: none; }
  /* Light, grain and the tape strip are all conveying nothing that text does
     not already say, and in forced-colors mode they render as flat blocks. */
  .aurora, .grain, .spool, .tape, .diagram svg, .stamp { display: none; }
  :focus-visible { outline: 3px solid Highlight; }
}
