/* BackstagePass components — the WIRE round (2026-08-19 overnight).
   HOUSE LIGHTS product language (Big Shoulders Display marquee caps over
   Archivo, IBM Plex Mono numbers) + the brand-gold DOOR + PAPER covenant,
   on the tokens in tokens.css. Source: design-imports/WIRE-Directions.html,
   reconciled in DESIGN-FEATURE-RECONCILIATION.md.
   Class names are stable — the suite asserts several (.topbar .wall-tile
   .chip--live .density .heart-float .peak-jump .chat__*) — the styling under
   them is the adopted language. Laws that survive every mood:
   one primary action per screen · live-red strictly for live/cut · hairlines,
   not boxes · no gradient headings · no glow shadows · gold means money and
   the Pass in the house, the brand itself at the door. */

/* THE STUDIO FENCE (2026-09-02) — every rule written for ONE creator screen is
   prefixed `body.studio`: the studio list, the tab rail, the Library (filters,
   cards, clips, the generate sheet and its caption preview), the encore meta
   row, moments, transcript lines, the members roster, the spend and link rows,
   the selection bar and the GO LIVE pair. Generic names on studio-only rules
   are exactly what leaked before: control-room.css's unscoped .chip silently
   restyled every chip in the product (found 2026-08-18). Watch the comment
   markers when editing this block — a stray terminator inside it drops the
   rule underneath, which cost a browser-caught 10px overflow on the way in.
   The class is hung on the body by
   ApplicationHelper#body_class for Studio::BaseController surfaces only — the
   same boundary as the door — and it COMPOSES with the mood ("" house / door /
   paper) rather than replacing it, so theme-color still reads the mood alone.

   Deliberately NOT fenced, because a fan surface must keep working the day it
   uses one: house vocabulary and its modifiers (the buttons, the cards, the
   chip states ok/warn/x/lit/dim, the segmented control, the menu, the rowlist,
   the small pass thumbnail), the utilities (.sr-only, .plain, .code-snippet),
   and the shared .checklist (replays/stub.html.erb renders .checklist__item).
   test/studio_scope_law_test.rb holds both halves: nothing fenced may be used
   off a studio screen, and a new rule for a fenced class may not ship
   unprefixed. */

* { box-sizing: border-box; }
html { color-scheme: light dark; overflow-x: clip; } /* backstop only — real overflows get fixed at the source */
/* A custom element is display:inline until something says otherwise, and Turbo
   ships no stylesheet. A frame that wraps a tab rail and its panel has to be a
   block or the two are laid out inside an inline box. */
turbo-frame { display: block; }

/* THE WHOLE-PIXEL RULE (r8) — the reason it is universal is in tokens.css.
   Short version: an inherited RATIO (--lh) becomes a whole-pixel line box on
   every element, so blocks land on the pixel grid and a 1px hairline on a
   rounded box paints as ONE row at full strength instead of two at half. */
*, *::before, *::after { line-height: var(--lh-snap); }
/* Form controls keep the UA's `normal` — they are sized by padding around a
   text metric, and handing them a 1.55 line box grows every field on the
   platform by ~6px. A field that WANTS its own leading writes both halves
   (see input.field-row). */
input, select, textarea { line-height: normal; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 15px;
  --lh: 1.55; line-height: var(--lh-snap);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

@view-transition { navigation: auto; }

/* TURBO DRIVE'S OWN LOADING BAR, IN THE HOUSE ACCENT (Maaz 2026-09-12).
   The thin line that runs across the top of the page while a studio rail link
   is in flight is `.turbo-progress-bar`, which Turbo draws itself and paints
   #0076ff — a stock blue, in a product with one accent. This recolours it and
   nothing else: no second progress UI, no height, no shadow, and the
   transition and the trickle stay Turbo's (which is also why there is nothing
   to add for prefers-reduced-motion — the house's own rules govern what
   animates, and a 300ms width tween on a 3px line is the one Turbo ships).

   ONE RULE IS ENOUGH because of the ORDER, not the specificity: Turbo installs
   its stylesheet with `insertBefore(el, document.head.firstChild)` (verified in
   turbo.min.js 2.0.23), so it is always ahead of ours in the head and an
   equal-specificity selector here wins on source order.

   UNFENCED on purpose — the bar belongs to every Turbo page in the product,
   fan side included, so a `body.studio` prefix would leave it blue everywhere
   a member can see it (studio_scope_law's law 2).

   And the browser's OWN load indicator — Safari's and Chrome's — is not ours
   and is untouched. */
.turbo-progress-bar { background: var(--gold); }

/* ---------- house lights: one element at a time, nothing bounces ---------- */
@keyframes house-fade { from { opacity: 0; } to { opacity: 1; } }
main.shell > * { animation: house-fade var(--dur) ease-out backwards; }
main.shell > :nth-child(2) { animation-delay: 90ms; }
main.shell > :nth-child(3) { animation-delay: 180ms; }
main.shell > :nth-child(4) { animation-delay: 270ms; }
main.shell > :nth-child(n+5) { animation-delay: 360ms; }

/* ---------- type: the marquee voice ---------- */
h1, h2 {
  margin: 0; font-family: var(--display); font-weight: 700;
  letter-spacing: 0.01em; text-transform: uppercase;
  text-wrap: balance; --lh: 1.02; line-height: var(--lh-snap);
}
h1 { font-size: clamp(2.2rem, 5.6vw, 3.4rem); font-weight: 800; --lh: 0.98; line-height: var(--lh-snap); }
h2 { font-size: 1.6rem; }
h3 { margin: 0; font-family: var(--sans); font-weight: 700; font-size: 1.02rem;
  letter-spacing: 0; text-wrap: balance; --lh: 1.25; line-height: var(--lh-snap); }
p { margin: 0; }
/* Free text — a creator's name, a stream title, a bio, a pasted URL — must
   break a too-long unbroken token instead of pushing the page sideways on a
   phone (measured: creator profile at 320px). */
h1, h2, h3, p, .cluster > b { overflow-wrap: anywhere; }
a { color: inherit; text-decoration: none; }

/* the label voice: letterspaced caps — the WIRE's kicker over every screen */
.eyebrow {
  font-family: var(--sans); font-size: 0.66rem; font-weight: 600;
  letter-spacing: 0.22em; text-transform: uppercase; color: var(--accent);
}
.label {
  font-family: var(--sans); font-size: 0.62rem; font-weight: 600;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--dim);
}
/* the stamp voice: mono meta — dates, house lines, foot-notes */
.meta {
  font-family: var(--mono); font-weight: 500; font-size: 0.66rem;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--dim);
}
.meta--accent { color: var(--accent); }
/* the spoken voice: plain sentences under the marquee */
.deck {
  font-family: var(--sans); font-weight: 400;
  font-size: clamp(0.95rem, 1.8vw, 1.08rem); --lh: 1.65; line-height: var(--lh-snap); color: var(--muted);
}
.headline { font-family: var(--display); font-weight: 800; --lh: 0.95; line-height: var(--lh-snap);
  text-transform: uppercase; letter-spacing: 0.01em;
  font-size: clamp(2.6rem, 7.2vw, 5.4rem); text-wrap: balance; }
.headline--md { font-size: clamp(1.9rem, 4.2vw, 3rem); --lh: 1; line-height: var(--lh-snap); font-weight: 700; }
.muted { color: var(--muted); }
.dim { color: var(--dim); }
.small { font-size: 0.82rem; }
.num { font-variant-numeric: tabular-nums lining-nums; }
/* A copy-paste code block (a curl call, an embed snippet). It WRAPS — a
   horizontal scrollbar is never the answer (Todd, 2026-08-24): pre-wrap keeps
   the newlines and indentation, overflow-wrap breaks an over-long token (a
   bearer key, a URL) so the block always fits its column at any width. */
.code-snippet {
  padding: 12px; margin: 0; background: var(--surface-2); border-radius: var(--radius);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
/* the moments where the number IS the copy: marquee 800, lit tungsten */
.figure { font-family: var(--display); font-weight: 800; color: var(--accent);
  font-variant-numeric: tabular-nums lining-nums; letter-spacing: 0.01em; }
/* the preflight number block — display 800 with its small sans unit */
.marquee-num { display: flex; align-items: baseline; gap: 10px;
  font-family: var(--display); font-weight: 800; color: var(--accent);
  font-size: clamp(2.7rem, 8vw, 3.6rem); --lh: 1; line-height: var(--lh-snap);
  font-variant-numeric: tabular-nums lining-nums; }
.marquee-num small { font-family: var(--sans); font-size: 0.66rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); }
.gold { color: var(--gold); }
.accent { color: var(--accent); }

/* ---------- shell ---------- */
.shell { max-width: 1080px; margin: 0 auto; padding: 0 20px 96px; }
.shell--wide { max-width: 1280px; }

/* ---------- the masthead ---------- */
.topbar {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: 20px;
  flex-wrap: wrap; row-gap: 6px; /* never clips — worst case the nav wraps */
  padding: 12px 20px;
  /* viewport-fit=cover (the layout meta) lets content run under the status
     bar / Dynamic Island in the iOS shell — the masthead must reserve that
     inset or the wordmark renders behind the clock. env() is 0 everywhere
     the inset doesn't exist, so plain browsers are untouched. */
  padding-top: calc(12px + env(safe-area-inset-top));
  /* NO COMPOSITING LAYER ON THE CHROME (r9). This was the canvas at 88% with
     backdrop-filter: blur(14px), which promotes the whole masthead to its own
     layer — and a layer is rasterised on its own origin, so every hairline
     inside it (the GO LIVE pill, the bell, the avatar's ring) is snapped
     against the layer rather than the page. It is also two alpha reads on one
     stroke: the ring's own, then the layer's. The masthead is opaque now. What
     it loses is a frosted edge nobody asked for; what it gains is that the
     four strokes in the top-right corner of every studio screen are painted
     exactly like the ones in the page. */
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
body.door .topbar { border-bottom-color: var(--hair-25); }
/* The nameplate, extracted verbatim from the WIRE masthead (Todd 2026-08-20):
   a 26px gold rule + "BACKSTAGE PASS" at 12px/600/.3em tracking — #e0b669 on
   dark surfaces, #b8860b on paper, which is exactly what var(--gold) resolves
   to per mood. Every surface wears this; the old SVG lockup lives only in the
   /d reference gallery. */
.topbar__brand {
  display: inline-flex; align-items: center; gap: 12px;
  font-family: var(--sans); font-size: 0.75rem; font-weight: 600; --lh: 1; line-height: var(--lh-snap);
  letter-spacing: 0.3em; text-transform: uppercase; color: var(--gold);
  white-space: nowrap;
}
.topbar__brand::before { content: ""; width: 26px; height: 1px; background: var(--gold); opacity: 0.6; }
.topbar__brand em { font-style: normal; color: inherit; }
/* min-height is the whole-pixel rule applied to a CENTRED row (r8). The tallest
   thing in here is the avatar's 34px ring; the row's own content height was 33,
   so `align-items: center` centred an even box in an odd row and put the bell,
   the avatar and its ring on a HALF pixel — every one of them a stroked, fully
   rounded control, which is the worst place in the product for a hairline to
   land off-grid. 34 makes the row as tall as its tallest stroke. */
.topbar__nav { display: flex; gap: 2px; margin-left: auto; align-items: center; min-height: 34px; }
.topbar__link {
  padding: 7px 12px; font-size: 0.82rem; font-weight: 700;
  letter-spacing: 0.1em; font-variant-caps: all-small-caps; color: var(--muted);
  border-bottom: 1px solid transparent;
  transition: color 150ms ease, border-color 150ms ease;
}
.topbar__link:hover { color: var(--text); }
.topbar__link--active { color: var(--accent); border-bottom-color: var(--accent); }
/* `button.topbar__link` is DELETED (Maaz 2026-09-12). It existed for exactly
   one control — the fan bar's Log out `button_to`, which wears the UA's grey
   pill without it — and that control moved into the identity menu, where
   `.menu__item` already resets the same box. Nothing renders a button in this
   nav any more (four `link_to`s), and a rule named after retired markup is how
   a new surface inherits a box by reaching for a familiar class (the `.gl-side`
   P0). */

/* condensed header on phones: secondary links hide, CTA shortens, nothing clips */
.btn--compact { padding: 8px 16px; font-size: 0.68rem; white-space: nowrap; }
.cta-short { display: none; }
/* The logged-out door pill (WIRE 6b header): ONE bordered gold control — the
   filled-gold primary belongs to the page body alone (one primary per screen). */
.topbar__pill {
  border: var(--hair-w) solid var(--gold); border-radius: 999px; color: var(--gold);
  padding: 8px 18px; font-size: 0.78rem; font-weight: 600; white-space: nowrap;
  transition: background 150ms ease, color 150ms ease;
}
.topbar__pill:hover { background: color-mix(in srgb, var(--gold) 12%, transparent); }
/* GO LIVE, one tap from every screen (ref-5). Same rule as the door pill
   above: BORDERED, because the filled primary belongs to the page body alone
   and the Studio's own GO LIVE block is that primary. Tungsten type, never a
   gold fill (gold is money and the Pass in the house), and no live-red dot —
   nothing is live at the moment this is tapped. */
.topbar__go {
  border: var(--hair-w) solid var(--border-strong); border-radius: 999px;
  padding: 6px 16px; margin-left: 6px; white-space: nowrap; color: var(--text);
  font-family: var(--display); font-weight: 800; font-size: 0.95rem;
  letter-spacing: 0.12em; text-transform: uppercase; --lh: 1.25; line-height: var(--lh-snap);
  transition: border-color 150ms ease;
}
.topbar__go:hover { border-color: var(--text); }
.topbar__go:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* THE PILL AND WHAT'S BESIDE IT (r14) — one slot, because it is one broadcast
   target. The line to the LEFT of the pill is r14-ref-2's position; the words
   are ours. min-width: 0 is load-bearing: without it the title's nowrap box
   refuses to shrink and pushes the bell and the avatar off a narrow masthead. */
/* A LIVE'S MASTHEAD IS THE LIVE'S (product lead, live-flow r2 item 18). The
   way back, the live's own name, and the member's own menu — Home / Live /
   Encore came off, and the rows moved into the avatar disclosure so nothing
   became unreachable. The wordmark yields to the title (the lead's own
   allowance); the way home is a row in that menu. */
/* A LIVE'S MASTHEAD IS TWO THINGS (product lead, live-flow r3 item 7): the
   wordmark and the member's own menu, hard left and hard right.
   `.topbar__show` / `.topbar__back` / `.topbar__show-title` are GONE with the
   chevron and the repeated title — a rule with no markup is the #415 trap in
   reverse. */
.topbar--showroom { justify-content: space-between; }
.topbar__up { display: inline-flex; align-items: center; gap: 10px; min-width: 0; }
.topbar__up-line {
  display: inline-flex; align-items: baseline; gap: 6px; min-width: 0;
  font-size: 0.78rem; letter-spacing: 0.06em; --lh: 1.25; line-height: var(--lh-snap);
}
.topbar__up-eyebrow { color: var(--muted); font-variant-caps: all-small-caps; letter-spacing: 0.12em; }
/* Truncated in CSS, never in Ruby: the whole title stays in the DOM, so a
   screen reader reads it whole. It carries no [data-tip] — that rule is
   scoped to body.studio and hangs above its trigger, so on this masthead it
   was nothing on the fan side and off the top of the viewport on the studio
   side (both measured). 28ch holds
   the mock's own name ("Session 1 with Maaz Azam") whole — a truncation that
   eats the creator's surname on a masthead with room to spare is the pill
   failing to say which live it means. */
.topbar__up-title {
  color: var(--text); font-weight: 700; max-width: 28ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.topbar__up-note { color: var(--muted); white-space: nowrap; }
/* The brief's own line reads "Up next · <title> · Starting soon" — the
   separators are the copy, so they live in the copy's own colour and not a
   hairline's (punctuation dim enough to squint at reads as grit, r9). */
.topbar__up-eyebrow::after,
.topbar__up-note::before { content: "\00b7"; color: var(--muted); }
.topbar__up-eyebrow::after { margin-left: 6px; }
.topbar__up-note::before { margin-right: 6px; }
/* Live-red is only ever a claim about something that IS live (A2), and here it
   is one: the state is read off Stream#live? and off nothing else. */
.topbar__up-note--live { color: var(--live); }
/* ON AIR THE STATE LEADS (product lead, live-flow r2 item 1): Live now → the
   live's name → Join. The note is the FIRST segment there, so its separator
   has to move with it — a leading "·" would open the masthead with
   punctuation. The dot is inline-flex'd into the badge and takes the note's
   own live-red through currentColor. */
.topbar__up-line--live .topbar__up-note { display: inline-flex; align-items: center; gap: 6px; }
.topbar__up-line--live .topbar__up-note::before { content: none; margin: 0; }
.topbar__up-line--live .topbar__up-note::after { content: "\00b7"; color: var(--muted); margin-left: 6px; }
/* A SLOW BLINK, and the reason is that this one is not a counter. The house
   `.live-dot` ticks at 1.2s because it sits beside a number that is moving;
   this badge is simply true for the length of a show, on the masthead of every
   other screen in the studio, and at that cadence it strobes. Two seconds,
   mostly lit — a pulse rather than a tally. */
.live-dot--slow { animation: live-breathe 2.4s ease-in-out infinite; }
@keyframes live-breathe { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
/* The kill for BOTH of these lives beside the `.live-dot` declaration further
   down, and it has to: this block is at line ~310 and `.live-dot`'s own
   `animation: tally` is declared at ~635, same specificity — so a reduce rule
   written HERE was overridden by the animation it was meant to stop, and had
   been since it was written. Measured under CDP's own
   `prefers-reduced-motion: reduce` (animationName came back `tally`), which is
   what the flywheel's six new dots are what finally caught. */
/* The cable source is a custom element, i.e. display: inline by default — and
   .topbar / .topbar__nav are flex boxes with a gap, so a zero-width child of
   either takes a gap with it. It has nothing to draw anywhere. */
turbo-cable-stream-source { display: none; }
/* The bell: the way to Notifications. A hairline square in the masthead's own
   vocabulary, never a filled control. The badge is the ONE cheap read the old
   comment asked for (creator_notifications.unread.count) and draws only when
   nonzero — tungsten on the canvas, because gold means money and live-red
   means live. Integer box + fixed px type: the whole-pixel rule (r8). */
.topbar__bell {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; margin-left: 6px; color: var(--muted);
  border: var(--hair-w) solid var(--border); border-radius: 999px;
  transition: color 150ms ease, border-color 150ms ease;
}
.topbar__bell:hover { color: var(--text); border-color: var(--text); }
.topbar__bell:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.topbar__bell-badge {
  position: absolute; top: -5px; right: -6px;
  min-width: 16px; height: 16px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--text); color: var(--bg);
  border-radius: 999px; font-size: 10px; font-weight: 700; line-height: 1;
}
/* THE BELL IS A MENU NOW (Maaz 2026-09-07): a native <details> like the
   avatar's, holding the per-person dropdown. Same override discipline as
   summary.topbar__me — `.menu > summary` is (0,1,1), ships later in this file,
   and would lay its 30x28 rounded-rect box under the bell's circle (two
   strokes on one control, the r8 defect). Naming the details wins. */
.topbar__bell-wrap { position: relative; display: inline-flex; }
.topbar__bell-menu > summary.topbar__bell {
  cursor: pointer; list-style: none; user-select: none;
  width: 30px; height: 30px; padding: 0; background: transparent;
  border: var(--hair-w) solid var(--border); border-radius: 999px;
}
summary.topbar__bell::-webkit-details-marker { display: none; }
summary.topbar__bell::marker { content: ""; }
/* The panel hangs at the masthead's right edge; .menu__panel's own max-width
   (280px) is a row-kebab's size, so the compound selector outranks it. */
.menu__panel.bell-panel {
  right: 0; left: auto; top: calc(100% + 8px); padding: 0;
  width: min(360px, calc(100vw - 24px)); max-width: none;
}
.bell-panel__quiet { margin: 0; padding: 18px 16px; font-size: 0.84rem; color: var(--dim); }
.bell-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 16px 10px; border-bottom: 1px solid var(--border);
}
.bell-head__title { font-size: 0.9rem; font-weight: 600; }
.bell-head__all {
  background: transparent; border: 0; padding: 0; cursor: pointer;
  font: inherit; --lh: 1.2; line-height: var(--lh-snap); font-size: 0.76rem; color: var(--muted);
  text-decoration: underline; text-underline-offset: 3px;
}
.bell-head__all:hover { color: var(--text); }
.bell-rows { max-height: min(60vh, 480px); overflow-y: auto; padding: 6px; }
.bell-row {
  display: flex; align-items: flex-start; gap: 10px; padding: 10px;
  border-radius: var(--radius-sm); text-decoration: none; color: var(--text);
  transition: background 150ms ease;
}
.bell-row:hover { background: var(--surface-3); }
.bell-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.bell-row__who { flex: none; }
.bell-row__who .avatar { width: 32px; height: 32px; font-size: 0.72rem; }
/* the BSP mark: the house icon in the same 32px circle a creator's picture
   wears — the avatar is what says which world a row came from */
.bell-row__mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 999px;
  border: var(--hair-w) solid var(--border); background: var(--surface-3);
}
.bell-row__mark img { width: 16px; height: 16px; }
.bell-row__body { flex: 1 1 auto; min-width: 0; }
.bell-row__line { display: block; font-size: 0.84rem; --lh: 1.4; line-height: var(--lh-snap); }
.bell-row--unread .bell-row__line { font-weight: 600; }
.bell-row__time { display: block; margin-top: 2px; font-size: 0.72rem; color: var(--dim); }
/* live-red means live and nothing else — the one CTA the panel draws */
.bell-row__cta {
  flex: none; align-self: center; padding: 4px 12px; border-radius: 999px;
  background: var(--live); color: #fff; font-size: 0.74rem; font-weight: 700;
}
/* unread is tungsten like the badge (gold means money, live-red means live) */
.bell-row__dot {
  flex: none; align-self: center; width: 7px; height: 7px;
  border-radius: 999px; background: var(--text);
}
/* the avatar is the way to what you hold — one control, and it carries its own
   name in title/aria-label rather than a second text link beside it */
.topbar__me { display: inline-flex; align-items: center; margin-left: 6px; }
/* The avatar is a <summary> now (the account menu): strip the marker, keep the
   pointer, and let the panel hang under it at the masthead's right edge. */
.topbar__me-wrap { position: relative; display: inline-flex; }
/* ONE STROKE MECHANISM PER ELEMENT (r8, the owner's r8-ref-5). This summary is
   a `.menu > summary`, so it wore that trigger's whole control box: a 30x28
   panel with a 1px --border-strong hairline at a 6px radius. Under a 34x34
   circular .ring of the SAME colour that is TWO concentric hairlines 1px apart
   with two different geometries — a circle crossed by a rounded rectangle —
   and it read as a wobbly, hand-drawn ring. (Proven by recolouring the ring:
   the inner stroke stayed tungsten.) The avatar IS the trigger; the ring is
   its only stroke. */
/* The selector is deliberately (0,2,1): `.menu > summary` is ALSO (0,1,1) and
   ships later in this file, so a plain `summary.topbar__me` loses to it on
   source order. Naming the details element is what makes this override real. */
.topbar__me-menu > summary.topbar__me {
  cursor: pointer; list-style: none;
  width: auto; height: auto; padding: 0;
  border: 0; border-radius: 50%; background: transparent;
}
summary.topbar__me::-webkit-details-marker { display: none; }
summary.topbar__me::marker { content: ""; }
.topbar__me-menu .menu__panel { right: 0; left: auto; top: calc(100% + 8px); min-width: 190px; }
.topbar__me .ring { border-width: var(--hair-w); padding: 2px; transition: border-color 150ms ease; }
.topbar__me .avatar--sm { width: 28px; height: 28px; font-size: 0.7rem; }
.topbar__me:hover .ring { border-color: var(--text); }
.topbar__me--active .ring { border-color: var(--accent); }
.topbar__me:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 50%; }
@media (max-width: 639px) {
  .topbar { gap: 10px; padding: 10px 14px; padding-top: calc(10px + env(safe-area-inset-top)); }
  .topbar__brand { font-size: 0.62rem; letter-spacing: 0.26em; }
  .topbar__brand::before { width: 16px; }
  .topbar__nav { gap: 0; }
  .topbar__link { padding: 6px 8px; letter-spacing: 0.08em; }
  .topbar__link--collapsible { display: none; }
  .topbar__pill { padding: 7px 14px; font-size: 0.72rem; }
  .topbar__go { padding: 5px 12px; font-size: 0.82rem; margin-left: 2px; }
  /* r14: the PILL survives the phone header — it is the one thing this product
     does, and the shell's test counts it at every width. The line beside it is
     the decoration and the first thing to go; the pill's own word ("Join now",
     "Start now") already carries the state. */
  .topbar__up { gap: 0; }
  .topbar__up-line { display: none; }
  .topbar__me { margin-left: 2px; }
  .topbar__me .avatar--sm { width: 26px; height: 26px; }
  .btn--compact { padding: 7px 12px; font-size: 0.66rem; }
  .cta-long { display: none; }
  .cta-short { display: inline; }
}

/* ---------- buttons: one ink block per screen, hairlines for the rest ------ */
/* `[hidden]` HAS TO WIN (product owner, r14 item 5). The UA's own
   `[hidden] { display: none }` is out-ranked by any rule that gives an
   element a display, so a `.btn` carrying the attribute stayed on screen —
   which is why "at the third question Add question disappears" was true in
   the markup and false on the page, and why an assert_select could never see
   it (the #415 lesson). Declared beside the base rule so the two are read
   together, and it is the ONLY copy of the claim: the composer's head used to
   say it again for its own two submits, and that duplicate went with the head
   (Maaz 2026-09-13). */
.btn[hidden] { display: none; }
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm); cursor: pointer;
  font: inherit; line-height: var(--lh-snap); font-weight: 700; font-size: 0.8rem;
  letter-spacing: 0.1em; font-variant-caps: all-small-caps;
  padding: 11px 20px; color: var(--text); background: transparent;
  transition: background 150ms ease, border-color 150ms ease, color 150ms ease;
}
.btn:hover { border-color: var(--text); }
.btn:active { transform: translateY(1px); }
.btn--primary { background: var(--btn-bg); color: var(--btn-fg); border-color: var(--btn-bg); }
.btn--primary:hover { background: var(--btn-hover); border-color: var(--btn-hover); }
/* the live switch — Control Room: red means live or cut, nothing else */
.btn--live { background: var(--live); color: #fff; border-color: var(--live); }
.btn--live:hover { background: #ef5b60; border-color: #ef5b60; }
.btn--cut { background: transparent; border: 2px solid var(--live); color: var(--live); font-weight: 700; }
.btn--cut:hover { background: color-mix(in srgb, var(--live) 12%, transparent); }
.btn--ghost { border-color: var(--border-strong); color: var(--muted); }
.btn--ghost:hover { color: var(--text); border-color: var(--text); background: transparent; }
/* the Pass wears the only gold in the house */
.btn--pass { border-color: var(--gold); color: var(--gold); }
.btn--pass:hover { background: color-mix(in srgb, var(--gold) 10%, transparent); border-color: var(--gold); }
/* big CTAs speak marquee: Big Shoulders 800, letterspaced, real caps */
.btn--lg, .btn--xl {
  font-family: var(--display); font-weight: 800; text-transform: uppercase;
  font-variant-caps: normal; letter-spacing: 0.12em;
}
.btn--lg { padding: 14px 28px; font-size: 1.2rem; }
.btn--xl { padding: 18px 44px; font-size: 1.45rem; }
.btn--block { width: 100%; }
.btn[disabled] { opacity: 0.45; pointer-events: none; }

/* the GO LIVE block — marquee 800 with its mono subline (WIRE preflight) */
body.studio .golive { display: inline-flex; flex-direction: column; gap: 3px; align-items: center; justify-content: center;
  background: var(--btn-bg); color: var(--btn-fg); border: 1px solid var(--btn-bg); border-radius: var(--radius-sm);
  padding: 16px 34px; text-align: center; cursor: pointer; font: inherit; line-height: var(--lh-snap);
  transition: background 150ms ease; }
body.studio .golive .word { font-family: var(--display); font-weight: 800; font-size: 1.7rem;
  letter-spacing: 0.14em; text-transform: uppercase; --lh: 1.05; line-height: var(--lh-snap); }
body.studio .golive .sub { font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.22em; text-transform: uppercase; opacity: 0.7; }
body.studio .golive:hover { background: var(--btn-hover); border-color: var(--btn-hover); }
body.studio .golive--live { background: var(--live); border-color: var(--live); color: #fff; }
body.studio .golive--live:hover { background: #ef5b60; border-color: #ef5b60; }
/* The pair (ref-5): the one button, and the calendar beside it. "Schedule it"
   used to be a text link under the block, which read as a footnote to the one
   thing the screen is for — scheduling is the OTHER way to use it, so it gets
   the other tile. The block itself stays TUNGSTEN (--btn-bg, the house
   light): gold means money and the Pass in the house (tokens.css), and the
   reference's gold tile is door styling on a product surface. */
body.studio .golive-pair { display: flex; align-items: stretch; justify-content: center; gap: 14px; flex-wrap: wrap; margin: 30px 0 10px; }
body.studio .golive-cal {
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  min-width: 116px; padding: 16px 22px;
  background: var(--surface); color: var(--muted);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  transition: color 150ms ease, border-color 150ms ease;
}
body.studio .golive-cal:hover { color: var(--text); border-color: var(--text); }
body.studio .golive-cal:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .golive-cal__word { font-family: var(--mono); font-size: 0.58rem; letter-spacing: 0.18em; text-transform: uppercase; }

/* ---------- cards & panels: the printed sheet ---------- */
.card {
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius); padding: 20px;
}
.card--flush { padding: 0; overflow: hidden; }
/* the program page: double-ruled, like a keepsake (Marquee receipt) */
.program { background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius); padding: clamp(20px, 4vw, 40px); position: relative; }
.program::before { content: ""; position: absolute; inset: 9px; border: 1px solid var(--border);
  border-radius: 2px; pointer-events: none; }
.program > * { position: relative; }

/* section rules: the label sits IN the rule (Marquee) */
.panel-title { display: flex; align-items: center; gap: 14px; margin: 42px 0 16px; }
.panel-title::after { content: ""; flex: 1; border-top: 1px solid var(--border); }
.panel-title .eyebrow { margin: 0; flex: none; }
.section-rule { display: flex; align-items: center; gap: 14px; margin: 42px 0 16px; }
.section-rule::before, .section-rule::after { content: ""; flex: 1; border-top: 1px solid var(--border); }
/* a section that names itself and counts itself (ref-4: "Live now · 5 rooms
   open"). The heading is the marquee voice; the figure sits on its baseline
   and is only ever a real row count — a zero renders no head at all. */
.section-head { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; margin: 42px 0 16px; }
.section-head > h1, .section-head > h2 { flex: none; }
.section-head__n { font-size: 0.86rem; color: var(--muted); }

/* enter choreography for stream/toast cards */
@starting-style {
  .enter { opacity: 0; transform: translateY(8px); }
}
.enter { transition: opacity 300ms ease, transform 300ms ease; }

/* ---------- live ring / avatars ---------- */
.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; background: var(--surface-3); color: var(--accent);
  font-family: var(--sans); font-weight: 700; flex: none; overflow: hidden;
}
.avatar--sm { width: 34px; height: 34px; font-size: 0.85rem; }
.avatar--md { width: 52px; height: 52px; font-size: 1.15rem; }
.avatar--lg { width: 76px; height: 76px; font-size: 1.6rem; }
.ring { padding: 3px; border-radius: 50%; border: 2px solid var(--border-strong); display: inline-flex; }
.ring--live { border-color: var(--live); animation: ringpulse 2s ease-in-out infinite; }
@keyframes ringpulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(229, 72, 77, 0.35); }
  50% { box-shadow: 0 0 0 6px rgba(229, 72, 77, 0); }
}

/* ---------- chips / badges: printed pills ---------- */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  border: var(--hair-w) solid var(--border-strong); border-radius: 999px;
  padding: 4px 12px; font-size: 0.78rem; font-weight: 600;
  letter-spacing: 0.05em; font-variant-caps: all-small-caps; color: var(--muted);
  background: transparent;
  /* Chips carry free text — a stream title, a fan name, a pass name — which can
     arrive as one unbroken token. In a wrapping .cluster the chip must break and
     shrink so it never widens the page past the viewport (no horizontal scroll,
     banned product-wide — R20 mobile pass). No-op in a .chip-rail: those chips
     are white-space:nowrap and scroll on purpose. */
  overflow-wrap: anywhere; min-width: 0; max-width: 100%;
}
/* A RULE THAT HANDS AN ELEMENT A DISPLAY HAS TO HAND THE HIDDEN STATE BACK
   (Maaz 2026-09-15, screenshot 8 — "the empty pill beside LIVE NOW").
   `display: inline-flex` above out-ranks the UA stylesheet's own
   `[hidden] { display: none }`, so a chip rendered `hidden` went on painting:
   a 999px-radius bordered box with 4px/12px of padding and nothing in it.
   That is what the fan's live room carried beside LIVE NOW — the presence
   chip's LIVE shape is deliberately an empty hidden span (one broadcast
   target, no live phrase for a member), and the separator rule below even
   names it `.chip[hidden]` while hiding the middot, so the markup was right
   and had been right for rounds. Only a browser could see it (#415).
   The r14 trap, and this is the sixth time this house has paid for it, so it
   goes BESIDE THE BASE RULE rather than in one more host: `.cap-cards`,
   `.rating`, `.btn`, `.link-u` and the studio's own `.summary .chip[hidden]`
   (which stays, out-ranks this, and agrees) each discovered it alone. */
.chip[hidden] { display: none; }
.chip--live { color: #fff; background: var(--live); border-color: var(--live); }
/* r9: a chip's stroke is an opaque token, not its colour at an alpha —
   coverage on an arc already dims a hairline, and an alpha dims it again. */
.chip--gold { color: var(--gold); border-color: var(--hair-gold); }
.chip--ok { color: var(--ok); border-color: var(--hair-ok); }
.chip--warn { color: var(--warn); border-color: var(--hair-warn); }
.chip--dim { color: var(--dim); }
/* REVOKED IS DANGER (Maaz r12): a revoked seat wears live-red so it reads as
   obviously gone, not as a quiet dim "canceled". */
.chip--danger { color: var(--live); border-color: var(--hair-live); }
/* position:relative is load-bearing, not decoration. The real control below is
   position:absolute; without a positioned ancestor its containing block is
   whatever IS positioned further up — in the GENERATE CLIPS sheet that is the
   <dialog>, so every hidden radio in a scrolled .chip-rail was laid out past
   the sheet's right edge, outside the rail's clipping, and the whole modal
   scrolled sideways on a phone. The chip contains its own control. */
.chip--input { position: relative; cursor: pointer; user-select: none; }
/* Selection is LIT, never gold (§2.1): gold means money and the marquee. */
.chip--input:has(input:checked),
.chip--lit {
  color: var(--btn-fg); background: var(--btn-bg); border-color: var(--btn-bg);
}
.chip--input input { position: absolute; opacity: 0; pointer-events: none; }
/* a single row of chips that scrolls in place on narrow screens (stage rail) */
.chip-rail { display: flex; align-items: center; gap: 8px; overflow-x: auto; padding: 2px 0 8px; scrollbar-width: none; }
.chip-rail::-webkit-scrollbar { display: none; }
.chip-rail > * { flex: none; white-space: nowrap; max-width: none; }

/* the tally lamp (Control Room organ): steps, not fades */
/* A LUCIDE GLYPH IS NEVER THE EVENT TARGET (product lead, live-flow r2 item
   22, 2026-09-06). r12 item 7 landed this for `.btn--icon` and it was then
   written again for `.cr-msg-act` and `.room__chrome-btn` — three copies of one
   rule, and every OTHER glyph-bearing control in the product still had the
   defect: the pointer resolved on the 15px SVG in the middle of the box, so
   the cursor read `default` over the glyph and `pointer` only in the padding
   around it, and `event.target` / `elementFromPoint` answered a `<path>`.
   Swept 2026-09-06 across /studio, /studio/lives, the composer, Destinations,
   Encores, Clips, Audience and a live's own page: ~18 distinct controls.

   It is ONE rule, on the class every glyph in this house carries, and it is
   deliberately NOT scoped to body.studio — the fan side draws the same glyphs
   inside the same controls. A glyph is decoration here: wherever one is
   tappable it sits INSIDE a control, and a pointer event that passes through a
   child still applies :hover to whatever ancestor receives it (which is what
   keeps a [data-tip] on a plain <span> wrapping a glyph working). Chart SVGs
   are not `.lucide` and are untouched.

   Verify a hit-area claim with document.elementFromPoint at the glyph's own
   centre — and note that Selenium's element#click then REFUSES on the glyph
   ("Other element would receive the click"), which is the fix reporting
   itself; send a pointer at the coordinate with the Actions API instead. */
.lucide { pointer-events: none; }

.live-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; animation: tally 1.2s steps(1) infinite; }
/* AND THE ONE PLACE THAT STOPS IT, immediately after the declaration it
   stops — never earlier in the sheet, where source order silently reverses it
   (see the note beside `.live-dot--slow`). Both marks, one rule: the tally and
   the masthead's slow pulse. */
@media (prefers-reduced-motion: reduce) {
  .live-dot, .live-dot--slow { animation: none; }
}
@keyframes tally { 0%, 55% { opacity: 1; } 56%, 100% { opacity: 0.2; } }

/* ---------- rails (Live Now etc.) ---------- */
.rail { display: flex; gap: 14px; overflow-x: auto; padding: 6px 2px 14px; scrollbar-width: none; }
.rail::-webkit-scrollbar { display: none; }
.rail__item { display: flex; flex-direction: column; align-items: center; gap: 7px; width: 76px; flex: none; text-align: center; }
.rail__name { font-size: 0.7rem; color: var(--muted); max-width: 76px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- Upcoming: the scheduled rooms, as a scroller (ref-2) ----------
   The rows became cards on a rail, and the cards became the ENCORE TILE'S
   SHAPE (Maaz 2026-09-14, item 7): a cover with a date badge, the title, who
   it is, and when. The typographic playbill this block used to describe is
   gone — a night without an upload wears `default-cover` through
   `cover_still_url`, the house's one reader, rather than a frame of marquee
   type nothing else in the product draws.

   The control under each card is still whatever the door resolved, at card
   width: a price, sold out, a place in line, the guest list, or RSVP for the
   one free case. Never a uniform reminder over all seven — and never "See the
   details", which is the one state that was neither an act nor a fact. */
.up-rail { display: flex; gap: 14px; overflow-x: auto; padding: 6px 2px 14px; scrollbar-width: none; }
.up-rail::-webkit-scrollbar { display: none; }
.up-card { flex: none; width: min(300px, 78vw); display: flex; flex-direction: column; }
/* THE FRAME IS A COVER (Maaz 2026-09-14, item 7) — the Encore tile's own box,
   to the radius and the hairline, so a mixed wall reads as one wall. It was a
   PLAYBILL: `--frame-mat`, 14px of padding, the title set in marquee caps
   inside it, a countdown chip top-left and the announced clock bottom-right,
   with a cover only where the creator had uploaded one and a scrim only when
   there was one to darken. Every one of those rules went with that shape
   (`.up-card__in`, `.up-card__at`, `.up-card__who`, the frame's own `h3`, the
   `:has(.up-card__cover)` scrim) — the .gl-side P0: a class named after retired
   markup is how the next surface inherits a box by reaching for a familiar
   name. */
.up-card__frame {
  position: relative; aspect-ratio: 16 / 9; overflow: hidden;
  border-radius: var(--radius); background: #000;
  border: 1px solid color-mix(in srgb, var(--text) 35%, transparent);
  transition: border-color 150ms ease;
}
.up-card:hover .up-card__frame, .up-card:focus-within .up-card__frame { border-color: var(--text); }
.up-card__cover { width: 100%; height: 100%; object-fit: cover; display: block; background: #000; }
/* Top-right, which is the LIVE badge's slot on a live tile — one shape, one
   mark's difference. The plate is the wall tile's own corner chip, because the
   date sits on a photograph. */
.up-card__badge { position: absolute; top: 10px; right: 10px; z-index: 2; display: flex; gap: 6px; }
.up-card__badge .chip { background: rgb(10 8 6 / 0.7); border-color: transparent; color: #EFE7D6; }
.up-card__link { position: absolute; inset: 0; z-index: 3; -webkit-tap-highlight-color: transparent; }
.up-card__link:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--radius); }
/* Title, then who, then when — the Encore tile's body, in its order. */
.up-card__body { padding: 12px 2px 0; }
.up-card__body h3 { font-size: 0.98rem; margin: 0; }
.up-card__body h3 a:hover { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }
.up-card__when { margin: 5px 0 0; color: var(--muted); }
.up-card__seats { margin: 3px 0 0; }
/* THE DOOR IS A FLEX ITEM OF THE BY-ROW NOW (Maaz 2026-09-15 r3, item 3, his
   screenshot 4): it was a full-width block under the date, with its own 12px
   of top margin and a chip stretched to 100%. Beside the name it is a pill
   that hugs its words, so the three declarations that made it a block are
   gone — and the PILL itself is declared next to `.foll-btn`, whose box it
   shares, because these two are the same control in the same slot one round
   apart and one recipe is what stops them drifting.

   `flex: none` BECAUSE THE PILL IS `nowrap`: both it and the name default to
   `flex-shrink: 1`, so on a narrow card the browser would take width off BOTH
   and a nowrap pill given less than its content runs its words out of its own
   stroke. The NAME is the thing with something to give (`min-width: 0` plus an
   ellipsis), and past that the row wraps the pill onto its own line. */
.up-card__door { display: inline-flex; flex: none; }
.up-card__door form { display: inline-flex; margin: 0; }

/* ---------- browse grids (the Encores wall lives in these) ---------- */
.grid { display: grid; gap: 16px; }
/* min(300px, 100%) keeps the track from ever exceeding its container. */
.grid--cards { grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); }
/* the four-up row under a hero (ref-4). Still auto-fill, so it is four across
   the shell and two, then one, on the way down to a phone — never a fixed
   `repeat(4, …)` that would push the page sideways at 390px. */
.grid--4up { grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr)); }

/* ---------- the playbill list (Marquee 1a·3) ---------- */
.bill-rows > * { padding: 18px 0; border-top: 1px solid var(--border); display: flex;
  flex-direction: column; gap: 6px; }
.bill-rows > *:last-child { border-bottom: 1px solid var(--border); }
.bill-rows .toprow { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.bill-rows h3 { font-family: var(--display); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.01em; --lh: 1.05; line-height: var(--lh-snap); font-size: clamp(1.4rem, 3.4vw, 1.8rem); }
.bill-rows h3 a:hover { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }
.bill-rows .facts { font-size: 0.82rem; color: var(--muted); }
.lead-story { display: grid; grid-template-columns: 1.4fr 1fr; gap: 32px; align-items: start; }
.lead-story > * { min-width: 0; }
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; }
.two-col > * { min-width: 0; }
/* a framed print: the hairline mat around a moving image */
.frame { border: 1px solid var(--border-strong); padding: 6px; background: var(--frame-mat); border-radius: var(--radius); }

/* ---------- density scrubber (A3: the room's loudest moments) ---------- */
.density { display: flex; align-items: flex-end; gap: 1px; height: 26px; width: 100%; }
.density--card { height: 18px; opacity: 0.9; }
.density span { flex: 1 1 0; min-width: 1px; background: color-mix(in srgb, var(--text) 20%, transparent); border-radius: 1px; }
.density span.hot { background: color-mix(in srgb, var(--text) 45%, transparent); }
.density span.peak { background: var(--accent); }

/* THE WAVE'S OWN SLOT (Maaz 2026-09-10) — hard against the player's bottom
   edge, because it is a control for the tape rather than a figure about it.
   UNFENCED on purpose: `shared/_encore_wave` has two hosts, the member's
   encore and the creator's Encore tab, so a `body.studio` scope here would
   render the member's copy bare (studio_scope_law's law 2). */
.encore-wave { margin-top: 10px; }
.scrubber { position: relative; padding-top: 4px; }
.scrubber .density { cursor: pointer; }
.scrubber__playhead {
  position: absolute; top: 0; bottom: 0; width: 2px; background: var(--accent);
  border-radius: 2px; pointer-events: none; opacity: 0.9;
}
.peak-jump {
  font-family: var(--mono); font-size: 0.72rem; font-weight: 500; letter-spacing: 0.06em;
  font-variant-caps: all-small-caps; color: var(--text); background: none;
  border: 1px solid var(--border-strong); border-radius: 2px; padding: 6px 12px; cursor: pointer;
}
.peak-jump:hover { border-color: var(--accent); color: var(--accent); }

/* ---------- the room ---------- */
.room { display: grid; gap: 0; border-radius: var(--radius); overflow: hidden; border: 1px solid var(--border-strong); background: #000; }
.room__stage { position: relative; aspect-ratio: 16 / 9; background: #000; min-width: 0; }
.room__stage video { width: 100%; height: 100%; object-fit: contain; display: block; background: #000; }
/* gh#369 direct join: the audience's own WebRTC seat receives one TRACK per
   stage participant, not the compositor's finished picture — so the panel
   layout is ours here. One tile fills the stage exactly as the CDN player
   did; a second speaker splits it. The topline/hearts/slate keep their own
   z-index above this grid. */
.room__tiles { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; height: 100%; background: #000; }
/* The crew call (the stage's own video room, shared/_crew_stage). The wrapper
   is STATE-sized: Daily Prebuilt's pre-join panel (camera tile + device
   pickers) is taller than a joined call, and a fixed 560px box clipped it
   mid-row behind an inner scrollbar (Todd, 2026-08-27). The crew-call
   controller flips lobby ↔ incall on the call's own events. */
.crew-call { position: relative; width: 100%; background: #000; border: 1px solid var(--border-strong); border-radius: 10px; overflow: hidden; }
/* ── WHEN DAILY NEVER ARRIVES (control-room QA item 11) ──────────────────────
   Our chrome over the frame, drawn only while the call is stalled — never over
   a prejoin card the host is reading and never over a call they are in (see
   crew_call_controller's `#watch`). It is a CHILD of the call box, so it
   positions against the frame in every one of the four hosts with no wrapper.
   `position: relative` above is what makes that true and is the only reason
   it was added.

   The scrim is opaque enough to read against Prebuilt's own spinner, which is
   still turning underneath: this covers it rather than replacing it, because
   nothing of ours reaches inside a cross-origin iframe. */
.crew-call__stall { display: none; }
.crew-call--stalled > .crew-call__stall {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  /* 6 + 8 = the 14 this column carried when it held one sentence: the two
     sentences are one thought and sit together, the button is the other item.
     A wrapper would say the same thing in more markup and a negative margin
     would say it by cancelling a gap. */
  gap: 6px; position: absolute; inset: 0; z-index: 2;
  padding: 20px; text-align: center; background: rgb(3 3 3 / 0.88);
}
.crew-call--stalled > .crew-call__stall > .btn { margin-top: 8px; }
.crew-call__stall-said { margin: 0; max-width: 34ch; color: var(--text); font-size: 0.95rem; }
/* The advice, one step quieter than the sentence it follows (Maaz 2026-09-15):
   it is what a creator can DO, not what happened, and two lines at one weight
   read as two problems. */
.crew-call__stall-said--sub { color: var(--muted); font-size: 0.85rem; }
/* ── AND WHEN THEY DROP MID-CALL (QA item 3, 2026-09-16) ────────────────────
   The stall's sibling, and never on at the same time (`#deviceSaid` clears the
   stall as it paints this). Same scrim, same geometry — one box, two different
   claims: the stall is a call that never arrived, this is a call somebody is
   IN whose devices went away, where a page refresh costs the show.

   THREE STATES ON ONE ATTRIBUTE, and the sentences are the ERB's: the
   controller writes `data-capture` and nothing else, so no copy lives in the
   JavaScript (date_picker_controller's law). `trying` draws no controls at all
   — a Reconnect button over a recovery already in flight is a second answer to
   the same question. */
.crew-call__recover { display: none; }
.crew-call[data-capture] > .crew-call__recover {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; position: absolute; inset: 0; z-index: 2;
  padding: 20px; text-align: center; background: rgb(3 3 3 / 0.88);
}
.crew-call__recover-said { display: none; margin: 0; max-width: 34ch; color: var(--text); font-size: 0.95rem; }
.crew-call[data-capture="trying"] .crew-call__recover-said--trying,
.crew-call[data-capture="stuck"] .crew-call__recover-said--stuck,
.crew-call[data-capture="denied"] .crew-call__recover-said--denied { display: block; }
.crew-call__recover-acts { display: none; gap: 10px; flex-wrap: wrap; justify-content: center; }
.crew-call[data-capture="stuck"] .crew-call__recover-acts,
.crew-call[data-capture="denied"] .crew-call__recover-acts { display: flex; }
/* THE LOBBY HUGS THE PREJOIN CARD (owner-approved theming, 2026-09-03).
   Prebuilt's prejoin panel is a FIXED-WIDTH card (~455px) centred in whatever
   iframe you give it, and the space around it is painted in
   theme.colors.background — so a full-bleed wrapper drew a wide slab of dead
   Daily chrome around a narrow card. Two halves fix it and both are needed:
   the theme now paints that area in our own canvas (crew_call_controller), and
   the wrapper stops being wider than the thing inside it. The HEIGHT stays
   generous on purpose: the card is video 16:9 + tray + three device selects,
   and cutting it short is what put an inner scrollbar through the middle of it
   (Todd, 2026-08-27). There is no option that reports the card's own size. */
/* THE CAMERA MUST NOT BE THE WHOLE VIEWPORT (product lead, live-flow r2 item
   10). Reported as "can't scroll while the pointer is over the camera", and
   the mechanism is not ours: a wheel over a CROSS-ORIGIN iframe is delivered
   to that iframe's document, and Daily Prebuilt neither publishes a way to opt
   into scroll chaining nor is reachable by any stylesheet of ours (the theming
   research doc §3). `pointer-events: none` on the iframe would hand the wheel
   back and take the camera and mic controls with it, which is not a trade.

   What WAS ours is the arithmetic. At 80vh the lobby card ran 196→856 on a
   900px viewport, so the control bar sat at ~875 and the tab rail below the
   fold: the pointer was over the camera by default and there was nothing else
   on screen to scroll from. Capped at 72vh/620px the rail is always visible at
   a desk height, so the creator is never trapped — and 620px still clears the
   prejoin card's own content (a 16:9 preview + tray + three device selects,
   ~600px), which is what stops Prebuilt growing an INNER scroller: the thing
   most likely to be eating the wheel in the first place. */
.crew-call--lobby { width: min(100%, 520px); margin-inline: auto; height: min(72vh, 620px); }
.crew-call--incall { height: min(56vh, 520px); }
/* Inside the control room's program-monitor slot the joined call fills the
   16:9 frame; the lobby releases the slot's ratio so the pre-join panel gets
   its full height, and the peak/presence chips stay off the device pickers. */
.room__stage .crew-call { border: 0; border-radius: 0; }
.room__stage .crew-call--incall { height: 100%; }
.room__stage:has(.crew-call--lobby) { aspect-ratio: auto; }
.room__stage:has(.crew-call--lobby) .room__topline { display: none; }
.room__topline { position: absolute; top: 12px; left: 12px; right: 12px; display: flex; gap: 8px; align-items: center; z-index: 3; flex-wrap: wrap; }
.room__topline .chip { background: rgb(10 8 6 / 0.78); border-color: transparent; color: #EFE7D6;
  backdrop-filter: blur(6px); font-family: var(--mono); font-weight: 500; letter-spacing: 0.08em; }
.room__topline .chip--live { background: var(--live); color: #fff; }
/* THE LAYER'S WIDTH IS THE MARK'S OWN ARITHMETIC, NOT A ROUND NUMBER. This box
   carries `overflow: hidden`, so it is what decides whether a heart is CLIPPED:
   the widest extent a mark reaches from the column's centre is its drift plus
   half its rendered width at the rise's end scale, i.e. 28px (the `--drift`
   ceiling all three writers mint — Reaction.burst!, Tip#announce! and
   room#paintReaction) + 38 * 1.15 / 2 = 49.85px, so 99.7px is the floor and
   112px leaves 6.15px a side.
   MEASURED at 1512 (2026-09-15): the 90px this was — tuned to the old 20px
   mark, which cleared it by 4.3px — clipped the widest of the seven (U+1F602,
   38.0px wide) by 4.8px on EVERY max-drift heart once the mark grew. It grows
   LEFTWARD (`right` is the anchor), so the column's centre moved 11px in off
   the frame's edge and nothing else about where a heart flies moved. */
.room__hearts { position: absolute; right: 14px; bottom: 4px; width: 112px; height: 65%; pointer-events: none; overflow: hidden; z-index: 2; }
/* A6 #2: ingest loss shows the slate, never a frozen frame. Empty target when connected. */
.room__slate { position: absolute; inset: 0; z-index: 4; pointer-events: none; }
.room__slate-inner {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.82); color: #EFE7D6; font-weight: 600;
}
/* 20px IS THE WALL TILE'S SIZE NOW, and it is left here on purpose: the base
   rule is the one every host of this class falls back to, and the other host is
   `.wall-tile__hearts` — a live heart arriving on a grid thumbnail, beside the
   seeded `.wall-heart` bloom, which carries no size of its own and takes the
   tile's own type. A 38px mark there would be one thing at two sizes on one
   surface, on a surface nobody asked about. */
.heart-float {
  position: absolute; bottom: 0; left: 50%; font-size: 20px; color: var(--live);
  animation: heartrise 2.4s ease-out forwards;
}
/* ON THE VIDEO THE MARK IS 38px (Maaz 2026-09-15). 20px read as too small on
   the frame for the member AND the creator, and this is ONE declaration for
   every `.room__hearts` there is — the creator's program monitor, the member's
   live hero, the member's encore and the studio Encore tab's own player — so a
   mark cannot rise at one size on the surface a host reads their room back on
   and another on the surface the room saw. It is the middle of the range he
   named (36-40), and the layer above is what makes it fit. THE PICKER TRAY IS
   NOT TOUCHED: `.react__go`/`.react__pick` are a 38px circle around a 17px
   glyph, and nothing here can reach them. */
.room__hearts .heart-float { font-size: 38px; }
/* REACTIONS PLAY ON THE VIDEO (live-flow r4 item 17, reversing item 23): the
   member's layer wears the creator's own `.room__hearts` geometry inside
   `.lobby__hero` now — one broadcast target, one float recipe. */
.chat { position: relative; }
/* THE TRAY (item 23). The trigger is a one-tap heart; hover or focus opens the
   four in a vertical stack above it (YouTube's own shape). The REVEAL IS CSS:
   a tray that only opens once a controller has connected is a control that is
   missing for the first second of every room. */
.react { position: relative; flex: none; }
.react__go, .react__pick {
  display: inline-grid; place-items: center; width: 38px; height: 38px;
  padding: 0; font-size: 17px; line-height: 1; cursor: pointer;
  background: var(--surface-2); color: var(--text);
  border: var(--hair-w) solid var(--border-strong); border-radius: 999px;
}
.react__go:hover, .react__pick:hover { background: var(--surface-3); }
/* THE TRAY IS CENTRED ON ITS OWN CIRCLE (product lead, live-flow r2 item 16 —
   "shift it left"). `left: 0` aligned the tray's BOX with the trigger's box,
   and the tray's 6px padding plus its hairline then pushed every mark inside
   it 7.5px to the right of the circle below (measured 2026-09-06: trigger
   centre 1300.5, tray centre 1308). Centring on the trigger is the claim the
   shape was making, so it makes it: the offset is 0 at every width and the
   translateX rides both transform states. */
.react__tray {
  position: absolute; left: 50%; bottom: calc(100% + 8px); z-index: 5;
  display: grid; gap: 6px; padding: 6px;
  background: var(--surface-2); border: var(--hair-w) solid var(--border-strong);
  border-radius: 999px;
  /* SEVEN marks is 314px of stack (item 16), which is taller than a phone's
     chat panel has above the composer — so the column scrolls rather than
     running off the top of the room. On a desk it never reaches the cap. */
  max-height: min(360px, 52vh); overflow-y: auto; scrollbar-width: none;
  opacity: 0; visibility: hidden; transform: translateX(-50%) translateY(4px);
  transition: opacity 90ms linear, transform 90ms linear, visibility 90ms linear;
}
.react__tray::-webkit-scrollbar { display: none; }
.react:hover .react__tray, .react:focus-within .react__tray {
  opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .react__tray { transition: none; }
}
/* HEART IS RED (item 16). U+2764 has no emoji-presentation selector, so the
   browser draws it as TEXT and it takes currentColor — which meant the one
   mark in the set that is a colour was rendering in the room's off-white. The
   six emoji beside it carry their own colour and ignore this. */
.react__pick--heart { color: var(--live); }
/* A CREW SEAT'S OWN CARD ON THE LIVE'S PAGE (product lead, live-flow r2 item
   21) — the sentence, the way in, and for an admitted panelist their camera.
   The gold edge is the house saying something happened TO you, which is the
   same reason the old call-up wore it. */
.crew-callup { margin-bottom: 16px; border-color: var(--accent); }
.crew-callup__stage { width: 100%; margin-top: 14px; }

/* THE ONE DOOR, OVER A LIVE (product lead, live-flow r2 item 3). A sheet, not
   a page: the show stays behind it. The <dialog>'s own box IS the panel, which
   is what makes "click outside" answerable — anything landing on the element
   itself is the backdrop. */
.door-sheet {
  /* SMALLER THAN THE PAGE (Maaz 2026-09-15: "smaller" than his own full-page
     screenshot of the door). Declared once, here — a second `.door-sheet` rule
     carrying a width 400 lines down is the r8 hazard this house keeps paying
     for. The compact TYPE that goes with it is the block under this one. */
  width: min(392px, calc(100vw - 32px)); max-width: none; max-height: min(92vh, 780px);
  padding: 0; overflow: auto;
  border: var(--hair-w) solid var(--border-strong); border-radius: 16px;
  background: var(--surface); color: var(--text);
}
.door-sheet::backdrop { background: rgb(0 0 0 / 0.72); }
/* The house's 40x40 icon box, declared here because `.btn--icon` is fenced
   behind body.studio and this sheet is the fan side's. */
.door-sheet__x {
  position: absolute; top: 8px; right: 8px; z-index: 2;
  display: inline-grid; place-items: center; width: 40px; height: 40px;
  padding: 0; font-size: 1.3rem; line-height: 1;
  background: none; border: 0; border-radius: 8px;
  color: var(--muted); cursor: pointer;
}
.door-sheet__x:hover { color: var(--text); background: var(--surface-2); }
/* The door's own body inside the sheet — on its own page the door is a framed
   card on a lit canvas (see THE DOOR, below), and in here the dialog IS that
   frame, so this only owns the padding. */
.door-sheet .door-body { padding: 8px 22px 22px; max-width: none; }
/* Every refusal on this journey is a flash rendered in the page's LAYOUT, and
   a frame has none — the frame layout renders them here instead, or a mistyped
   code would re-draw the same screen with nothing said. */
.door-sheet__said { padding: 16px 22px 0; }
.door-sheet__said .toast { position: static; margin: 0 0 8px; width: auto; }
.door-sheet__landed { padding: 40px 22px; text-align: center; color: var(--muted); }
/* The door's own page gets its lead-in from the card's padding; inside a sheet
   the close button is already the top edge, so the heading block takes its own.
   :first-child is the .door-head, or the signup arc's step line above it. */
.door-sheet .door-body > div:first-child { padding-top: 26px !important; }
/* THE SHEET'S OWN SCALE (Maaz 2026-09-15). The door's page-sized heading —
   clamp(2.6rem, 8vw, 3.7rem) of the house display face, and `vw` is the
   VIEWPORT's width, not the dialog's — rendered a 59px COME IN inside a 392px
   box: a page dropped onto the profile rather than a dialog over it. Nothing
   about the door changes; only its scale is the sheet's, which is the
   `.pass-modal` idiom one dialog over. */
.door-sheet .door-head { margin-bottom: 18px; }
.door-sheet .door-head h1 { font-size: clamp(1.9rem, 7vw, 2.2rem); }
.door-sheet .door-head__said { margin-top: 6px; font-size: 0.88rem; }
.door-sheet .door-providers { gap: 8px; }
.door-sheet .door-providers__btn { padding: 11px 16px; font-size: 0.9rem; }
.door-sheet .door-or { margin: 14px 0 13px; }
/* The consent line rides both shells now (Maaz 2026-09-15) — continuing IS the
   consent wherever the door is drawn, so it is on the screen the tap happens
   on. In here it is the last thing in the box and takes its own room. */
.door-sheet .door-legal { margin-top: 16px; font-size: 0.76rem; }
/* AND THE SENTENCE DOES NOT ORPHAN ITS LAST WORD. In the page's 620px column
   this line is one line; in a 392px dialog it wrapped and left "Privacy."
   alone under it (the FOLLOWERS r9 complaint, one screen over). The size above
   is what fits it on one line at every width the sheet has — measured, desk
   and phone — and this is the floor under that: the break can then only fall
   before the two links, never between them. `.fol-deck__hold`'s own idiom,
   scoped to the sheet so the page's own line is untouched. */
.door-sheet .door-legal__hold { white-space: nowrap; }

/* THE ENCORE'S "MORE FROM" IS ONE BOX THAT LEADS TO A PERSON (product lead,
   encore addendum E5). live-flow item 29 made it a collapsed `<details>` and
   left the creator's other nights inside it; the ruling here is that a page
   about ONE show carries no inventory of the others, so the box is a single
   link whose subtitle is a COUNT. The three rules it replaced (the summary
   trigger and the panel) are deleted with the markup they styled. */
.encore-more {
  display: flex; align-items: center; gap: 14px; padding: 16px;
  border: var(--hair-w) solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: inherit; text-decoration: none;
  transition: border-color var(--dur) var(--ease);
}
.encore-more:hover { border-color: var(--border-strong); }
/* THE CIRCLE, LEFT (encore addendum 2 / E8). `avatar_for` brings its own
   `.ring` + `.avatar--lg`, so this slot only has to stop the flex row
   squeezing it and stop the ring's inline-flex box adding a descender's worth
   of height (the r9 lesson: a baseline-aligned box inside a line box
   overhangs it). */
.encore-more__face { flex: none; display: inline-flex; }
.encore-more__main { flex: 1; min-width: 0; }
.encore-more__label { display: block; font-size: 0.98rem; font-weight: 600; }
.encore-more__said { display: block; margin-top: 3px; font-size: 0.86rem; color: var(--muted); }
/* The chevron is the affordance, so it takes the accent on hover and never the
   pointer — the whole box is the target (the one-glyph rule). */
.encore-more__go { flex: none; display: inline-flex; color: var(--dim); }
.encore-more:hover .encore-more__go { color: var(--accent); }

/* THE LIVE ROOM'S OWN TWO CONTROLS (item 24), bottom-right of the frame where
   the native bar used to end. A live has no timeline, so there is no bar. */
.room__chrome {
  position: absolute; right: 12px; bottom: 12px; z-index: 3;
  display: flex; align-items: center; gap: 8px;
}
.room__chrome-btn {
  display: inline-grid; place-items: center; width: 36px; height: 36px; padding: 0;
  background: rgb(10 8 6 / 0.72); color: #EFE7D6; cursor: pointer;
  border: var(--hair-w) solid rgb(255 255 255 / 0.18); border-radius: 999px;
}
.room__chrome-btn:hover { background: rgb(10 8 6 / 0.9); }
.room__chrome-wait {
  margin: 0; padding: 5px 11px; border-radius: 999px;
  background: rgb(10 8 6 / 0.72); color: #EFE7D6; font-size: 0.78rem;
}
.room__chrome-wait[hidden] { display: none; }
@keyframes heartrise {
  0% { transform: translate(-50%, 0) scale(0.7); opacity: 0; }
  12% { opacity: 1; }
  100% { transform: translate(calc(-50% + var(--drift, 0px)), -240px) scale(1.15); opacity: 0; }
}
.room__panel { background: var(--surface); display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.chat { display: flex; flex-direction: column; min-height: 0; min-width: 0; flex: 1; }
.chat__scroll { flex: 1; overflow-y: auto; padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; min-height: 0; }
.chat__msg { font-size: 0.88rem; --lh: 1.4; line-height: var(--lh-snap); overflow-anchor: none; overflow-wrap: anywhere; }
/* THE HOST VERBS NEVER WRAP TO THE START OF A LINE (Maaz QA, 2026-09-16). Pin
   and Delete used to be the LAST inline-level content in the row, so a long
   message wrapped past them and stranded them at the LEFT edge of their own
   new line — which then grew the right-anchored `[data-tip]` tooltip further
   off the panel's left edge and clipped it in half.

   `.chat__msg-acts` pins that cluster (report/pin/delete) to the row's
   TOP-RIGHT instead — only `.chat__msg-body` wraps, so the buttons never
   drift and the existing right-anchored tooltip below is always hovering a
   control that sits near the panel's right edge, never the left.

   Scoped with :has() (the house idiom — `.cta-gate:has(.btn[hidden])`, etc.)
   rather than a blanket `display: flex` on `.chat__msg`, because the pinned
   banner and the tip callout reuse that base class with no acts column and a
   bare text node for a body — flexing THEM turns the name and the message
   into two side-by-side boxes instead of one wrapping paragraph. */
.chat__msg:has(> .chat__msg-acts) { display: flex; align-items: flex-start; gap: 4px; }
.chat__msg-body { flex: 1; min-width: 0; }
.chat__msg-acts { flex: none; display: flex; align-items: center; gap: 2px; }
/* A URL in chat is a real link (journey r3 item 2) — the words' own colour,
   underlined so it reads as one, new tab so the room is never navigated away. */
.chat-link { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.chat-link:hover { color: var(--gold); }
.chat__msg .who { font-weight: 600; color: var(--accent); margin-right: 6px; }
.chat__msg--creator .who { color: var(--gold); }
.chat__msg--pinned { border: 1px solid var(--border-strong); border-radius: var(--radius-sm); padding: 8px 10px; background: var(--surface-2); }
/* "Removed by the creator" — the one line a sender reads under their own held
   link (control-room QA item 6). Its own row, quieter than the message, and it
   renders for nobody else: `visible_to` only ever hands a shadowed message to
   its own author. */
.chat__msg-said { display: block; margin-top: 2px; color: var(--dim); font-size: 0.78rem; }
/* a tip is money: the one gold surface in the chat — a full hairline,
   never a one-sided accent (banned tell, A2) */
.chat__msg--tip { border: 1px solid var(--hair-gold); border-radius: var(--radius-sm);
  padding: 8px 10px; background: color-mix(in srgb, var(--gold) 10%, var(--surface-2)); }
.chat__msg--tip .who { color: var(--gold); }
.chat__form { display: flex; gap: 8px; padding: 12px; border-top: 1px solid var(--border); }
.chat__form input[type="text"] { flex: 1; }
/* PRE-LIVE the composer is disabled rather than absent (a box that isn't there
   can't explain itself) — but it was styled exactly like the live one, so it
   read as an open chat that swallowed what you typed (measured 2026-09-05).
   A control that refuses has to look like it refuses. */
.chat__form input[type="text"]:disabled,
.chat__form .btn:disabled {
  opacity: 0.45; cursor: not-allowed;
}
/* CHAT COOLDOWN, in the chat panel's own foot (r12 item 4). It lived here
   before r10 as an unlabelled "Slow mode" chip row; the product owner asked
   what that meant, so the row now carries the name AND the rule. The chips
   are the house .chip/.chip--lit, so nothing new is declared for them. */
/* Two rows under the composer, ONE recipe: the chat cooldown and (2026-09-06)
   the waiting room's chat lock. They get their own class names rather than
   sharing one, because a selector that matches two blocks is ambiguous to
   every browser test that reaches for it — and both of these are controls a
   creator taps mid-show. */
/* THE CHAT GATE (product lead, live-flow r3 items 5/6). One broadcast target
   holding a SENTENCE and never a form (a broadcast render has no session and
   therefore no CSRF token), so the composer stays session-rendered in the page
   and this is what takes it off. `~` and not `+` because the cooldown line
   sits between them.
     `.chat--host` is the one per-viewer bit: the creator can always send, and
   one gate rendering serves the whole room, so the exemption has to live in
   CSS rather than in the partial. */
.chat__gate:empty { display: none; }
.chat__gate-said {
  margin: 0; padding: 12px; border-top: 1px solid var(--border);
  color: var(--muted); font-size: 0.85rem;
}
/* Pause is retired (journey r3 item 8) — the gate has one closed sentence. */
.chat__gate:has(.chat__gate-said) ~ .chat__form { display: none; }
.chat--host .chat__gate:has(.chat__gate-said) ~ .chat__form { display: flex; }

/* `.chat__cool` / `.chat__lock` are GONE (live-flow r3 item 5): the two
   labelled blocks they styled moved into the chat bar's gear below. A class
   nothing renders is the #415 trap in reverse — a rule with no markup. */

/* ---------- THE CHAT BOX'S OWN BAR AND ITS GEAR ----------------------------
   (product lead, live-flow r3 item 5.) The waiting room's lock and the chat
   cooldown were two labelled blocks with chip rows stacked under the composer,
   which is what made the chat box read as weird. They are settings, not the
   chat: one gear, top-right of the box, and the panel is the chat again.
   The bar carries what this chat IS ("Waiting room" / "Live chat"), which the
   panel used to say from a head outside the box. */
body.studio .chat__bar {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 12px; border-bottom: 1px solid var(--border);
  /* THE PANEL HANGS OFF THE BAR, NOT OFF THE GEAR (Maaz 2026-09-10). It was
     the gear's own `position: relative` <details>, so `top: calc(100% + 6px)`
     resolved against a 28px trigger that top-aligns in this row — and the
     panel opened straight onto "Members can see your messages.", the line
     directly under the title. The bar is the containing block now, so the
     panel clears the WHOLE head (title, subtitle and the bar's own hairline)
     whatever those sentences wrap to, which is the one thing a fixed offset
     on the gear could never promise. The containing block has to move; a
     bigger `top` cannot know how tall the head is. */
  position: relative;
}
body.studio .chat__bar .eyebrow { margin: 0; }
body.studio .chat__set { flex: none; }
/* Static, so the bar above is what the panel measures itself against. */
body.studio .chat__set .menu { position: static; }
body.studio .chat__set .menu__panel { top: calc(100% + 10px); right: 0; }
/* menu_controller flips a panel with no room under it; from the top of a tray
   there always is, and if it ever flips it now clears the bar upward too. */
body.studio .chat__set .menu.menu--up > .menu__panel { top: auto; bottom: calc(100% + 10px); }
/* Wider than the house menu, because the cooldown row is FIVE chips on ONE
   LINE and no wrap (his words) — a five-chip row inside a 210px panel would
   wrap into three, which is the shape this round took off the panel. */
body.studio .chat__set-panel { min-width: 292px; max-width: min(340px, calc(100vw - 44px)); padding: 8px; }
body.studio .chat__set-row { padding: 8px 6px; }
body.studio .chat__set-row + .chat__set-row { border-top: 1px solid var(--border); }
body.studio .chat__set-label {
  display: flex; align-items: center; gap: 6px; margin-bottom: 7px;
  font-size: 0.8rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.08em;
}
/* ONE LINE, NO WRAP. The chips shrink before the row does. */
body.studio .chat__set-chips { display: flex; flex-wrap: nowrap; gap: 5px; }
body.studio .chat__set-chips .chip { padding: 5px 8px; white-space: nowrap; }
/* THE (i)'s SENTENCE IS PANEL-WIDE AND HANGS BELOW THE PANEL (Maaz
   2026-09-10, screenshots 1 and 2). Two faults, one cause: the tip anchored
   on the GLYPH, which sits two thirds across its own label.
     · HORIZONTALLY it ran off the right of the screen. This tray is at the
       right edge of the window, so a 268px box starting mid-label is ~80px
       past the panel and past the viewport with it — and clamping the width
       cannot fix WHERE the box starts. So `.tip-i` goes `position: static`
       and the PANEL becomes the containing block (the `.chn-h2` idiom):
       `left: 0` is the panel's own left edge and `max-width: 100%` its own
       width, so the sentence is inside the tray by construction at any tray
       width and at any window size.
     · VERTICALLY the base tip hangs ABOVE its trigger, which from row two
       laid it over row one's Enable/Disable chips — a tooltip covering the
       control it explains. Panel-relative, `top` puts it under the whole
       panel, where it covers no control at all.
   `box-sizing` is declared because the base rule pads 6px 10px and a 100%
   max-width on a content box would be the overflow again, 20px smaller. */
body.studio .chat__set-label .tip-i[data-tip] { position: static; }
body.studio .chat__set-label .tip-i[data-tip]::after {
  box-sizing: border-box;
  left: 0; right: auto; width: max-content; max-width: 100%;
  top: calc(100% + 8px); bottom: auto;
  white-space: normal; text-transform: none; letter-spacing: 0;
}
.tier-badge {
  display: inline-block; font-family: var(--mono); font-size: 0.6rem; font-weight: 600; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--gold); border: 1px solid var(--hair-gold);
  padding: 1px 6px; border-radius: 3px; margin-right: 6px; vertical-align: 1px;
}

/* ---------- B56: the drop (WIRE 7/8) — on top of the room's rail, confirmed on a sheet ---------- */
/* IT IS NOT OVER THE VIDEO ANY MORE (Maaz, 2026-09-10). Two absolute blocks
   used to live here — one for the old two-column `rooms#show` (`.room`, a
   class this app no longer renders anywhere) and one for `.lobby__hero` — and
   both are gone with them. The card is a card now: a sibling above
   `.lobby__panel` inside `.lobby__side`, wearing the panel's own radius so
   the two read as one stack, with the gold hairline as the only difference.
   No `backdrop-filter` and no alpha fill: there is nothing behind it to blur,
   and an alpha stroke over an unknown backdrop is the r9 hairline trap. */
.drop-pin { display: block; }
/* THE GAP RIDES THE CARD, NOT THE COLUMN. `.drop-pin` is a BROADCAST TARGET,
   so it is in the DOM all through a live with nothing inside it until the host
   fires — and a `gap` on `.lobby__side` would then hold 14px of dead space
   over the chat for the whole show. A card that isn't there brings no margin
   with it. (`:empty` cannot help: the wrapper's own newlines are a text node.) */
.drop-pin__card {
  border: 1px solid var(--hair-gold); border-radius: var(--radius);
  background: var(--surface);
  padding: 14px 16px; margin-bottom: 14px;
  display: flex; flex-direction: column; gap: 9px;
}
/* ── THE CARD, TO THE TWO MOCKS (Maaz 2026-09-15) ──────────────────────────
   One line of state, the offer, one gold tap. UNFENCED like everything else
   in this block: `drops/_card` is a FAN view (the room's own pin) and the
   studio's preview renders the same partial, so a `body.studio` rule here
   would leave the room's card bare and say nothing about it (law 2). */
/* THE STATE LINE. Both halves are claims about a LIVE, which is what
   live-red is for in this house; the count's own separator lives in the
   markup so an unlimited drop reads "LIVE ONLY" and stops. It WRAPS rather
   than squeezing the clock: in a 276px tray "LIVE ONLY · 11 OF 11 LEFT"
   beside a pill is tight. */
/* THE ROW WRAPS AND THE SENTENCE DOES NOT, and that split is measured: in a
   276px tray the state line wants ~175px and the pill takes ~74 of the ~240
   inside the card, so something has to give. What gives is the PILL, which
   drops to its own line and stays hard right — because the alternative is the
   phrase breaking after "LIVE ONLY" and the separator leading the second line
   ("· 11 OF 11 LEFT"), which is the mock's own sentence read as two. On the
   room's wider card both sit on one line, which is the mock. */
.drop-pin__top { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
/* IT WRAPS, AND EACH HALF DOES NOT. `nowrap` on the whole phrase is 167px of
   min-content, and at the tray's own floor — a host dragging `--cr-tray-w` to
   its 220px minimum — the card is 124px: MEASURED 27px of horizontal scroll
   inside the card, on the one instruction this round's brief spells out. The
   two runs hold themselves together and the line breaks between them, with the
   separator trailing the first (see `.drop-pin__word::after`) so the mock's
   sentence never reads as two with a middot leading line two. */
.drop-pin__live {
  display: inline-flex; align-items: baseline; flex-wrap: wrap;
  column-gap: 5px; row-gap: 2px; min-width: 0;
  font-family: var(--mono); font-size: 0.66rem; font-weight: 700;
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--live);
}
.drop-pin__word, .drop-pin__count { white-space: nowrap; }
/* Drawn by the WORD and only where a count follows it, so an unlimited drop —
   which draws no count at all — can never render a dangling separator, and the
   count's own text stays the sentence the pin's test reads off the DOM. */
.drop-pin__word:has(+ .drop-pin__count:not([hidden]))::after { content: " ·"; }
/* THE CLOCK PILL. `flex: none` so the state line is what gives, and the
   digits are tabular-ish mono so a tick never re-measures the pill. */
.drop-pin__timer {
  display: inline-flex; align-items: center; gap: 5px; flex: none; margin-left: auto;
  padding: 3px 8px; border-radius: 999px;
  border: var(--hair-w, 1px) solid var(--hair-gold); color: var(--gold);
  font-family: var(--mono); font-size: 0.68rem; font-weight: 700; letter-spacing: 0.02em;
}
.drop-pin__timer[hidden] { display: none; }
.drop-pin__timer .lucide { flex: none; }
/* THE OFFER: the name takes the width, the money holds its own. */
.drop-pin__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
/* `overflow-wrap: anywhere` because the global rule that carries it names
   `h1, h2, h3, p` and these are divs: `min-width: 0` lets the BOX shrink and
   an unbroken token then paints straight out of it, so a creator's own
   120-character word in a title or a blurb ran out of a 124px card. */
.drop-pin__title { font-weight: 700; font-size: 0.95rem; min-width: 0; overflow-wrap: anywhere; }
.drop-pin__price { font-family: var(--display); font-weight: 800; font-size: 1.7rem; --lh: 1; line-height: var(--lh-snap); color: var(--accent); flex: none; }
/* THE CARD'S FOUR OPTIONAL LINES (Maaz, 2026-09-12) — the picture a source
   Pass lends it, the blurb, the discount word and the clock. UNFENCED, and
   that is law 2 of the scope fence rather than a preference: `drops/_card` is
   a FAN view (the room's own pin) and Box Office's preview renders the same
   partial, so a `body.studio` rule here would leave the room's card bare and
   say nothing about it.
   Each is drawn only when it has something to say, which is why every one of
   them needs its own `[hidden]` — a rule that hands an element a `display`
   out-ranks the UA's own `[hidden]` (the r14 trap, four times over now). */
.drop-pin__shot { display: block; border-radius: 10px; overflow: hidden; aspect-ratio: 16 / 9; background: var(--surface-2); }
.drop-pin__shot[hidden] { display: none; }
.drop-pin__shot img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* THE BLURB IS A FLEX ITEM ON THE DEAL ROW NOW (see `.drop-pin__deal`), so its
   own negative top went with it — a margin on an item shifts it INSIDE its
   line, which is the baseline it shares with the strike beside it. It GROWS, on
   both of that row's shapes: on its own line the box is then the full width and
   the words read from the left edge (in `row-reverse` a short un-grown box sits
   hard right with its text adrift from the card's own left margin), and on a
   shared line it takes the slack so the sticker stays hard right. */
.drop-pin__blurb { flex: 1 1 auto; min-width: 0; color: var(--muted); font-size: 0.86rem; overflow-wrap: anywhere; }
.drop-pin__blurb[hidden] { display: none; }
/* WAS / OFF (the mocks' third line). `.drop-pin__money` — the column that
   stacked the price over the discount word — went out WITH the markup: a
   class named after retired furniture is how the next surface inherits a box
   by reaching for a familiar name (the `.gl-side` P0).

   IT SITS UNDER THE BIG PRICE (Maaz 2026-09-15, r3), which is a MOVE in the
   markup and `justify-content: flex-end` here: under the money rather than
   under the title. It was under the BLURB — a strike and a badge two elements
   away from the number they are a claim about, with the offer's own sentence in
   between. (Its negative top moved onto `.drop-pin__deal` when the blurb joined
   its row: a margin on a flex ITEM shifts it inside its own line, i.e. off the
   baseline it now shares.) */
/* ── AND THE BLURB SHARES ITS LINE WHERE IT FITS (Maaz 2026-09-16) ─────────
   "When blurb is short enough to sit on the strike+$OFF line with breathing
   space, same line. If it doesn't fit, wrap under. Not always-under."

   One wrapping row, and each of its four declarations is load-bearing:

     · `row-reverse` IS WHAT MAKES THE STICKER THE ITEM THAT KEEPS LINE ONE. A
       flex line is collected in order-modified document order, so the first
       item wins the line — and r3 put the sticker under the PRICE, so it has to
       be first in the markup AND painted on the right. Reversing the direction
       is the only way to have both: `order` and DOM order each move an item's
       collection and its paint together, so either would wrap the blurb onto
       line one and push the sticker below it, which is r3's own defect made by
       a line break.
     · `column-gap` IS THE BREATHING SPACE, because a gap counts in the
       line-collection arithmetic (Flexbox §9.3 collects "considering gaps") —
       and the blurb GROWS into the slack, so a shared line's ink is at LEAST
       this far from the strike by construction. 14px, against the title/price
       row's own 12px one line up. MEASURED in the studio's 256px card:
       "Numbered" shares the line with 73.5px between the words and the strike,
       while "Numbered and signed" would have landed 3.9px away and so wraps.
     · `flex-wrap` and nothing else decides whether it fits. The blurb's
       hypothetical main size is its MAX-CONTENT (flex-basis auto over width
       auto), so the row breaks exactly when the sentence could not stand
       unwrapped beside the sticker — no length in Ruby, no length here, and
       right at every width this card is drawn at.
     · `align-items: baseline` sits the sentence on the strike's own baseline
       when they share the line. The was is itself a flex box, so its first
       baseline is the strike's — the chip stays centred against it.

   AND THE WAS KEEPS ITS DEFAULT `flex: 0 1 auto`: it must not grow (it is a
   sticker, hard right) and it MUST be able to shrink — it measures 118px in the
   studio's own card, and the content box left by the 124px card a host gets
   dragging `--cr-tray-w` to its 220px floor (this file's own measurement, one
   screen up) is ~92px, where shrinking is what lets it wrap its chip under its
   strike exactly as it did before this row existed. `flex: none` there would
   run it out of the card.
   The negative top is the one the was carried, moved up a level: it pulls the
   row onto the price's own baseline gap so the three read as one sticker. */
.drop-pin__deal {
  display: flex; flex-direction: row-reverse; flex-wrap: wrap;
  align-items: baseline; column-gap: 14px; row-gap: 5px; margin-top: -4px;
}
/* BOTH HALVES HIDDEN IS NO ROW AT ALL, and it is CSS rather than a `hidden:`
   in the ERB because the studio's preview toggles those two attributes as a
   creator types — a server-rendered answer would be stale the moment they did.
   An empty flex item still takes the card's own 9px gap (the `.lobby__side`
   rule: a card that isn't there brings no margin with it), and `:empty` cannot
   help — the wrapper's own newlines are a text node. Spelled as "no child that
   is NOT hidden", so a third line added to this row is counted with no edit. */
.drop-pin__deal:not(:has(> :not([hidden]))) { display: none; }
.drop-pin__was { display: flex; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
.drop-pin__was[hidden] { display: none; }
.drop-pin__strike { color: var(--dim); font-size: 0.86rem; text-decoration: line-through; }
/* THE CHIP. The count is a stamp and shouts; this is the creator's own claim
   about their own price, so it wears the gold that means money and a hairline
   rather than a fill — one filled action per card, and it is the tap. */
/* `inline-block` AND NOT `inline-flex`, and it is a real defect it fixes:
   flex BLOCKIFIES its children, so the number's own span and the "% off"
   beside it became two block boxes and the browser's `innerText` put a
   NEWLINE between them — "30\n% OFF" on screen reads as one chip and to
   Capybara (and to a screen reader) as two lines. Caught in a browser. */
.drop-pin__off {
  display: inline-block; white-space: nowrap; flex: none;
  padding: 2px 8px; border-radius: 999px;
  border: var(--hair-w, 1px) solid var(--hair-gold);
  font-family: var(--mono); font-size: 0.65rem; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--gold);
}
/* THE ONE TAP IS THE HOUSE'S ONE FILLED PRIMARY (Todd's ruling, 2026-09-15),
   which is `.btn--primary` in the markup and therefore no fill of this card's
   own. `--btn-bg` is the warm tungsten every other filled action in the room
   wears — the Pass's buy, the drop's own CONFIRM — so the card's tap reads as
   that one action and not as a fourth recipe. `.btn--gold` is fenced behind
   `body.studio` and this is a fan view, which is why the alternative was a
   card-local copy of the gold rather than the modifier; the ruling is that
   there is no second filled gold in the room to copy.
   This class survives as the card's own hook for the mark below. */
/* No `:hover` of its own either — `.btn--primary:hover` is the house's, and at
   (0,2,0) this class would have out-ranked it on source order alone. */
.drop-pin__buy .lucide { flex: none; }
.drop-pin__count[hidden] { display: none; }
.drop-sheet { position: fixed; left: 0; right: 0; bottom: 0; z-index: 60; display: flex; justify-content: center; }
.drop-sheet__card {
  width: min(560px, 100%); background: var(--surface);
  border: 1px solid var(--border-strong); border-bottom: none;
  border-radius: 18px 18px 0 0; padding: 18px 22px calc(18px + env(safe-area-inset-bottom));
  box-shadow: 0 -18px 48px rgb(0 0 0 / 0.55);
}
.drop-sheet__rows > div {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 9px 0; border-bottom: 1px solid var(--border); font-size: 0.9rem;
}
.drop-sheet__rows > div:last-child { border-bottom: none; }

/* ---------- WHOP ELEMENTS: the drop's checkout sheet (Kunal 2026-09-16) ----------
   DELIBERATELY UNFENCED. The live room is a FAN surface, so a `body.studio`
   rule here would render this sheet bare and silently — the scope fence's own
   law 2 (test/studio_scope_law_test.rb), which this markup would fail.

   One sheet, three states, ONE attribute (`data-state`, written by
   whop_checkout_controller and by nothing else): loading while the Element's
   frame is on its way, ready once it has actually painted, hosted the moment
   we give up on it and point at the new tab. Every sentence is SERVER-rendered
   and revealed from here (the date_picker law — no copy in the JavaScript), so
   a rewording is an ERB edit and the controller cannot disagree with the page.

   The default is `display: none` on OUR OWN classes rather than `[hidden]`: no
   rule in this sheet hands these elements a display, so there is nothing for
   the UA's `[hidden]` to lose to (the trap this house has paid for five times).
   With no JavaScript nothing writes the attribute and the server's own opening
   state stands — which is why the no-script host renders `element: false` and
   opens on `hosted`, the one state that needs no script to act on. */
.drop-pay__mount { display: none; margin-top: 12px; }
.drop-sheet[data-state="loading"] .drop-pay__mount,
.drop-sheet[data-state="ready"] .drop-pay__mount { display: block; }

.drop-pay__said { display: none; }
.drop-sheet[data-state="loading"] .drop-pay__said[data-when="loading"],
.drop-sheet[data-state="ready"] .drop-pay__said[data-when="ready"],
.drop-sheet[data-state="hosted"] .drop-pay__said[data-when="hosted"] { display: block; }

/* Always drawn, in every state: it is the path that always works (a real
   target="_blank" anchor a click opens with no script), so it may never be
   the thing a failed mount takes away. */
.drop-pay__hosted { margin-top: 12px; }

/* The no-script host has no room behind the sheet to overlay, so the overlay
   comes off and it sits in the page's own flow. */
.drop-sheet--flow { position: static; display: block; }
.drop-sheet--flow .drop-sheet__card {
  width: 100%; border: 1px solid var(--border-strong);
  border-radius: 18px; box-shadow: none;
  padding: 18px 22px;
}

/* ---------- B57: the greenroom (WIRE 16/21) — doors in m:ss ---------- */
.greenroom__clock {
  font-family: var(--display); font-weight: 800; font-size: clamp(56px, 14vw, 96px);
  --lh: 1; line-height: var(--lh-snap); color: var(--accent); margin-top: 6px;
}

@media (min-width: 900px) {
  /* desktop: video left, chat rail right — same template, one breakpoint (A2). */
  .room { grid-template-columns: minmax(0, 1fr) 340px; }
  .room__panel { position: relative; border-left: 1px solid var(--border); }
  .room__panel .chat { position: absolute; inset: 0; }
}
@media (max-width: 899px) {
  .room__panel { max-height: 46vh; }
}

/* ---------- r9: THE STREAM PAGE — one layout, and the clock decides the slots ----------
   One address per live for its whole life, so one shape for every state:
   hero (16:9), the status line under it, the title row, the details card, and
   ONE full-height panel on the right. Nothing here moves between states —
   only what the slots hold does (StreamPage). */
.lobby { display: grid; gap: 18px; align-items: start; margin-top: 18px; }
.lobby__main { min-width: 0; display: flex; flex-direction: column; gap: 14px; }
.lobby__hero {
  position: relative; aspect-ratio: 16 / 9; background: #000; overflow: hidden;
  border: 1px solid var(--border-strong); border-radius: var(--radius);
}
/* A broadcast is never cropped (the room's own rule), a still is — a letterboxed
   cover would sit in a black frame inside a black frame. */
.lobby__hero video { width: 100%; height: 100%; object-fit: contain; display: block; background: #000; }
.lobby__hero .lobby__cover { width: 100%; height: 100%; object-fit: cover; display: block; }
.lobby__hero--canceled .lobby__cover { filter: grayscale(1) brightness(0.55); }
.lobby__badge { position: absolute; top: 12px; left: 12px; z-index: 3; }
/* The stage is the frame plus the one thing to do. It exists so the overlay can
   leave the frame on a phone without leaving the layout: `.lobby__hero` is a
   fixed 16:9 box with overflow:hidden, so a static child of it is clipped. */
.lobby__stage { position: relative; }
.lobby__overlay {
  position: absolute; left: 12px; right: 12px; bottom: 12px; z-index: 3;
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
}
/* A scrim, so a pill over a bright cover reads as ours rather than as damage.
   Both conditions are load-bearing: it is drawn only where an overlay actually
   sits ON the frame, and only over the COVER — in the encore the overlay holds
   the rating card over a real <video>, and a gradient across the bottom 40%
   there would dim the player's own controls. */
.lobby__stage:has(.lobby__cover):has(.lobby__overlay)::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 40%;
  border-radius: 0 0 var(--radius) var(--radius); z-index: 2; pointer-events: none;
  background: linear-gradient(to top, rgb(3 3 3 / 0.55), transparent);
}
/* PHONE: the frame is ~200px tall here, and a wrapped pill + CTA stood over the
   middle of the cover art (measured at 390px, 2026-09-05). Below the art they
   are the same two things, in the same order, legible. */
@media (max-width: 640px) {
  .lobby__overlay {
    position: static; left: auto; right: auto; bottom: auto;
    margin-top: 10px;
  }
  .lobby__stage:has(.lobby__cover):has(.lobby__overlay)::after { content: none; }
}
/* WHO CAN ACCESS? (Maaz 2026-09-15) — the keys to a gated door, under the
   description in Details. It replaced the door card's grid of full Pass cards,
   so the whole point of these rules is that a key is a CHIP: house furniture
   (`.chip`/`.chip--gold`, both deliberately unfenced — this is a FAN surface,
   and a `body.studio` rule here would render bare, which is scope-law 2), with
   nothing of its own but the rhythm around it.

   UNFENCED for the same reason. `.access-who` is the stream page's, and the
   stream page is nobody's studio. */
/* It sits in the Details card's `.stack` (gap 10px), and 10px between a
   paragraph of the creator's prose and a section label is tight — these are two
   different kinds of thing. 6px more, measured in a browser. */
.access-who { margin-top: 6px; }
.access-who__ask { margin-bottom: 8px; }
/* Tighter than `.cluster`'s 10px: these are small-caps pills in a row, and at
   10px a three-key door reads as three separate things rather than one list. */
.access-who__keys { gap: 8px; }
/* A key that OPENS the popup says so on hover. The plain `<span>` twin (a
   suspended creator, whose profile 404s) gets none — it is a word, not a
   control, and giving it a hover would promise a tap that does nothing. */
.access-who__key:hover, .access-who__key:focus-visible {
  color: var(--text); border-color: var(--text);
}
.access-who__said { margin-top: 8px; }
/* THE TICKET'S OWN PRICE, where its buy button used to be (the button is the
   hero's one Unlock now). `flex: none` because it sits in a `.spread` beside a
   growing column of facts, and `nowrap` because a price broken across two
   lines reads as two numbers. */
.door-price { flex: none; white-space: nowrap; color: var(--text); font-weight: 600; }

/* THE PILL WEARS A STROKE NOW (journey r3 item 7: "with a stroke so it reads
   on the cover") — the fill alone dissolved into the gold lockup. It sits on
   a picture/video, so the light hairline is composited against the frame; the
   round-stroke width keeps the arc even at every dPR. */
.lobby__pill {
  display: inline-flex; align-items: center; gap: 8px; min-height: 34px; padding: 0 14px;
  border-radius: 999px; background: rgb(10 8 6 / 0.82); backdrop-filter: blur(6px);
  border: var(--hair-w) solid rgb(239 231 214 / 0.32);
  color: #EFE7D6; font-family: var(--mono); font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap;
}
.lobby__pill--live { background: var(--live); color: #fff; }
.lobby__pill .live-dot { margin-right: 2px; }
/* The encore's play affordance — the whole frame is the tap target, and the
   card names what pressing it gets you and how long it runs.

   IT CARRIES ITS OWN SCRIM (product lead, encore addendum E3, 2026-09-07: "the
   poster behind Watch recording needs a darker overlay so the control is
   readable on the gold lockup"). The house cover IS a gold lockup — near-white
   "BackstagePass" at 249/255, measured — and the affordance lands dead centre
   on it, so the control was a dark pill dissolving into bright busy type. The
   scrim is on the BUTTON, not the frame, which is what makes it exactly as
   temporary as the affordance: `replay#playFromCover` (and the video's own
   first `play`) removes this element, and the poster comes back undimmed with
   the tape. It cannot dim a playing picture, and it cannot dim native controls
   this button was not already covering — `inset: 0` has always sat over them. */
.lobby__play {
  position: absolute; inset: 0; z-index: 2; display: flex; align-items: center; justify-content: center;
  background: rgb(3 3 3 / 0.55); border: 0; padding: 0; cursor: pointer; color: #fff;
}
/* THE PILL WEARS A STROKE (journey r3 item 22): the gold hairline, at the
   round-stroke width, so the card reads as a control on any cover. It sits on
   a picture, where the r9 opaque-hairline law does not bind — but the token is
   opaque anyway, so the stroke's weight never depends on the frame under it. */
.lobby__play-card {
  display: inline-flex; align-items: center; gap: 12px; min-height: 52px; padding: 0 20px;
  border-radius: 999px; background: rgb(10 8 6 / 0.82); backdrop-filter: blur(6px);
  border: var(--hair-w) solid var(--hair-gold);
  text-align: left; --lh: 1.25; line-height: var(--lh-snap);
}
/* THE CENTERED PLAY MARK (journey r3 item 23): once the tape has a source,
   pausing shows one centered play control on the frame beside the native bar
   — paused visible, playing hidden (the controller flips [hidden]). */
.lobby__center-play {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  z-index: 2; display: grid; place-items: center; width: 64px; height: 64px;
  border-radius: 999px; border: var(--hair-w) solid var(--hair-gold);
  background: rgb(10 8 6 / 0.82); backdrop-filter: blur(6px); color: var(--text);
  padding: 0; cursor: pointer;
}
.lobby__center-play[hidden] { display: none; }
.lobby__center-play:hover { color: var(--gold); }
/* THE TAP IS VISIBLY WORKING (encore-play item 3, 2026-09-08). Measured, not
   guessed: a 424 MB fragmented Daily MP4 with no `sidx` costs Chrome 72 range
   requests before frame one (~40s on an 11:32 tape), and nothing in this app
   shortens that today — so the tap has to say it landed. It sits dead centre,
   in the slot the play mark just vacated, so the mark going and this arriving
   read as one gesture. NO percentage anywhere: we cannot know what fraction of
   that walk is done, and a bar we invented is the fabricated-figure class this
   house refuses. Sits on a picture, so the r9 opaque-hairline law does not
   bind — the tokens are opaque anyway. */
.lobby__starting {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  z-index: 3; display: inline-flex; align-items: center; gap: 10px;
  max-width: calc(100% - 24px); padding: 9px 16px; border-radius: 999px;
  background: rgb(10 8 6 / 0.82); backdrop-filter: blur(6px);
  border: var(--hair-w) solid var(--hair-gold); color: var(--text);
  font-size: 0.85rem; --lh: 1.25; line-height: var(--lh-snap);
}
/* `display: inline-flex` outranks the UA's `[hidden]` rule — the r14 item 5
   trap, and the reason "Add question" never actually disappeared. */
.lobby__starting[hidden] { display: none; }
.lobby__starting-spin {
  flex: none; width: 14px; height: 14px; border-radius: 999px;
  border: 2px solid color-mix(in srgb, var(--gold) 28%, transparent);
  border-top-color: var(--gold);
  animation: encore-starting 900ms linear infinite;
}
@keyframes encore-starting { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .lobby__starting-spin { animation: none; } }
.lobby__play-mark { display: inline-flex; font-size: 1.15rem; }
/* THE LENGTH IS NOT `.dim` HERE, and that is what measuring found rather than
   guessed. `--dim` is 45% of the warm text over whatever it sits on, and 45%
   of #F4EDE1 has a CEILING of 4.14:1 even over pure black — so on this pill
   the stamp measured 4.0:1 against its own backdrop and no amount of scrim
   could carry it to AA's 4.5, because darkening the backdrop darkens the
   translucent text with it. An OPAQUE tint breaks the coupling: measured 6.9:1
   here, still visibly quieter than the white line above it (48% of its
   luminance). Local on purpose — `--dim` is a house-wide token and this is one
   control that happens to sit on a picture. */
.lobby__play-stamp { color: color-mix(in srgb, var(--text) 58%, #0a0806); }
.lobby__status { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; min-height: 30px; }
/* LIVE is the one live-red word on this page (A2's one accent, one live-red). */
.lobby__live { color: var(--live); font-weight: 700; letter-spacing: 0.04em; }
/* live-flow r4 items 4/6: the waiting room's own words wear gold — the status
   sentence for a looker, the "you are in" line for a member inside — and the
   "Starts soon" pill beside Join carries the same highlight. Unfenced: these
   are fan surfaces. */
.lobby__gold { color: var(--accent); font-weight: 600; }
.lobby__pill--soon { border-color: var(--hair-gold); color: var(--accent); }
/* A measured zero is not a number to show a fan — "0 in the room right now"
   reads as a verdict on the night. The element stays (it is the presence
   broadcast's one target); only the words come off, and they come back the
   instant somebody walks in, without a reload. */
.lobby__status .chip[data-count="0"],
.lobby__status [data-presence-sep="0"] { display: none; }
/* live-flow r4 item 18: the live headcount chip is rendered hidden for
   members (one broadcast target, no live phrase) — its separator goes with
   it or a bare middot dangles. */
.lobby__status [data-presence-sep]:has(+ .chip[hidden]) { display: none; }
.lobby__hosted { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between; }
/* A COLUMN, because the drop stacks on top of the chat (Maaz, 2026-09-10).
   With one child this is exactly what it was; with two, the card's own
   `margin-bottom` is the gap (see `.drop-pin__card` — a `gap` here would hold
   space open for an empty broadcast target) and the desktop rule below is what
   makes the chat give up the height rather than pushing its composer off. */
.lobby__side { min-width: 0; display: flex; flex-direction: column; }
.lobby__panel {
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); overflow: hidden; display: flex; flex-direction: column; min-height: 0;
}
.lobby__panel-head { padding: 12px 16px; border-bottom: 1px solid var(--border); flex: none; }
.lobby__zero { padding: 18px 16px; }
/* "You're in ✓" IS A STATE, NOT AN ACTION, AND NOW IT LOOKS LIKE ONE (product
   lead, live-flow r3 item 6: "not a button, not clickable, not a gold CTA
   that looks tappable").
     #430 gave it "the same shape as the button it replaces", which was the
   wrong instinct: a `<span>` wearing `.btn .btn--lg` in an accent fill is a
   large gold pill where the primary action was, and correct markup is no
   defence against a member reaching for it. It has dropped `.btn` entirely —
   this rule is the whole control now, and there is nothing to tap: no fill of
   the primary's weight, no hover, no pointer. The check mark is what says it
   already happened. */
.lobby__in {
  display: inline-flex; align-items: center; gap: 8px; min-height: 34px; padding: 0 14px;
  border-radius: 999px; background: none;
  border: var(--hair-w) solid var(--hair-gold); color: var(--accent);
  font-family: var(--mono); font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap;
  cursor: default;
}
/* The tape-is-coming notice, over the cover it is standing in for. */
.lobby__pending {
  position: absolute; inset: 0; z-index: 2; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 12px; padding: 20px; text-align: center;
  background: rgb(10 8 6 / 0.72); backdrop-filter: blur(4px);
}
.lobby__owner {
  display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between;
  margin-top: 18px; padding: 12px 14px; min-height: 58px;
  border: 1px solid var(--border-strong); border-radius: var(--radius); background: var(--surface-2);
}
@media (min-width: 900px) {
  .lobby { grid-template-columns: minmax(0, 1fr) 360px; }
  /* Full height, and it stays put while the left column scrolls: the panel IS
     the room, and a room that scrolls away is a room you left.
     `--lobby-top` is the sticky offset, and the height subtracts it TWICE —
     once for the gap above, once for the same gap below — so the composer at
     the panel's foot lands inside the viewport instead of just past its
     bottom edge, which is where a bare `100vh` put it. The masthead is
     sticky, so the offset has to clear it. */
  .lobby { --lobby-top: 78px; }
  /* THE HEIGHT MOVED FROM THE PANEL TO THE COLUMN (Maaz, 2026-09-10: "chat
     shrinks; the drop card sits on top of the right rail"). The panel used to
     own `calc(100vh - …)` itself, so a card above it pushed the panel's own
     foot — the composer — below the fold, which is the exact thing the
     subtract-the-gap-twice arithmetic exists to prevent. The COLUMN owns the
     viewport height now and `.lobby__panel` takes what is left of it: with no
     drop the panel resolves to the identical box it always had, and with one
     the chat is the thing that gives. `min-height: 0` is what lets a flex item
     actually shrink below its content. */
  .lobby__side {
    position: sticky; top: var(--lobby-top);
    height: calc(100vh - var(--lobby-top) - 22px); min-height: 440px;
  }
  .lobby__panel { height: auto; flex: 1; min-height: 0; }
}
@media (max-width: 899px) {
  .lobby__panel { height: 60vh; min-height: 320px; }
}

/* ---------- forms ---------- */
/* `date` joined this enumeration on 2026-09-14 and it was an accidental
   omission rather than a decision: `datetime-local` has always been on the
   list, every date input in this app has had to dress itself locally
   (`.dpick__native`, `.pf-when__part input[type="date"]`), and the Account
   page's new birth-date row rendered as bare UA chrome on a near-black card
   until it was added. The COLOUR half is one rule below — mood-fenced,
   because `color-scheme: dark` on a light page is the same defect the other
   way round. */
input[type="text"], input[type="email"], input[type="tel"], input[type="password"],
input[type="number"], input[type="url"], input[type="date"],
input[type="datetime-local"], input[type="search"],
select, textarea {
  /* 16px is not a taste call: anything smaller makes iOS Safari zoom the page
     on focus, and the field the fan is typing into scrolls off screen. */
  width: 100%; padding: 11px 14px; font-family: inherit; font-size: 16px;
  color: var(--text);
  background: var(--input-bg); border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); outline: none;
  transition: border-color 150ms ease;
}
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
/* THE BROWSER'S OWN DATE CHROME FOLLOWS THE MOOD. Without `color-scheme` the
   UA paints the text, the spinners and the whole calendar popover as a white
   box in a near-black card — `.dpick__native` has carried this line since it
   was written, and this is the same declaration for the date inputs that are
   NOT wrapped by a picker. Fenced to the dark surfaces on purpose: `body.paper`
   is a light mood and a dark picker there is the identical defect inverted. */
body.studio input[type="date"], body.studio input[type="datetime-local"],
body.door input[type="date"], body.door input[type="datetime-local"] { color-scheme: dark; }
/* NUMBER FIELDS DROP THE UA'S SPINNERS, on the `select` rule's own reasoning
   one line below (Maaz 2026-09-16: the drop form's discount, price and seat
   limit "change the value on scroll. Kill spinner arrows").

   BY TYPE AND UNFENCED, which is a scope call with an argument: the two arrows
   are the same UA chrome beside a styled input that `select` already strips
   here, every one of the ~40 number fields in this app has them, and the WHEEL
   half of the same defect is money on more forms than the one that reported it
   (the composer's ticket price, a call offer's, a promo's % off). Three fields'
   worth of hook would have left the rest of them stepping.

   BOTH SPELLINGS, because they are two engines' answers: `appearance:
   textfield` is the property (and the one the brief names), the two
   pseudo-elements are WebKit's own control. Do NOT try to verify either with
   `getComputedStyle(el, "::-webkit-inner-spin-button")` — it returns the
   ELEMENT's style in Chrome (measured: `display: block`, `width: 77.31px`,
   which is the input's own width, both before this rule and after it).

   AND IN THIS CHROME THE SECOND RULE TAKES THE WHEEL WITH THE ARROWS, because
   the step is the SPIN BUTTON's behaviour: with the JavaScript guard removed
   on purpose, a trusted wheel over a focused field left a typed 30 at 30, and
   forcing `appearance: auto` back on inline did not bring the step back either
   (a pseudo-element's rule is independent of its host's `appearance`). The
   guard in app/javascript/number_wheel.js is therefore not what fixes Chrome —
   it is what stops this promise depending on a chrome property also removing a
   behaviour, in an engine that hides two arrows and keeps what was behind them.

   WHAT IS UNTOUCHED: ArrowUp/ArrowDown still step a focused field, `min`/`max`
   /`step` still validate, and the box does not move — measured against the same
   field with the UA's appearance forced back on, to the pixel in both axes. */
input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button { display: none; -webkit-appearance: none; }
/* selects drop the UA chrome and wear the token layer — the first screen every
   fan sees puts one beside a styled input (finding 5, 2026-08-22) */
select {
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' fill='none' stroke='rgba(244,237,225,0.55)' stroke-width='1.5'/></svg>");
  background-repeat: no-repeat; background-position: right 12px center;
  padding-right: 32px;
}
body.paper select {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' fill='none' stroke='rgba(32,33,35,0.55)' stroke-width='1.5'/></svg>");
}
/* Mobile Input Font Size Rule (AGENTS.md — all projects, non-negotiable):
   iOS zooms the viewport whenever a focusable control under 16px gains focus,
   and never zooms back without a pinch. The base rule above already sets 16px,
   but its selector list misses untyped <input>s (the greenroom's readonly
   invite field), and the /d gallery rigs carry 12-14px fields — this floor
   catches every focusable control, present and future. */
@media (max-width: 768px) {
  input, textarea, select { font-size: 16px !important; }
}

/* File pickers. The UA paints its own light-gray system button, which reads as
   a hole punched in a near-black panel — the channel step puts three of them on
   one screen, and the CSV upload has carried the same seam since #280. Only the
   picker BUTTON is dressed, in the .btn--ghost vocabulary already in this file;
   the control's behavior and the surrounding field are untouched. */
/* max-width/min-width are load-bearing, not tidiness: a file input has an
   INTRINSIC width (its button plus "No file chosen") and will not shrink for a
   flex parent, so at 390px the profile-picture row pushed the page 56px
   sideways — horizontal scroll, banned product-wide. Caught on CI's Linux
   Chrome; macOS headless never renders below ~500px, so the local 390px pass
   couldn't see it. */
input[type="file"] {
  padding: 8px 0; font-size: 0.9rem; color: var(--muted); cursor: pointer;
  max-width: 100%; min-width: 0;
}
input[type="file"]::file-selector-button {
  margin-right: 12px; padding: 7px 14px; cursor: pointer;
  font: inherit; line-height: var(--lh-snap); font-weight: 700; font-size: 0.78rem;
  letter-spacing: 0.1em; font-variant-caps: all-small-caps;
  color: var(--text); background: transparent;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  transition: border-color 150ms ease, color 150ms ease;
}
input[type="file"]::file-selector-button:hover { border-color: var(--text); }

label.field { display: block; margin-bottom: 16px; }
label.field > span { display: block; font-size: 0.68rem; font-weight: 700; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 6px; }
.checkbox-row { display: flex; gap: 10px; align-items: flex-start; font-size: 0.82rem; color: var(--muted); margin-bottom: 14px; }
.checkbox-row input { margin-top: 3px; }
/* native toggles wear the house light, never the UA blue */
input[type="checkbox"], input[type="radio"] { accent-color: var(--accent); }
.form-errors { border: 1px solid var(--hair-live); border-radius: var(--radius-sm); padding: 10px 14px; color: var(--live); font-size: 0.85rem; margin-bottom: 16px; }
/* the marquee title line: the show's name in marquee caps, underlined
   (input.field-row outguns the generic input[type=text] attribute selector) */
input.field-row, .field-row { display: block; border: 0; border-bottom: 1px solid var(--hair-35); background: transparent;
  width: 100%; min-width: 0; font-family: var(--display); font-weight: 700; text-transform: uppercase;
  font-size: 1.7rem; --lh: 1.05; line-height: var(--lh-snap); padding: 8px 2px; border-radius: 0; color: var(--text); }
input.field-row:focus, .field-row:focus { outline: none; border-bottom: 2px solid var(--accent); }
input.field-row::placeholder, .field-row::placeholder { color: var(--dim); }

/* The one door's field: country picker and the number read as a single control. */
.identity-field { display: flex; gap: 8px; }
.identity-field__country {
  width: auto; flex: 0 1 auto; max-width: 40%; padding-right: 30px;
  color: var(--muted); text-overflow: ellipsis;
}
.identity-field__country[hidden] { display: none; }
.identity-field__input { flex: 1 1 auto; min-width: 0; }
.code-input { font-family: var(--mono); letter-spacing: 0.42em; font-variant-numeric: tabular-nums; }
/* Secondary links are not gold (§5.3 rule 1) — gold is the money and the marquee. */
.link { color: var(--text); text-decoration: underline; text-underline-offset: 2px; }

/* ---------- stats: the letterpress stat-row (Marquee program) ---------- */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  border-top: 1px solid var(--text); border-bottom: 1px solid var(--border); }
.stat { padding: 16px 14px; border-right: 1px solid var(--border); background: transparent; }
.stat:last-child { border-right: 0; }
.stat b { display: block; font-family: var(--display); font-weight: 800; font-size: 2.1rem;
  letter-spacing: 0.01em; --lh: 1.05; line-height: var(--lh-snap); font-variant-numeric: tabular-nums lining-nums; }
.stat span { font-size: 0.74rem; font-weight: 700; letter-spacing: 0.1em;
  font-variant-caps: all-small-caps; color: var(--muted); }

/* the tally: mono receipt lines — the WIRE's ledger language */
.tally { list-style: none; margin: 0; padding: 0; font-family: var(--mono); }
.tally li { display: flex; align-items: baseline; gap: 10px; padding: 8px 0; font-size: 0.82rem; }
.tally li .leader { flex: 1; border-bottom: 1px dotted var(--border-strong); transform: translateY(-4px); min-width: 20px; }
.tally li b { font-family: var(--mono); font-weight: 600; font-size: 0.9rem;
  font-variant-numeric: tabular-nums lining-nums; }
.tally li.held { color: var(--muted); font-size: 0.78rem; }
.tally li.held b { color: var(--accent); font-size: 0.82rem; }
/* the sum that proves itself: right-aligned, computed never typed */
.tally .reconcile { justify-content: flex-end; font-size: 0.7rem; color: var(--dim); }

/* ---------- tables / rows ---------- */
.rowlist { display: flex; flex-direction: column; }
.rowlist > * { display: flex; align-items: center; gap: 14px; padding: 14px 4px; border-bottom: 1px solid var(--border); }
.rowlist > *:last-child { border-bottom: none; }

/* ---------- the Studio's own list (ref-3, 2026-09-02) ---------------------
   Thirteen links in a flat centered cluster was a tag cloud: nothing said
   what any of them was for, and nothing said which ones belong together.
   Three groups, each a .rowlist — the component was already exactly "flex
   row, hairline bottom, last child none" — and every row is a bold title with
   the one line that says what the screen is for.

   The reference's right-hand status value is deliberately not here yet: #283
   says a brand-new creator sees no numeric figures at all, so those values
   need real per-surface reads and a render-only-when-real rule first. */
body.studio .studio-group { margin-top: 34px; }
body.studio .studio-group > .label { display: block; margin-bottom: 12px; }
body.studio .studio-nav { border-top: 1px solid var(--border); }
/* The row is the words on the left and the state on the right (ref-3). The
   two lines stack inside .studio-nav__words, so the state can sit opposite
   them instead of under them — and a row with no state to report is just the
   words, with nothing where the value would be (#283: no zeros). */
body.studio .studio-nav > a {
  align-items: baseline; gap: 14px; justify-content: space-between;
  padding: 16px 4px; flex-wrap: wrap; transition: color 150ms ease;
}
body.studio .studio-nav__words { display: flex; flex-direction: column; gap: 3px;
  flex: 1 1 260px; min-width: 0; }
body.studio .studio-nav__words > b { font-size: 1rem; font-weight: 700; }
body.studio .studio-nav > a:hover .studio-nav__words > b { color: var(--accent); }
body.studio .studio-nav > a:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--radius-sm); }
/* A tab that is named but not built says so in the same stamp the state uses,
   so the desk list never implies a screen that isn't there. */
body.studio .studio-nav__soon, body.studio .studio-rail__soon {
  font-family: var(--mono); font-size: 0.58rem; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--dim);
}
body.studio .studio-rail__soon { margin-left: 8px; }
/* The state is a stamp, not a headline: mono, dim, the receipt voice the
   product already uses for a measured number. Never gold — a count of
   connected destinations is not money. */
body.studio .studio-nav__state {
  flex: none; font-family: var(--mono); font-size: 0.7rem; font-weight: 500;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted);
  white-space: nowrap;
}

/* ---------- THE STUDIO SHELL (phase 2 of the reference round, 2026-09-02) ---
   The nav used to live at the BOTTOM of one screen, which meant "set it once"
   was only reachable from the screen a creator had already left. It stands
   beside every studio page now, rendered from Studio::Nav — the same array the
   desk list above reads.

   All of it is fenced (body.studio), which is what makes a 248px column and a
   phone drawer safe to add to the one sheet that dresses the whole product.
   A room gets no rail at all (StudioNavHelper#studio_rail?): the control room
   IS the show, and 248px of navigation is 248px off the picture. */
body.studio {
  /* the ref's geometry, named once */
  --rail: 248px;
  --rail-top: calc(58px + env(safe-area-inset-top));
  --drawer: min(300px, 86vw);
}
body.studio .studio-frame {
  display: grid; grid-template-columns: var(--rail) minmax(0, 1fr);
  align-items: start; max-width: 1560px; margin: 0 auto;
}
/* grid-row is pinned on the RAIL and the collapse toggle and on nothing else
   (r8). The toggle shares the rail's cell so it can sit on the rail's own
   hairline, and a definitely-placed item is laid out first — so without a row
   on the rail, auto-placement finds column 1 row 1 taken and drops the rail to
   row 2, under a 28px button. `main` must stay UNPINNED: it auto-places into
   column 2 row 1 on a desk anyway, and on a phone the drawer HANDLE is the
   auto-placed item above the page — pinning main to row 1 sent that handle to
   row 2, 1,687px down, below everything (caught in the browser at 390px). */
/* minmax(0, 1fr) above and min-width: 0 here are the same law twice: a grid
   track's automatic minimum is min-content, and one long unbroken fan name in
   the roster would otherwise push the whole page sideways instead of wrapping
   (the GEN SHEET round's 1fr lesson). */
/* width + margin-inline are load-bearing, not tidiness. .shell carries
   `margin: 0 auto`, and on a GRID ITEM auto margins beat `justify-self:
   stretch` — so the page stopped filling its track and shrank to fit-content
   (measured: 656px inside a 1264px column, four stat cards folding to two).
   The column is the page's width now, and .shell's max-width still caps it. */
body.studio .studio-frame > main {
  grid-column: 2; min-width: 0; width: 100%; margin-inline: 0;
}
body.studio .studio-rail {
  grid-column: 1; grid-row: 1; position: sticky; top: var(--rail-top);
  max-height: calc(100vh - var(--rail-top)); overflow-y: auto;
  /* one axis scrollable means the other computes to auto, and an auto
     overflow-x is exactly what the no-horizontal-scroll probe flags. Clip
     keeps the promise without a bar; the labels wrap rather than reach it. */
  overflow-x: clip; overscroll-behavior: contain;
  /* The hairline runs the full height of the viewport, not the height of
     sixteen rows: a rule that stops two thirds of the way down the page
     reads as an unfinished panel. */
  min-height: calc(100vh - var(--rail-top));
  /* 10px + the link's own 10px of padding puts the rail's text on the same
     20px gutter as the masthead wordmark. */
  padding: 30px 18px 40px 10px;
  border-right: 1px solid var(--border);   /* a hairline, never a panel */
}
body.studio .studio-rail__group + .studio-rail__group { margin-top: 26px; }
/* Overview stands alone above the groups, so its block carries no label and
   needs no heading space. */
body.studio .studio-rail__group--bare + .studio-rail__group { margin-top: 20px; }
body.studio .studio-rail__group > .label { display: block; margin-bottom: 8px; padding-left: 10px; }
/* A row is a glyph and a word (studio-ref-1): the icon is 17px, dim until the
   row is the one you are on, and it never carries meaning of its own — the
   word is the destination. */
body.studio .studio-rail__link {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 12px; border-radius: var(--radius);
  font-size: 0.92rem; font-weight: 600; color: var(--muted);
  overflow-wrap: anywhere;
  transition: color 150ms ease, background-color 150ms ease;
}
/* A ROW THE FILTER HID STAYS HIDDEN (r13, the e2e round's A-1). The rule above
   is (0,2,1), which outranks the UA stylesheet's `[hidden] { display: none }`,
   so studio_rail_controller's correct `row.hidden = true` painted anyway:
   typing "clip" left Encores, Clips AND Analytics on screen for a one-row
   match, because a group only collapsed by having no `display` of its own.
   The same fact `.dur-custom[hidden]` was written for in r10 — a declared
   display box outranks `[hidden]`, and every element that gets one needs its
   own override. */
body.studio .studio-rail__link[hidden] { display: none; }
/* A RAIL LABEL IS ONE LINE (standing Maaz complaint), AND THE TWO-LINE LABEL
   DID NOT REPRODUCE — so this is a structural guard and the measurement is
   written down rather than assumed. Nothing wrapped at 1512, 1280, 1100,
   1000, 950, 920 or 900, in the 390px drawer (a 300px rail, 17 rows), or at a
   root font size up to 24px. What IS on the page is the mechanism: the row
   above carries `overflow-wrap: anywhere` — written so a row can never widen
   the column — the word INHERITS it, and the first width pressure of any kind
   therefore breaks a label MID-WORD rather than not at all. Narrow the rail's
   own token to 150px and Destinations, Moderation, Transactions and
   Automations each come back two line boxes tall: Maaz's own four labels.
   Every label is BOUNDED (Studio::Nav's own list; the longest is "Meet &
   greet"), so there is nothing here for `anywhere` to protect the rail from —
   which is exactly the r4 chip rule, one surface over. The word goes nowrap,
   and it ellipsises rather than pushing the column if a future label
   outgrows it: `min-width: 0` is what lets a flex item shrink far enough for
   the ellipsis to be reached at all. `.studio-rail__soon` is left alone — it
   is a chip beside the word, not the word. */
body.studio .studio-rail__link > span:not(.studio-rail__soon) {
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.studio .studio-rail__glyph { flex: none; opacity: 0.7; }
body.studio .studio-rail__link--on .studio-rail__glyph { opacity: 1; }
body.studio .studio-rail__link:hover { color: var(--text); background: var(--surface-2); }
body.studio .studio-rail__link:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The screen you are standing on: lit, and it carries the whole hairline of
   the row rather than a one-sided accent border (which the design system bans
   outright). aria-current says the same thing to a screen reader. */
body.studio .studio-rail__link--on {
  color: var(--text); background: var(--surface-2);
  box-shadow: inset 0 0 0 1px var(--border-strong);
}

/* The rail's own controls: a search over its rows, and a narrow toggle. */
/* The field is the width of a ROW, because that is what it filters (r8). It
   used to be inset 10/4 and share its line with the collapse button, which
   made it both narrower than the list and visually a pair with a control that
   does something else entirely. */
body.studio .studio-rail__find { margin: 0 0 18px; }
body.studio .studio-rail__find input[type="search"] {
  width: 100%; min-width: 0; height: 36px; padding: 0 10px; font-size: 0.82rem;
}
/* ON THE DIVIDER, half over the line, level with the field (studio-ref-2).
   It is a grid item in the rail's own cell: `justify-self: end` puts its right
   edge on the track's edge — which is where the rail's hairline is — and the
   negative margin walks it half a box further so the line runs through it. The
   background is the canvas so the hairline stops at the button rather than
   drawing under it (two strokes crossing is what r8 just took off the avatar).
   Sticky at the rail's own offset plus the field's inset, so the two stay
   level through every scroll. */
body.studio .studio-frame { --find-mid: 48px; }   /* 30px rail padding + half of a 36px field */
body.studio .studio-rail__narrow {
  grid-column: 1; grid-row: 1; justify-self: end; align-self: start; z-index: 1;
  position: sticky; top: calc(var(--rail-top) + var(--find-mid) - 14px);
  margin: calc(var(--find-mid) - 14px) -14px 0 0;
  width: 28px; height: 28px; display: inline-flex;
  align-items: center; justify-content: center; cursor: pointer;
  background: var(--bg); color: var(--dim); font: inherit; line-height: var(--lh-snap); font-size: 0.8rem;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
body.studio .studio-rail__narrow:hover { color: var(--text); border-color: var(--text); }
body.studio .studio-rail__narrow:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
body.studio .studio-rail__none { padding: 6px 12px; }

/* NARROW: the rail keeps its glyphs and drops its words. The words stay in the
   DOM with their own title attribute, so the destination is still readable by
   pointer and by screen reader — display:none on a nav label would take it out
   of the accessibility tree entirely. */
body.studio .studio-frame--narrow { --rail: 64px; }
body.studio .studio-frame--narrow .studio-rail { padding-inline: 8px; }
body.studio .studio-frame--narrow .studio-rail__link { justify-content: center; padding-inline: 0; }
body.studio .studio-frame--narrow .studio-rail__link > span:not(.studio-rail__soon),
body.studio .studio-frame--narrow .studio-rail__group > .label,
body.studio .studio-frame--narrow .studio-rail__find input,
body.studio .studio-frame--narrow .studio-rail__none { display: none; }
/* the field's SLOT survives so the rows and the toggle stay where they were */
body.studio .studio-frame--narrow .studio-rail__find { height: 36px; }
body.studio .studio-frame--narrow .studio-rail__narrow span[aria-hidden] { transform: rotate(180deg); }
/* A narrowed rail is a desk decision, not a phone one: below the drawer's
   breakpoint the panel is a sheet and always carries its words. */
@media (max-width: 899px) {
  body.studio .studio-frame--narrow .studio-rail__link > span,
  body.studio .studio-frame--narrow .studio-rail__group > .label,
  body.studio .studio-frame--narrow .studio-rail__find input { display: revert; }
  body.studio .studio-frame--narrow .studio-rail__find { height: auto; }
  /* The toggle is a desk control and it rode the rail's divider — there is no
     divider on a phone, where the rail is a sheet. */
  body.studio .studio-rail__narrow { display: none; }
}

/* the drawer's handle and its own way out — phones only (see the query below) */
body.studio .studio-drawer, body.studio .studio-rail__close { display: none; }
body.studio .studio-scrim { display: none; }

@media (max-width: 899px) {
  /* One column, and the rail becomes a sheet: the page keeps the full width it
     had before the shell existed, which is what keeps every studio screen's
     390px pass unchanged. */
  body.studio .studio-frame { grid-template-columns: minmax(0, 1fr); }
  body.studio .studio-frame > main { grid-column: 1; }
  body.studio .studio-rail { display: none; }
  body.studio .studio-drawer {
    /* justify-self, because a grid item stretches by default and a menu
       handle the width of the screen reads as a field, not a button. */
    display: inline-flex; justify-self: start; align-items: center; gap: 8px;
    margin: 16px 20px 0; padding: 8px 14px;
    background: transparent; color: var(--muted); cursor: pointer;
    font: inherit; line-height: var(--lh-snap); font-size: 0.78rem; font-weight: 700;
    letter-spacing: 0.1em; text-transform: uppercase;
    border: var(--hair-w) solid var(--border-strong); border-radius: 999px;
  }
  body.studio .studio-drawer:hover { color: var(--text); border-color: var(--text); }
  body.studio .studio-drawer:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  /* Closed is display:none, not a transform off-screen: a fixed panel parked
     to the left is invisible but still a thing a screen reader walks. */
  body.studio .studio-frame--open .studio-rail {
    display: block; position: fixed; inset: 0 auto 0 0; z-index: 60;
    width: var(--drawer); max-height: none; height: 100%;
    padding: 22px 18px 34px; background: var(--surface);
    border-right: 1px solid var(--border-strong);
    animation: studio-drawer-in 200ms var(--ease);
  }
  body.studio .studio-frame--open .studio-scrim {
    display: block; position: fixed; inset: 0; z-index: 55;
    background: rgb(0 0 0 / 0.55);
  }
  body.studio .studio-frame--open .studio-rail__close {
    display: inline-flex; align-items: center; justify-content: center;
    width: 32px; height: 32px; margin: 0 0 10px auto;
    background: transparent; color: var(--muted); cursor: pointer;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
  }
  body.studio .studio-frame--open .studio-rail__close:hover { color: var(--text); border-color: var(--text); }
}
@keyframes studio-drawer-in { from { transform: translateX(-100%); } to { transform: none; } }
@media (prefers-reduced-motion: reduce) {
  body.studio .studio-frame--open .studio-rail { animation: none; }
}

/* At the desk, GO LIVE is FILLED GOLD (product owner, 2026-09-02). Everywhere
   else the pill stays bordered tungsten: the house rule is that gold means
   money and the Pass, and that the filled primary belongs to the page body —
   both still hold on every fan-facing surface, because this override is
   fenced. In the studio the one button IS the page's primary, and the desk is
   the one place a creator cannot mistake it for a price. */
body.studio .topbar__go {
  background: var(--gold); border-color: var(--gold); color: var(--gold-ink);
}
body.studio .topbar__go:hover { background: var(--gold-bright); border-color: var(--gold-bright); }

/* ---------- THE OVERVIEW (Maaz 2026-09-10) --------------------------------
   The desk's own furniture, swapped whole: the greeting and its window
   dropdown, four figure cards, the focus rows, and the month beside last
   month. `.ov-spark`, `.ov-chips`, `.ov-board`, `.ov-split`, `.ov-next` and
   `.ov-crew` are DELETED with the blocks they styled — a class named after a
   retired block is how a new surface inherits a box by reaching for a
   familiar name (the `.gl-side` P0). */
body.studio .ov-head {
  display: flex; gap: 20px; align-items: flex-start; justify-content: space-between;
  flex-wrap: wrap; padding: 40px 0 0;
}
body.studio .ov-hello { margin: 0; }
body.studio .ov-head .deck { margin-top: 8px; max-width: 52ch; }

/* The window dropdown is the house disclosure (`details.menu`), so the panel
   and its rows are already furniture; this is the trigger's own face.

   `width: auto` IS LOAD-BEARING — the r8 trap, third time. `.menu > summary`
   is a 30x28 ICON BOX (it ships later in this file, for the kebabs), so a
   summary that carries WORDS keeps that width unless it says otherwise, and
   its nowrap content spills straight out of the 30px box. At the right edge
   of a space-between row that is 9px of document overflow at every width,
   with no element's own scroll container to blame — measured on /studio at
   1512 and 390. Override the box, not just the paint. */
body.studio .ov-range { flex: none; }
body.studio .ov-range__trigger {
  display: inline-flex; align-items: center; gap: 8px;
  width: auto; height: auto; min-height: 38px;
  padding: 0 14px; border: var(--hair-w) solid var(--border-strong);
  border-radius: 999px; background: var(--surface); color: var(--text);
  font-size: 0.88rem; cursor: pointer; list-style: none; white-space: nowrap;
}
body.studio .ov-range__trigger::-webkit-details-marker { display: none; }
body.studio .ov-range__trigger:hover { border-color: var(--hair-gold); }
body.studio .ov-range__panel { right: 0; left: auto; min-width: 172px; }

body.studio .ov-cards {
  display: grid; gap: 14px; margin-top: 26px;
  /* 200px, so four cards sit in ONE row beside the rail and still fall to
     two, then one, as the column narrows. */
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
}
/* `position: relative` is load-bearing, not decoration: it is the containing
   block the attendance card's (i) hangs its sentence off (see below). */
body.studio .ov-card {
  padding: 18px 20px 16px; background: var(--surface); border: var(--hair-w) solid var(--border);
  border-radius: var(--radius); min-width: 0; position: relative;
}
/* The mark is gold and small — the card's subject, not its decoration. */
body.studio .ov-card__mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 8px; margin-bottom: 12px;
  background: color-mix(in srgb, var(--gold) 12%, var(--surface)); color: var(--gold);
}
body.studio .ov-card__label { font-size: 0.86rem; color: var(--muted); }
/* THE (i) BESIDE "AVERAGE LIVE ATTENDANCE" (Maaz 2026-09-10) — what the figure
   is the average OF. The tip is a SENTENCE, and `.tip-i`'s own box is 236px
   anchored on a glyph two thirds of the way across a card that is ~200px wide
   at four across, so it would lay out well past the card's right edge. A width
   clamp alone cannot fix WHERE the box starts, so the containing block moves
   instead — the card is positioned, the glyph goes `static` — which is the
   `.chat__set-label` / `.cap-cost` idiom. `box-sizing` because a `::after`
   does NOT inherit the universal border-box reset (r15) and the base rule pads
   6px 10px, so `max-width: 100%` on its own still overflows by 21px. Both
   transform states are re-declared: the base hover carries a
   translateX(-50%) that pairs with `left: 50%` and must not survive here. */
body.studio .ov-card__label .tip-i[data-tip] { position: static; }
body.studio .ov-card__label .tip-i[data-tip]::after {
  box-sizing: border-box; width: max-content; max-width: 100%;
  left: 0; right: auto; text-align: left; transform: translateX(0) translateY(2px);
}
body.studio .ov-card__label .tip-i[data-tip]:hover::after,
body.studio .ov-card__label .tip-i[data-tip]:focus-visible::after { transform: translateX(0) translateY(0); }
body.studio .ov-card__figure {
  margin-top: 6px; font-family: var(--sans); font-weight: 700;
  font-size: clamp(1.8rem, 3.4vw, 2.3rem); --lh: 1.05; line-height: var(--lh-snap); letter-spacing: -0.02em;
}
body.studio .ov-card__note { margin-top: 4px; font-size: 0.74rem; color: var(--dim); }

/* Movement: house green up, plain muted down. NEVER live-red for a fall —
   live-red means live or cut, and a quiet fortnight is neither. */
body.studio .ov-delta {
  display: inline-flex; align-items: center; gap: 5px;
  margin-top: 10px; font-size: 0.78rem;
}
body.studio .ov-delta--up { color: var(--ok); }
body.studio .ov-delta--down { color: var(--muted); }

/* ---- what to focus on next ---- */
body.studio .ov-focus { margin-top: 38px; }
body.studio .ov-focus__head h2 { margin: 0; }
body.studio .ov-focus__head .deck { margin-top: 6px; }
/* ONE SENTENCE, ONE LINE (Maaz 2026-09-10). `.deck`'s 58ch was the house's
   reading measure for prose and this is not prose — it is a 74-character
   caption over a list whose own rows are one line each, and the measure broke
   it after "don't" with "need." alone underneath. It takes the column now, so
   it wraps only where the line is genuinely full (below ~640px), and it wraps
   between WORDS: nothing here hyphenates. */
body.studio .ov-focus__deck { max-width: none; }
body.studio .ov-focus__rows {
  margin-top: 18px; border: var(--hair-w) solid var(--border);
  border-radius: var(--radius); background: var(--surface); overflow: hidden;
}
body.studio .ov-row {
  display: flex; align-items: center; gap: 14px; padding: 16px 18px;
  border-top: var(--hair-w) solid var(--border);
}
body.studio .ov-row:first-child { border-top: 0; }
/* THE ROW'S MARK — a card's own gold language, in the round (the mock draws a
   circle), and it is the row's SUBJECT rather than its decoration: same 12%
   gold wash, same gold ink. It borrows `.ov-card__mark` for the paint and
   overrides the three things a card wants and a row does not: the shape, the
   bottom margin (a card stacks under its mark, a row sits beside it) and the
   36px box a 48px row can hold. */
body.studio .ov-row__mark {
  flex: none; margin-bottom: 0; width: 36px; height: 36px; border-radius: 999px;
}
body.studio .ov-row__words { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
/* The chip's text here is BOUNDED — "Incomplete", "0/6 completed", "5 ready" —
   so `.chip`'s own `overflow-wrap: anywhere` (which exists to stop free text
   widening a page) has nothing to protect and would only break a two-word
   answer across two lines. The r4 rule, on a flex row rather than a cell. */
body.studio .ov-row__chip { flex: none; white-space: nowrap; overflow-wrap: normal; }
/* EVERY GOLD CTA ON THIS LIST IS THE SAME BOX (Maaz 2026-09-10). `.btn` sizes
   to its label, so a column of primaries read as ragged — measured at 1512:
   Complete 99px, Set up 78px, Connect 93px, Publish 87px, Post now 98px, a
   21px spread over five buttons in one stack. The floor clears the widest of
   them with room for "Schedule", which is what stops the box moving with the
   word the next time a row is added. */
body.studio .ov-row__cta {
  flex: none; min-width: 116px; padding: 8px 18px;
}
/* One primary per ROW is the gold button, so the X is a quiet 32px box.
   `.ov-row__x` is the FORM (button_to's wrapper) because that is the flex
   item the order rules below have to move; the button inside it is the box. */
body.studio .ov-row__x { flex: none; margin: 0; }
body.studio .ov-row__x button {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0;
  border: 0; background: none; color: var(--dim); cursor: pointer; border-radius: 8px;
}
body.studio .ov-row__x button:hover { color: var(--text); background: var(--surface-2); }
@media (max-width: 720px) {
  body.studio .ov-row { flex-wrap: wrap; }
  /* The mark keeps the words company on line one. Its basis is the row minus
     the mark and the gap, so the two fill that line exactly and the chip, the
     CTA and the X wrap under them — a bare `100%` would push the words to
     their own line and leave a 36px circle sitting alone above them. */
  body.studio .ov-row__words { flex-basis: calc(100% - 50px); order: 1; }
  body.studio .ov-row__chip { order: 2; }
  body.studio .ov-row__cta { order: 3; margin-left: auto; }
  body.studio .ov-row__x { order: 4; }
}

/* ---- this month at a glance ---- */
body.studio .ov-glance { margin-top: 38px; }
body.studio .ov-glance__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px; flex-wrap: wrap;
}
body.studio .ov-glance__head h2 { margin: 0; }
body.studio .ov-glance__cards {
  display: grid; gap: 14px; margin-top: 18px;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
}
body.studio .ov-glance__card {
  padding: 18px 20px 16px; background: var(--surface); border: var(--hair-w) solid var(--border);
  border-radius: var(--radius); min-width: 0;
}
body.studio .ov-glance__move { display: inline-block; margin-top: 8px; font-size: 0.78rem; }
body.studio .ov-glance__move--up { color: var(--ok); }
body.studio .ov-glance__move--down { color: var(--muted); }

/* ---------- THE EXPLANATION VIEW (Maaz's flywheel mocks, 2026-09-11) -------
   Two blocks: the era switcher above the welcome, and the flywheel where the
   four cards sit on the desk. Furniture, all of it — `/studio?era=first` is
   the only screen that draws any of these names. */

/* THE SWITCHER. A label and the house pill rail (`.tabs--rng`, the same one
   Channels and Transactions wear for their windows), with a hairline under
   the row so the welcome below reads as the page and this reads as chrome.
   It is drawn on BOTH eras: it is the only way in and the only way back. */
body.studio .ov-era {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 22px 0 14px; border-bottom: var(--hair-w) solid var(--border);
}
body.studio .ov-era__said { font-size: 0.8rem; color: var(--dim); }

/* ---- the flywheel ---- */
body.studio .ov-fly { margin-top: 28px; }
body.studio .ov-fly__head .eyebrow { display: block; margin-bottom: 6px; }

/* THE CARD IS A TWO-TRACK GRID, and all five of them are in the DOM.
   The min-height is MEASURED, not chosen: 340 is the tallest the five panels
   resolve to at the desk (step one's, with the 16:9 frame in it), so every
   card is one height and stepping through the loop never moves the focus list
   underneath. It goes on a phone, where a stacked card is as tall as its own
   content. */
body.studio .ov-fly__card {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  margin-top: 18px; min-height: 340px;
  background: var(--surface); border: var(--hair-w) solid var(--border);
  border-radius: var(--radius); overflow: hidden;
}
/* The rule above hands the card a display, which out-ranks the UA's own
   `[hidden]` rule — the r14 trap, and without this line all five steps paint
   at once, in a stack, with the markup perfectly correct. */
body.studio .ov-fly__card[hidden] { display: none; }

/* A flex COLUMN, so "Step 1 of 5" can be pinned to the foot of the card with
   `margin-top: auto` however short the paragraph above it runs. */
/* THE TWO COLUMNS ARE TWO SURFACES, and the pair is MEASURED off his mock
   rather than picked: at 1492 the copy column samples rgb(20,20,22), the
   illustration column rgb(15,15,17) and the page rgb(10,10,11) — i.e. the
   right column sits exactly half way between the page and the left. So the
   words take the raised booth and the panel takes half of it, which holds that
   relationship in any mood instead of hard-coding his three greys. The card's
   own fill stays as the base the phone layout paints on. */
body.studio .ov-fly__words {
  display: flex; flex-direction: column; padding: 28px 26px; min-width: 0;
  background: var(--surface);
}
body.studio .ov-fly__cap { display: flex; align-items: center; gap: 12px; }
/* The number is filled gold with dark ink — the mock's own strongest mark for
   "step N". It is unmistakably not a control: a 34px circle with a numeral in
   it, with the page's one filled-gold BUTTON up in the masthead. */
body.studio .ov-fly__num {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 999px;
  background: var(--gold); color: var(--gold-ink);
  font-family: var(--sans); font-weight: 700; font-size: 0.92rem;
}
body.studio .ov-fly__cap .eyebrow { margin: 0; }
body.studio .ov-fly__title { margin-top: 18px; text-wrap: balance; }
body.studio .ov-fly__body {
  margin-top: 14px; max-width: 34ch; color: var(--muted); font-size: 0.92rem;
}
body.studio .ov-fly__foot {
  margin-top: auto; padding-top: 22px; font-size: 0.76rem; color: var(--dim);
}

body.studio .ov-fly__fig {
  display: flex; align-items: center; justify-content: center;
  padding: 24px 26px; min-width: 0;
  background: color-mix(in srgb, var(--surface) 50%, var(--bg));
  border-left: var(--hair-w) solid var(--border);
}

/* THE STEPPERS. The dots are the position, the arrows are the control, and
   both are real links — so nothing here has to be revealed and no state is
   held anywhere but in the address. */
body.studio .ov-fly__nav {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin-top: 16px;
}
body.studio .ov-fly__dots { display: flex; align-items: center; gap: 2px; }
/* A 16x5 BAR WITH A 22x23 HIT AREA. `content-box` is load-bearing twice over:
   the universal reset is border-box, so without it the padding would eat the
   bar down to 10px — and `background-clip: content-box` is what keeps the
   PAINT the bar while the target stays a thumb's worth of box. A 5px-tall tap
   target on a phone is the dead control this house refuses to draw. It is
   re-declared on every state below because the `background` SHORTHAND resets
   `background-clip` to border-box — so a state that only changed the colour
   would paint the whole 22x23 target instead of the bar. */
body.studio .ov-fly__dot {
  display: block; box-sizing: content-box; width: 16px; height: 5px;
  padding: 9px 3px; border-radius: 999px;
  background: var(--surface-3); background-clip: content-box;
  transition: width var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
body.studio .ov-fly__dot:hover { background: var(--border-strong); background-clip: content-box; }
body.studio .ov-fly__dot--on { width: 30px; background: var(--gold); background-clip: content-box; }
/* The house ring, on both steppers: there is no global `:focus-visible` rule
   in this sheet, so a control that draws its own box declares its own — and a
   16x5 bar with nothing but a background has no box to show one otherwise.
   The outline follows the BORDER box, which is the padded target. */
body.studio .ov-fly__dot:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 999px; }
body.studio .ov-fly__arrows { display: flex; align-items: center; gap: 10px; }
body.studio .ov-fly__arrow {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 999px;
  border: var(--hair-w) solid var(--border-strong); color: var(--text);
}
body.studio .ov-fly__arrow:hover { border-color: var(--hair-gold); color: var(--gold); }
body.studio .ov-fly__arrow:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ON A PHONE THE CARD STACKS, and the vertical hairline between the columns
   becomes a horizontal one — the seam has to follow the axis or it draws
   across the middle of nothing. The min-height goes with the two tracks: a
   stacked card is as tall as its own content, never padded out to a desk
   measurement. */
@media (max-width: 900px) {
  body.studio .ov-fly__card { grid-template-columns: minmax(0, 1fr); min-height: 0; }
  body.studio .ov-fly__fig {
    border-left: 0; border-top: var(--hair-w) solid var(--border);
  }
  body.studio .ov-fly__body { max-width: none; }
}

/* ---- the five panels (studio/dashboard/_flywheel_figure) ----
   Each one is a crop of his own mock with OUR chrome over it (2026-09-12, and
   this reverses the drawn-only rule the round before it shipped). The width cap
   is what stops a 16:9 frame becoming the tallest thing on the page in a wide
   column — and it is what keeps the five cards
   near enough one height that stepping through them does not shift the focus
   list underneath. */
body.studio .flyfig { width: 100%; max-width: 420px; min-width: 0; }
/* THE ONE PANEL WITH A 16:9 FRAME IN IT IS CAPPED NARROWER, and the number is
   arithmetic rather than taste: at 420 the frame resolves 236px tall and the
   card came out 66px taller than the other four, so stepping through the loop
   moved the focus list under it. A `max-height` on the frame cannot do this —
   `aspect-ratio` on a block whose width the container decides clamps the
   HEIGHT and breaks the ratio rather than narrowing the box.
   RE-DERIVED when the tiles moved into their own card (2026-09-12), because
   that card's own padding put step one 18px over the other four again
   (measured 357.9 against 340). This panel's height is
   0.7292W + 48.67 — the 16:9 frame (0.5625W), a tile row of six (W/6) and 49px
   of caption, gap and padding. The card's min-height is a BORDER box, so its
   content is 338 and this column spends 48 of that on its own padding: the
   panel has to come in under 290, i.e. W <= 331. 330 measures 289.3, which
   leaves the card sitting exactly on its 340 rather than 0.42px over it — a
   fractional box under a hairline is r8's own smear. */
body.studio .flyfig--live { max-width: 330px; }

/* the frame: a live, or a tape. 16:9, our own glyph, no invented footage. */
body.studio .flyfig__frame {
  position: relative; aspect-ratio: 16 / 9;
  display: grid; place-items: center; overflow: hidden;
  border-radius: 12px; background: var(--surface-2);
  border: var(--hair-w) solid var(--border);
}
body.studio .flyfig__frame--sm { aspect-ratio: 16 / 10; border-radius: 8px; }

/* THE STILL FILLS ITS FRAME, on one rule for all five panels. It is a GRID
   ITEM rather than an absolute box, so nothing here depends on the frame
   staying positioned; `object-fit: cover` is what lets a 277x156 crop sit in a
   16:9 box and a 112x171 one in a 9:16 tile without either being squashed.
   `overflow: hidden` on the frame above is what rounds its corners. */
body.studio .flyfig__still {
  grid-area: 1 / 1; width: 100%; height: 100%; object-fit: cover; display: block;
}
/* THE LIVE MARK IS CENTRED ON THE SHOT, which is where his mock puts it — the
   round before this hung it top-right because there was nothing behind it to
   centre over. `inset: 0` + `margin: auto` on a max-content box is the house's
   own centring idiom and needs no transform, so the pill never lands on a
   half pixel. */
body.studio .flyfig__badge {
  position: absolute; inset: 0; margin: auto;
  width: max-content; height: max-content;
}
body.studio .flyfig__tag { position: absolute; top: 10px; left: 10px; }
body.studio .flyfig__frame-foot {
  position: absolute; left: 10px; right: 10px; bottom: 9px;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
body.studio .flyfig__foot-said {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 0.7rem; color: var(--dim); min-width: 0;
}
body.studio .flyfig__len { font-size: 0.7rem; color: var(--muted); }
/* OVER A PHOTOGRAPH, BOTH MARKS TAKE A PLATE. `--dim` and `--muted` are ink
   for a surface; on a still they are a smudge — so the two foot marks wear the
   same dark plate the 9:16 tiles' duration already wears below. */
body.studio .flyfig__frame--shot .flyfig__foot-said,
body.studio .flyfig__frame--shot .flyfig__len {
  padding: 2px 6px; border-radius: 6px;
  background: rgb(3 3 3 / 0.66); color: #fff;
}
/* The Encore tag keeps the house's outline-gold chip — filled gold is money
   and the Pass, nothing else — and takes the same plate, because the corner it
   sits in is a photograph and the next still to land there might be a white
   wall. */
body.studio .flyfig__frame--shot .flyfig__tag { background: rgb(3 3 3 / 0.66); }

/* 1 — broadcast once: one shot, then a card of six lit wells.
   THE THREE LEVELS ARE HIS, MEASURED: the card under the shot samples
   rgb(20,20,22) — the copy column's own level, i.e. `--surface` — and the six
   tiles inside it sample rgb(10,10,11), which is the PAGE. So the plate is
   raised and the wells are sunk, on a column that sits between them. Ours had
   the caption and the tiles floating on the column with `--surface-2` plates,
   which is the same three things drawn as two. */
/* NOT `.flyfig__reach` — step 3 has owned that name since the round before
   this one (the row that says which rail reaches a follower), and taking it
   here made this card a 0.74rem dim FLEX ROW: the caption beside the tiles,
   the tiles collapsed to their glyphs, on markup that was perfectly correct.
   Same specificity, later in the file, so step 3's rule won. Caught in a
   browser, which is the only place it existed (#415). */
body.studio .flyfig__lands {
  margin-top: 10px; padding: 10px 10px 12px;
  background: var(--surface); border: var(--hair-w) solid var(--border);
  border-radius: 12px;
}
body.studio .flyfig__cap { display: block; text-align: center; }
body.studio .flyfig__tiles {
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 6px; margin-top: 8px;
}
body.studio .flyfig__tile {
  position: relative; aspect-ratio: 1; display: grid; place-items: center;
  border-radius: 10px; background: var(--bg);
  border: var(--hair-w) solid var(--border); color: var(--muted);
}
/* THE SIX DOTS PULSE (Maaz 2026-09-12, item 3). The element carries
   `.live-dot` as well, which is where the animation and the `currentColor`
   fill come from — so this sets the SIZE and the colour and inherits the tick,
   and the house's own prefers-reduced-motion rule kills it with every other
   live dot in the product. `body.studio` (0,2,0) out-ranks `.live-dot`
   (0,1,0), which is what lets 5px beat the 7px that dot wears elsewhere. */
body.studio .flyfig__lit {
  position: absolute; top: 4px; right: 4px;
  width: 5px; height: 5px; color: var(--live);
}

/* 2 — the cut: six going dim, the room staying live, the seam between them. */
body.studio .flyfig--cut {
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 0.85fr);
  gap: 10px; align-items: center;
}
body.studio .flyfig__socials {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px;
}
body.studio .flyfig__social {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  padding: 7px 8px; border-radius: 8px; background: var(--surface-2);
  border: var(--hair-w) solid var(--border); color: var(--dim);
}
body.studio .flyfig__social-name {
  flex: 1; min-width: 0; font-size: 0.7rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
body.studio .flyfig__off {
  flex: none; width: 5px; height: 5px; border-radius: 50%; background: var(--border-strong);
}
/* THE SEAM IS THE CUT: live-red above the scissors, gold below it — the
   socials end there and the Backstage carries on. Two pseudos rather than one
   gradient, so each half is a flex item and the scissors sits on the middle
   whatever the card's height resolves to. */
body.studio .flyfig__seam {
  align-self: stretch; display: flex; flex-direction: column; align-items: center; gap: 6px;
}
body.studio .flyfig__seam::before, body.studio .flyfig__seam::after {
  content: ""; flex: 1; width: var(--hair-w); min-height: 14px;
}
body.studio .flyfig__seam::before { background: var(--live); }
body.studio .flyfig__seam::after { background: var(--gold); }
body.studio .flyfig__scissors { flex: none; color: var(--live); }
body.studio .flyfig__stays {
  display: grid; justify-items: center; gap: 4px; text-align: center;
  padding: 16px 12px; border-radius: 12px; background: var(--surface-2);
  border: var(--hair-w) solid var(--hair-gold);
}
/* HER STILL, where the mock drew a glyph — the same live as step 1, because
   the socials have ended and this room has not. 4:3 rather than the frames'
   16:9: this card is narrow and tall, and a letterbox of a portrait crop cut
   the top of her head off (measured in a browser). */
body.studio .flyfig__stays-shot {
  display: grid; width: 100%; aspect-ratio: 4 / 3; overflow: hidden;
  border-radius: 8px; margin-bottom: 4px;
}
body.studio .flyfig__stays-said { font-size: 0.88rem; }
body.studio .flyfig__stays-pill { margin-top: 6px; }

/* 3 — every arrival becomes a follower: a name and the rail that reaches it. */
body.studio .flyfig--aud { display: grid; gap: 10px; }
body.studio .flyfig__aud-head { display: flex; align-items: center; gap: 8px; }
body.studio .flyfig__aud-mark { display: inline-flex; color: var(--gold); }
body.studio .flyfig__aud-said { flex: 1; min-width: 0; font-size: 0.84rem; color: var(--muted); }
body.studio .flyfig__gain { flex: none; font-size: 0.8rem; color: var(--gold); }
body.studio .flyfig__people {
  border: var(--hair-w) solid var(--border); border-radius: 12px; overflow: hidden;
}
body.studio .flyfig__person {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px;
  background: var(--surface-2); border-top: var(--hair-w) solid var(--border);
}
body.studio .flyfig__person:first-child { border-top: 0; }
/* THE INITIAL IS THE MOCK'S OWN MARK and his letters are WHITE on a darker
   circle (measured off step-3.png) — ours were muted ink on a lighter one,
   which is the half of "bland" this panel could actually be guilty of. */
body.studio .flyfig__av {
  flex: none; display: grid; place-items: center;
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--bg); color: var(--text);
  font-family: var(--sans); font-weight: 700; font-size: 0.82rem;
}
body.studio .flyfig__who { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
body.studio .flyfig__who b { font-size: 0.86rem; }
body.studio .flyfig__reach {
  display: flex; align-items: center; gap: 5px; min-width: 0;
  font-size: 0.74rem; color: var(--dim);
}
body.studio .flyfig__said { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
body.studio .flyfig__check {
  flex: none; display: grid; place-items: center;
  width: 22px; height: 22px; border-radius: 50%;
  border: var(--hair-w) solid var(--hair-gold); color: var(--gold);
}

/* 4 — the whole session kept: the tape, and the chapters beside it. */
body.studio .flyfig--enc {
  display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: 10px; align-items: start;
}
body.studio .flyfig__enc { min-width: 0; }
body.studio .flyfig__enc-title { margin-top: 10px; font-size: 0.86rem; font-weight: 600; }
body.studio .flyfig__chapters {
  display: grid; gap: 7px; padding: 12px 14px; border-radius: 12px;
  background: var(--surface-2); border: var(--hair-w) solid var(--border);
}
body.studio .flyfig__chapter { display: flex; gap: 8px; align-items: baseline; font-size: 0.8rem; }
body.studio .flyfig__at { flex: none; font-size: 0.76rem; color: var(--gold); }

/* 5 — one hour becomes a week: one tape, three cuts, six socials. */
body.studio .flyfig--clips { display: grid; gap: 14px; }
body.studio .flyfig__flow {
  display: grid; grid-template-columns: minmax(0, 0.8fr) auto minmax(0, 1.5fr);
  gap: 10px; align-items: center;
}
body.studio .flyfig__tape {
  display: grid; gap: 6px; justify-items: center; padding: 8px;
  border-radius: 12px; background: var(--surface-2);
  border: var(--hair-w) solid var(--border);
}
body.studio .flyfig__tape .flyfig__frame--sm { width: 100%; }
body.studio .flyfig__becomes { display: grid; gap: 6px; justify-items: center; color: var(--gold); }
body.studio .flyfig__cuts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
body.studio .flyfig__cut { min-width: 0; }
body.studio .flyfig__vert {
  position: relative; display: grid; aspect-ratio: 9 / 16; overflow: hidden;
  border-radius: 8px;
  background: var(--surface-2); border: var(--hair-w) solid var(--border);
}
/* The duration rides ON the tile here, so it takes its own plate rather than
   the muted ink it wears under a 16:9 frame. */
body.studio .flyfig__vert .flyfig__len {
  position: absolute; right: 4px; bottom: 4px; padding: 1px 5px;
  border-radius: 5px; background: rgb(3 3 3 / 0.66); color: #fff; font-size: 0.62rem;
}
body.studio .flyfig__cut-title {
  display: block; margin-top: 4px; font-size: 0.68rem; color: var(--dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
body.studio .flyfig__ready {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; color: var(--muted);
}

/* THE PANELS' OWN SQUEEZE. The card stacks at 900 and the figure column keeps
   its width there; below this it is narrower than any column the mock draws,
   so the two panels that lay a list BESIDE a card give up the second column
   rather than ellipsising a platform's name down to one letter (measured at a
   true 390: 18px of an "Instagram"). Three 9:16 tiles in a 170px row leave
   53px each — enough for the tile and its duration, not for a title, and the
   tiles are the claim while the titles are flavour. */
@media (max-width: 560px) {
  body.studio .flyfig__socials { grid-template-columns: minmax(0, 1fr); }
  body.studio .flyfig--enc { grid-template-columns: minmax(0, 1fr); }
  body.studio .flyfig__cut-title { display: none; }
}

/* ---------- THE BOARD (r3-ref-1, 2026-09-02) -------------------------------
   Lives: three figures, the tab rail, and one row per night. Hairlines, not
   boxes — a night is a ROW here (the reference), which is what keeps twenty
   of them readable in one screen where twenty cards would not be.

   The row is a three-track grid: a fixed frame, the night (the only track
   that flexes, and the only one allowed to wrap), and the actions, which must
   never wrap mid-pair — a Share that lands under its own kebab is how a row
   stops reading as one row. Under 720px the actions drop to their own line
   under the words and the frame stays beside the title, so the night is still
   identifiable at a glance on a phone. */
body.studio .lives-figs { display: flex; flex-wrap: wrap; gap: 14px 46px; margin-top: 28px; }
body.studio .lives-fig { min-width: 0; }
body.studio .lives-fig__n {
  font-family: var(--sans); font-weight: 700; --lh: 1.05; line-height: var(--lh-snap); letter-spacing: -0.02em;
  font-size: clamp(1.9rem, 3.6vw, 2.4rem);
}
body.studio .lives-fig > .label { display: block; margin-top: 4px; }

body.studio .lives-board { margin-top: 4px; }
body.studio .lives-row {
  display: grid; grid-template-columns: 116px minmax(0, 1fr) auto;
  gap: 18px; align-items: center;
  padding: 18px 0; border-bottom: 1px solid var(--border);
}
/* the panel of an open kebab is taller than the row it hangs off */
body.studio .lives-row:has(details.menu[open]) { position: relative; z-index: 40; }
body.studio .lives-row__frame {
  position: relative; aspect-ratio: 16 / 10; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--border); overflow: hidden;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
body.studio .lives-row__frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* No cover exists for a night in this schema, so the frame is the DATE. The
   month is the stamp voice and the day is the figure — the same pairing the
   playbill frames use, at thumbnail scale. */
body.studio .lives-row__mon {
  font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--dim);
}
body.studio .lives-row__day {
  font-family: var(--sans); font-weight: 700; font-size: 1.45rem; --lh: 1.05; line-height: var(--lh-snap);
  color: var(--muted);
}
body.studio .lives-row__badge { position: absolute; top: 6px; left: 6px; }
body.studio .lives-row__words { min-width: 0; }
body.studio .lives-row__words h3 { font-size: 1.02rem; font-weight: 600; --lh: 1.3; line-height: var(--lh-snap); overflow-wrap: anywhere; }
body.studio .lives-row__when { margin-top: 5px; }
body.studio .lives-row__meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; margin-top: 8px;
}
/* icon and number are one fact: they wrap together or not at all */
body.studio .lives-row__fact { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
body.studio .lives-row__fact .lucide { color: var(--dim); flex: none; }
/* A destination's initials, in the mono face — this house vendors no brand
   logos (Lucide carries none of these), so the mark is the platform's own
   name, shortened, with the full name on the tooltip and in the sr-only line
   beside it. */
body.studio .lives-mark {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 24px; height: 20px; padding: 0 5px; margin-right: 4px;
  border: 1px solid var(--border-strong); border-radius: 4px;
  font-size: 0.62rem; letter-spacing: 0.06em; color: var(--muted);
}
body.studio .lives-row__acts { display: flex; align-items: center; gap: 8px; flex: none; }

@media (max-width: 719px) {
  /* the actions take their own line, so the frame rides the TITLE rather than
     centering itself against a row that is now two rows tall */
  body.studio .lives-row { grid-template-columns: 84px minmax(0, 1fr); align-items: start; }
  body.studio .lives-row__acts { grid-column: 1 / -1; }
}

/* ---------- the studio voice: sentence case, in the body face --------------
   Big Shoulders caps are the SELL voice — the door, the wall, the room, the
   receipt. The studio is a desk a creator works at for an hour, and condensed
   caps at desk density read as shouting every heading at them; the reference
   sets studio headings in Archivo, sentence case, at a working scale. The
   marquee is not banished from the studio: GO LIVE keeps it (it is the one
   thing this product does), and every number keeps .figure/.marquee-num. */
body.studio h1, body.studio h2, body.studio .headline, body.studio .headline--md {
  font-family: var(--sans); text-transform: none; letter-spacing: -0.012em;
}
body.studio h1 { font-size: clamp(1.55rem, 3.2vw, 2.05rem); font-weight: 700; --lh: 1.15; line-height: var(--lh-snap); }
body.studio h2 { font-size: 1.25rem; font-weight: 700; --lh: 1.25; line-height: var(--lh-snap); }
body.studio .headline { font-size: clamp(1.9rem, 4vw, 2.6rem); font-weight: 700; --lh: 1.1; line-height: var(--lh-snap); }
body.studio .headline--md { font-size: clamp(1.45rem, 3vw, 1.85rem); --lh: 1.2; line-height: var(--lh-snap); }

/* ---------- toasts ---------- */
/* bottom rides the home-indicator inset (0 where none exists) — same
   discipline as .drop-sheet__card below. */
/* ── THE HOUSE CONFIRM (control-room QA items 9 and 10) ──────────────────────
   One dialog for every `data-turbo-confirm` in the app (house_confirm.js sits
   on Turbo.setConfirmMethod). It is DELIBERATELY NOT `body.studio`-scoped —
   fan-side confirms paint here too (Unfollow, cancel a booking), and the
   studio fence's own law is that a view outside app/views/studio may not wear
   a scoped class (test/studio_scope_law_test.rb), which this partial is.

   The box is the Advanced sheet's, narrower: same border, same radius, same
   surface, same backdrop, so it reads as the same furniture rather than as a
   second look. `showModal()` puts it in the top layer, so there is no z-index
   here and nothing on the page can cover it. */
dialog.hconfirm {
  border: 1px solid var(--border-strong); border-radius: 16px; padding: 20px;
  background: var(--surface); color: var(--text);
  width: min(420px, calc(100vw - 40px));
  box-shadow: 0 30px 80px rgb(0 0 0 / 0.6);
}
dialog.hconfirm::backdrop { background: rgb(0 0 0 / 0.7); }
.hconfirm__said { margin: 0 0 18px; font-size: 0.95rem; --lh: 1.5; line-height: var(--lh-snap); }
/* The safe answer first in DOM order (it takes focus on open) and the go
   button last, where the eye and the thumb both end up. */
.hconfirm__acts { display: flex; justify-content: flex-end; gap: 10px; flex-wrap: wrap; }

.toasts { position: fixed; bottom: calc(20px + env(safe-area-inset-bottom)); left: 50%; transform: translateX(-50%); z-index: 90; display: flex; flex-direction: column; gap: 8px; max-width: min(92vw, 560px); }
.toast {
  background: var(--surface); border: 1px solid var(--border-strong); color: var(--text);
  border-radius: 4px; padding: 10px 20px; font-size: 0.88rem;
  transition: opacity 300ms ease, transform 300ms ease;
}
/* Failure never dresses like success (QA P2-11): live-red hairline all the
   way around + red text — red means something went wrong, right now. */
.toast--alert { border-color: rgb(255 68 56 / 0.55); color: var(--live); }
@starting-style { .toast { opacity: 0; transform: translateY(10px); } }

/* ---------- the door (Gilt) ---------- */
.hero { padding: 72px 0 44px; max-width: 760px; }
.hero h1 { font-size: clamp(3rem, 9vw, 6rem); --lh: 0.94; line-height: var(--lh-snap); }
.hero p { margin-top: 20px; font-size: 1.05rem; --lh: 1.65; line-height: var(--lh-snap); color: var(--muted); max-width: 56ch; }
.hero .btn { margin-top: 30px; }

/* the landing hero split (WIRE 6b): the words and the one field left, the film
   slot right — the full 1440 canvas composed, collapsing to one column on phones */
.hero-split {
  display: grid; grid-template-columns: 1.1fr 1fr;
  gap: clamp(28px, 4.5vw, 64px); align-items: center; padding: 64px 0 56px;
}
.hero-split h1 { font-size: clamp(3rem, 7vw, 5.6rem); --lh: 0.94; line-height: var(--lh-snap); }
.hero-split > * { min-width: 0; }
@media (max-width: 899px) { .hero-split { grid-template-columns: 1fr; padding: 44px 0 36px; } }

/* the inline one-field start (WIRE 6b): GETs the one door with the identity
   prefilled — the same /login, brought forward, never a second auth path */
.identity-inline { display: flex; gap: 12px; margin-top: 30px; max-width: 480px; }
.identity-inline input { height: 54px; flex: 1 1 auto; min-width: 0; }
.identity-inline .btn { flex: none; height: 54px; }
@media (max-width: 479px) {
  .identity-inline { flex-wrap: wrap; }
  .identity-inline .btn { width: 100%; }
}

/* the 90-second film slot (WIRE 6b): ships empty and says so as design —
   striped placeholder, circled play glyph, mono caption. Never a stock loop. */
.film-slot {
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  min-height: clamp(240px, 30vw, 460px);
  background: repeating-linear-gradient(45deg, #0d0c0a 0 18px, #121008 18px 36px);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; padding: 24px; text-align: center;
}
.film-slot__play {
  width: 64px; height: 64px; border-radius: 50%; border: 1.5px solid var(--accent);
  display: flex; align-items: center; justify-content: center;
}
.film-slot__play::after {
  content: ""; width: 0; height: 0; margin-left: 5px;
  border-left: 18px solid var(--accent);
  border-top: 11px solid transparent; border-bottom: 11px solid transparent;
}

/* the paper sheet: WIRE's mid-page paper band, adapted as a sheet inside the
   shell (a 50vw breakout would add scrollbar-width overflow). The full paper
   token set re-scoped, so every component on the sheet reads paper-native. */
.paper-scope {
  color-scheme: light;
  --bg: #F1F2F4; --surface: #ffffff; --surface-2: #eeeef0; --surface-3: #e6e6e9;
  --frame-mat: #ffffff;
  --border: rgb(32 33 35 / 0.15); --border-strong: rgb(32 33 35 / 0.3);
  --text: #202123; --muted: rgb(32 33 35 / 0.65); --dim: rgb(32 33 35 / 0.45);
  --accent: #b8860b; --accent-soft: #a9873f; --accent-deep: #8a6a1e;
  --gold: #b8860b; --gold-bright: #a9873f; --gold-ink: #171207;
  --ok: #16a34a; --warn: #a9612e;
  --btn-bg: #202123; --btn-fg: #F1F2F4; --btn-hover: #35363a;
  --input-bg: #ffffff;
  --hair-25: rgb(32 33 35 / 0.12); --hair-35: rgb(32 33 35 / 0.18);
  --hair-48: rgb(32 33 35 / 0.28); --hair-60: rgb(32 33 35 / 0.4);
}
.sheet {
  background: var(--bg); color: var(--text);
  border-radius: var(--radius); padding: clamp(26px, 5vw, 56px);
}

/* ---------- the claim band (ref-1: "Your turn.") --------------------------
   The flip, promoted out of a dashed row at the foot of a page and into the
   section it deserved: its own hairline-topped band, centered, the marquee
   heading at display scale, and the door's own one field — the host, then a
   handle. Full width of the shell, NOT of the viewport: a 100vw breakout adds
   scrollbar-width overflow, which is the exact trap the paper sheet documents
   a few rules up. */
.claim-band { text-align: center; padding: clamp(52px, 8vw, 96px) 0 clamp(40px, 6vw, 72px);
  margin-top: clamp(32px, 5vw, 64px); border-top: 1px solid var(--border); }
body.door .claim-band { border-top-color: var(--hair-35); }
.claim-band .deck { margin: 18px auto 0; max-width: 46ch; }
.claim-band__form { display: flex; gap: 12px; justify-content: center; margin: 30px auto 0; max-width: 580px; }
/* the host and the handle read as ONE control, the way the claim screen's
   field does — the border lives on the wrapper, never on the input */
.claim-band__field {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 2px;
  height: 56px; padding: 0 14px; text-align: left;
  background: var(--input-bg); border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); transition: border-color 150ms ease;
}
.claim-band__field:focus-within { border-color: var(--accent); }
.claim-band__host { font-family: var(--mono); color: var(--dim); white-space: nowrap; }
.claim-band__field input, .claim-band__field input:focus {
  flex: 1 1 auto; min-width: 0; width: auto; height: 100%;
  background: none; border: 0; border-radius: 0; padding: 0;
}
.claim-band__form .btn { flex: none; height: 56px; }
.claim-band__fine { margin-top: 16px; }
.claim-band #handle-availability { display: block; }
@media (max-width: 479px) {
  .claim-band__form { flex-wrap: wrap; }
  .claim-band__form .btn { width: 100%; }
}

/* the graduated gold hairline stack — the door's signature (BRAND-ANCHOR) */
.hairstack { display: flex; flex-direction: column; gap: 7px; }
.hairstack span { display: block; border-top: 1px solid var(--border); transform-origin: left;
  animation: g-draw var(--dur) var(--ease) both; }
.hairstack span:nth-child(2) { animation-delay: 90ms; }
.hairstack span:nth-child(3) { animation-delay: 180ms; }
.hairstack span:nth-child(4) { animation-delay: 270ms; }
.hairstack span:nth-child(1) { border-color: var(--hair-25); }
.hairstack span:nth-child(2) { border-color: var(--hair-35); }
.hairstack span:nth-child(3) { border-color: var(--hair-48); }
.hairstack span:nth-child(4) { border-color: var(--hair-60); }
@keyframes g-draw { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.rise { animation: g-rise var(--dur) var(--ease) both; }
.rise--2 { animation-delay: 110ms; }
.rise--3 { animation-delay: 220ms; }
@keyframes g-rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }

/* the ink ledger panel — the roster proof (Gilt 2a) */
.ledger {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px; display: flex; flex-direction: column; gap: 9px;
  font-family: var(--mono); font-size: 12px;
}
.ledger .row { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.ledger .theirs { color: var(--dim); }
.ledger .ours { color: var(--gold); }
.ledger hr { border: 0; border-top: 1px solid var(--border); margin: 0; width: 100%; }
body.door .ledger hr { border-color: var(--hair-25); }

/* pricing rungs / the ladder (Gilt 2a → paper too) */
.rung { border-top: 1px solid var(--hair-25); padding: 22px 0; }
.rung--marked { border-top-color: var(--hair-60); }
.rung .top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px 18px; flex-wrap: wrap; }
.rung h3 { font-family: var(--display); font-weight: 700; text-transform: uppercase; font-size: 1.5rem; }
.rung .price { font-family: var(--mono); font-size: 1.35rem; font-weight: 500; color: var(--gold); white-space: nowrap; }
.rung .price small { font-size: 0.8rem; color: var(--dim); }
.rung p { margin-top: 8px; max-width: 60ch; }

/* the three moves (Gilt creators) */
.move { display: flex; gap: 16px; }
.move .n { font-family: var(--mono); font-size: 13px; color: var(--gold); padding-top: 8px; }
.move h3 { font-family: var(--display); font-weight: 700; text-transform: uppercase; font-size: 1.55rem; }
.move p { margin-top: 6px; max-width: 52ch; color: var(--muted); }

/* ---------- the ticket (composer, Marquee 1a·1) ---------- */
.ticket { background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius);
  padding: clamp(24px, 4.5vw, 44px) clamp(20px, 4vw, 44px) 0; }
.core { font-family: var(--sans); font-size: clamp(1.05rem, 2.3vw, 1.3rem); --lh: 1.6; line-height: var(--lh-snap); text-wrap: balance; }
.core .num { font-weight: 700; color: var(--accent); font-variant-numeric: tabular-nums lining-nums; }
.platform-rows > div { display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; padding: 13px 0; border-top: 1px solid var(--border); font-size: 0.95rem; }
.platform-rows > div:last-child { border-bottom: 1px solid var(--border); }
.platform-rows .state { font-family: var(--mono); font-size: 0.7rem; letter-spacing: 0.06em; }
.state--on { color: var(--ok); }
.state--off { color: var(--dim); }
.tear { border-top: 2px dashed color-mix(in srgb, var(--border-strong) 80%, var(--text)); margin: clamp(24px, 4vw, 40px) -1px 0; padding: 18px 0 22px; }

/* the pass line: the only gold rule in the house (Marquee) */
.pass-line { border-top: 1px solid var(--gold); margin-top: 44px; padding-top: 18px;
  display: flex; justify-content: space-between; align-items: center; gap: 18px; flex-wrap: wrap; }

/* pull quotes: the crowd, on the record — full hairline, never one-sided */
.quote-block { border: 1px solid var(--border-strong); border-radius: var(--radius-sm); padding: 14px 18px; margin: 18px 0; }
.quote-block p { font-family: var(--sans); font-weight: 500; font-size: 1.1rem; --lh: 1.5; line-height: var(--lh-snap); }

/* ---------- misc ---------- */
.divider { height: 1px; background: var(--border); border: 0; margin: 34px 0; }
.spread { display: flex; align-items: center; justify-content: space-between; gap: 14px; }
/* gh#39: the push card shows ONE state at a time — .spread's display:flex
   outranks the UA's [hidden] rule (the identity-field lesson, line ~443). */
[data-controller="push"] [hidden] { display: none; }
.stack { display: flex; flex-direction: column; gap: 14px; }
.cluster { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.prose { max-width: 68ch; color: var(--text); }
.prose p { margin: 0 0 1em; }
.transcript p { margin: 0 0 1.1em; color: var(--muted); }
.transcript p strong { color: var(--text); }
.checklist { display: flex; flex-direction: column; gap: 0; }
.checklist__item { display: flex; gap: 14px; align-items: center; padding: 13px 4px;
  border-top: 1px solid var(--border); font-size: 0.92rem; }
.checklist__item:last-child { border-bottom: 1px solid var(--border); }
.checklist__num { font-family: var(--mono); font-weight: 600; color: var(--accent); font-size: 0.95rem; width: 22px;
  text-align: center; flex: none; }
code { font-family: var(--mono); font-size: 0.85em; }

/* ---------- the signup progress line (Russell's re-sequenced door) ----------
   Five rules for the five steps of becoming a creator, the ones behind you
   lit. Existing tokens only: the house light for done, the same hairline the
   rest of the product uses for what's still ahead. Not a score — no fill
   percentage, no gradient, no glow. */
.stepline { display: flex; flex-direction: column; gap: 8px; }
.stepline__bar { display: flex; gap: 6px; list-style: none; margin: 0; padding: 0; }
.stepline__seg { flex: 1 1 0; height: 3px; border-radius: 2px; background: var(--hair-25); }
.stepline__seg--done { background: var(--accent); }
/* the step they're standing on: lit, but not finished */
.stepline__seg--current { background: color-mix(in srgb, var(--accent) 55%, transparent); }

/* ---------- the channel panel (the arc's step 5, reflected on /:handle) -----
   A YouTube-style header band and a profile photo, on the same hairline-and-
   frame vocabulary as every other card in the house. */
.channel-banner {
  display: block; width: 100%; aspect-ratio: 4 / 1; min-height: 96px;
  object-fit: cover; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface-2);
}
.avatar--photo { object-fit: cover; padding: 0; }
/* a Pass's own picture (Maaz 2026-09-01): the same frame vocabulary as the
   channel header, at the card's width. Optional everywhere — when there
   isn't one the card shows nothing, never a placeholder band. */
.pass-thumb {
  /* 1.91:1 — the crop a share card shows, so the picture on the card and the
     picture in the message are the same picture, cropped the same way. */
  display: block; width: 100%; aspect-ratio: 1.91 / 1;
  object-fit: cover; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface-2);
}
/* the studio shelf and the edit form: a stamp beside the words, not a banner */
.pass-thumb--sm { max-width: 220px; }
/* THE ALTAR (Maaz's QA 2026-09-14, item 3). `.channel-links` went out with the
   pill row it dressed (the .gl-side P0 — a class named after retired markup is
   how the next surface inherits a box by reaching for a familiar name).

   Two columns that WRAP rather than a grid: the identity takes the slack
   (`flex: 1 1 380px`) and the right column keeps its content width, so at any
   desk width the name is beside the picture and the three lines are beside the
   name — and below ~900px of column the right block drops under the left
   instead of squeezing it. */
.prof-head {
  display: flex; flex-wrap: wrap; justify-content: space-between;
  align-items: flex-start; gap: 28px 40px; padding: 44px 0 0;
}
/* THE PICTURE TOP-ALIGNS WITH THE NAME (Maaz 2026-09-15, 03:54 — "a little
   bigger and higher, gold ring, sits WITH the name, not sunk"). This REVERSES
   that morning's own `align-items: center`, and the reversal is safe now for a
   reason that was not true then: `center` puts the ring's middle on the
   identity block's middle, so on a profile carrying a bio, a link and both
   figures the ring's TOP sank ~20px below the name's — which is what he read
   as sunk. `flex-start` was the earlier answer and looked wrong because the
   name was rendering an 84px line box on a 54px face (30px of dead leading,
   fixed in `.prof-name` the same day): with the leading gone, the ring's top
   edge and the name's cap sit in one band.

   IT ALSO KILLS THE SPARSE-BIO INVERSION BY CONSTRUCTION. `center` + an
   `align-self` on the words was a two-part answer to the two content shapes;
   with both items at the top there is nothing left to invert, so
   `.prof-who`'s own `align-self` went with it rather than being left standing
   as a rule that can no longer do anything. */
.prof-id { display: flex; align-items: flex-start; gap: 22px; min-width: 0; flex: 1 1 380px; }
/* HOLD THE COLUMN (his own words): `.ring` is inline-flex, so in a flex row it
   would shrink before the text does — the one thing a 132px picture must not
   do. */
.prof-id .ring { flex: none; }
.prof-who { min-width: 0; }
/* A STEP BIGGER, AND SCOPED TO THIS ONE HOST. `.avatar--xl` is house
   vocabulary with THREE hosts — the studio Account card's crop preview (whose
   box `account_page_test` measures), the follower profile's hero, and this
   altar — so the size moves here and nowhere else, exactly as the 760px rule
   below already narrows it for a phone. */
.prof-id .avatar--xl { width: 132px; height: 132px; font-size: 2.7rem; }
/* THE NAME'S OWN LEADING, and it is the other half of "huge gap under the
   name" (Maaz 2026-09-15). `--lh-snap` is declared once on `:root`, so the
   `var(--lh)` inside it was substituted THERE and every element inherits the
   token with 1.55 already frozen in — the 86 `--lh: N` rules in this sheet are
   inert, `h1`'s own 0.98 included. MEASURED at 1400: a 54.4px display name
   rendered an 84px line box, i.e. 30px of leading nobody asked for, half of it
   dead air between the name and the sentence under it. Spelled locally, where
   `var(--lh)` does resolve against the element (the `.page-title` precedent) —
   the token is deliberately left alone, because repairing it would re-lead the
   whole app. */
.prof-name { margin: 0; --lh: 1.06; line-height: round(calc(1em * var(--lh)), 1px); }
/* BIO FIRST, FIGURES SECOND (Maaz 2026-09-15). The bio's own margin is the
   tight one now, because it is the H1's second line — the "huge gap under the
   name" was a row of figures standing between somebody's name and the one
   sentence that says who they are. */
.prof-meta {
  display: flex; flex-wrap: wrap; gap: 6px; margin: 14px 0 0;
  color: var(--muted); font-size: 0.92rem;
}
.prof-meta__dot { color: var(--dim); }
.prof-bio { margin: 10px 0 0; color: var(--muted); max-width: 56ch; }
.prof-links { display: flex; flex-wrap: wrap; gap: 18px; margin-top: 14px; }
/* A PLAIN LINK WITH A MARK, never a pill: somebody's own address is a link,
   and a chip beside a bio reads as a control this product owns. */
.prof-link {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--muted); font-size: 0.92rem;
}
.prof-link:hover { color: var(--text); text-decoration: underline; }
/* THE TWO COLUMNS ARE LEVEL (Maaz 2026-09-15 — this REVERSES the 09-14 "the
   three lines sit lower" ruling, on his own later mock). The 58px it carried
   put the first line level with the BIO; today the first line starts even with
   the NAME, so the header reads as one band rather than as two blocks at two
   heights. There is nothing left to undo on a phone, which is why the stacked
   override went with it. */
.prof-side { flex: none; display: flex; flex-direction: column; gap: 20px; }
/* A VERTICAL RULE TO THE LEFT OF THE THREE LINES (Maaz 2026-09-15, item 4:
   "vertical hairline between the identity and the Live streams column — not a
   second card"). A border on the RIGHT COLUMN and not a box of its own: the
   whole note is that this is a column divider, so there is no fill, no radius
   and no padding on the far side to make it read as a panel.

   1px, NEVER `--hair-w`. That variable is 1.5px past 1.5dppx and is spent on
   the FULLY-ROUND strokes alone (the r9 ruling): Chrome snaps an axis-aligned
   1px border to one whole device pixel, so a straight rule needs nothing else
   to land clean — which is exactly what the fan rail's own divider uses.
   `--border` has been OPAQUE since r9, so the line is the same weight wherever
   it sits.

   ITS HEIGHT IS THE RIGHT COLUMN'S, on purpose. `.prof-head` is
   `align-items: flex-start`, so the rule runs the three lines plus the one
   control they belong to and stops — a divider beside what it divides, rather
   than a full-bleed line under a short bio.

   IT IS DRAWN ONLY WHERE THERE ARE TWO COLUMNS TO DIVIDE, and the breakpoint
   is deliberately the one this block ALREADY has. `.prof-head` wraps on
   content rather than on a query, so there is no selector for "this column
   came second on its own line" — and a `border-left` on a stacked, full-width
   block is a vertical tick floating at its left edge, which is worse than no
   divider. MEASURED: the wrap is between 700px of viewport (side by side) and
   680px (stacked), i.e. where the shell's content box falls under
   380 + 40 + 221 = 641px. So the rule rides `min-width: 760px`, which leaves
   80px of margin over the measured boundary and reuses the altar's own phone
   breakpoint instead of minting a second number that could drift from it.
   The cost is a 680–760px band with a plain 40px gutter and no rule — exactly
   what shipped before this round — and the error can only ever be a MISSING
   divider, never one drawn across a stack. */
@media (min-width: 760px) {
  .prof-side { padding-left: 34px; border-left: 1px solid var(--border); }
}
.prof-what { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.prof-what li { display: flex; align-items: center; gap: 10px; }
/* A TITLE AND ONE MUTED SUBLINE (his screenshot, 2026-09-15). The title is the
   ink and the subline is the quiet half — one line each, so the mark stays
   centred against the pair and the list keeps reading as three facts rather
   than as six. `>` on both, because these are the only children this box has
   and a descendant rule would reach anything a later round nests in here. */
.prof-what__said { display: flex; flex-direction: column; min-width: 0; }
/* TITLE TO SUBLINE IS A LEADING PROBLEM, NEVER THE `gap` (Maaz 2026-09-15:
   "tighten the gap between the title and its subtext a tad"). That gap was
   already 1px — what stood between the two lines was `--lh-snap`'s 1.55 baked
   in at `:root`, which on a 15.2px title and a 13.1px subline spent ~15px of
   leading between two lines that are one fact. Spelled locally, where
   `var(--lh)` does resolve against the element (`.prof-name`'s own precedent),
   at a ratio that is still a comfortable line rather than a collapse. The
   `gap` is gone with it: two numbers holding one distance is how the next edit
   moves one and not the other. */
.prof-what__said > b { font-weight: 600; font-size: 0.95rem; color: var(--text);
  --lh: 1.3; line-height: round(calc(1em * var(--lh)), 1px); }
.prof-what__said > span { color: var(--muted); font-size: 0.82rem;
  --lh: 1.35; line-height: round(calc(1em * var(--lh)), 1px); }
/* …AND A 22px MARK STAYS 22px. `lucide_icon` sizes the SVG, but an `<svg>` is
   a flex item with the default `flex-shrink: 1`, so the mark is the thing that
   gives when a subline is long — which is the one place item 2's step up from
   18 could quietly be undone. The colour is deliberately NOT set here: it was
   inherited before this round and the item is about the SIZE. */
.prof-what li > .lucide { flex: none; }
.prof-cta { display: flex; flex-wrap: wrap; gap: 10px; }
@media (max-width: 760px) {
  /* 132px of picture beside a name in ~200px of column is the name wrapping to
     four lines — the mark shrinks and the row stays a row. This is the same
     breakpoint the divider above rides, from the other side: below it the two
     columns are one, and there is nothing for a rule to divide. */
  .prof-id { gap: 16px; }
  .prof-id .avatar--xl { width: 84px; height: 84px; font-size: 1.8rem; }
}
/* the trailer: a framed 16:9 player, the film-slot's geometry with real video */
.channel-trailer { position: relative; aspect-ratio: 16 / 9; border-radius: var(--radius);
  overflow: hidden; border: 1px solid var(--border-strong); background: #000; }
.channel-trailer video { width: 100%; height: 100%; object-fit: contain; display: block; background: #000; }
/* ---------- the onboarding arc's geometry (mobile web r1, 2026-09-14) ------
   THE ARC'S VERTICAL RHYTHM WAS INLINE, WHICH IS THE ONE PLACE A MEDIA QUERY
   CANNOT REACH. Every step shipped as `<main class="shell" style="max-width:…">`
   wrapping `<div style="padding: 44px 0 8px">`, so on a phone the step's height
   was not a thing any stylesheet could answer — and the step Maaz screenshotted
   spent 910px at 390 against a phone's ~664 of visible viewport. (That step was
   GET THE APP, retired for launch on 2026-09-16 with every other
   app-download surface; its `.arc__qr`, `.arc__channels` and `.arc--app` rules
   went with it. The rhythm below is the arc's, and the other four steps wear
   it.)

   The last 130px of that 910 is DEAD SPACE (`.shell`'s 96px trailing padding
   plus the skip line), so a flick to the bottom parks the emptiness on screen
   and pushes the claim off the top. That is Maaz's screenshot, measured at
   390x664: scrollY 246, the stepline at -147, the h1's bottom at -2 — gone
   entirely — and the muted paragraph's first two lines behind the masthead with
   only its last clause showing. Nothing is clipped at rest; the bug is that the
   only place the one ACTION is readable is the one place the claim is not.

   The three values below are the inline ones VERBATIM, so nothing moves at any
   width (asserted at 1512x982 in test/system/mobile_web_arc_test.rb). The phone
   block after them is the whole fix. Fenced `body.studio` because only the arc
   wears these names — studio_scope_law_test's law 2. */
body.studio .arc__head { padding: 44px 0 8px; }

@media (max-width: 639px) {
  /* The trailing padding is the bug's own cause, so it goes first: 96px of
     nothing under the last control is ~15% of a phone viewport, and it is what
     lets the scroll run past the fold that matters. Scoped to `.arc` and not to
     `.shell` on purpose — `.shell` dresses every page in the product, and a
     round that fixes one screen does not get to move all of them. */
  body.studio .arc { padding-bottom: 24px; }
  /* 44px of air above a progress line that already sits under a 55px masthead
     is a desk's opening, not a phone's. */
  body.studio .arc__head { padding: 16px 0 4px; }
  /* THE ONE PRIMARY IS THE ONE BLOCK (Maaz's mobile item 2). Every step of the
     arc carries exactly one `.btn--primary`, and on a phone that is the whole
     width — which also settles the alignment question by removing it. The
     ghosts beside it ("See what they'll see", "Read the file") keep their own
     width: one primary per screen means one block per screen.

     `.arc__cta` WENT WITH THE APP STEP: that form was its only host, so once
     #707 deleted the screen the rule had no markup left — and a class named
     after retired markup is how the next surface inherits a box by reaching
     for a familiar name (the `.gl-side` P0). Verified zero hosts across app/,
     test/ and lib/ before deleting. */
  body.studio .arc .btn--primary { width: 100%; }
}

/* ---------- the Encores wall (IDEA-REACTION-OVERLAY-WALL) ----------
   A wall of tiles already playing, silently; the room's reactions rise and
   fade over the frame. Live-red stays on the LIVE chip; hearts are
   stage-white on every tile — a replay never borrows the red.

   The tile is a FRAME plus a BODY (the 2026-09-02 reference round, phase 1).
   The chrome rides the frame — ENCORE top-left, LIVE or the duration
   bottom-right, the density strip laid across the foot — and the words that
   NAME the room (title, creator, when) sit under it, permanently. Nothing
   that tells a person what they are looking at waits for a hover, which is
   an interaction a phone was never going to deliver anyway.

   The HERO tile is the one exception: its words ride the frame on the one
   sanctioned scrim (§8.2), the way the marquee card does.

   No gold in here. tokens.css reserves it for money and the Pass, so the
   ENCORE badge wears the dark translucent chip the wall already used and the
   density strip is stage-white — which is exactly what the old reveal's band
   was. The one gold thing on a tile stays the Backstage chip, because that
   one IS the Pass. */
.wall-tile { position: relative; }
.wall-tile__frame {
  position: relative; aspect-ratio: 16 / 9; overflow: hidden;
  border-radius: var(--radius); background: #000;
  border: 1px solid color-mix(in srgb, var(--text) 35%, transparent);
  transition: border-color 150ms ease;
}
.wall-tile:hover .wall-tile__frame,
.wall-tile:focus-within .wall-tile__frame { border-color: var(--text); }
.wall-tile__frame video, .wall-tile__frame img {
  width: 100%; height: 100%; object-fit: cover; display: block; background: #000;
}
/* the two corners the chrome lives in — both above the hearts, both under the
   frame's own stretched link, so a tap anywhere on the picture enters */
.wall-tile__chips {
  position: absolute; top: 10px; left: 10px; z-index: 2;
  display: flex; gap: 6px; flex-wrap: wrap; max-width: calc(100% - 20px);
}
.wall-tile__corner { position: absolute; right: 10px; bottom: 14px; z-index: 2; display: flex; gap: 6px; }
/* TOP-RIGHT IS THE BADGE SLOT (item 7): LIVE here, where the Upcoming card
   carries its date. It used to be bottom-right on a grid tile and top-LEFT on
   a hero — one fact in two places, neither of them the sibling card's. */
.wall-tile__badge { position: absolute; top: 10px; right: 10px; z-index: 2; display: flex; gap: 6px; }
.wall-tile__chips .chip, .wall-tile__corner .chip, .wall-tile__badge .chip {
  background: rgb(10 8 6 / 0.7); border-color: transparent; color: #EFE7D6;
}
.wall-tile__badge .chip--live { background: var(--live); color: #fff; }
/* THE BADGE SLOT'S THIRD VALUE (Maaz 2026-09-16): a pass-gated Encore's lock,
   where a live tile wears LIVE and the Upcoming card wears its date — so the
   three corners read Encore / lock / duration.

   A MARK AND NOT A CHIP. The gold "Backstage" pill came off this tile by name,
   and what replaces it is a glyph on the SAME dark plate the chips around it
   wear rather than a stroke-only icon: this sits over a frame whose colour is
   the creator's, so an unplated mark is one nobody can find on a bright
   poster. Fully round, like every chip beside it.

   23px IS MEASURED AGAINST `.chip--stamp`, not chosen: that chip is 0.62rem
   (9.92px) on `--lh-snap`'s 1.55 → a 15px line box, plus 3px of padding a side
   and two hairlines. Equal boxes are what make the two top corners read as one
   row of marks rather than two sizes of chrome. */
.wall-tile__lock {
  display: inline-flex; align-items: center; justify-content: center;
  width: 23px; height: 23px; border-radius: 999px;
  background: rgb(10 8 6 / 0.7); color: #EFE7D6;
}
/* a stamp on the frame, not a sentence: ENCORE and the running clock */
.chip--stamp {
  font-family: var(--mono); font-weight: 600; font-size: 0.62rem;
  letter-spacing: 0.16em; text-transform: uppercase; font-variant-caps: normal;
  padding: 3px 9px;
}
.wall-tile__hearts {
  position: absolute; inset: 10% 4% 0; pointer-events: none; overflow: hidden; z-index: 1;
}
/* The density strip across the foot of the frame (ref-1's bar, meant our own
   way): the room's loudest moments, not an invented progress fill — nothing
   here knows how far this viewer got. min-height keeps a quiet bucket from
   rendering as a sub-pixel gap, so the strip reads as one continuous rule. */
.wall-tile__bar { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; }
/* gap:0 is the difference between a rule and a dotted line — a long night
   buckets into ~90 spans, and the density's own 1px gutter reads as dashes at
   this height. min-height keeps a quiet bucket from vanishing sub-pixel. */
.wall-tile__bar .density { height: 7px; gap: 0; }
.wall-tile__bar .density span { min-height: 2px; border-radius: 0; background: rgb(245 245 247 / 0.32); }
.wall-tile__bar .density span.hot { background: rgb(245 245 247 / 0.58); }
.wall-tile__bar .density span.peak { background: #F5F5F7; }
.wall-heart {
  position: absolute; bottom: -22px; left: 50%;
  color: rgb(245 245 247 / 0.85);
  animation: wallrise var(--dur, 4.2s) ease-out forwards;
  will-change: transform, opacity;
}
/* Live hearts arrive from the room's channel as .heart-float — on the wall
   they trade the room's quick red rise for the wall's slow white one. */
.wall-tile__hearts .heart-float {
  color: rgb(245 245 247 / 0.85);
  animation: wallrise var(--dur, 4.2s) ease-out forwards;
  will-change: transform, opacity;
}
@keyframes wallrise {
  0% { transform: translate(0, 0) scale(0.6); opacity: 0; }
  14% { opacity: 0.8; }
  60% { opacity: 0.55; }
  100% { transform: translate(var(--drift, 0px), calc(-1 * var(--rise, 130px))) scale(1.05); opacity: 0; }
}
/* Reduced motion / save-data: the static density bloom — presence, no motion. */
.wall-heart--still { animation: none; bottom: auto; }

/* THE HOVER'S MOMENTS (Maaz 2026-09-12; restored 2026-09-15 r3 after the muted
   tape was ruled unreliable): the cycled stills sit OVER the resting picture,
   which is the whole reason this card can never flash dark — the poster
   element is never touched, paused, emptied or re-loaded. z-index 1 with the
   hearts and FIRST in the DOM, so a room's reactions stay over whichever
   picture is showing.

   ABSOLUTE, NOT JUST TRANSPARENT. `.wall-tile__frame video, .wall-tile__frame
   img` sizes an image to the frame but does NOT take it out of flow, so a
   second <img> in there would stack UNDER the first and DOUBLE the tile's
   height. The `[hidden]` rule is the same r14 trap that rule sets up — it
   hands every image a `display`, which out-ranks the UA's own `[hidden]`. */
.wall-tile__still { position: absolute; inset: 0; z-index: 1; pointer-events: none;
  opacity: 0; transition: opacity 160ms ease; }
.wall-tile__still.is-on { opacity: 1; }
.wall-tile__still[hidden] { display: none; }
/* `.wall-tile__play` went with the circle-play (Maaz 2026-09-15 r2, item 1:
   "Kill `.wall-tile__play` on encore tiles"). It was revealed on
   `.wall-tile:hover` — the WHOLE tile, by-row included — so it painted a play
   button over a still whenever a pointer came near the card, and over the one
   tile whose tape would not start it was a control that did nothing. The
   resting state is the still and the hover is the tape; the mark was the only
   thing on this frame that was neither. Rules and markup go together (the
   .gl-side P0: a class named after retired markup is how the next surface
   inherits a box by reaching for a familiar name). */

/* THE OPEN TILE'S OWN STACKING CONTEXT HAS TO WIN (Maaz 2026-09-15 r2, item
   2). `.wall-tile` carries `view-transition-name`, which forces a stacking
   context — so `.chover__card`'s `z-index: 60` is resolved INSIDE the tile it
   hangs off, not against the page, and every later tile in the grid paints
   over it in DOM order. His screenshot is the popover cut off dead on the row
   below's top edge: name, avatar, then the category, the followers and the
   Follow all behind the next row of tiles.
   z-index on the TILE is what lifts the whole context above its siblings; the
   card's own 60 is untouched (it still has to clear the frame's chrome and the
   stretched link inside its own tile). Keyed to the OPEN card, so a resting
   wall's paint order is exactly what it always was.
   `.up-card` takes the same rule and needs `position: relative` for it to do
   anything — that card has no view-transition-name, so it is belt and braces
   for a sibling that grows one rather than a bug measured today. */
.wall-tile:has(.chover__card:not([hidden])) { z-index: 5; }
.up-card:has(.chover__card:not([hidden])) { position: relative; z-index: 5; }

/* the picture is the click target; the title keeps a real link of its own, so
   cmd-click and open-in-new-tab still work (the .lib-card discipline) */
.wall-tile__link { position: absolute; inset: 0; z-index: 3; -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; }
.wall-tile__link:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; border-radius: var(--radius); }

.wall-tile__body {
  position: relative; z-index: 4;
  display: flex; align-items: flex-start; gap: 10px; padding: 12px 2px 0;
}
.wall-tile__words { flex: 1 1 auto; min-width: 0; }
.wall-tile__body h3 { font-size: 0.98rem; }
.wall-tile__body h3 a:hover { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }
.wall-tile__meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0 5px;
  margin-top: 5px; font-size: 0.76rem; --lh: 1.45; line-height: var(--lh-snap); color: var(--muted);
}
/* `.wall-tile__fact` went with the eye and the ☰ (Maaz 2026-09-14, item 7 —
   "no clock/info icon", "Views only"): the meta line says "views" in words
   now, so the glyph-and-number item it existed for has no markup, and a class
   named after retired markup is the .gl-side P0. */
.wall-tile__actions { gap: 8px; margin-top: 10px; }

/* WHO IT IS, ON ITS OWN LINE, ON BOTH CARDS (Maaz 2026-09-14, item 7: "Avatar
   + creator name + Follow / ✓ Following"). One set of declarations for the
   Encore tile and the Upcoming card, because they are the same row — and the
   avatar is inside it on a HERO too, which is what retired the hero's own
   avatar-in-the-meta special case.

   `min-width: 0` + ellipsis on the name, so a long display name shortens
   rather than pushing the Follow button off a 300px card. */
.wall-tile__by, .up-card__by {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 7px; font-size: 0.82rem;
}
.wall-tile__by .ring, .up-card__by .ring { flex: none; }
.wall-tile__by .avatar, .up-card__by .avatar { width: 26px; height: 26px; font-size: 0.66rem; }
/* `display: block` since the name became a LINK inside `.chover` (Maaz
   2026-09-15, item 4): the ellipsis needs a block container, and this element
   is no longer the flex item that was being blockified for free. */
.wall-tile__name, .up-card__name {
  display: block; min-width: 0; font-weight: 600; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.wall-tile__name:hover, .up-card__name:hover {
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px;
}

/* the hero: one room, the size of the section, its words on the frame */
.wall-tile--hero .wall-tile__frame { border-radius: var(--radius); }
.wall-tile--hero .wall-tile__body {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 72px clamp(16px, 3vw, 26px) clamp(14px, 2.4vw, 22px);
  background: linear-gradient(transparent, rgb(0 0 0 / 0.78)); /* the one sanctioned scrim (§8.2) */
  color: #F5F5F7;
}
.wall-tile--hero .wall-tile__body h3 {
  font-family: var(--display); font-weight: 800; text-transform: uppercase;
  letter-spacing: 0.01em; --lh: 1.02; line-height: var(--lh-snap);
  font-size: clamp(1.5rem, 3.6vw, 2.7rem); color: #F5F5F7;
}
.wall-tile--hero .wall-tile__meta { color: rgb(245 245 247 / 0.78); font-size: 0.82rem; }
.wall-tile--hero .wall-tile__name { color: #F5F5F7; }
/* The ✓ Following mark rides the by-row now, so on a hero it needs the scrim's
   own ink like the meta beside it — `--muted` over a photograph is mud. */
.wall-tile--hero .foll { color: rgb(245 245 247 / 0.78); }
.wall-tile--hero .wall-tile__body .btn,
.wall-tile--hero .wall-tile__body .foll-btn { color: #F5F5F7; border-color: rgb(245 245 247 / 0.4); }
.wall-tile--hero .wall-tile__body .btn--live { color: #fff; border-color: var(--live); }
/* The hero's foot belongs to its words: both the density strip and the corner
   chip would sit under the darkest band of the scrim, where a 7px rule is mud
   and a chip fights the meta line. So the hero moves its stamps to the
   top-left (ref-4) — including an ENCORE hero's running length, which the
   partial renders up there rather than losing it with this corner. */
.wall-tile--hero .wall-tile__bar, .wall-tile--hero .wall-tile__corner { display: none; }

@media (max-width: 900px) {
  .lead-story, .two-col { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-delay: 0ms !important; transition-duration: 0.01ms !important; }
}

/* A7 #1: vertical streams go portrait full-bleed instead of letterboxing. */
.room__stage--vertical video { aspect-ratio: 9 / 16; max-height: 78vh; margin: 0 auto; width: auto; }

/* S2: the one-tap report flag on chat messages. */
/* live-flow r4 item 9: a member never sees the report flag on their OWN
   row. data-own rides the page render and the author's echo; room_controller
   stamps it back onto broadcast copies by sender token. Unfenced: fan pages. */
.chat__msg[data-own] .chat__report { display: none; }
.chat__report { background: none; border: 0; color: inherit; cursor: pointer; opacity: 0.35; font-size: 0.75rem; padding: 0 4px; }
.chat__report:hover { opacity: 1; }
/* S2/B116: the report button renders on every message (incl. the session-less
   live broadcast copy) but is a fans-only affordance — hide it for a viewer
   with no fan session. The container's room-anon class is set server-side with
   the session, and a broadcast-appended message inherits it via this selector. */
.room-anon .chat__report { display: none; }

/* ---- The help shelf (/help) — docs typeset like the product ---------------- */
.help-search { display: flex; gap: 8px; margin-top: 20px; }
.help-search input { flex: 1; min-width: 0; }
.help-card { display: block; padding: 16px 18px; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); text-decoration: none; color: inherit; transition: border-color var(--dur) var(--ease); }
.help-card:hover { border-color: var(--border-strong); }
.help-card h3 { font-size: 0.98rem; }
.help-card p { margin-top: 6px; }
.article { max-width: 70ch; }
.article > p, .article li { --lh: 1.65; line-height: var(--lh-snap); }
.article > p { margin-top: 14px; }
.article > h2 { margin-top: 36px; font-size: 1.15rem; }
.article > h3 { margin-top: 24px; font-size: 1rem; }
.article > h2 + p, .article > h3 + p { margin-top: 10px; }
.article ul, .article ol { margin: 12px 0 0 1.2em; }
.article li { margin-top: 6px; }
.article li::marker { color: var(--dim); }
.article a { color: var(--text); text-decoration: underline; text-underline-offset: 2px; }
.article b { font-weight: 700; }
/* the one gold in an article body belongs to product-noun definitions */
.article dfn { font-style: normal; font-weight: 700; color: var(--gold); }
.help-shot { margin: 22px 0 0; }
.help-shot img { display: block; width: 100%; height: auto; border: 1px solid var(--border-strong);
  border-radius: var(--radius); background: var(--frame-mat); }
.help-shot figcaption { margin-top: 8px; }
.help-callouts { list-style: none; margin: 10px 0 0; padding: 0; counter-reset: callout; }
.help-callouts li { counter-increment: callout; position: relative; padding-left: 34px; margin-top: 8px;
  font-size: 0.88rem; --lh: 1.5; line-height: var(--lh-snap); }
.help-callouts li::before { content: counter(callout); position: absolute; left: 0; top: 1px;
  width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--gold); border-radius: 50%; color: var(--gold);
  font-family: var(--mono); font-size: 0.72rem; }
.help-crumb { display: flex; gap: 8px; align-items: center; padding-top: 26px; flex-wrap: wrap; }

/* ---------- Studio Library (Maaz 2026-08-31) ----------
   The creator's own shelf: the nights, and everything cut from them. Built out
   of the existing token layer — no new palette, no new radius, no new shadow.
   Class names are stable; the suite asserts .tab--on, .lib-card and .cap-frame.
   NOT `.sheet`: that one is taken (the landing page's paper band, and the
   marquee/playbill galleries) and overriding it broke all three. */
body.studio .tabs { display: flex; gap: 22px; border-bottom: 1px solid var(--border); }
body.studio .tab { display: inline-block; padding: 0 2px 10px; color: var(--muted);
  font-size: 0.9rem; font-weight: 600; text-decoration: none;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease); }
body.studio .tab:hover { color: var(--text); }
body.studio .tab--on { color: var(--text); border-bottom-color: var(--gold); }

/* A filter row is a row, not a stack: the base input rule sets width:100%, so
   every control in here needs its own basis or one field eats the line. */
body.studio .filters { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
body.studio .filters input, body.studio .filters select { width: auto; flex: none; padding: 8px 12px; font-size: 0.85rem; }
body.studio .filters select { padding-right: 30px; }
body.studio .filters input[type="search"] { min-width: 200px; }

/* ---------- the Library filter row (Maaz 2026-08-31) ----------
   ONE row: search · sort · Filters · the one-tap chip. Sort is a separate
   control from the filters on purpose — "how is this ordered" and "which
   nights am I looking at" are two questions. Whatever is on renders as
   removable chips UNDER the row, so the state is readable without opening
   anything. */
body.studio .filter-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 18px; }
body.studio .filter-row input, body.studio .filter-row select { width: auto; flex: none; padding: 8px 12px; font-size: 0.85rem; }
body.studio .filter-row select { padding-right: 30px; }
body.studio .filter-row input[type="search"] { min-width: 220px; flex: 1 1 220px; max-width: 340px; }

/* the popover trigger is a control, not the ⋯ square the overflow menu uses */
body.studio .filter-pop > .menu > summary {
  width: auto; height: auto; padding: 8px 14px; gap: 6px;
  font-size: 0.85rem; color: var(--muted);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
}
body.studio .filter-pop > .menu > summary::after { content: "\25BE"; font-size: 0.7em; opacity: 0.7; }
body.studio .filter-pop > .menu > summary:hover { color: var(--text); border-color: var(--text); }
/* an open popover wins over the sticky selection bar (z-index 45), the way an
   open card menu wins over the rows under it */
body.studio .filter-pop:has(details[open]) { position: relative; z-index: 50; }
/* left-anchored, unlike the card overflow menu: the Filters trigger sits at
   the LEFT of its row, so a right-anchored panel hangs off a 390px screen */
body.studio .filter-pop__panel { left: 0; right: auto; min-width: 250px; padding: 14px; }
body.studio .filter-pop__set { border: 0; margin: 0 0 14px; padding: 0; }
body.studio .filter-pop__set:last-of-type { margin-bottom: 10px; }
body.studio .filter-pop__set legend, body.studio .filter-pop__field .label { padding: 0; margin-bottom: 7px; display: block; }
body.studio .filter-pop__opt { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 0.86rem; cursor: pointer; }
body.studio .filter-pop__opt input { width: auto; margin: 0; accent-color: var(--accent); }
body.studio .filter-pop__field { display: block; margin: 0 0 14px; }
body.studio .filter-pop__field select { width: 100%; padding: 7px 10px; font-size: 0.85rem; }
body.studio .filter-pop__foot { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding-top: 10px; border-top: 1px solid var(--border); }

/* the state, on screen: one chip per active filter, each removable alone */
body.studio .filter-active { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.chip--x { cursor: pointer; }
.chip--x:hover { color: var(--text); border-color: var(--text); }

.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

.plain { color: inherit; text-decoration: none; }
.plain:hover { color: var(--gold); }

/* the honest tenses of a tape that isn't a video: no tape, still cutting, absent */
body.studio .lib-empty { display: flex; flex-direction: column; align-items: center; justify-content: center;
  height: 100%; gap: 8px; text-align: center; padding: 24px; }

/* MEDIA DOMINATES (Maaz 2026-09-08, mock 3). The card lost its own box — no
   border, no fill, no padded body — so what reads on the shelf is the frame
   and then three quiet lines under it, the way a clip tile already reads. The
   frame is the whole card's chrome now, which is also why it carries the
   rounded corners and the one hairline. */
body.studio .lib-card { display: flex; flex-direction: column; position: relative; min-width: 0;
  transition: transform var(--dur) var(--ease); }
body.studio .lib-card:hover, body.studio .lib-card:focus-within { transform: translateY(-2px); }
body.studio .lib-card:hover .lib-card__frame,
body.studio .lib-card:focus-within .lib-card__frame {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
}
body.studio .lib-card__title { position: relative; z-index: 2; }
body.studio .lib-card__frame { position: relative; aspect-ratio: 16 / 9; background: var(--frame-mat);
  overflow: hidden; border-radius: var(--radius); border: 1px solid var(--border);
  transition: border-color var(--dur) var(--ease); }
body.studio .lib-card__frame img, body.studio .lib-card__frame video {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
/* ONE DESTINATION (Maaz 2026-09-12). This was a <button> that played the tape
   in the card; it is an <a> to the night's Encore tab carrying `play=1`, and
   the stretched `inset: 0` is what makes the whole picture the hit target
   while the circle is the visible affordance — one element instead of a
   stretched hit fighting a play control over the same pixels. It stays under
   the chips, the stamp and the selection circle (z-index 3), which are its
   SIBLINGS, so a tap on one of those is never a visit.
   Nothing in a clip's tile plays either, since 2026-09-15 — the class that
   used to mark a playing tile is retired from this sheet entirely. (The frame
   itself DOES play on hover as of the same day, muted and one decoder at a
   time; the mark above it is still about the TAP, which navigates.) */
body.studio .lib-card__play { position: absolute; inset: 0; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  border: 0; padding: 0; background: transparent; color: #F4EDE1; cursor: pointer;
  text-decoration: none;
  opacity: 0; transition: opacity var(--dur) var(--ease); }
body.studio .lib-card:hover .lib-card__play,
body.studio .lib-card:focus-within .lib-card__play { opacity: 1; }
body.studio .lib-card__play:focus-visible { opacity: 1; outline: 2px solid var(--accent); outline-offset: -4px; }
body.studio .lib-card__play .lucide { border-radius: 999px; background: rgb(3 3 3 / 0.5); }
/* THE HOVER'S MOMENTS ON THE SHELF CARD (Maaz 2026-09-12, restored 2026-09-15
   r3): the cycled stills sit over the poster and take no pointer events, so
   the picture changes and the thing they click does not. The `[hidden]` rule
   is load-bearing — `.lib-card__frame img` hands every image in here a
   `display`, which out-ranks the UA's own `[hidden]` (the r14 trap), so
   without it a card with stills paints a blank box at rest. */
body.studio .lib-card__still { z-index: 1; pointer-events: none;
  opacity: 0; transition: opacity 160ms var(--ease); }
body.studio .lib-card__still.is-on { opacity: 1; }
body.studio .lib-card__still[hidden] { display: none; }
/* the mock's corners: the shelf pill top-LEFT, the length bottom-RIGHT */
body.studio .lib-card__chips { position: absolute; top: 10px; left: 10px; z-index: 3; }
body.studio .lib-card__stamp { position: absolute; bottom: 10px; right: 10px; display: flex; gap: 6px; z-index: 3; }
body.studio .lib-card__chips .chip, body.studio .lib-card__stamp .chip { background: rgba(0, 0, 0, 0.62); backdrop-filter: blur(4px); }
body.studio .lib-card__body { padding: 12px 2px 0; }
/* two lines of title at most — the card keeps its height down the shelf */
body.studio .lib-card__title { display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 2; overflow: hidden; }
body.studio .lib-card__meta { margin-top: 5px; }
body.studio .lib-card__clips { margin-top: 9px; }
body.studio .lib-card__clips .chip { display: inline-flex; align-items: center; gap: 6px; }

/* selection (r2 item 8): a circular checkbox at the frame's top-RIGHT —
   invisible until the card is hovered or focused, permanent the moment
   anything on the shelf is picked */
body.studio .lib-card--picked .lib-card__frame { border-color: var(--accent); }
body.studio .lib-card__pick { position: absolute; top: 8px; right: 8px; z-index: 3;
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; cursor: pointer;
  opacity: 0; transition: opacity var(--dur) var(--ease); }
body.studio .lib-card:hover .lib-card__pick, body.studio .lib-card:focus-within .lib-card__pick,
body.studio .lib-picking .lib-card__pick { opacity: 1; }
/* One stroke, circular, opaque (the r8/r9 hairline law) — checked is the gold
   fill with the check drawn in the button-ink dark, never a second ring. */
body.studio .lib-card__pick input { appearance: none; -webkit-appearance: none;
  width: 20px; height: 20px; margin: 0; cursor: pointer;
  border-radius: 999px; border: var(--hair-w) solid #cfccc7;
  background-color: rgba(0, 0, 0, 0.55); }
body.studio .lib-card__pick input:checked {
  background-color: var(--accent); border-color: var(--accent);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23111111' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-size: 12px 12px; background-position: center; background-repeat: no-repeat; }
body.studio .lib-card__pick input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* the selection bar: nothing until something is ticked, then sticky at the
   foot of the screen. Publish / Unpublish over the picked set — no Delete and
   no bulk Generate (one is irreversible, the other spends money). */
body.studio .bulk-bar { position: sticky; bottom: calc(16px + env(safe-area-inset-bottom));
  z-index: 45; margin-top: 22px; display: flex; justify-content: center; }
/* `display: flex` beats the hidden attribute's UA `display: none` — without
   this the bar sits on screen saying "0 selected" from first paint (#377's
   trap, kept fixed). */
body.studio .bulk-bar[hidden] { display: none; }
body.studio .bulk-bar__in { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0;
  padding: 10px 14px; border: var(--hair-w) solid var(--border-strong); border-radius: 999px;
  background: var(--surface); }
body.studio .bulk-bar__n { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--muted); padding-right: 4px; }

/* the tab panel loads in place — a skeleton while the frame is in flight, so
   Transcript on a long night has a shape instead of a jump */
@keyframes tab-skeleton { from { background-position: 200% 0; } to { background-position: -200% 0; } }
#encore-tab[busy] > * { display: none; }
#encore-tab[busy]::after { content: ""; display: block; height: 240px; margin-top: 18px;
  border-radius: var(--radius); background-size: 200% 100%;
  background-image: linear-gradient(90deg, var(--surface) 0%, var(--surface-3) 50%, var(--surface) 100%);
  animation: tab-skeleton 1.2s var(--ease) infinite; }

/* ---------- the Encores redesign (Maaz's 2026-09-04 mocks) ---------- */

/* the shelf's one control line: state pills left, the jump dropdown and the
   date range right */
body.studio .enc-row { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-top: 26px; }
body.studio .enc-row__pills { margin-top: 0; }
body.studio .enc-row__right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* the shelf's search box (r2 item 2 — it replaced the jump dropdown): same
   height and stroke as the range trigger beside it, so the right group reads
   as one row of controls */
body.studio .enc-search { margin: 0; }
body.studio .enc-search input[type="search"] {
  width: min(240px, 60vw); height: 34px; padding: 0 14px;
  font-size: 0.85rem; color: var(--text); background: transparent;
  border: var(--hair-w) solid var(--border-strong); border-radius: 999px;
}
body.studio .enc-search input[type="search"]::placeholder { color: var(--muted); }
body.studio .enc-search input[type="search"]:focus-visible {
  outline: none; border-color: var(--text); }
/* ONE DECLARATION, TWO HOSTS: the shelf's date range and the Encore record
   rail's Download menu wear the same ghost pill, so the two triggers cannot
   drift into two shapes (the `cr-back--icon` idiom — a second host joins the
   selector list rather than copying the rule). */
body.studio .enc-range__trigger,
body.studio .enc-dl__trigger,
body.studio .clip-lives__trigger {
  width: auto; height: 34px; padding: 0 14px; gap: 8px;
  display: inline-flex; align-items: center;
  font-size: 0.85rem; color: var(--muted);
  border: var(--hair-w) solid var(--border-strong); border-radius: 999px;
}
body.studio .enc-range__trigger:hover,
body.studio .enc-dl__trigger:hover,
body.studio .clip-lives__trigger:hover {
  color: var(--text); border-color: var(--text); }
body.studio .enc-range__trigger .lucide,
body.studio .enc-dl__trigger .lucide,
body.studio .clip-lives__trigger .lucide {
  flex: none; color: var(--dim); }

/* ── THE ENCORE'S RECORD RAIL (Maaz 2026-09-13) ──────────────────────────
   The Highlights / Transcript / Chat pills and the one Download menu on one
   row: the NAV takes the slack and the menu hugs the right edge. That is the
   Followers r4 rule facing this way round — give the control `flex: none` to
   "push" it and the row can no longer wrap, so the pills would push the
   document sideways on a phone instead of dropping to a second line. The
   rail carries the top margin the nav used to (a 26px inline style), so the
   menu sits on the pills' own line rather than above them. */
body.studio .enc-record-rail {
  display: flex; align-items: flex-start; gap: 14px; margin-top: 26px;
}
body.studio .enc-record-rail > .tabs { margin-top: 0; flex: 1 1 auto; min-width: 0; }
/* The trigger is 34px against the pills' 40: centred on their line rather
   than hanging off the top of it. */
body.studio .enc-dl { flex: none; margin-top: 3px; }
/* An icon and a label on one line, with the note stacking under the label
   rather than becoming a third item in the row. */
body.studio .enc-dl .menu__item {
  display: flex; align-items: flex-start; gap: 9px;
}
body.studio .enc-dl .menu__item .lucide { flex: none; margin-top: 3px; color: var(--dim); }
body.studio .enc-dl__label { min-width: 0; }
/* An open panel must beat the cards even while the shell's house-fade is
   animating: opacity < 1 makes .enc-row its OWN stacking context, trapping the
   panel's z-index inside it — so the ROW is raised, not the panel (the
   filter-pop idiom, one level up). */
body.studio .enc-row:has(details[open]) { position: relative; z-index: 50; }

/* the date range: shortcut rows, then the house's own two calendars */
body.studio .enc-range__panel { right: 0; left: auto; width: min(320px, 88vw);
  max-width: none; padding: 12px; }
/* On a phone the row wraps, so a trigger can sit anywhere on the line — anchor
   the panel to the RIGHT GROUP instead of the trigger, or a right-anchored
   panel under a left-wrapped trigger lays out off the screen's left edge. */
@media (max-width: 620px) {
  body.studio .enc-row__right { position: relative; }
  body.studio .enc-range .menu { position: static; }
  body.studio .enc-range__panel { left: 0; right: 0; width: auto; }
}
body.studio .enc-range__shortcuts { display: flex; flex-direction: column; }
body.studio .enc-range__quick { display: block; padding: 8px 10px; border-radius: var(--radius-sm);
  font-size: 0.86rem; color: var(--muted); text-decoration: none; }
body.studio .enc-range__quick:hover { background: var(--surface-3); color: var(--text); }
body.studio .enc-range__quick--on { color: var(--gold); }
body.studio .enc-range__form { display: flex; flex-direction: column; gap: 10px; margin: 12px 0 0;
  padding-top: 12px; border-top: 1px solid var(--border); }
body.studio .enc-range__field .label { display: block; margin-bottom: 6px; }
body.studio .enc-range__form .btn { align-self: flex-start; }

/* ── FROM EVERY LIVE (Maaz 2026-09-13) ───────────────────────────────────
   The native <select> it replaced could hold one live and printed no dates,
   so a creator who streams weekly read twenty identical rows. This is the
   house dropdown (`.enc-range`'s own trigger, above, by joining its selector
   list rather than copying it) over the house multi-pick rows (`.pf-pick`,
   the Pass form's), with a field that narrows them.

   THE PANEL IS ABSOLUTE AND RIGHT-ANCHORED, like the range's beside it —
   `.enc-row:has(details[open])` already raises the row over the cards — and
   it takes the same phone treatment for the same reason: on a wrapped row a
   right-anchored panel under a left-wrapped trigger lays out off the
   screen's left edge. */
body.studio .clip-lives__said { max-width: 22ch; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
/* A FACE CARRYING AN ANSWER READS AS ONE (the `pf-drop--value` rule): "From
   every live" is what the filter is DOING, so it is not dimmed away. */
body.studio .clip-lives__trigger:has(~ .menu__panel input:checked) { color: var(--text); }
body.studio .clip-lives__chev { transition: transform 150ms var(--ease); }
body.studio .clip-lives details[open] .clip-lives__chev { transform: rotate(180deg); }
body.studio .clip-lives__panel { right: 0; left: auto; width: min(360px, 90vw);
  max-width: none; padding: 10px; }
/* On a phone the toolbar row wraps and the right group is a flex item sized
   to its own content — the trigger — so a panel anchored to it with
   `left: 0; right: 0` came out 210px wide with the search field squeezed
   inside it (measured at 390). It anchors LEFT and carries its own width,
   which the viewport bounds. */
@media (max-width: 620px) {
  body.studio .clip-lives .menu { position: static; }
  body.studio .clip-lives__panel { left: 0; right: auto;
    width: min(320px, calc(100vw - 40px)); }
}
body.studio .clip-lives__q { width: 100%; margin-bottom: 6px; }
/* The rows scroll INSIDE the panel — a creator with sixty lives must not be
   handed a dropdown taller than the window. */
body.studio .clip-lives__list { display: flex; flex-direction: column; gap: 2px;
  max-height: 300px; overflow-y: auto; }
body.studio .clip-lives__row { padding: 8px 10px; }
/* `[hidden]` has to win: `.pf-pick` hands the row a `display: flex`, which
   out-ranks the UA's own hidden rule (r14 item 5), so a narrowed-out live
   would go on being drawn. */
body.studio .clip-lives__row[hidden] { display: none; }
body.studio .clip-lives__zero { margin: 10px; }
body.studio .clip-lives__zero[hidden] { display: none; }
body.studio .clip-lives__foot { display: flex; align-items: center;
  justify-content: space-between; gap: 12px; margin-top: 10px;
  padding-top: 10px; border-top: 1px solid var(--border); }

/* THE SHELF'S DENSITY IS THE FRAME'S HEIGHT (Maaz 2026-09-08): three across a
   desk rather than five, which is the whole of "give the frame more height" —
   a 16:9 box gets taller by getting wider, and nothing under it grew. Still
   auto-fill, so it collapses to two and then one on the way down to a phone. */
body.studio .grid--encores { grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr)); gap: 22px; }

/* the shelf banner on the Encore tab: the state said plainly on the left, the
   one action on the right — PUBLISH in gold on a draft, Unpublish as a quiet
   ghost on a published night (r2 item 9: same slot, opposite weight) */
body.studio .enc-draft { display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; padding: 14px 18px; margin-bottom: 16px;
  border: 1px solid var(--border-strong); border-radius: var(--radius); background: var(--surface); }
body.studio .enc-draft__word { font-size: 0.95rem; }
body.studio .enc-draft--published { border-color: var(--border); }
body.studio .enc-draft--published .enc-draft__word { color: var(--muted); }
body.studio .enc-draft__act { display: flex; align-items: center; gap: 14px; flex: none; }

/* ── UPDATE THUMBNAIL, over the Encore's own frame (Maaz 2026-09-10) ───────
   Top-right of the player, revealed by hover or focus of the frame — the
   clip card's hover-chrome idiom, with one difference that matters: this
   control carries its own visible words, so it needs no [data-tip] and
   therefore cannot reproduce the tip-overflow class that has cost three
   rounds (a nowrap ::after on a small control at a card's right edge).

   IT IS A <label> WRAPPING A REAL FILE INPUT, so the whole pill opens the
   picker with no JavaScript. The input is CLIPPED, not display:none, because
   a keyboard cannot focus what isn't rendered — and it is `position:
   absolute`, so the label MUST be positioned or the containing block becomes
   the ICB and a 1px input lands at the document's own origin (the Team
   round's 12,627px lesson).

   z-index 4 puts it over the play mark (3) and the starting overlay; the
   frame it sits in is `position: relative` (inline, in the partial) but is
   NOT an overflow box, so nothing here is clipped to the picture. */
body.studio .enc-frame { position: relative; aspect-ratio: 16 / 9;
  background: var(--frame-mat); }
/* THE WRAPPER CARRIES THE POSITION, NOT THE FORM (Maaz 2026-09-13). Remove
   is a `button_to`, which renders a FORM, and an HTML form cannot nest — so
   the pick and the way back off it are two forms inside one positioned box.
   The visual is byte-for-byte what the single form drew: the box is the flex
   column it always was, and each form inside is one too, so the refusal
   still hangs under its own pill. */
body.studio .enc-thumb { position: absolute; top: 10px; right: 10px; z-index: 4;
  margin: 0; display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
  opacity: 0; transition: opacity var(--dur) var(--ease); }
body.studio .enc-thumb form { margin: 0; display: flex; flex-direction: column;
  align-items: flex-end; gap: 6px; }
/* THE REVEAL IS KEYED TO `.enc-frame`, NOT TO `.card--flush`, and that is a
   house law rather than a preference: `.card--flush` is declared UNFENCED (it
   is shared — accounts/show wears it), so a `body.studio .card--flush` rule
   makes it furniture and `studio_scope_law_test` fails twice over. */
body.studio .enc-frame:hover .enc-thumb,
body.studio .enc-frame:focus-within .enc-thumb { opacity: 1; }
body.studio .enc-thumb__btn { position: relative; display: inline-flex; align-items: center;
  gap: 7px; min-height: 30px; padding: 0 11px; cursor: pointer;
  font-size: 0.78rem; letter-spacing: 0.01em; color: #F4EDE1;
  border-radius: 999px; border: var(--hair-w) solid #cfccc7;
  background: rgba(0, 0, 0, 0.62); backdrop-filter: blur(4px);
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease); }
body.studio .enc-thumb__btn:hover { border-color: var(--hair-gold); color: var(--gold); }
/* `font-family` because one host is a <button> now and a bare button takes
   the UA's own face; the label never needed it and is unmoved by it. */
body.studio .enc-thumb__btn { font-family: inherit; }
/* Removing is not the primary act here, so it does not reach for gold on
   hover — it wears the house's own live-red for "this undoes something",
   the `.menu__item--danger` weight (text, never a fill). */
body.studio .enc-thumb__btn--off:hover { border-color: var(--hair-live); color: var(--live); }
body.studio .enc-thumb__btn:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .enc-thumb__pick {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* The refusal sits UNDER the pill, on its own plate — it lands on the tape's
   own frame, so it needs a fill of its own to be readable at all. */
body.studio .enc-thumb__error { max-width: 260px; margin: 0; padding: 6px 10px;
  border-radius: var(--radius-sm); font-size: 0.78rem; color: var(--live);
  background: rgba(3, 3, 3, 0.82); border: var(--hair-w) solid var(--hair-live); }
body.studio .enc-thumb__error[hidden] { display: none; }

/* the four figures under the player: label over value (the mock's order —
   the Lives board's stat-row is value over label, so this is its variant) */
body.studio .stat-row--labeled { gap: clamp(24px, 5vw, 80px); }
body.studio .stat-row--labeled .stat .meta { display: block; margin: 0 0 8px; }
body.studio .stat-row--labeled .stat b { font-size: 1.7rem; }
body.studio .stat-row--labeled .stat .small { display: block; margin-top: 4px; }
body.studio .enc-metrics { margin-top: 24px; }

/* the room's record, on the Encore clock */
body.studio .enc-chat__head { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-top: 18px; }
body.studio .enc-chat__line { display: grid; grid-template-columns: 52px 128px 1fr;
  gap: 14px; padding: 10px 16px; border-bottom: 1px solid var(--border);
  --lh: 1.5; line-height: var(--lh-snap); font-size: 0.88rem; }
body.studio .enc-chat__line:last-child { border-bottom: 0; }
body.studio .enc-chat__t { font-family: var(--mono); font-size: 0.78rem; color: var(--dim); }
body.studio .enc-chat__who { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
body.studio .enc-chat__said { min-width: 0; overflow-wrap: anywhere; }
@media (max-width: 560px) {
  body.studio .enc-chat__line { grid-template-columns: 44px 1fr; }
  body.studio .enc-chat__said { grid-column: 2; }
}

/* 9:16 is the shape every destination wants — filled or empty, the frame is
   the same size, so a shelf with two clips reads as a shelf, not a gap. */
body.studio .clip-rack { display: grid; gap: 14px; margin-top: 18px;
  grid-template-columns: repeat(auto-fill, minmax(min(178px, 100%), 1fr)); }
body.studio .clip-frame { position: relative; aspect-ratio: 9 / 16; border-radius: var(--radius);
  background: var(--frame-mat); border: 1px solid var(--border); overflow: hidden;
  display: flex; flex-direction: column; }
body.studio .clip-frame--empty { border-style: dashed; align-items: center; justify-content: center;
  gap: 8px; color: var(--dim); text-align: center; padding: 14px; }
body.studio .clip-frame--empty b { font-size: 0.66rem; font-weight: 700; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--muted); }
/* the destination placeholder's mark (Maaz 2026-09-07): the same cut-for-you
   sparkle the Generate button wears, gold, over the dashed 9:16 mat. */
body.studio .clip-frame__spark { display: inline-flex; color: var(--accent); }

/* a cut, worded BELOW its frame (the 09-04 mock; ONE card on both hosts, r3
   item 5): the 9:16 media with the length stamped on it, the hover-only
   circular pick top-LEFT, the hover BIN top-right, a circular PLAY in the
   centre, then title + status, the live's title (flat queue only), the date
   it was cut, the marks for where it went. No views figure — no per-platform
   count exists.

   THE FRAME OPENS THE MODAL PLAYER (Kunal/TagMango 2026-09-15), which
   reverses the 09-07 "it plays where it sits": a 9:16 tile is too small to
   watch a cut in and far too small to seek in. The stretched control is an
   ANCHOR again — the clip's own page, preventDefaulted into the dialog — so
   with no JavaScript it is a working way to watch rather than a dead button,
   and the title stays its own explicit link to the record. Nothing on this
   card is a primary button: Approve/Discard/Post came off the body entirely,
   and a refusal is ONE line whose body lives in this clip's own dialog. */
/* relative because the bin/restore is positioned against the CARD, not the
   frame — see .clip-card__actform. */
body.studio .clip-card { position: relative; display: flex; flex-direction: column; min-width: 0; }
body.studio .clip-card__frame { position: relative; aspect-ratio: 9 / 16; border-radius: var(--radius);
  background: var(--frame-mat); border: 1px solid var(--border); overflow: hidden;
  transition: border-color var(--dur) var(--ease); }
body.studio .clip-card:hover .clip-card__frame, body.studio .clip-card:focus-within .clip-card__frame {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
body.studio .clip-card__frame video, body.studio .clip-card__frame img { position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; }
/* the house card is 16:9 — COVERing it into a 9:16 frame blows the wordmark
   up into gibberish, so a tile standing on the house cover letterboxes on the
   frame mat. `--fit` is set when the pipeline gave us no still of its own. */
body.studio .clip-card__media--fit { object-fit: contain; }

/* ── PLAY AND VOLUME, NOTHING ELSE (Maaz 2026-09-13) ─────────────────────
   `controlslist` on the element takes Download, Playback speed, PiP and
   remote playback off, and with nothing left to hold Chrome draws no
   overflow button at all — all of that MEASURED, side by side against a
   plain <video>, in HeadlessChrome 152.
   THE FULLSCREEN BUTTON IS THE ONE THAT SURVIVES IT. `controlsList.supports
   ("nofullscreen")` answers TRUE on this build and the button is drawn
   anyway (measured the same way), so the token alone cannot keep Maaz's
   list. This pseudo-element does keep it — also measured, by adding the rule
   and re-shooting the same pair: the left player came back play + time +
   volume and the right kept both buttons.
   It is NOT the dead `-webkit-media-controls-loading-panel` this house
   already recorded as doing nothing (replay_controller's own note) — that
   was a different pseudo on a different part, and this one was proven by
   breaking it rather than assumed from it.
   SCOPED TO A CLIP'S OWN PLAYERS. The Encore's tape keeps its full native
   bar: a creator watching their own two-hour show has every reason to
   fullscreen it, and its download is the record rail's menu. */
body.studio .clip-card__media::-webkit-media-controls-fullscreen-button,
body.studio .clip-player__video::-webkit-media-controls-fullscreen-button,
body.studio .clip-detail__player video::-webkit-media-controls-fullscreen-button {
  display: none;
}

/* PLAY IN PLACE: centre of the frame, hover/focus only, above the media and
   below nothing. It is a real button — the frame carries no link to swallow
   the tap — and it disappears while the clip is playing so the browser's own
   controls have the picture to themselves. */
/* IT IS AN ANCHOR NOW (Kunal/TagMango 2026-09-15) — the clip's own page, which
   `clip-player#open` preventDefaults into the modal, so no JavaScript is still
   a working way to watch the cut. Stretched over the whole frame, which is
   what makes "clicking the clip opens the player" true of the picture and not
   just of a 44px glyph; the pick and the bin sit above it on z-index. */
body.studio .clip-card__play { position: absolute; inset: 0; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  border: 0; padding: 0; background: transparent; color: #F4EDE1; cursor: pointer;
  text-decoration: none; opacity: 0; transition: opacity var(--dur) var(--ease); }
body.studio .clip-card:hover .clip-card__play,
body.studio .clip-card:focus-within .clip-card__play { opacity: 1; }
body.studio .clip-card__play:focus-visible { opacity: 1; outline: 2px solid var(--accent); outline-offset: -4px; }
/* the glyph reads on any frame: a soft scrim under it rather than a fill over
   the picture (the encore affordance's own rule — the scrim is ON the control,
   so it is exactly as temporary as the control). */
body.studio .clip-card__play .lucide { border-radius: 999px; background: rgb(3 3 3 / 0.5); }
/* the pick rides the frame's top-LEFT (the mock; the Encores shelf's is its
   frame's top-right) — same reveal: hover, focus, or anything already picked */
body.studio .clip-card__pick { position: absolute; top: 8px; left: 8px; z-index: 3;
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; cursor: pointer;
  opacity: 0; transition: opacity var(--dur) var(--ease); }
body.studio .clip-card:hover .clip-card__pick, body.studio .clip-card:focus-within .clip-card__pick,
body.studio .lib-picking .clip-card__pick { opacity: 1; }
body.studio .clip-card__pick input { appearance: none; -webkit-appearance: none;
  width: 20px; height: 20px; margin: 0; cursor: pointer;
  border-radius: 999px; border: var(--hair-w) solid #cfccc7;
  background-color: rgba(0, 0, 0, 0.55); }
body.studio .clip-card__pick input:checked {
  background-color: var(--accent); border-color: var(--accent);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23111111' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-size: 12px 12px; background-position: center; background-repeat: no-repeat; }
body.studio .clip-card__pick input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .lib-card--picked .clip-card__frame { border-color: var(--accent); }
/* THE SAME SLOT, TWO VERBS (Maaz 2026-09-07): the bin on every live pill, the
   bring-back on the Discarded one. Top-RIGHT, mirroring the pick, above the
   media so a tap can never reach the player under it.

   HOVER OF **THIS** CARD, AND NOTHING ELSE (Maaz 2026-09-07, 22:40). It used
   to carry `.lib-picking` too — every bin in the grid lit the moment one
   clip was ticked, on the argument that selection mode is when a creator is
   working the grid. Wrong: the PICK is the control selection mode is about
   (a checkbox you cannot see is a checkbox you cannot untick, so
   `.lib-picking .clip-card__pick` stays), while the bin is a verb aimed at
   one clip. Drawn on twenty cards at once it reads as twenty offers to
   delete, and it must match the play — which is hover-of-this-card only.

   IT IS POSITIONED AGAINST THE CARD, NOT THE FRAME, and that is not a detail:
   `.clip-card__frame` is `overflow: hidden` (the 9:16 crop), which would clip
   this control's tooltip to the picture. */
body.studio .clip-card__actform { position: absolute; top: 8px; right: 8px; z-index: 3;
  margin: 0; opacity: 0; transition: opacity var(--dur) var(--ease); }
body.studio .clip-card:hover .clip-card__actform,
body.studio .clip-card:focus-within .clip-card__actform { opacity: 1; }
body.studio .clip-card__act { display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; cursor: pointer;
  border-radius: 999px; border: var(--hair-w) solid #cfccc7;
  background: rgba(0, 0, 0, 0.55); color: #F4EDE1; }
body.studio .clip-card__act:hover { border-color: var(--live); color: var(--live); }
body.studio .clip-card__act--back:hover { border-color: var(--hair-gold); color: var(--gold); }
body.studio .clip-card__act:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* THE TIP GROWS LEFTWARD from the control's own right edge. A `left: 50%`
   nowrap box on a 28px control at a card's right edge is the overflow class
   that cost the post tray 50px of sideways scroll — right-anchored, it can
   only ever extend INTO the grid. */
body.studio .clip-card__act[data-tip]::after { left: auto; right: 0; transform: none; }
body.studio .clip-card__act[data-tip]:hover::after,
body.studio .clip-card__act[data-tip]:focus-visible::after { transform: none; }

/* THE TWO STAMPS ON THE FRAME, one plate, opposite corners: the measured
   length bottom-RIGHT (a peaks cut's own window), Mosaic's virality estimate
   bottom-LEFT (2026-09-08). They are two different claims from two different
   sources and never collide — a vendor cut reports no duration and a peaks cut
   carries no estimate — but the corners keep them apart the day either vendor
   starts reporting the other. Neither carries a [data-tip]: this frame is
   `overflow: hidden`, which clips one to the picture (the hover bin's own
   reason for sitting outside the frame). */
body.studio .clip-card__len, body.studio .clip-card__score { position: absolute; bottom: 10px; z-index: 2;
  background: rgba(0, 0, 0, 0.62); backdrop-filter: blur(4px); }
body.studio .clip-card__len { right: 10px; }
body.studio .clip-card__score { left: 10px; }
body.studio .clip-card__live { margin-top: 4px; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
body.studio .clip-card__body { padding: 10px 2px 0; }
body.studio .clip-card__title { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
body.studio .clip-card__title h4 { font-size: 0.85rem; --lh: 1.3; line-height: var(--lh-snap);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
body.studio .clip-card__title .chip { flex: none; }
body.studio .clip-card__meta { margin-top: 5px; }
body.studio .clip-card__marks { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
body.studio .clip-card__where { display: inline-flex; align-items: center; color: var(--muted); }
/* the label on a swatch ROW (the highlight palette), which had none: the row
   is a flex line of colour inputs, so its name sits in the line with them. */
body.studio .cap-swatches__lab { align-self: center; }

body.studio .clip-card__actions { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }

/* ── A FAILED POST IS ONE LINE, AND THE RECORD IS A DIALOG ──────────────────
   (Kunal/TagMango 2026-09-15.) Every refusal used to print truncated under the
   card, one line per RETRY — a retry is a new clip_posts row by design — so
   three goes at Facebook was three lines of vendor prose stacking under a 9:16
   tile until the grid broke. The card carries ONE line now (the platform, or a
   count) and the whole record lives in the top layer, where nothing a vendor
   sends can reflow a card.

   THE LINE IS A REAL LINK to the clip's own page, so with no JavaScript it
   still reaches the record; it is live-red because it reports a failure, and
   `nowrap` because its text is BOUNDED ("Facebook refused", "2 platforms
   refused") and there is no free text here for `overflow-wrap: anywhere` to
   protect the card from (the FOLLOWERS r4 rule, on a card instead of a cell). */
body.studio .clip-card__err { display: inline-flex; align-items: center; gap: 6px;
  margin-top: 6px; padding: 3px 8px 3px 6px; border-radius: var(--radius-sm);
  border: var(--hair-w) solid var(--hair-live); background: rgb(0 0 0 / 0.12);
  color: var(--live); font-size: 0.78rem; text-decoration: none;
  white-space: nowrap; overflow-wrap: normal; max-width: 100%; }
body.studio .clip-card__err:hover { background: color-mix(in srgb, var(--live) 12%, transparent); }
body.studio .clip-card__err:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* "Error" is Kunal's own word for the control, so it reads as the thing to
   press rather than as more of the sentence. */
body.studio .clip-card__err-word { padding-left: 6px; margin-left: 2px;
  border-left: var(--hair-w) solid var(--hair-live);
  text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.7rem; }

/* WHERE IT IS — the per-clip dialog. Every platform, success beside refusal,
   each refusal's whole message behind its own <details>. `.clip-errs__row` and
   its children have TWO hosts: this dialog and the clip page's own "Where it
   is" row, which renders the same partial inline. */
body.studio .clip-errs::backdrop { background: rgb(0 0 0 / 0.72); backdrop-filter: blur(3px); }
body.studio .clip-errs { position: relative; width: min(560px, calc(100vw - 32px));
  max-height: calc(100vh - 48px); overflow: auto; padding: 26px;
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text); }
body.studio .clip-errs__x { position: absolute; top: 8px; right: 8px; z-index: 3; }
body.studio .clip-errs__x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .clip-errs__title { font-size: 1.3rem; padding-right: 44px; }
body.studio .clip-errs__deck { margin-top: 4px; }
body.studio .clip-errs__rows { margin-top: 18px; }
body.studio .clip-errs__foot { display: flex; justify-content: flex-end; margin-top: 20px; }

body.studio .clip-errs__row { padding: 10px 0; border-top: 1px solid var(--border); }
body.studio .clip-errs__row:first-child { border-top: 0; padding-top: 0; }
body.studio .clip-errs__who { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
body.studio .clip-errs__name { font-weight: 500; }
body.studio .clip-errs__said { color: var(--live); font-size: 0.82rem; }
/* THE ERROR CONTROL IS A <details>, so it works with no JavaScript, inside the
   dialog and on the page alike — a reveal, not a second modal. */
body.studio .clip-errs__detail { margin-top: 8px; }
body.studio .clip-errs__detail > summary { display: inline-flex; align-items: center; gap: 6px;
  width: max-content; padding: 3px 9px; border-radius: var(--radius-sm); cursor: pointer;
  border: var(--hair-w) solid var(--hair-live); color: var(--live);
  font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em;
  list-style: none; }
body.studio .clip-errs__detail > summary::-webkit-details-marker { display: none; }
body.studio .clip-errs__detail > summary:hover { background: color-mix(in srgb, var(--live) 12%, transparent); }
body.studio .clip-errs__msg { margin-top: 10px; }
/* THE MESSAGE IS WHOLE, so it wraps — a vendor's refusal is free text and can
   be any length, which is the one place `anywhere` earns its keep (a URL in a
   message has no break opportunity of its own). */
body.studio .clip-errs__msg .code-snippet { white-space: pre-wrap; overflow-wrap: anywhere;
  max-height: 40vh; overflow-y: auto; }
body.studio .clip-errs__msg .ld5-copy { margin-top: 10px; }

/* ── THE MODAL PLAYER (Kunal/TagMango 2026-09-15) ───────────────────────────
   A clip used to play inside its own 9:16 tile: too small to watch and far too
   small to seek, which was the complaint. One dialog per page, the tapped
   card's file in it, big enough to do the job — and the creator never leaves
   the grid they are working.

   THE VIDEO IS BOUNDED BY HEIGHT, not by width: a 9:16 file sized to a
   dialog's width is taller than any screen, so the height is the constraint
   and `aspect-ratio` gives the box back its width. */
body.studio .clip-player::backdrop { background: rgb(0 0 0 / 0.82); backdrop-filter: blur(3px); }
body.studio .clip-player { position: relative; padding: 0; border: 0; border-radius: var(--radius);
  background: transparent; color: var(--text); overflow: visible;
  max-width: calc(100vw - 32px); max-height: calc(100vh - 32px); }
body.studio .clip-player__x { position: absolute; top: 8px; right: 8px; z-index: 3;
  background: rgb(3 3 3 / 0.55); }
body.studio .clip-player__x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .clip-player__video { display: block; height: min(84vh, 900px); aspect-ratio: 9 / 16;
  max-width: calc(100vw - 32px); width: auto; border-radius: var(--radius);
  background: #000; }
body.studio .clip-player__title { margin-top: 10px; text-align: center;
  font-size: 0.9rem; color: var(--muted); }
/* A LANDSCAPE PHONE has no 84vh to give a 9:16 file, so width leads there and
   the height follows the ratio. */
@media (max-height: 560px) {
  body.studio .clip-player__video { height: auto; width: min(320px, calc(100vw - 32px)); }
}


/* THE CLIP'S OWN PAGE (r3 item 6): label/value rows on the left, the 9:16
   player on the right, hairline rules between rows — the mock's ledger read. */
body.studio .clip-detail { display: grid; grid-template-columns: minmax(0, 1fr) minmax(240px, 360px);
  gap: clamp(24px, 4vw, 56px); margin-top: 30px; align-items: start; }
@media (max-width: 860px) { body.studio .clip-detail { grid-template-columns: minmax(0, 1fr); } }
body.studio .clip-detail__row { display: grid; grid-template-columns: 128px minmax(0, 1fr);
  gap: 20px; padding: 18px 0; border-bottom: 1px solid var(--border); }
body.studio .clip-detail__row:first-child { padding-top: 0; }
body.studio .clip-detail__row .label { padding-top: 4px; }
@media (max-width: 560px) { body.studio .clip-detail__row { grid-template-columns: minmax(0, 1fr); gap: 8px; } }
body.studio .clip-detail__caption textarea { width: 100%; }
body.studio .clip-detail__where { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
body.studio .clip-detail__send { display: flex; gap: 10px; flex-wrap: wrap; }
/* a send chip is a form (button_to) — keep the form from breaking the row */
body.studio .clip-detail__send form { margin: 0; display: inline-flex; }
body.studio .clip-detail__chip { gap: 8px; cursor: pointer; }
body.studio .clip-detail__chip:hover { border-color: var(--gold); color: var(--gold); }
body.studio .clip-detail__chip--off { cursor: default; }
body.studio .clip-detail__chip--off:hover { border-color: var(--border-strong); color: var(--dim); }
body.studio .clip-detail__player { position: relative; aspect-ratio: 9 / 16;
  border-radius: var(--radius); background: var(--frame-mat); border: 1px solid var(--border);
  overflow: hidden; }
body.studio .clip-detail__player video, body.studio .clip-detail__player img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
body.studio .clip-detail__player img { object-fit: contain; }

/* ---------- the encore's HIGHLIGHTS (Maaz 2026-09-10) ----------
   A highlight row is A TIMESTAMP AND A TITLE ON ONE LINE, and that is the
   whole shape. `.moment*` was a bordered, filled card carrying a "Jump here"
   verb on its right, and twelve of those down a page read as twelve controls
   rather than as a table of contents — which is what Maaz's side-by-side with
   Loom is pointing at. So: no border, no fill, no verb. The stamp is the
   affordance and the ROW is the seek.

   The old names are DELETED, not aliased. A class named after a word this
   product retired, still declared and styled as a thing it is no longer, is
   how a new surface inherits a card by reaching for a familiar name — the
   `.gl-side` P0's lesson facing the other way. `.moment-field` in
   design/afterglow.css is a different file and a different control; untouched.

   Declared UNFENCED, once, because `shared/_encore_moments` has THREE hosts —
   the public stream page, the member's encore extras and the creator's Encore
   tab — and a `body.studio` copy is `studio_scope_law`'s law 2: a fan surface
   wearing a fenced class renders bare and says nothing about it. */
.highlights { display: flex; flex-direction: column; }
/* baseline, not center: a title that wraps hangs under its own first line
   instead of under the stamp — the `.transcript-line--timed` precedent, which
   is the same shape one panel over. */
.highlight { display: flex; align-items: baseline; gap: 14px; width: 100%;
  padding: 7px 0; border: 0; background: none; text-align: left; color: inherit;
  font: inherit; line-height: var(--lh-snap); cursor: pointer;
  transition: color var(--dur) var(--ease); }
/* Tabular comes from `.num` on the span (one source for that rule); this owns
   the accent and the size. */
.highlight__t { flex: none; font-family: var(--mono); font-size: 0.82rem; color: var(--gold); }
.highlight__name { flex: 1; min-width: 0; font-size: 0.95rem; }
.highlight:hover .highlight__name { color: var(--gold); }
/* A borderless button still has to show where the keyboard is. */
.highlight:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
/* The look side of the door reads the same rows with no player to seek
   (journey r3 item 24): same line, no hand, no hover invitation. */
.highlight--still { cursor: default; }
.highlight--still:hover .highlight__name { color: inherit; }
/* THE SUMMARY TAKES ITS COLUMN. It was `.prose` (68ch) with a 72ch inline cap
   on top, which left a gutter of nothing to the right of every line beside a
   Details card filling the same column. `.prose`'s measure is the house's
   reading rule for long documents and does not move — this paragraph is one
   block in a card column, so it takes the column and nothing recreates the
   rag. */
.encore-summary { max-width: none; color: var(--text); }

body.studio .transcript-line { padding: 12px 16px; border-bottom: 1px solid var(--border); --lh: 1.6; line-height: var(--lh-snap); }
body.studio .transcript-line:last-child { border-bottom: 0; }
body.studio .transcript-line mark { background: color-mix(in srgb, var(--gold) 28%, transparent); color: inherit; }
/* a timed cue (phase 1): the transcript with its REAL gutter — the stamp is a
   seek button, the same jump the moments rows make. Baseline-aligned so a
   wrapped cue hangs under its own first line, not under the stamp. */
body.studio .transcript-line--timed { display: flex; gap: 14px; align-items: baseline; }
body.studio .transcript-cue-t { font-family: var(--mono); font-size: 0.8rem; color: var(--gold);
  flex: none; background: none; border: 0; padding: 0; cursor: pointer; }
body.studio .transcript-cue-t:hover { text-decoration: underline; }
body.studio .transcript-cue-t:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* THE ROW HIDES (Maaz 2026-09-13). `--timed` hands the row a `display`, which
   out-ranks the UA's own `[hidden]` rule — the r14 trap, and the search would
   otherwise narrow to nothing visible while every row stayed on screen. */
body.studio .transcript-line[hidden] { display: none; }
/* WHO IS TALKING, where the vendor diarized one: a muted column between the
   stamp and the words, wide enough that "Speaker 1" and "Speaker 2" line the
   words up down the whole tape. Never a name — see Transcripts::Cues. */
body.studio .transcript-line__who {
  flex: none; min-width: 76px; font-size: 0.78rem; color: var(--muted);
}
/* The words take the rest. It was an inline `flex: 1; min-width: 0` on the
   span; it is a class now because the paragraph fallback wears it too. */
body.studio .transcript-line__said { flex: 1; min-width: 0; }
/* THE SEARCH CLEARS THE PILL'S STROKE (Maaz 2026-09-13). It sat directly
   under the Transcript pill and read as touching it — the pills are a 40px
   arc and the field's own border starts the next thing, so the gap has to be
   the rail's own rhythm rather than the 18px a panel usually takes. Measured
   in a browser against the pill's bottom edge, never against this number. */
body.studio .enc-find { margin-top: 24px; }

/* ---------- the caption faces (2026-09-01) ----------
   Every family in ClipCaptionStyle::FONTS, self-hosted, because a FONT picker
   whose preview never changes is worse than no picker: the creator picks Anton,
   sees Archivo, and finds out what they actually bought when the clip renders.
   Same convention as the /d gallery's faces — latin-subset woff2 under
   public/fonts/design, no fonts.googleapis.com at runtime. Anton is the file
   flyposter.css already vendored; the other six are new here. Nothing
   downloads until the sheet is opened (a closed <dialog> is display:none), so
   the studio pays for these only on the screen that uses them.

   Weight coverage is the family's own, not a promise we invent: Montserrat and
   Inter carry 100–900 and Oswald 200–700 as variable fonts; Poppins ships five
   real cuts, one per weight the picker offers; Anton, Archivo Black and Bebas
   Neue exist in exactly one weight, so the browser synthesises the rest —
   which is what Mosaic's renderer faces too. */
@font-face { font-family: "Montserrat"; src: url("/fonts/design/montserrat-var.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap; }
@font-face { font-family: "Anton"; src: url("/fonts/design/anton.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Archivo Black"; src: url("/fonts/design/archivo-black.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Bebas Neue"; src: url("/fonts/design/bebas-neue.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Oswald"; src: url("/fonts/design/oswald-var.woff2") format("woff2");
  font-weight: 200 700; font-style: normal; font-display: swap; }
@font-face { font-family: "Inter"; src: url("/fonts/design/inter-var.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap; }
@font-face { font-family: "Poppins"; src: url("/fonts/design/poppins-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Poppins"; src: url("/fonts/design/poppins-600.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: "Poppins"; src: url("/fonts/design/poppins-700.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: "Poppins"; src: url("/fonts/design/poppins-800.woff2") format("woff2");
  font-weight: 800; font-style: normal; font-display: swap; }
@font-face { font-family: "Poppins"; src: url("/fonts/design/poppins-900.woff2") format("woff2");
  font-weight: 900; font-style: normal; font-display: swap; }
/* The Tag Mango chip rail's four additions (2026-09-11). "Poppins Black" is a
   CATALOG FAMILY NAME on the vendor's side (their hook default), so the chip
   has to ask for that family by name — it reuses the 900 cut already vendored,
   declared across the whole weight axis so no synthetic bold ever doubles it. */
@font-face { font-family: "Poppins Black"; src: url("/fonts/design/poppins-900.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap; }
@font-face { font-family: "Archivo"; src: url("/fonts/design/archivo.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Luckiest Guy"; src: url("/fonts/design/luckiest-guy.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "TikTok Sans"; src: url("/fonts/design/tiktok-sans-var.woff2") format("woff2");
  font-weight: 400 900; font-style: normal; font-display: swap; }

/* ---------- GENERATE CLIPS: the sheet ----------
   A native <dialog>, so Escape and the backdrop come free and focus is trapped
   without a line of JS. Opening it enqueues NOTHING — a preview must never
   spend a cut. Rebuilt to the signed 2026-09-11 mock (Tag Mango Generation
   contract): a 9:16 preview on the left — caption on Y only, hook card, BSP
   mark, double-crop divider — and Cut / Captions / Opening hook / Watermark
   on the right. */
body.studio .gen-sheet::backdrop { background: rgba(0, 0, 0, 0.72); backdrop-filter: blur(3px); }
body.studio .gen-sheet { width: min(1040px, calc(100vw - 32px)); max-height: calc(100vh - 48px);
  padding: 0; border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text); overflow: auto;
  /* the containing block for the close button, which is pinned to the corner
     rather than sitting in the right column's head — that column's first line
     is the eyebrow, and a 40px box in the middle of it is a squeeze */
  position: relative; }
/* A PROPER 40px HIT TARGET (r13 item 1) — the house's icon-button box, which
   the old &times; text button never had. .btn--icon puts the pointer-events
   on the button and not the glyph (r12), so the whole square takes the tap. */
body.studio .gen-sheet__x { position: absolute; top: 8px; right: 8px; z-index: 3; }
/* It is autofocused (see the partial), so its ring is the first thing on the
   sheet — the house's, never Chrome's blue. Never `outline: none`: the ring is
   how a keyboard knows where it is. */
body.studio .gen-sheet__x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* minmax(0, 1fr), never a bare 1fr: `1fr` is `minmax(auto, 1fr)`, so the column
   cannot shrink below its content's min-content — and the FONT grid's
   min-content is the widest chip. With a 0 floor the column fits and the grid
   wraps inside it. */
body.studio .gen-sheet__grid { display: grid; grid-template-columns: minmax(200px, 280px) minmax(0, 1fr);
  gap: 30px; padding: 26px; align-items: start; }
@media (max-width: 760px) { body.studio .gen-sheet__grid { grid-template-columns: minmax(0, 1fr); } }

/* ── THE PREVIEW HOLDS, THE CONTROLS SCROLL (Kunal/TagMango 2026-09-15) ──
   Scrolling to the bottom of this sheet used to take the 9:16 preview with
   it: the dialog was the scroller (`overflow: auto` above), so by the time a
   creator reached the emoji controls the frame those controls are about was
   off the top of the screen. The dialog is a fixed-height COLUMN at the desk
   now — the left pane pinned, the right pane the only scroller, and the foot
   (which holds the sheet's one gold commit) always on screen.

   FOUR THINGS THIS DEPENDS ON, each one a bug if it is edited away.

     `display: flex` IS SCOPED TO `[open]`. A <dialog> is `display: none` from
     the UA stylesheet until it is opened, and a class rule handing it a
     display OUT-RANKS that — so an unscoped `display: flex` here paints the
     whole sheet, closed, on every Clips tab in the studio. (The same trap as
     `.rating[hidden]`, which this house has now paid for four times.)

     `min-height: 0` ON THE GRID. A flex item's floor is its content's
     min-content height, so without it the grid refuses to shrink and the
     dialog grows past the viewport again — the scroll simply moves back up a
     level and the pane is un-pinned.

     `grid-template-rows: minmax(0, 1fr)` is what gives the row a DEFINITE
     height to scroll inside. An `auto` row sizes to its content, and
     `overflow-y: auto` on an item in a content-sized row never scrolls.

     THE PANE IS `align-self: start` so the preview sits at the top of its
     column rather than stretching, and the frame's own aspect ratio holds.

   It is a MIN-WIDTH rule, pairing with the 760px collapse above: in one
   column there is no "other column" to scroll, and pinning a full-width
   preview on a phone would leave the controls a letterbox at the bottom of
   the screen. Below the breakpoint the dialog scrolls as a whole, exactly as
   it always did. */
@media (min-width: 761px) {
  body.studio .gen-sheet[open] { display: flex; flex-direction: column; overflow: hidden; }
  body.studio .gen-sheet__form { flex: 1; min-height: 0; display: flex; flex-direction: column; }
  body.studio .gen-sheet__grid { flex: 1; min-height: 0; grid-template-rows: minmax(0, 1fr); }
  body.studio .gen-sheet__pane { align-self: start; }
  /* the scroller. The right padding keeps the bar off the controls, and
     `overscroll-behavior` stops a flick at the bottom of the list scrolling
     the page underneath the open sheet. */
  body.studio .gen-sheet__controls { min-height: 0; max-height: 100%; overflow-y: auto;
    overscroll-behavior: contain; padding-right: 10px; }
}
/* THE LIVE'S OWN NAME IS THE HEADLINE (the mock's display type). Padded on the
   right so a long title wraps clear of the close button above it. */
body.studio .gen-sheet__title { margin-top: 8px; padding-right: 44px;
  font-size: clamp(1.5rem, 3.2vw, 2.1rem); font-weight: 700; letter-spacing: -0.015em;
  --lh: 1.1; line-height: var(--lh-snap); }
body.studio .gen-sheet__len { margin-top: 10px; }
/* THE OFFER, NOT A QUOTA. Seven is what a run cuts; plan_tiers has no clip
   allowance to read, so this line never says "N left this month". */
body.studio .gen-sheet__offer { margin-top: 12px; font-size: 1.05rem; }
body.studio .gen-sheet__offer b { font-weight: 600; }
body.studio .gen-sheet__caveat { margin-top: 12px; --lh: 1.5; line-height: var(--lh-snap); }

/* the 9:16 preview: the frame the crop answer describes, with the caption
   where the slider puts it — plus the hook card, the BSP mark and the
   double-crop divider, each drawn only while its section is on */
body.studio .cap-frame { position: relative; aspect-ratio: 9 / 16; border-radius: var(--radius);
  background: radial-gradient(120% 90% at 50% 0%, #211a10 0%, #0a0a0b 70%);
  border: 1px solid var(--border); overflow: hidden;
  /* the preview's font sizes are a share of the FRAME, not the viewport —
     the same proportional relationship the real render has */
  container-type: inline-size; }
/* BOTH FIGURES ARE ALWAYS IN THE DOM; data-crop on the frame decides whether
   the second shows and how the two share the frame. JS writes attributes and
   paints no frames. */
body.studio .cap-pane { position: absolute; inset: 0; display: grid; place-items: center; }
body.studio .cap-pane--b { display: none; }
body.studio .cap-figure { width: 52%; height: auto; color: var(--dim); margin-bottom: 12%; }
/* DOUBLE CROP: two stacked frames inside the one 9:16 output — the vendor's
   own framing (falls back to single when only one speaker reads, which is why
   both caption profiles always post). */
body.studio .cap-frame[data-crop="double"] .cap-pane--a { inset: 0 0 50% 0; }
body.studio .cap-frame[data-crop="double"] .cap-pane--b { display: grid; inset: 50% 0 0 0; }
body.studio .cap-frame[data-crop="double"] .cap-figure { width: 38%; margin-bottom: 2%; }
/* The divider: a solid, full-width line between the stacked speakers — color
   and thickness are the two controls, painted inline by the controller. */
body.studio .cap-divider { display: none; position: absolute; left: 0; right: 0; top: 50%;
  transform: translateY(-50%); z-index: 1; background: #ffffff; height: 2px; }
body.studio .cap-frame[data-crop="double"] .cap-divider { display: block; }
/* The opening hook's sample card: centered on X (the render's own layout),
   its Y/width/colors/roundness painted inline from the section's controls. */
body.studio .cap-hook { display: none; position: absolute; left: 50%; top: 20%; width: 84%;
  transform: translate(-50%, -50%); z-index: 2; padding: 5% 4%; text-align: center;
  background: #ffffff; color: #111111; font-weight: 800; letter-spacing: 0.02em;
  --lh: 1.1; line-height: var(--lh-snap); }
body.studio .cap-frame[data-hook="on"] .cap-hook { display: block; }
/* The BackstagePass mark: a static wordmark stand-in in whichever of the nine
   anchors is picked — position, size, opacity and margins painted inline. */
body.studio .cap-mark { display: none; position: absolute; z-index: 2; text-align: center;
  padding: 4% 0; border: 1px solid var(--hair-gold); border-radius: 6px; color: var(--gold);
  background: rgba(10, 10, 11, 0.55); font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; white-space: nowrap; overflow: hidden; }
body.studio .cap-frame[data-mark="on"] .cap-mark { display: block; }
/* The caption is CENTERED ON X — Tag Mango centers horizontally and exposes
   no X param, so the block is pinned to the midline and only Y moves. */
body.studio .cap-line { position: absolute; left: 50%; transform: translate(-50%, -50%); z-index: 2;
  width: max-content; max-width: 92%; text-align: center; --lh: 1.12; line-height: var(--lh-snap);
  text-transform: uppercase; letter-spacing: 0.01em;
  /* the caption IS the position control: drag it, or focus it and nudge.
     touch-action:none so a drag on a phone moves the caption instead of
     scrolling the sheet out from under the finger. */
  cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none;
  padding: 4px 8px; border-radius: 4px; outline-offset: 0; }
/* CAPTIONS OFF MEANS NO WORDS ON SCREEN — the preview says so too, or the
   switch is a control that changes nothing you can see. */
body.studio .cap-frame[data-captions="off"] .cap-line { display: none; }
/* --accent, never --gold: gold is money and the Pass (tokens.css), and this is
   a handle. Same ring the segmented control and the menu items wear. */
body.studio .cap-line:hover { outline: 1px dashed color-mix(in srgb, var(--accent) 50%, transparent); }
body.studio .cap-line:focus-visible { outline: 2px solid var(--accent); }
body.studio .cap-line--dragging { cursor: grabbing; outline: 1px dashed var(--accent); }
body.studio .cap-line em { font-style: normal; display: block; }
/* ONE COLUMN MEANS THE PREVIEW IS CAPPED, or a 9:16 frame at the full width of
   a phone is 700px tall and every control on the sheet is below the fold — the
   creator would scroll past the picture to reach the questions. It sits AFTER
   the base rules on purpose: a media query carries no extra specificity, so
   source order is the only thing that makes this win. */
@media (max-width: 760px) {
  body.studio .cap-frame { max-width: 210px; margin-inline: auto; }
  body.studio .gen-sheet__caveat { text-align: center; }
}

/* ONE QUESTION, N ANSWERS ACROSS (r13 item 2) — the composer sheet's own
   .choice card, laid in a row instead of a stack. [hidden] must beat the grid:
   the second question only exists inside the first one's second answer. */
body.studio .cap-fieldset { border: 0; padding: 0; margin: 22px 0 0; min-width: 0; }
body.studio .cap-legend { padding: 0; color: var(--muted); font-size: 0.9rem; }
body.studio .cap-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px; margin-top: 10px; }
body.studio .cap-cards--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
body.studio .cap-cards[hidden] { display: none; }
body.studio .cap-cards .choice + .choice { margin-top: 0; }
body.studio .choice--card { display: block; padding: 13px 15px; }
body.studio .choice--card b { font-size: 0.97rem; }
body.studio .choice--card span span { font-size: 0.84rem; --lh: 1.35; line-height: var(--lh-snap); }
@media (max-width: 620px) {
  body.studio .cap-cards, body.studio .cap-cards--two { grid-template-columns: minmax(0, 1fr); }
}

/* THE CAPTIONS SWITCH (r13 item 4). Off takes every caption control off the
   screen — a settings block nobody can act on is noise on the densest sheet in
   the studio — and the preview loses its line at the same moment. */
body.studio .cap-toggle { margin-top: 22px; }
body.studio .cap-settings[hidden] { display: none; }
body.studio .cap-settings__label { margin-top: 22px; }
body.studio .cap-control { display: block; margin-top: 14px; }
/* SENTENCE CASE, not the studio's tracked micro-caps (the r13 mocks): these
   labels are questions a first-time creator reads ("Left / right", "Font
   size"), and this sheet's own two classes are the only place they appear. */
body.studio .cap-control__label { display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  font-size: 0.85rem; color: var(--muted); }
body.studio .cap-control__label output { font-family: var(--mono); font-size: 0.8rem; color: var(--text); }
body.studio .cap-control input[type="range"] { width: 100%; margin-top: 7px; accent-color: var(--gold); }
body.studio .cap-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px 18px; }
/* THE FONT PICKER IS A GRID, not a scrolling rail (the mock): every face is on
   screen at once, and the chips wrap inside the column. */
body.studio .cap-fonts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
/* A CHIP HERE IS A TYPE SPECIMEN, so the house chip's all-small-caps comes off:
   the whole point of the picker is that "Archivo Black" is drawn in Archivo
   Black, and small caps hide half of what a face looks like. */
body.studio .cap-fonts .chip { font-variant-caps: normal; letter-spacing: 0.01em;
  font-size: 0.84rem; padding: 6px 13px; }
/* The chips are BUTTONS now (they write the exact catalog name into the font
   field beside them) — a JS shortcut over an always-working text input. */
body.studio .cap-fonts button.chip { cursor: pointer; }
body.studio .cap-fonts button.chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .cap-swatches { display: flex; gap: 18px; flex-wrap: wrap; margin-top: 20px;
  padding-top: 18px; border-top: var(--hair-w) solid var(--border); }
/* The plain variant: a swatch row INSIDE a group, which already has its own
   separation — a second hairline would be grit. */
body.studio .cap-swatches--plain { margin-top: 12px; padding-top: 0; border-top: 0; }
body.studio .cap-swatches[hidden] { display: none; }
body.studio .cap-swatch { display: flex; align-items: center; gap: 8px; font-size: 0.85rem; color: var(--muted); }
body.studio .cap-swatch input[type="color"] { width: 34px; height: 26px; padding: 0; border-radius: 5px;
  border: 1px solid var(--border-strong); background: transparent; cursor: pointer; }
/* Highlight OFF means the swatch picks nothing right now — dimmed, never
   removed, so the stored colour survives the mode round trip. */
body.studio .cap-swatch--off { opacity: 0.4; }

/* THE SECTION EYEBROWS — Cut / Captions / Opening hook / Watermark, the
   mock's own running order, in the studio's tracked micro-caps. */
body.studio .cap-eyebrow { padding: 0; margin-top: 4px; font-size: 0.68rem; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--gold); }
body.studio .cap-fieldset + .cap-fieldset,
body.studio .cap-fieldset .cap-fieldset { margin-top: 20px; }
body.studio .cap-fieldset--tight { margin-top: 14px; }
body.studio .cap-fieldset--tight .cap-cards { margin-top: 8px; }
/* A grid or flex box has to hand [hidden] back (the r14 law: a class display
   outranks the UA's [hidden]). */
body.studio .cap-grid[hidden] { display: none; }
body.studio .cap-fonts[hidden] { display: none; }
body.studio .cap-settings .cap-grid { margin-top: 6px; }

/* The prompt, the number boxes, the font field — plain house inputs sized for
   the sheet's column. */
body.studio .cap-prompt { width: 100%; margin-top: 7px; resize: vertical; }
body.studio .cap-num { width: 82px; margin-top: 7px; }
body.studio .cap-font-field { width: 100%; margin-top: 7px; }
/* A checkbox row: the box and its sentence on one line. */
body.studio .cap-check { display: flex; align-items: center; gap: 9px; margin-top: 14px;
  font-size: 0.9rem; cursor: pointer; }
body.studio .cap-check input[type="checkbox"] { accent-color: var(--gold); width: 16px; height: 16px; flex: none; }
/* Small enum pickers (style, highlight mode, color mode, emoji position):
   chip radios, the composer's own idiom. */
body.studio .cap-pills { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
body.studio .cap-pills--sub { align-items: center; margin-top: 10px; }

/* THE PROFILE TABS — single | double. Both panels are ALWAYS in the DOM and
   both post; the tabs only pick which one the controls and the preview edit.
   --live marks the profile the picked crop will actually render with. */
body.studio .cap-tabs { display: flex; gap: 8px; margin-top: 16px; }
body.studio .cap-tab { border: 1px solid var(--border-strong); border-radius: 999px;
  background: transparent; color: var(--muted); font-size: 0.8rem; padding: 7px 14px;
  cursor: pointer; letter-spacing: 0.03em; }
body.studio .cap-tab--on { background: color-mix(in srgb, var(--gold) 16%, transparent);
  border-color: var(--hair-gold); color: var(--text); }
body.studio .cap-tab--live::after { content: " •"; color: var(--gold); }
body.studio .cap-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .gen-profile { margin-top: 6px; }
body.studio .gen-profile[hidden] { display: none; }

/* THE WATERMARK'S NINE ANCHORS — a 3×3 of radio cells, the picked one gold. */
body.studio .cap-anchor { display: grid; grid-template-columns: repeat(3, 30px); gap: 5px; margin-top: 8px; }
body.studio .cap-anchor__cell { position: relative; display: grid; place-items: center;
  width: 30px; height: 30px; border: 1px solid var(--border-strong); border-radius: 6px; cursor: pointer; }
body.studio .cap-anchor__cell span[aria-hidden] { width: 10px; height: 10px; border-radius: 3px;
  background: var(--border-strong); }
body.studio .cap-anchor__cell:has(input:checked) { border-color: var(--hair-gold); }
body.studio .cap-anchor__cell:has(input:checked) span[aria-hidden] { background: var(--gold); }
body.studio .cap-anchor__cell:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

/* THE FOOTER: the "up to N" sentence left (it tracks the slider — never a
   promise), the two acts right. */
/* ── THE SHEET'S INFO TIPS, CONTAINED (Kunal/TagMango 2026-09-15) ───────────
   Fifty-four hover boxes went onto this sheet, and the sheet's right column is
   a SCROLL CONTAINER now — `overflow-y: auto` computes `overflow-x` to auto as
   well, so one tip laying out past the column's right edge grows a sideways
   scrollbar INSIDE the dialog. The document-level fence cannot see that (it
   measures the document; a dialog's own overflow never reaches it), and an
   `opacity: 0` tip is still part of scrollable overflow — so it is there at
   rest, on every render, with nobody hovering anything. MEASURED at 182px
   before this rule, on a 676px column.

   `.tip-i`'s own rule is a FIXED 236px box anchored to the 12px glyph, which
   is right in a studio table cell and wrong here. So inside this sheet every
   tip is anchored to its own GROUP instead and clamped to it: the group is a
   box that is inside the column by construction, so no sentence — however
   long a later round writes one — can reach the column's edge.

   THE LIST IS THE IMMEDIATE GROUP OF EVERY TIPPED LABEL, narrowest first, and
   it has to be exhaustive: a tip whose nearest positioned ancestor is missed
   falls back to `.gen-sheet` itself (which is `position: relative` for its
   close button) and lands below the whole dialog. A first cut positioned only
   the four label-wrapping controls and left every LEGEND behind — which is
   what the 182px was. Add a tipped label, add its group here.

   Both anchor LEFT and hang BELOW, which is what `.tip-below` already does
   here: above would land on the control the tip is explaining. Both transform
   states are re-declared, because the base rule's hover carries a
   translateX(-50%) that pairs with its own `left: 50%`. */
/* SPELLED OUT, NEVER `:is(...)`: `studio_scope_law_test` reads this sheet by
   splitting selector runs on commas, so every argument after the first in an
   `:is()` list reads to it as an UNFENCED rule on studio furniture — it failed
   on exactly that. One fenced selector per line keeps the fence able to see
   what it is guarding. */
body.studio .gen-sheet .cap-control,
body.studio .gen-sheet .cap-check,
body.studio .gen-sheet .toggle-row,
body.studio .gen-sheet .cap-settings__label,
body.studio .gen-sheet .cap-fieldset--tight,
body.studio .gen-sheet .cap-pills,
body.studio .gen-sheet .cap-swatches,
body.studio .gen-sheet .cap-grid { position: relative; }
body.studio .gen-sheet .tip-i[data-tip] { position: static; }
body.studio .gen-sheet .tip-i[data-tip]::after {
  /* A ::after DOES NOT INHERIT THE UNIVERSAL BORDER-BOX RESET (r15 rule 10,
     where it cost the analytics tiles 22px), so `max-width: 100%` plus this
     tip's own 10px padding and hairline is a box WIDER than the group it is
     clamped to — measured 12px of sideways scroll with everything else right. */
  box-sizing: border-box;
  left: 0; right: auto; width: max-content; max-width: 100%; transform: translateY(2px);
}
body.studio .gen-sheet .tip-i[data-tip]:hover::after,
body.studio .gen-sheet .tip-i[data-tip]:focus-visible::after { transform: translateY(0); }

body.studio .gen-sheet__foot { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 18px 26px; border-top: 1px solid var(--border); flex-wrap: wrap;
  /* the sheet is a flex COLUMN at the desk (see the pinned-preview block
     above), and the foot is the part that must not give: it holds the one
     gold commit. Inert in one column, where the dialog scrolls as a whole. */
  flex: none; }
body.studio .gen-sheet__upto { margin: 0; font-size: 0.9rem; color: var(--muted); }
body.studio .gen-sheet__upto .num { color: var(--text); }
body.studio .gen-sheet__acts { display: flex; align-items: center; gap: 12px; margin-left: auto; }
/* The mock's footer voice: both words tracked and upper, the gold one filled.
   .btn--gold is the same fill the masthead's GO LIVE pill and a live's Save
   wear (r12) — never a third gold recipe. */
body.studio .gen-sheet__acts .btn { text-transform: uppercase; letter-spacing: 0.09em; font-size: 0.82rem; }
body.studio .gen-sheet__go { padding: 14px 24px; }

/* THE POST SHEET (clip pipeline phase 3) — its own class: .gen-sheet is the
   caption preview's furniture and .sheet is the landing page's (the 08-31
   lesson). One dialog per page; the cards' Post buttons point it at a clip. */
body.studio .post-sheet::backdrop { background: rgba(0, 0, 0, 0.72); backdrop-filter: blur(3px); }
body.studio .post-sheet { position: relative;
  width: min(620px, calc(100vw - 32px)); max-height: calc(100vh - 48px);
  padding: 26px; border: var(--hair-w) solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text); overflow: auto; }
/* the close ×: the house's 40×40 icon box, top-right, the gen-sheet's own. */
body.studio .post-sheet__x { position: absolute; top: 14px; right: 14px; }
body.studio .post-sheet__title { font-size: 1.5rem; margin: 0 44px 20px 0; }
body.studio .post-sheet__block { border: 0; padding: 0; margin: 0 0 20px; }
body.studio .post-sheet__legend { padding: 0; font-size: 0.68rem; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); margin-bottom: 10px; }
/* the same containing-block rule as the tray's row (see .post-tray__where):
   the sheet scrolls, so a nowrap tip on a disabled chip would be clipped by
   its own dialog rather than readable. */
body.studio .post-sheet__chips { position: relative; display: flex; flex-wrap: wrap; gap: 8px; }
body.studio .post-sheet__chips[hidden] { display: none; }
body.studio .post-sheet__chips .chip-pick[data-tip] { position: static; }
body.studio .post-sheet__chips .chip-pick[data-tip]::after {
  left: 0; right: auto; width: max-content; max-width: 100%;
  white-space: normal; text-transform: none; letter-spacing: 0; }
body.studio .post-sheet__sched { display: flex; flex-wrap: wrap; align-items: flex-end;
  gap: 18px; margin-top: 14px; }
body.studio .post-sheet__sched[hidden] { display: none; }
body.studio .post-sheet__field { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
body.studio .post-sheet__field[hidden] { display: none; }
body.studio .post-sheet__lab { font-size: 0.8rem; color: var(--muted); }
body.studio .post-sheet__zone { white-space: nowrap; }
body.studio .post-sheet__num { width: 68px; text-align: center; }
body.studio .post-sheet textarea, body.studio .post-sheet input[type="text"] { width: 100%; }
body.studio .post-sheet textarea[hidden] { display: none; }
/* the clips this send covers, one row each: poster, title, the beat it goes
   out on, and (One per clip) its own caption box. */
body.studio .post-sheet__rows { border-top: var(--hair-w) solid var(--border); margin-top: 4px; }
body.studio .post-sheet__rows:empty { border-top: 0; }
body.studio .post-row { display: flex; gap: 14px; padding: 14px 0;
  border-bottom: var(--hair-w) solid var(--border); }
body.studio .post-row:last-child { border-bottom: 0; }
body.studio .post-row__poster { width: 52px; aspect-ratio: 9 / 16; object-fit: cover;
  border-radius: 8px; background: var(--frame-mat); flex: none; }
body.studio .post-row__body { flex: 1; min-width: 0; }
body.studio .post-row__title { margin: 0; overflow-wrap: anywhere; }
body.studio .post-row__when { margin: 3px 0 0; font-size: 0.85rem; color: var(--accent); }
body.studio .post-row__caption { width: 100%; margin-top: 10px; }
body.studio .post-sheet__foot { display: flex; align-items: center; justify-content: flex-end; gap: 12px;
  margin-top: 20px; }
body.studio .post-sheet__foot .btn--ghost { text-transform: uppercase; letter-spacing: 0.09em;
  font-size: 0.8rem; }

/* A PICKED CHIP IS GOLD, AN UNCONNECTED ONE IS DIM AND REFUSES (Maaz
   2026-09-07). One rule set for the tray and the sheet, so the two renderings
   of one selection cannot look like two controls. The input is .sr-only and
   the LABEL is the control, which is the composer's own destination-pill
   shape — one stroke, on the item (r8). */
body.studio .chip-pick { display: inline-flex; align-items: center; gap: 8px;
  min-height: 34px; padding: 0 14px; border-radius: 999px;
  border: var(--hair-w) solid var(--border-strong); background: transparent;
  color: var(--muted); font-size: 0.85rem; cursor: pointer; }
/* the picked fill is the armed destination's own (body.studio .dest--on) —
   the same gold, because "this clip goes here" is the same claim as "this
   feed is armed tonight"; never a second gold recipe. */
body.studio .chip-pick:has(input:checked) { border-color: var(--hair-gold); color: var(--gold);
  background: color-mix(in srgb, var(--gold) 8%, transparent); }
body.studio .chip-pick svg { color: var(--muted); }
body.studio .chip-pick:has(input:checked) svg { color: var(--gold); }
body.studio .chip-pick:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
/* NOT CONNECTED: drawn so a creator can see what connecting would buy them,
   refusing so it can never be picked. */
body.studio .chip-pick--off { opacity: 0.45; cursor: not-allowed; }

/* THE SELECTION TRAY (Maaz 2026-09-07, screenshot 8) — the bulk bar grown to
   hold the platform chips and the gold Post. It reuses .bulk-bar's sticky
   shell (and its `[hidden] { display: none }`, #377's trap), and widens its
   inner row to the page rather than hugging its own content. */
body.studio .post-tray .bulk-bar__in { width: 100%; max-width: 1240px;
  border-radius: var(--radius); gap: 16px; padding: 12px 16px; }
body.studio .post-tray__count { display: flex; align-items: baseline; gap: 10px; flex: none; }
/* THE ROW IS THE CONTAINING BLOCK, NOT THE CHIP. An unconnected chip carries
   a sentence-length [data-tip], and `[data-tip]::after` is
   `position: absolute; left: 50%; white-space: nowrap` against its OWN
   element — a ~180px box on a ~105px chip, which inflates the chip's
   scrollWidth, then the row's, then the bar's, then the document's: measured
   50px of horizontal scroll at a true 390px with NO element's bounding box
   outside the viewport (the control-room-shell class of bug, and a rect-only
   probe cannot see it). Clamping a width cannot fix WHERE the control landed,
   so the block moves: the row goes relative, the chip goes static, and the
   tip resolves against the row — which is inside the bar by construction at
   any width — and wraps. */
body.studio .post-tray__where { position: relative;
  display: flex; flex-wrap: wrap; gap: 8px; flex: 1; min-width: 0; }
body.studio .post-tray__where .chip-pick[data-tip] { position: static; }
body.studio .post-tray__where .chip-pick[data-tip]::after {
  left: 0; right: auto; width: max-content; max-width: 100%;
  white-space: normal; text-transform: none; letter-spacing: 0; }
body.studio .post-tray__act { display: flex; align-items: center; flex-wrap: wrap;
  justify-content: flex-end; gap: 10px; flex: none; }
body.studio .post-tray__bulk { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 0; }
@media (max-width: 900px) {
  body.studio .post-tray .bulk-bar__in { flex-direction: column; align-items: stretch; }
}
/* the card's per-platform post lines sit under the meta, one truth per line */
body.studio .clip-card__postrow { margin-top: 4px; overflow-wrap: anywhere; }

/* ---------- the members table (Maaz 2026-08-31) ----------
   Audience became a roster a creator can act on: who each person is, what
   access they hold, what they're worth. Built out of the existing token layer
   — no new palette, no new radius, no new shadow. Gold appears on money and
   nowhere else, which is the token's own rule.

   Columns drop from the right as the viewport narrows rather than growing a
   horizontal scrollbar (banned product-wide — the R20 mobile pass): a phone
   keeps the three that answer the question, who / what they hold / what
   they're worth. `overflow-wrap: anywhere` because a fan owns their own name
   and it can arrive as one 100-character token. */
body.studio .roster { width: 100%; border-collapse: collapse; font-size: 0.86rem; }
body.studio .roster th, body.studio .roster td { padding: 12px 14px; text-align: left; vertical-align: middle;
  border-bottom: 1px solid var(--border); overflow-wrap: anywhere; }
body.studio .roster thead th { font-size: 0.64rem; font-weight: 700; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--muted); white-space: nowrap; }
body.studio .roster tbody tr:last-child th, body.studio .roster tbody tr:last-child td { border-bottom: 0; }
body.studio .roster tbody th { font-weight: 600; }
body.studio .roster tbody tr { transition: background var(--dur) var(--ease); }
body.studio .roster tbody tr:hover { background: var(--surface-2); }
body.studio .roster__num { text-align: right; font-variant-numeric: tabular-nums lining-nums; }
body.studio .roster__money { color: var(--gold); }
body.studio .roster__sub { display: block; margin-top: 2px; }
body.studio .roster__access .chip { margin: 1px 4px 1px 0; }

/* HOLDER-TABLE HEADER ⓘ MUST NOT CLIP (Maaz r12). The table lives in a
   `.card--flush` (overflow: hidden), so a tip hanging ABOVE the header row is
   cut against the card's top edge. Open it DOWNWARD into the body instead, and
   anchor the right-aligned numeric headers' tips to the right so the 236px box
   never runs off into the horizontal clip. */
body.studio .pass-holders thead .tip-i[data-tip]::after { top: calc(100% + 6px); bottom: auto; }
body.studio .pass-holders thead th.roster__num .tip-i[data-tip]::after { left: auto; right: 0; }
body.studio .roster--log td:first-child, body.studio .roster--log th:first-child { white-space: nowrap; }

/* Reachability, three letters: lit = a send lands right now, dim = it doesn't.
   Which dial closed a rail — the fan's own choice, a missing token, absent
   consent — is never disclosed on a creator surface (A16.4). */
body.studio .reach { display: inline-flex; gap: 4px; }
body.studio .reach span { width: 20px; height: 20px; display: inline-flex; align-items: center;
  justify-content: center; border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: var(--mono); font-size: 0.62rem; color: var(--dim); }
body.studio .reach span.reach--on { color: var(--text); border-color: var(--border-strong);
  background: var(--surface-3); }

@media (max-width: 1180px) { body.studio .roster__col--since { display: none; } }
@media (max-width: 1040px) { body.studio .roster__col--seen { display: none; } }
@media (max-width: 900px) { body.studio .roster__col--money { display: none; } }
@media (max-width: 640px) {
  body.studio .roster__col--reach, body.studio .roster__col--extra { display: none; }
  body.studio .roster th, body.studio .roster td { padding: 10px 8px; }
  body.studio .roster { font-size: 0.8rem; }
}

/* ---------- the overflow menu (Maaz 2026-08-31) ----------
   "I have never seen any platform where there are four buttons at the same
   time on the same card to do one thing." One visible action per card —
   Share — and everything else behind three dots. The disclosure is a native
   <details>/<summary>, so the toggle, the focus ring and Enter/Space are the
   platform's; menu_controller adds Escape and outside-click.

   No new palette, no new radius: the trigger is a .btn--ghost the width of a
   square, the panel is the .card surface at card radius, and the one shadow
   is the elevation cue the drop sheet already uses — a floating layer has to
   read as floating. Nothing here glows. */
.menu { position: relative; display: inline-block; }
.menu > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 28px; padding: 0; border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong); color: var(--muted);
  font-size: 0.95rem; --lh: 1; line-height: var(--lh-snap); letter-spacing: 0.06em;
  transition: color 150ms ease, border-color 150ms ease;
}
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary:hover { color: var(--text); border-color: var(--text); }
.menu > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* an open card wins over the ones under it — the panel is taller than its row */
.card:has(details.menu[open]) { position: relative; z-index: 40; }
.menu__panel {
  position: absolute; z-index: 30; top: calc(100% + 6px); right: 0;
  min-width: 210px; max-width: min(280px, calc(100vw - 44px));
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius); padding: 6px;
  box-shadow: 0 14px 38px rgb(0 0 0 / 0.5);
  text-align: left;
}
/* IT OPENS UPWARD WHERE THERE IS NO ROOM BELOW (Maaz's Pass QA, 2026-09-09:
   "every kebab option must be fully visible"). menu_controller measures on
   open and adds this — a fixed direction is right on one surface and off the
   fold on the next, and the kebab at the foot of a Pass card is the second. */
.menu.menu--up > .menu__panel { top: auto; bottom: calc(100% + 6px); }
/* AND IT ESCAPES AN OVERFLOW BOX WHERE IT HAS TO (r6 item 2). The Followers
   roster's wrap is a scroll container on both axes, and an overflow box clips
   its content whether or not it scrolls — no z-index gets out of that. A
   fixed panel's containing block is the viewport, so it is not clipped at
   all; menu_controller writes the two coordinates on the <details> because
   CSS cannot know where a row landed after a sideways scroll. AFTER the
   menu--up rule on purpose: the two selectors are the same specificity and
   both classes can be on at once, so source order is what decides.
   With no JavaScript neither property is ever written and the panel stays the
   house's own absolute one — clipped for the last rows of a long table, which
   is a degraded control and not a dead one. */
.menu.menu--anchored > .menu__panel {
  position: fixed; top: var(--menu-y); left: var(--menu-x); right: auto; bottom: auto;
}
.menu__panel form { display: block; margin: 0; }
/* THE DISCLOSURE DOES NOT CHANGE MOOD (Maaz 2026-09-12: "Fan dropdown
   background/border must match the creator panel — one .menu__panel, no second
   fan palette"). MEASURED, because the CSS reads as though it already did:
   `.menu__panel` is one unfenced rule on one class and `studio` is a SCOPE
   rather than a fourth mood, so the two panels looked identical by
   construction — and they were not. The fan side is the DOOR mood
   (`ApplicationHelper::DOOR_CONTROLLERS` carries home, directory, accounts,
   creators, clips, subscriptions, notification_prefs, help), which resolves
   `--surface` to the cool grey #202123 against the studio's warm #17130e,
   `--border-strong` a step lighter, and `--accent` to the brand GOLD instead
   of the house tungsten. Read off a real browser: the offer row's ink came
   back #e0b669 on Fan Home and #ffd9a3 in the studio.

   IT IS THE TOKENS THAT ARE PINNED, NOT THE COMPONENT. Every rule in the
   panel keeps reading `var(--surface)` / `var(--border)` / `var(--accent)`
   exactly as it does in the studio — there is no second set of component
   declarations to drift, which is his "no second fan palette" as a mechanism
   rather than as an intention. Anything added to this menu later inherits the
   pin for free.

   IT IS EVERY DISCLOSURE ON A DOOR-MOOD PAGE, not just the avatar's, and
   deliberately so: the bell hangs off the same bar and wears the same
   `.menu__panel`, so two panels a few pixels apart in two different fills
   would be this defect one control over. The profile's own follow menu is on a
   door-mood page too, and reads as the same panel for the same reason.

   PAPER IS DELIBERATELY UNTOUCHED. `body.paper` is a LIGHT mood (legal, the
   trust page) and the masthead it hangs off is light with it, so a pinned dark
   panel there would be the same defect facing the other way. The two DARK
   moods share one panel; the light one keeps its own.

   WHY NOT MOVE THE FAN SIDE OFF THE DOOR MOOD: that is the root cause and it
   is a whole-product repaint — eight controllers' worth of cards, chips and
   hairlines — not a chrome fix. Reported, not done. */
body.door .menu__panel {
  --surface: #17130e;        /* house lights' own raised booth */
  --surface-3: #221c15;      /* …and the row hover it pairs with */
  --border: #2d271f;
  --border-strong: #544837;
  --text: #f4ede1;
  --muted: rgb(244 237 225 / 0.62);
  --dim: rgb(244 237 225 / 0.45);
  --accent: #ffd9a3;         /* tungsten: the current half and the lit offer row */
}
.menu__item {
  display: block; width: 100%; box-sizing: border-box; text-align: left;
  background: transparent; border: 0; border-radius: var(--radius-sm);
  padding: 9px 11px; cursor: pointer;
  font: inherit; line-height: var(--lh-snap); font-size: 0.84rem; color: var(--text); text-decoration: none;
  transition: background 150ms ease;
}
.menu__item:hover { background: var(--surface-3); }
.menu__item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.menu__note { display: block; margin-top: 2px; font-size: 0.72rem; color: var(--dim); }
/* the separator before an irreversible item — a rule, never a red FILL: a red
   block is a live badge or the cut, and nothing else. The item's own words may
   wear live-red (r3-ref-1's "Cancel this live"), which is the same red the
   alert toast and the form-errors band already carry for a refusal — one red,
   two weights: text for "this undoes something", fill for "this is on air". */
.menu__rule { height: 1px; margin: 6px 4px; background: var(--border); }
/* WHO the menu belongs to (r3-ref-5): the name, then the handle as the door a
   fan would type. For a fan-only identity it is still a plain label — they
   have no public page for it to lead to. */
.menu__who { padding: 8px 11px 6px; }
.menu__who b { display: block; font-size: 0.86rem; font-weight: 600; }
.menu__who span { display: block; margin-top: 2px; font-size: 0.74rem; color: var(--dim); }
/* AND FOR A CREATOR IT IS A LINK TO THEIR OWN PAGE (Maaz 2026-09-11, item 1).
   This REVERSES the rule that used to sit above it — "a label that navigates
   is a control nobody asked for" — because this one was asked for: the handle
   printed under the name IS an address, and what a creator wants from reading
   their own address is to look at it.

   ONE HIT TARGET, which is the whole of his note: the anchor wraps the name,
   the handle AND the mark, so a hover anywhere across the row lights the same
   background the item rows use. Two of them (a link on the name, a button on
   the glyph) would be two targets in a box 190px wide. */
.menu__who--link {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  border-radius: var(--radius-sm); text-decoration: none;
  transition: background 150ms ease;
}
.menu__who--link:hover { background: var(--surface-3); }
.menu__who--link:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* `min-width: 0` so a long display name shortens the words column rather than
   pushing the mark out of the panel. The two inner rules re-declare what
   `.menu__who b` / `.menu__who span` already say, at equal specificity and
   later in the file, because the words now sit one level deeper: without them
   the wrapper span inherits the handle's dim, small type and takes the name
   down with it. */
.menu__who-id { display: block; min-width: 0; }
.menu__who-id b { display: block; font-size: 0.86rem; font-weight: 600; color: var(--text); }
.menu__who-id span { display: block; margin-top: 2px; font-size: 0.74rem; color: var(--dim); }
/* The mark brightens from dim to the row's own ink rather than to GOLD: gold
   in this house is money, the Pass, and (below) the half you are standing in.
   A mark that says "this opens" is none of those. */
.menu__who-mark { flex: none; color: var(--dim); transition: color 150ms ease; }
.menu__who--link:hover .menu__who-mark { color: var(--text); }
/* A leading mark (Your Studio's clapperboard) sits in the same slot Become a
   creator's radio uses. Flex lives here so the row does not need `--offer` —
   that class is the invitation, and wearing it when you already hold the half
   is what made Your Studio look selected on Following (Maaz 2026-09-14). */
.menu__item--lead {
  display: flex; align-items: center; gap: 9px;
}
.menu__item--lead .lucide { flex: none; }
/* Destinations' "On by default" and the Overview window still trail a check
   inside `--on`. That class is LAYOUT, not selected-gold — those rows wear
   `--on` even when the check is off. */
.menu__item--on { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.menu__check { flex: none; color: var(--accent); }
/* THE HALF YOU ARE IN (Maaz 2026-09-14). The identity switch dropped the tick:
   tungsten fill + ink on `[aria-current]` is the selected state. `--offer`
   stays the invitation. Both declarations ship AFTER `.menu__item:hover`. */
.menu__item[aria-current] {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.menu__item[aria-current]:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); }
/* A DRAWN-BUT-REFUSED ROW (the drops kebab's "Drop" with no live on the wire):
   dimmed and inert, its note says why. menu_controller already leaves an
   aria-disabled item alone (never acts, never closes). */
.menu__item[aria-disabled="true"] { color: var(--muted); cursor: default; }
.menu__item[aria-disabled="true"]:hover { background: transparent; }
.menu__item--danger { color: var(--live); }
.menu__item--danger .menu__note { color: color-mix(in srgb, var(--live) 68%, transparent); }
/* THE OFFER, LIT (Maaz 2026-09-12: "Highlighted (gold / menu accent), icon
   beside it"). The invitation into a half this identity does not hold — a
   fan's Become a creator. The current half uses the same tungsten fill via
   `--on` (Maaz 2026-09-14); this class is never a stand-in for selected.

   TUNGSTEN (`--accent`), never `--gold`: gold in this house is money and the
   Pass and nothing else, and this row is neither. The fill is 8% rather than
   the solid `--btn-bg` block so it reads as LIT and not as a second primary
   action (one primary per screen, and on Fan Home the claim band's own CTA is
   that primary). Both declarations have to ship AFTER `.menu__item` and
   `.menu__item:hover` — same specificity, so source order is what decides —
   which is why they sit here at the foot of the menu block rather than beside
   the base rule.

   The mark LEADS the row: a mark says what the row does and belongs at the
   start of it. */
.menu__item--offer {
  display: flex; align-items: center; gap: 9px;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.menu__item--offer:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.menu__item--offer .lucide { flex: none; }

/* ---------- Studio Analytics (Maaz 2026-08-31) ----------
   Four sub-tabs on the existing .tabs rail; no new palette, no new radius.
   Two row shapes, and both exist to stop a wrap mid-numbers:

   .spend-row — name on the left, the night's chips on the right. The chips
   wrap as a group rather than one chip at a time, so a long stream title can
   never orphan the total on a line of its own.

   .lnk-row — a tracking link is a LABEL row (label + address + Delete) and a
   NUMBERS row, stacked. At 1512px a long label used to push the earned chip
   and the Delete button onto their own wrap, mid-funnel (§5, layout). */
body.studio .spend-row { flex-wrap: wrap; }
body.studio .spend-row__name { flex: 1 1 240px; min-width: 0; overflow-wrap: anywhere; }
body.studio .spend-row__chips { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  flex: 1 1 auto; justify-content: flex-end; }

body.studio .lnk-row { display: block; }
body.studio .lnk-row__head { display: flex; align-items: flex-start; gap: 14px; justify-content: space-between; }
body.studio .lnk-row__head > *:first-child { flex: 1 1 auto; min-width: 0; }
body.studio .lnk-row__chips { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
/* THE ADDRESS, AND THE ONE CONTROL THAT HANDS IT OVER (Maaz 2026-09-13). It
   was `.small .muted` — 13px of the page's quietest ink under the label it
   belongs to — on the one string a creator opened this section to take away.
   Gold and a size up, with Copy link beside it; it WRAPS rather than pushing
   the card sideways, because a long handle plus a long slug is wider than a
   phone. */
body.studio .lnk-row__url { display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap; margin-top: 2px; }
body.studio .lnk-row__url > span { color: var(--gold); font-size: 0.95rem;
  overflow-wrap: anywhere; min-width: 0; }
/* Clear of the card above it — they were 12px apart and read as one box
   (his "they kiss"). */
body.studio .lnk-mint { margin-top: 22px; }

/* the fan-side unfollow: "Following" is the disclosure, Unfollow lives under
   it (Maaz declined a mute alternative — plain confirm).

   IT IS ONE CONTROL, AND IT IS THE HOUSE'S GOLD-OUTLINE BUTTON (Maaz
   2026-09-15: "Following cramped with a ⋮ — no kebab next to it, one
   control"). Two things were wrong with the pill it replaces. It was a 0.78rem
   box on 5px of padding — a 27px target in the slot a 44px Follow button had
   just been standing in — and it carried a `▾` of its own, which beside the
   word read as a second control to reach for. The values here are `.btn` +
   `.btn--pass` to the pixel rather than a fourth button recipe: same 11/20
   padding, same `--radius-sm`, same all-small-caps, so the Following state and
   the Follow button it succeeds are one box.

   THE CARET IS INSIDE THE CONTROL (Maaz 2026-09-15, 03:54: "gold-outline
   button WITH a dropdown caret"), which REVERSES that morning's own "no glyph,
   deliberately". The two halves of his earlier note were never one thing: the
   KEBAB standing BESIDE the word was the second control he refused, and a
   caret inside the button is the disclosure saying that it opens. It is
   `chevron-down` — the house's own disclosure mark, on the `gap: 8px` this
   rule was already carrying — and it rotates on open, which is what every
   other `<details>` in this product does. Never a check: that would read as
   the verification badge he ruled out by name. */
.follow-menu > summary {
  width: auto; height: auto; gap: 8px;
  padding: 11px 20px; border-radius: var(--radius-sm);
  font-size: 0.8rem; font-weight: 700; letter-spacing: 0.1em;
  font-variant-caps: all-small-caps; text-transform: none;
  color: var(--gold); border-color: var(--gold);
}
.follow-menu > summary:hover {
  color: var(--gold); border-color: var(--gold);
  background: color-mix(in srgb, var(--gold) 10%, transparent);
}
.follow-menu > summary .lucide { flex: none; transition: transform 150ms var(--ease); }
.follow-menu[open] > summary .lucide { transform: rotate(180deg); }
.follow-menu .menu__panel { min-width: 230px; }

/* ══ THE DOOR (Maaz's login mock, 2026-09-09) ═══════════════════════════════
   A card on a lit canvas. Everything in this block is worn by /login and
   /login/verify only — two steps of ONE journey, which is why the frame lives
   in `door_shell` and not in either view: a code screen on the old 440px
   column would read as a different product one tap in.

   THE GLOWS ARE THE CANVAS, NOT A COMPONENT. The design system bans glow
   SHADOWS — an unjustified halo around a box, which makes a flat interface
   look like it is sweating — and these are neither shadows nor attached to
   anything: two gold limbs of stage light behind the whole page, in CSS
   rather than a stock photograph. They are `position: fixed`, which is
   load-bearing twice over: a fixed box contributes nothing to the document's
   scrollable overflow, so a decorative arc can never be the thing that pushes
   a phone sideways; and `z-index: -1` puts them above the canvas paint and
   behind every static box on the page without touching the masthead's own
   stacking context. `aria-hidden` in the markup: there is nothing here to
   read out. */
.door-glow {
  position: fixed; z-index: -1; pointer-events: none; border-radius: 50%;
  background:
    /* the limb: a soft golden falloff inside a crisp lit rim, the way a lamp
       reads behind a body — the arc is the light, the interior is the object */
    radial-gradient(circle closest-side,
      transparent 0 91%,
      rgb(224 182 105 / 0.04) 95%,
      rgb(224 182 105 / 0.15) 98.2%,
      rgb(224 182 105 / 0.50) 99.4%,
      rgb(224 182 105 / 0.12) 99.85%,
      transparent 100%),
    /* the haze inside it, so the arc reads as lit air and not as a drawn ring */
    radial-gradient(circle closest-side, rgb(224 182 105 / 0.022) 0%, transparent 70%);
}
.door-glow--dawn { left: -64vmax; bottom: -48vmax; width: 104vmax; height: 104vmax; }
.door-glow--dusk { right: -60vmax; top: -46vmax; width: 100vmax; height: 100vmax; opacity: 0.85; }
@media (prefers-reduced-transparency: reduce) { .door-glow { display: none; } }
/* THE CANVAS TAKES NO STAGGER SLOT. `main.shell > *` fades the shell's children
   in at 90ms steps, so two decorative divs standing in front of the card pushed
   the one thing on the page to third place — and, for the 180ms the card spent
   translucent, let the arcs read straight through it (measured at 390px). The
   glows are exempted and the card is put back at the head of the queue; the
   selector carries `main.shell` because the stagger's own `:nth-child` rule
   outspecifies a bare `.door-stage >`. */
main.shell > .door-glow { animation: none; }
main.shell.door-stage > .door-card, main.shell.door-stage > .stepline { animation-delay: 0s; }

/* The column the card sits in. `.shell`'s own 1080px is the product's width;
   a door is one question and reads at a fraction of it. */
.door-stage { max-width: 620px; padding-top: clamp(24px, 7vh, 76px); padding-bottom: 96px; }
.door-card {
  background: linear-gradient(180deg, #100e0c 0%, #090807 100%);
  border: 1px solid var(--hair-35); border-radius: 14px;
  padding: clamp(34px, 6vw, 60px) clamp(20px, 5vw, 52px) clamp(28px, 5vw, 46px);
}
/* THE HEADING IS THE HOUSE DISPLAY FACE — Big Shoulders, the condensed all-caps
   the mock draws. No new family is introduced for one screen. */
.door-head { text-align: center; margin-bottom: 28px; }
.door-head h1 { font-size: clamp(2.6rem, 8vw, 3.7rem); }
.door-head__said { margin-top: 10px; color: var(--muted); font-size: 0.95rem; }
/* ONE LABEL SHOWS AT A TIME, and the base state is the one with JavaScript:
   the <noscript> block on the door flips this pair the other way. Written here
   rather than only in <noscript> because a rule that exists in only one of the
   two states draws BOTH labels in the other ("EMAIL PHONE OR EMAIL"). */
.door-label__nojs { display: none; }
.door-form { margin-top: 4px; }
/* Right-aligned under the field, like the mock: the way to the OTHER shape of
   the same box, never a second door. */
.door-swap { display: flex; justify-content: flex-end; margin: -6px 0 16px; }
.door-swap button { background: none; border: 0; padding: 0; cursor: pointer; font: inherit; }
.door-swap button[hidden] { display: none; }
/* A12.2.3, under the phone field alone. */
.door-consent { margin: -6px 0 16px; }
.door-consent[hidden] { display: none; }
/* THE CREDENTIAL-FREE BROWSE PATH (Apple 5.1.1 v). Gold because at the door
   the gold IS the accent (tokens.css) — this is not the money gold the house
   reserves for a Pass, which is why it reads --accent and never --gold. */
.door-live {
  margin-top: 12px; gap: 10px; color: var(--accent); border-color: var(--hair-gold);
  font-variant-caps: normal; text-transform: none; letter-spacing: 0.01em;
  font-size: 0.95rem; font-weight: 600;
}
.door-live:hover { color: var(--accent); border-color: var(--accent); }
.door-legal { margin-top: 22px; text-align: center; }
/* The two links read one step brighter than the sentence around them, the way
   the mock draws them — bright enough to be findable, never gold (gold on this
   screen is the one tap, §5.3). */
.door-legal .link { color: var(--muted); }

/* ── The door's provider leg (Sign in with Apple / Google) ──────────────────
   Bordered, never filled: the gold "Send me a code" is this screen's one
   primary (A2), and these are the second way to prove the same identity, not
   a competing offer. The Apple mark inherits currentColor; Google's four-colour
   G is fixed by Google's brand rules and is the one thing here that does not
   follow the mood. */
.door-or {
  display: flex; align-items: center; gap: 12px;
  margin: 20px 0 18px; color: var(--dim); font-size: 0.72rem;
  letter-spacing: 0.14em; text-transform: uppercase;
}
.door-or::before, .door-or::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--border);
}
.door-providers { display: grid; gap: 10px; }
.door-providers__form { margin: 0; }
/* font-variant-caps: normal — .btn draws all-small-caps, and "Continue with
   Apple" is a proper noun on somebody else's trademark. */
.door-providers__btn {
  gap: 10px; font-variant-caps: normal; text-transform: none;
  letter-spacing: 0.01em; font-size: 0.95rem; font-weight: 600;
  padding: 14px 20px; background: var(--input-bg); border-color: var(--hair-35);
}
.door-providers__btn:hover { border-color: var(--border-strong); background: var(--surface-2); }

/* ---------- THE GO LIVE PAGE (prototype r4-ref-4..7) -----------------------
   Four questions in one card, the show beside them. Everything here is
   fenced: .golive-*, .form-card, .gl-*, .summary*, .cover*, .notify-grid,
   .pill*, .segmented, .choice*, .adv* and .dest (the circle) are this screen's
   own furniture and no other screen wears them.

   .page-title / .page-head__id are shared with the destinations board and
   fenced there; .switch and .toggle-row are shared with the board's Default
   column, so they live in ONE place — here — and the board reads them. */
/* NO PADDING AT THE TOP: `gl-bar` is the page's top chrome now (there is no
   masthead above it), so the bar's own padding is the air over the page and a
   second helping here would leave it floating off the edge.
   AND A GUTTER AT THE SIDES, which this page has never had: 1120 + 20 either
   side is the SAME 1120 content column at the desk (measured, byte for byte)
   and the house's own 20px gutter everywhere narrower than it. The bar is what
   forced it — its arrow is a 40x40 box pulled 10px left to sit the glyph on
   the column, and its one tap is hard right, so with the shell flush to the
   viewport a phone got a clipped tap target and a gold primary bleeding off
   the edge. The card and the summary were flush too; they are not now. */
body.studio .golive-shell {
  max-width: 1160px; margin: 0 auto; padding: 0 20px 120px;
  /* THE BAR'S HEIGHT, ONCE. The bar is sticky at the top and the summary
     column is sticky under it, so one of them has to know the other's height
     — and two constants is how they start overlapping. MEASURED: 12px of
     padding either side of the 48px primary, plus the hairline. It is also the
     bar's own `min-height`, so the two cannot disagree. */
  --gl-bar-h: 73px;
}

/* ---------- THE COMPOSER'S OWN CHROME (Maaz 2026-09-13) --------------------
   The masthead came off this page (MASTHEADLESS_SURFACES) and this row is what
   replaces it: the way back, the page's name, and the one tap. It is the
   control room's `.cr-top` one screen over.

   It lives INSIDE the form, because the two gold controls in it are submits
   and a submit outside its form submits nothing — which is why this is a
   sticky row rather than a header above the `<form>`.

   `--bg` opaque and a hairline under it, for the masthead's own r9 reason: no
   `backdrop-filter`, because a compositing layer on the chrome rasterises on
   its own origin and snaps every hairline inside it against the layer instead
   of the page. min-height is an INTEGER (r8): a row of stroked controls on a
   half pixel is the hairline law.

   `.golive-head*` and `.golive-back` are DELETED with the markup that wore
   them — a class named after retired markup is how the next surface inherits a
   box by reaching for a familiar name (the `.gl-side` P0). */
body.studio .gl-bar {
  /* z-index 40 is the MASTHEAD's own — this is the chrome that replaced it, so
     it sits at the same height rather than at a number of its own. */
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; row-gap: 8px;
  min-height: var(--gl-bar-h); padding: 12px 0;
  /* viewport-fit=cover lets content run under the status bar / Dynamic Island
     in the iOS shell, and this bar is the top of the document now — the
     masthead used to reserve that inset, so the bar has to. env() is 0
     everywhere the inset does not exist. */
  padding-top: calc(12px + env(safe-area-inset-top));
  background: var(--bg); border-bottom: 1px solid var(--border);
}
/* NO MARGIN OF ITS OWN: `.golive-grid`'s existing 30px top margin is the air
   under this bar (adjacent siblings, so one of a pair would collapse into the
   other anyway) — and 30px is what the head it replaced left there. */
body.studio .gl-bar__who { display: flex; flex-direction: row; align-items: center; gap: 4px; min-width: 0; }
/* The page's name at the control room's own title weight, not the display
   `.page-title` — a 51px marquee heading in a 73px bar is not a bar. */
body.studio .gl-bar__title {
  margin: 0; font-size: 1.3rem; font-weight: 600; min-width: 0;
  --lh: 1.2; line-height: var(--lh-snap);
}
/* THE ACTS CLUSTER REFUSES TO WRAP INTERNALLY (live-flow r3 item 1's lesson,
   kept): the said line and the one tap are one row, so no future sentence can
   drop the primary onto a line of its own. The line takes the slack and
   ellipsises; the button takes only what it needs. The BAR itself still wraps
   (`flex-wrap` above), which is what puts the cluster on its own row on a
   phone instead of pushing the document sideways. */
body.studio .gl-bar__acts {
  display: flex; flex-direction: row; align-items: center; justify-content: flex-end;
  gap: 14px; flex-wrap: nowrap; min-width: 0;
}
/* The masthead's own beside-the-pill voice (`.topbar__up-line`, r14-ref-2):
   plain sentence type, muted, never `.meta`'s mono stamp — a tracked-out
   uppercase line shouting beside a gold control reads as a second primary. */
body.studio .gl-bar__said {
  margin: 0; min-width: 0; color: var(--muted);
  font-size: 0.78rem; letter-spacing: 0.06em; --lh: 1.25; line-height: var(--lh-snap);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Two submits, one visible. `[hidden]` on the button is the house's own base
   rule (`.btn[hidden]`, r14 item 5) and needs no second copy here; what does
   need saying is that the wrapper carrying the disabled tooltip goes with it,
   or an empty 0-width box keeps the gap beside the visible one. */
body.studio .cta-gate:has(.btn[hidden]) { display: none; }
/* THE PRIMARY'S THREE STATES (product owner, r12 item 1). `.btn--gilt` was a
   third gold recipe — 55% of the house gold washed over the surface — which
   read as DISABLED at rest and lit to full gold on hover: the two states
   inverted. The composer's one tap wears `.btn--gold` now, the same fill the
   masthead's GO LIVE pill and the live's own Save already wear, and this is
   only the size the composer's head wants.
     rest     — filled gold (.btn--gold)
     hover    — --gold-bright (.btn--gold:hover)
     disabled — dim, and the cursor says so. `.btn[disabled]` sets
                `pointer-events: none`, so the wrapper is what gets :hover and
                what draws the tooltip; the cursor has to be the wrapper's for
                the same reason. */
body.studio .gl-bar__acts .btn { font-weight: 700; padding: 12px 24px; font-size: 0.9rem; flex: none; }
body.studio .cta-gate { display: inline-flex; }
body.studio .cta-gate:has(.btn[disabled]) { cursor: not-allowed; }

body.studio .golive-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) 328px;
  gap: 30px; margin-top: 30px; align-items: start;
}
body.studio .form-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px; min-width: 0;
}
body.studio .form-card > section { padding: 24px 22px; border-top: 1px solid var(--border); }
body.studio .form-card > section:first-child { border-top: 0; }
body.studio .form-card .eyebrow { margin-bottom: 14px; }

/* THE SHOW */
body.studio .form-card > section.gl-about { padding-top: 26px; }
body.studio .title-input {
  width: 100%; background: none; border: 0; outline: 0; padding: 0; border-radius: 0;
  color: var(--text); font-family: var(--sans); font-weight: 400; text-transform: none;
  font-size: clamp(1.5rem, 2.8vw, 2rem); letter-spacing: -0.01em;
}
body.studio .title-input::placeholder { color: var(--muted); }
body.studio .textarea {
  width: 100%; margin-top: 16px; min-height: 84px; resize: vertical; padding: 14px 16px;
  background: var(--input-bg); border: 1px solid var(--border); border-radius: var(--radius);
  color: var(--text); font: inherit; line-height: var(--lh-snap); font-size: 0.92rem; outline: 0;
}
body.studio .textarea:focus { border-color: var(--border-strong); }
/* ONE LINE AT REST (Maaz 2026-09-13). `.textarea`'s `min-height: 80px` held an
   80px box around one 23px line, so its even 14px of padding read as 14 above
   the text and ~55 below — the field looked like a paragraph box nobody had
   filled in. `rows="1"` sets the intrinsic height and this drops the floor, so
   the box is exactly one line with the padding even on both sides. `resize:
   vertical` is untouched: the drag corner still grows it.

   `.textarea.gl-desc` and not `.gl-desc`, on purpose: `body.studio .textarea`
   is declared TWICE in this sheet (here, and again in the night's own Details
   block ~900 lines down), so an equal-specificity rule would depend on which
   side of the second one it landed. This one wins wherever it sits. */
body.studio .textarea.gl-desc { min-height: 0; }
body.studio .field-label { margin: 24px 0 12px; font-size: 0.92rem; color: var(--muted); }

/* The front stage, as marks. A label wrapping a visually-hidden checkbox, so
   the state is the input's and :has does the lighting — no JS owns it.

   ONE definition, worn by both hosts (this page and the night's own Details
   panel). It used to be two near-identical blocks 380 lines apart, which is
   how the two started disagreeing about which element carries a stroke. */
body.studio .dest-row { display: flex; gap: 10px; flex-wrap: wrap; }
body.studio .dest {
  width: 44px; height: 44px; flex: none; border-radius: 50%;
  border: var(--hair-w) solid var(--border); background: transparent; color: var(--dim);
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  transition: border-color 150ms ease, color 150ms ease, background-color 150ms ease;
}
body.studio .dest:hover { color: var(--text); border-color: var(--border-strong); }
/* PICKED IS GOLD (product owner 2026-09-03, item 5) — the same gold ring and
   gold-tinted mark the house's own always-on circle wears, because "this feed
   is armed tonight" is the same claim. It used to light `--text`, and the mono
   marks were drawn in black (see brand_svg), so a picked YouTube read as no
   change at all. Gold is money and the Pass by the token law; a destination is
   the front stage, which is what the $27 buys. */
body.studio .dest:has(input:checked) {
  color: var(--gold); border-color: var(--hair-gold);
  background: color-mix(in srgb, var(--gold) 8%, transparent);
}
body.studio .dest:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The house's own mark is gold and never a toggle — the room is always on. */
body.studio .dest--brand, body.studio .dest--brand:has(input:checked) { color: var(--gold); border-color: var(--hair-gold); }
/* THE RECORD (product owner, r13 item 3): a live that already played draws
   only the feeds it actually used, and it draws them as inert circles — so
   there is no checkbox for the picked rule's `:has(input:checked)` to match.
   Same gold, because "this feed carried the show" is the same claim as "this
   feed is armed tonight"; `--fixed`'s cursor, because there is nothing to
   press. The :hover re-declaration is load-bearing: `body.studio .dest:hover`
   is the SAME specificity and would win on source order otherwise. */
body.studio .dest--on {
  color: var(--gold); border-color: var(--hair-gold);
  background: color-mix(in srgb, var(--gold) 8%, transparent);
  cursor: default;
}
body.studio .dest--on:hover { color: var(--gold); border-color: var(--hair-gold); }
body.studio .dest--fixed { cursor: default; }
body.studio .dest--add { border-style: dashed; }
/* IMMEDIATE (product owner, r12 item 7). These were `title` attributes — the
   browser's own tooltip, which waits about a second — and the house tip they
   became still faded in over 120ms. On a row of six circles a creator is
   pointing at to find out what they are, any delay at all is the bug. */
body.studio .dest-row [data-tip]::after { transition: none; z-index: 30; }
/* AND IT IS ANCHORED TO THE ROW, ABOVE IT, NOT TO ITS OWN CIRCLE (Maaz
   2026-09-16: "never overlapping the icons behind"). The base rule hangs the
   tip 8px above its TRIGGER, which is right for a number in the middle of a
   row and wrong here for one reason CSS cannot be told about: `.dest-row` is
   `flex-wrap: wrap`, so below about 430px of panel the eight circles break
   onto two lines — and a tip 8px above a circle on line TWO lands squarely on
   line one. MEASURED at a true 390 (5 circles, then 3): LinkedIn's tip
   intersected FIVE of them, with the markup and every request test perfectly
   happy. A bigger `bottom` cannot fix it either, because the clearance is
   `circle_top - row_top` and CSS cannot know where a circle landed after the
   row wrapped — the same dead end the "+"'s own rule below records.
     So the containing block moves, exactly as it does for the "+": the ROW is
   already `relative`, these circles go `static`, and the tip's `left` and
   `bottom` resolve against the row — above LINE ONE, clear of every circle on
   every line, at any width. It anchors LEFT rather than centred because a
   `left: 50%` box on a row-wide containing block would park every platform's
   tip in the middle of the row, which is further from the mark it names than
   the row's own edge is; left is also where Maaz's screenshot shows it.
     Both transform states are re-declared (the house rule for an override):
   the base rule's pair carries a `translateX(-50%)` that goes with `left: 50%`
   and would otherwise drag this box half its own width off the row.
     `box-sizing` because `max-width: 100%` must count the 6/10 padding — a
   `::after` does NOT inherit the `*` reset beside it — and `width: max-content`
   so a short label stays on one line and a long one wraps inside the row
   rather than running out of the card. */
body.studio .dest-row .dest[data-tip] { position: static; }
body.studio .dest-row .dest[data-tip]::after {
  box-sizing: border-box; white-space: normal; width: max-content; max-width: 100%;
  text-align: left; left: 0; right: auto; bottom: calc(100% + 8px); top: auto;
  transform: translateY(2px);
}
body.studio .dest-row .dest[data-tip]:hover::after,
body.studio .dest-row .dest[data-tip]:focus-visible::after { transform: translateY(0); }
/* THE "+"'s TIP IS A SENTENCE, AND IT IS ANCHORED TO THE ROW, NOT TO THE
   CIRCLE. The base rule is `nowrap` and centred on its own control, which for
   "Add social platforms to go live on them at the same time." lays a ~300px
   box out from a 40px circle — and the "+" is the LAST circle in the row, so
   at 390px that pushed the document 86px (measured on CI, the r9 overflow
   class exactly: an absolutely-positioned tip counts toward scrollWidth even
   at opacity 0).
     Clamping the width could not fix it, because the overflow is
   `circle_left + width - card_right` and CSS cannot know where the circle
   landed after the row wrapped. So the containing block moves instead: the ROW
   is `relative` and this one circle is `static`, which makes the tip's `left`
   and `max-width` resolve against the row — inside the card by construction,
   at any width, wherever the "+" sits. It reads better too: a sentence belongs
   under the row it is about, not dangling off a 40px target. */
body.studio .dest-row { position: relative; }
body.studio .dest-row .dest--add[data-tip] { position: static; }
body.studio .dest-row .dest--add[data-tip]::after {
  white-space: normal; width: 240px; max-width: 100%; text-align: left;
  left: 0; right: auto; top: calc(100% + 8px); bottom: auto;
  transform: translateY(2px);
}
body.studio .dest-row .dest--add[data-tip]:hover::after,
body.studio .dest-row .dest--add[data-tip]:focus-visible::after { transform: translateY(0); }
/* A platform with no account is a way to Destinations, not a toggle.

   THE DIM IS A COLOUR, NEVER AN `opacity` ON THE CIRCLE (Maaz 2026-09-16, his
   screenshot). `opacity: 0.55` here was the whole of the unreadable tooltip,
   twice over: an element's opacity composites the element AND its
   `::after`, so the tip's own `opacity: 1` on hover was multiplied down to
   0.55 and the row's glyphs read straight through it — and an opacity below 1
   makes a STACKING CONTEXT, which trapped the tip's `z-index` inside this one
   circle so every later circle in the row painted over it. No `z-index` and no
   fill on the pseudo can escape that; the dim has to stop being an opacity.
     `color-mix` reproduces exactly what 0.55 did to the mark and the stroke —
   the mark is `currentColor` in both helpers (a lucide glyph and a brand path)
   — and it does it in each of the three moods without a hex of its own.
     The hover is re-declared because `body.studio .dest:hover` is the SAME
   specificity (0,2,1) and ships EARLIER, so without it that rule's `--text`
   and `--border-strong` would win and an unconnected circle would light like a
   connected one. It brightens to `--dim`, the resting ink of a circle that IS
   a toggle — a step up, never all the way. */
body.studio .dest--off {
  color: color-mix(in srgb, var(--dim) 55%, transparent);
  border-color: color-mix(in srgb, var(--border) 55%, transparent);
}
body.studio .dest--off:hover {
  color: var(--dim); border-color: var(--border);
}
body.studio .dest[hidden] { display: none; }
/* The mark inside, whichever helper drew it (a lucide glyph or a brand path). */
body.studio .dest .lucide, body.studio .dest .brand { width: 18px; height: 18px; }
/* The input is clipped by .sr-only in the markup; this is the belt for a host
   that hands us a bare checkbox. */
body.studio .dest input:not(.sr-only) { position: absolute; opacity: 0; width: 0; height: 0; }
body.studio .gl-dest-note { margin-top: 12px; color: var(--muted); }
/* `.gl-key*` LEFT THIS STYLESHEET WITH ITS MARKUP (Maaz 2026-09-15, r20).
   The composer's "TODAY'S KEY" pair was its only host, and the paste moved to
   the control room's Destinations tray, which dresses its own fields
   (`.cr-feedrow__key`). A class named after retired markup is how the next
   surface inherits a box by reaching for a familiar name — the `.gl-side` P0. */

/* A segmented control over radios: one question, N answers, no JS state.

   ONE STROKE, and the group carries it (product owner 2026-09-03, item 6). The
   picked answer used to add `inset 0 0 0 1px var(--border-strong)` on top of
   the group's own border — a bordered box 3px inside a bordered box — so the
   control read as two nested frames. The picked answer is a FILL now: raised
   out of the recessed group, which is the same thing a thumb says with one
   line instead of two.

   It wraps rather than scrolls at any width: three answers do not fit a phone
   in one row, and a hidden answer is an answer nobody picks. */
body.studio .segmented {
  display: inline-flex; flex-wrap: wrap; gap: 2px; padding: 3px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); background: var(--input-bg);
}
body.studio .segmented > label { margin: 0; }
body.studio .segmented > label > span {
  display: block; padding: 6px 14px; border-radius: 4px; cursor: pointer;
  color: var(--muted); font-size: 0.82rem; font-weight: 600; white-space: nowrap;
  transition: color 150ms ease, background-color 150ms ease;
}
body.studio .segmented > label:hover > span { color: var(--text); }
body.studio .segmented > label:has(input:checked) > span {
  background: var(--surface-3); color: var(--text);
}
body.studio .segmented > label:has(input:focus-visible) > span { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The belt for a host that hands us a bare radio (the markup uses .sr-only). */
body.studio .segmented input:not(.sr-only) { position: absolute; opacity: 0; width: 0; height: 0; }

/* WHEN */
body.studio .gl-when, body.studio .gl-who {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
}
body.studio .gl-when .eyebrow, body.studio .gl-who .eyebrow { margin: 0; }
/* WHEN A LIVE STARTS — the row, and then the two controls in it.
   ONE declaration (r10 item 7). `.gl-sched` was declared TWICE, 380 lines
   apart — once here for the composer and once in the live-panel block — which
   is the exact hazard the r8 round wrote down twice (`.pill`, `.segmented`,
   `.dest`). Both applied, so the row's font-size depended on which rule came
   last. The two hosts render ONE partial now, so there is one rule set. */
body.studio .gl-sched {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; margin-top: 20px;
}
@media (max-width: 640px) { body.studio .gl-sched { grid-template-columns: minmax(0, 1fr); } }

/* THE DATE. The native input is a value carrier once `date-picker` connects
   (it is switched to type=hidden), and it is the whole control until then —
   so it keeps a dressed look for the no-JavaScript path, `color-scheme: dark`
   included, and the face below matches it pixel for pixel. */
body.studio .dpick { position: relative; min-width: 0; }
body.studio .dpick__native, body.studio .tpick__sel {
  width: 100%; padding: 12px 14px; font-size: 1rem; border-radius: var(--radius);
  border: var(--hair-w) solid var(--border-strong); background: var(--input-bg);
  color: var(--text); font-family: var(--sans); color-scheme: dark;
}
body.studio .dpick__face {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; min-height: 48px; padding: 12px 14px; font-size: 1rem; text-align: left;
  border-radius: var(--radius); border: var(--hair-w) solid var(--border-strong);
  background: var(--input-bg); color: var(--text); font-family: var(--sans);
  text-transform: none; letter-spacing: 0; cursor: pointer;
}
body.studio .dpick__face[hidden] { display: none; }
body.studio .dpick__face:hover { border-color: var(--hair-60); }
body.studio .dpick__face .lucide { flex: none; color: var(--muted); }
body.studio .dpick__face:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* THE RESTING WORD, shown only while nothing is picked (r7). `faceText` is
   written by JavaScript and is EMPTY exactly when the input carries no date,
   so `:empty` is the whole condition — which is what keeps the word itself in
   the ERB, where every other word in this product lives. The partial renders
   that span with NOTHING inside its tags for the same reason: one newline in
   there and it is never `:empty` again. */
body.studio .dpick__said { min-width: 0; }
body.studio .dpick__rest { color: var(--dim); }
body.studio .dpick__said [data-date-picker-target="faceText"]:not(:empty) ~ .dpick__rest { display: none; }

/* THE POPOVER. The house's card, not the browser's: near-black over the
   canvas, one hairline, the tungsten ring on today and GOLD on the answer. */
body.studio .dpick__pop {
  position: absolute; z-index: 30; top: calc(100% + 8px); left: 0; width: 268px;
  padding: 12px; border-radius: var(--radius);
  background: var(--surface-2); border: var(--hair-w) solid var(--border-strong);
  box-shadow: 0 18px 44px rgb(0 0 0 / 0.55);
}
body.studio .dpick__pop[hidden] { display: none; }
/* A PICKER INSIDE A SCROLLING SHEET PUTS ITS CALENDAR IN FLOW (r7). The
   Followers filter sheet is `max-height` + `overflow-y: auto`, and its date
   pair is the LAST thing in it — so a popover hanging 8px below the face is
   drawn straight into the clipped zone: an overflow box clips its content
   whether or not it scrolls, and no z-index escapes an ancestor's clip. That
   sheet's own pass picker answers this by being in flow, and so does this;
   it is the one version a scroll position or a short viewport cannot break,
   and it needs no measurement and no JavaScript.

   Keyed to the VARIANT, never to `.fol-sheet .dpick__pop`, so the decision
   travels with the markup that asked for it (the data-hscroll rule) and the
   composer's own calendar — whose host does not clip — still floats. */
body.studio .dpick--inflow .dpick__pop {
  position: static; width: 100%; margin-top: 8px; box-shadow: none;
}
body.studio .dpick__nav { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
body.studio .dpick__month { font-size: 0.92rem; font-weight: 500; letter-spacing: 0; }
body.studio .dpick__arrow {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0; background: none; border: 0;
  border-radius: var(--radius-sm); color: var(--muted); cursor: pointer;
}
body.studio .dpick__arrow:hover { color: var(--text); background: var(--surface-3); }
body.studio .dpick__arrow:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
body.studio .dpick__dow, body.studio .dpick__grid {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px;
}
body.studio .dpick__dow { margin: 10px 0 4px; }
body.studio .dpick__dow span {
  text-align: center; font-size: 0.68rem; color: var(--dim);
  text-transform: uppercase; letter-spacing: 0.06em;
}
/* An INTEGER box on a rounded stroke (the r8/r9 whole-pixel rule): today's
   ring is a real border, so its box must be the box it snaps to. */
body.studio .dpick__day {
  display: flex; align-items: center; justify-content: center;
  height: 32px; padding: 0; background: none; border: var(--hair-w) solid transparent;
  border-radius: var(--radius-sm); color: var(--text); cursor: pointer;
  font-family: var(--mono); font-size: 0.82rem; font-variant-numeric: tabular-nums lining-nums;
  text-transform: none; letter-spacing: 0;
}
body.studio .dpick__day:hover { background: var(--surface-3); }
body.studio .dpick__day--pad { cursor: default; }
body.studio .dpick__day--pad:hover { background: none; }
/* A DAY BEFORE THE FLOOR (product owner, r12 item 4). Dim and unclickable —
   still drawn, because a calendar with holes in it reads as a calendar that
   broke, and the shape of the month is part of reading it. */
body.studio .dpick__day.is-past { color: var(--dim); cursor: not-allowed; }
body.studio .dpick__day.is-past:hover { background: none; }
body.studio .dpick__day.is-today { border-color: var(--border-strong); }
/* GOLD IS THE ANSWER — the house's own selected state, the same fill the
   picked destination and the picked segment wear. */
body.studio .dpick__day.is-on {
  background: var(--gold); border-color: transparent; color: var(--gold-ink); font-weight: 600;
}
body.studio .dpick__day:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The pop's own foot, drawn only where a date can be unset (r7). */
body.studio .dpick__foot { margin-top: 10px; padding-top: 8px;
  border-top: var(--hair-w) solid var(--border); }

/* THE TIME. A real <select>, undressed of the platform's arrow and given
   ours — the last un-themed pixel on the row. */
body.studio .tpick { position: relative; min-width: 0; }
body.studio .tpick__sel { appearance: none; padding-right: 38px; cursor: pointer; }
body.studio .tpick__sel:hover { border-color: var(--hair-60); }
body.studio .tpick__sel:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .tpick__caret {
  position: absolute; top: 50%; right: 13px; transform: translateY(-50%);
  display: inline-flex; color: var(--muted); pointer-events: none;
}
body.studio .gl-tz { margin-top: 8px; color: var(--dim); font-size: 0.78rem; }
body.studio .gl-dur { margin-top: 22px; }
body.studio .dur-head { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin-bottom: 12px; }
body.studio .dur-head .eyebrow { margin: 0; }
body.studio .dur-head .small { color: var(--muted); }
/* The length chips. ONE STROKE, and here it is the ITEM that carries it — a
   pill row is N independent answers with no frame around them, the opposite of
   the segmented control above (product owner 2026-09-03, item 6). The label is
   a bare wrapper: it used to carry a border of its own from a second,
   duplicated block, so every chip drew a 999px stroke inside a 999px stroke. */
body.studio .pill-row { display: flex; gap: 8px; flex-wrap: wrap; }
body.studio .pill { margin: 0; border: 0; padding: 0; background: none; }
body.studio .pill > span {
  display: block; padding: 9px 18px; border-radius: 999px; cursor: pointer;
  border: var(--hair-w) solid var(--border); color: var(--muted); font-size: 0.92rem;
  transition: color 150ms ease, background-color 150ms ease, border-color 150ms ease;
}
body.studio .pill:hover > span { color: var(--text); }
body.studio .pill:has(input:checked) > span { color: var(--text); background: var(--surface-3); border-color: var(--border-strong); }
body.studio .pill:has(input:focus-visible) > span { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .pill input:not(.sr-only) { position: absolute; opacity: 0; width: 0; height: 0; }

/* THE CUSTOM LENGTH (2026-09-03): the fifth chip's two numbers, under the row.
   Worn by BOTH hosts that ask how long a night runs — this page and the night's
   Details panel — from one partial, so this is one rule set and not two.
   The [hidden] line is load-bearing: a display:flex box outranks the UA's
   [hidden] rule, and without it the pair is on screen for every chip (the
   identity-field lesson, again). */
body.studio .dur-custom { display: flex; gap: 12px; flex-wrap: wrap; align-items: flex-end; margin-top: 14px; }
body.studio .dur-custom[hidden] { display: none; }
body.studio .dur-custom__field { display: flex; flex-direction: column; gap: 6px; margin: 0; }
body.studio .dur-custom__field > span {
  color: var(--dim); font-size: 0.72rem; letter-spacing: 0.06em; text-transform: uppercase;
}
body.studio .dur-custom__field input {
  width: 104px; padding: 10px 12px; background: var(--input-bg); border: 1px solid var(--border-strong);
  border-radius: var(--radius); color: var(--text); font: inherit; line-height: var(--lh-snap); font-size: 0.98rem;
}
body.studio .dur-custom__field input:disabled { color: var(--muted); }
body.studio .dur-custom__note { flex: 1 1 100%; margin: 0; color: var(--dim); font-size: 0.78rem; }

/* WHO GETS IN */
body.studio .gl-passes-panel { margin-top: 18px; }
body.studio .gl-passes { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
/* THE PASSES AS ONE MULTI-SELECT, past PASS_PILL_CAP (Maaz 2026-09-14). Its own
   wrapper and NOT `.gl-passes`, which is a wrapping row of pills — a <details>
   dropped in there becomes a flex item and the panel it opens is sized by the
   row. The width is the Pass form's own field measure (`pf-field`, 310px): the
   form column is 1120 wide, and a picker that spans it does not read as a
   control. No `display` here, so `[hidden]` still hides it (the r14 trap). */
body.studio .gl-passes-pick { max-width: 310px; margin-top: 14px; }
body.studio .pass-pill { margin: 0; }
body.studio .pass-pill > span, body.studio .pass-pill > i {
  display: inline-block; vertical-align: middle;
}
body.studio .pass-pill {
  display: inline-flex; align-items: center; gap: 10px; padding: 10px 20px;
  border-radius: 999px; border: var(--hair-w) solid var(--border); background: transparent;
  color: var(--text); font-size: 0.98rem; cursor: pointer;
  transition: border-color 150ms ease, background-color 150ms ease;
}
body.studio .pass-pill i { font-style: normal; color: var(--muted); font-size: 0.9rem; }
body.studio .pass-pill:hover { border-color: var(--border-strong); }
/* GOLD here is the token law, not decoration: a Pass is the money and the
   house's own membership, and this pill is the only place on the page that
   names one. */
body.studio .pass-pill:has(input:checked) { border-color: var(--gold); background: color-mix(in srgb, var(--gold) 9%, transparent); }
body.studio .pass-pill:has(input:checked) i { color: var(--gold); }
body.studio .pass-pill:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ADVANCED SETTINGS, the row */
body.studio .gl-adv {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  width: 100%; background: none; border: 0; padding: 0; text-align: left;
  cursor: pointer; color: var(--text); font: inherit; line-height: var(--lh-snap);
}
body.studio .gl-adv .eyebrow { margin-bottom: 6px; }
body.studio .gl-adv span.sum { display: block; color: var(--muted); font-size: 0.98rem; }
body.studio .gl-adv .gear {
  flex: none; width: 40px; height: 40px; border-radius: 50%;
  border: 1px solid var(--border-strong); display: grid; place-items: center;
  color: var(--muted); transition: color 150ms ease, border-color 150ms ease;
}
body.studio .gl-more > summary::-webkit-details-marker { display: none; }
body.studio .gl-more > summary::after { content: "\25BE"; font-size: 0.7em; opacity: 0.7; }
body.studio .gl-more[open] > summary::after { content: "\25B4"; }
body.studio .gl-more > summary:hover { color: var(--text); border-color: var(--text); }
body.studio .gl-more > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .gl-more .gl-picks { margin-top: 10px; }

/* ---------- THE BOARD, THE PROTOTYPE'S OWN (bsp-proto/studio/lives.html) ----
   The approved prototype is the spec for this screen, so these rules are its
   rules, variable for variable — the mock was written against this sheet's own
   tokens, which is why almost nothing here is new vocabulary.

   Two things it changes beyond the board:

   * .tab is a PILL now, not an underline. The prototype sets every studio tab
     rail that way (lives, analytics, channels, clips, encores all wear .tab),
     so the Library's and Analytics' rails move with it — same class, same
     markup, same .tab--on state, new shape.
   * [data-tip] is the prototype's tooltip, and it is the reason a plain number
     can stand alone in a row: "312" says nothing, and "312 RSVPs · 118 in the
     room now" on hover says everything without a second line of chrome. */
body.studio .page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
/* r3: the id column STACKS — crumb over H1 over deck (the mock's header
   idiom), never crumb beside H1. Rendered by studio/_page_header on every
   list page; the page's one action sits to the right.

   IT IS A BLOCK BOX AND IT STAYS ONE (tried and reverted, 2026-09-11). A
   flex column reads like the stronger way to say "these three rows stack",
   and it is the weaker one: block layout already stacks them, already cannot
   be shoved sideways, and already ignores a `gap` somebody adds here later —
   while a COLUMN stretches its items, which is how `.page-head__back` (an
   inline-flex link, the railless screens' way out) became a 321px-wide link
   across a 321px column, hit area and all. That is the exact stretch its own
   rule at the foot of this sheet was written to refuse, arriving through the
   parent instead of through a `display` on the link. */
body.studio .page-head__id { min-width: 0; }
/* THE RHYTHM IS THREE MARGINS AND THEY LIVE HERE, not inline on the markup
   (Maaz 2026-09-11): the title's 8px was a `style=` attribute on the H1, i.e.
   the highest specificity in the document, so the one number a reader would
   reach for in this block to pull the lede up could not be reached from this
   block at all. Crumb → title 8, title → lede 6, lede → figures 22
   (`.stat-row` below). The middle number is the smallest BY DESIGN: a lede is
   the H1's own second line and must read as attached to it, while the figures
   under it are the next thing on the page.

   `:not(:first-child)` because that 8px is the gap under whatever sits ABOVE
   the title — the crumb, or a railless screen's way back — and the composer
   draws a `.page-head__id` holding the H1 alone. It never had this margin (it
   was a `style=` on the shared partial's H1 and nowhere else), and 8px there
   would drop "Go live" off the row its own one tap starts on. */
body.studio .page-head__id > .page-title:not(:first-child) { margin-top: 8px; }
body.studio .page-head__deck { margin-top: 6px; max-width: 62ch; }
/* 24px is MEASURED, not eyeballed: it puts the action's box top on the H1's
   box top (both 131 at the desk) and its own middle on the title's optical
   middle. It was 26 against the 79px line box the H1 never asked for, where
   the button sat 11px above the title's centre — so the number moved because
   the leading above did, and it is the only compensating change this round
   makes. */
body.studio .page-head__act { flex: none; padding-top: 24px; }
/* AND THE LEADING IS WRITTEN OUT, because `var(--lh-snap)` could not carry it
   (measured 2026-09-11 — this is the cause of Maaz's report, and the margin
   above is only half the answer).

   `--lh-snap` is declared ONCE, on :root, as `round(calc(1em * var(--lh,
   1.55)), 1px)`. A custom property's own value is computed where it is
   DECLARED — its inner var()s are substituted there — so what every element
   in the document inherits is `round(calc(1em * 1.55), 1px)` with the 1.55
   already baked in. `--lh: 1.05` on this rule therefore changed nothing: the
   H1 measured a 79px line box on a 51.2px font (ratio 1.543, not 1.05), which
   is 25px of leading the design never asked for, half of it dead space under
   the title's baseline. That is what made the lede read as a detached second
   block — the INK gap under the title was 35.6px against 39.4px under the
   lede, two gaps the eye cannot tell apart.

   So the snap is spelled in the declaration, where `var(--lh)` does resolve
   against this element: 54px on a 51.2px font, whole-pixel, which is r8's
   rule kept rather than dodged. It is written HERE and not fixed in the token
   on purpose — 86 rules in this sheet carry their own `--lh` and every one of
   them is inert the same way, so repairing `--lh-snap` would re-leading the
   entire app in a copy round. That is its own measured lane. */
body.studio .page-title {
  font-family: var(--sans); font-weight: 700; letter-spacing: -0.02em;
  font-size: clamp(2.2rem, 4.4vw, 3.2rem);
  --lh: 1.05; line-height: round(calc(1em * var(--lh)), 1px);
  text-transform: none;
}

/* the trio: figure over a stamp label, no card and no borders */
body.studio .stat-row { display: flex; gap: clamp(28px, 6vw, 100px); margin-top: 22px; flex-wrap: wrap; }
body.studio .stat-row .stat { padding: 0; border-right: 0; }
body.studio .stat-row .stat b {
  display: block; font-family: var(--sans); font-weight: 500; font-size: 2.1rem;
  --lh: 1.05; line-height: var(--lh-snap); letter-spacing: -0.02em; color: var(--text);
}
body.studio .stat-row .stat .meta { display: block; margin-top: 6px; }
/* THE FIGURE SITS CLOSER TO ITS LABEL (Maaz 2026-09-13, screenshot 3). One
   rule for every studio trio of this shape — Lives, Destinations, Moderation,
   Encores, Clips, Box Office, Money, Transactions, Automations, Plan — which
   are the pages that draw the plain `.stat-row` trio.

   The gap is closed from the LABEL, not by re-leading the number: the figure's
   line box carries ~1.55 of leading (var(--lh-snap) bakes 1.55 at :root, so
   the `.stat b`'s own --lh: 1.05 is inert — the page-title's lesson), which
   leaves dead space UNDER the digits, and the label sits below THAT. Pulling
   the number's own line box tighter would move its ink UP and read as the
   figures detaching from the lede (studio_header_type_test measures exactly
   that), so instead the label rides up into the dead space the number already
   owns — the figure does not move, only the words under it. Scoped off the
   labeled variant and the Pass analytics tab so Analytics is untouched (and
   Followers, Channels, Account and Team draw no trio at all). */
body.studio .stat-row:not(.stat-row--labeled):not(.pass-stats) .stat .meta { margin-top: -6px; }

/* the tab rail, as pills (the prototype's .tab) */
body.studio .tabs { display: flex; gap: 12px; margin-top: 28px; border-bottom: 0; flex-wrap: wrap; }
/* An explicit INTEGER height, not vertical padding around a line box (r8): a
   999px radius means the whole pill is arc, and an arc on a fractional box is
   antialiased rather than snapped — half-strength hairline, top edge unlike the
   bottom edge. The whole-pixel rule already lands the line box on a pixel; this
   keeps the pill integral even if its type changes. */
body.studio .tab {
  display: inline-flex; align-items: center; height: 40px; padding: 0 18px; border-radius: 999px;
  border: var(--hair-w) solid var(--border-strong); background: transparent; color: var(--muted);
  font-size: 0.95rem; font-weight: 500; letter-spacing: 0; text-transform: none; cursor: pointer;
}
body.studio .tab:hover { color: var(--text); }
body.studio .tab--on, body.studio .tab[aria-selected="true"] { color: var(--text); border-color: var(--gold); }
body.studio .tab .count { margin-left: 6px; color: var(--dim); font-size: 0.82rem; }
body.studio .tab-panel { margin-top: 22px; border-top: 1px solid var(--border); }

/* the rows */
body.studio .live-row {
  display: grid; grid-template-columns: 144px minmax(0, 1fr) auto; gap: 20px; align-items: center;
  padding: 18px 0; border-bottom: 1px solid var(--border);
  border-radius: var(--radius); margin: 0 -14px; padding-left: 14px; padding-right: 14px;
  position: relative; transition: background-color 150ms ease;
}
body.studio .live-row--link { cursor: pointer; }
body.studio .live-row--link:hover { background: var(--surface); }
body.studio .live-row--link:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
body.studio .live-row--link .live-row__title { transition: color 150ms ease; }
body.studio .live-row--link:hover .live-row__title { color: var(--accent-soft); }
/* an open kebab is taller than its row */
body.studio .live-row:has(details.menu[open]) { z-index: 40; }

body.studio .live-row__thumb {
  aspect-ratio: 16 / 9; border-radius: var(--radius-sm); overflow: hidden;
  background: var(--surface-3); border: 1px solid var(--border); position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
body.studio .live-row__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; filter: saturate(0.8); }
/* a night that never happened keeps its picture, greyed — the prototype's own
   treatment, and the only place this sheet desaturates a photograph. TWO
   states wear it since 2026-09-15 (called off, and missed); the rule did not
   move, only the predicate that hands out the class. */
body.studio .live-row__thumb--off img { filter: grayscale(1) brightness(0.6); }
body.studio .live-row__thumb .chip { position: absolute; left: 8px; top: 8px; padding: 2px 8px; font-size: 0.66rem; background: rgb(11 10 9 / 0.75); }
/* No cover and no poster is the HOUSE CARD now (product owner 2026-09-03) —
   a real image, so the rules the row needs are the <img> rules above and the
   date tile's two rules came off with the markup that wore them. */

body.studio .live-row__title-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
body.studio .live-row__title { font-size: 1.08rem; font-weight: 500; overflow-wrap: anywhere; }
body.studio .live-row__title a { color: inherit; text-decoration: none; }
body.studio .live-row__when-line { display: flex; align-items: center; gap: 10px; margin-top: 3px; }
body.studio .live-row__when { color: var(--muted); font-size: 0.92rem; }
body.studio .live-row__meta {
  display: flex; align-items: center; gap: 14px; margin-top: 10px;
  color: var(--muted); font-size: 0.88rem; flex-wrap: wrap;
}
body.studio .live-row__meta > span { display: inline-flex; align-items: center; gap: 6px; }
body.studio .live-row__meta .lucide { flex: none; color: var(--dim); }
body.studio .live-row__meta .dests { display: inline-flex; align-items: center; gap: 9px; color: var(--dim); }
/* same baseline-overhang fix as .brand (r8): each mark sits in its own tooltip
   span, and a baseline-aligned SVG made that span 23.5px tall — which is what
   put the ROW's bottom hairline on a half pixel. */
body.studio .live-row__meta .dests > span { display: inline-flex; align-items: center; }
/* MEASURED, NOT GUESSED (2026-09-08): at 390px the row's meta wraps and the
   marks land hard against the right edge, so the room mark's centred nowrap
   `[data-tip]::after` laid a ~110px box 20px past the document — with no
   element's own rect outside the viewport, which is why a rect-only probe
   never saw it. The containing block MOVES (the post-tray class): the marks'
   own box is the positioned ancestor and the tip resolves against it, so it is
   inside the row by construction at any width. Pre-existing; the grid's cards
   get the same treatment a row down. */
body.studio .live-row__meta .dests { position: relative; }
body.studio .live-row__meta .dests > span[data-tip] { position: static; }
body.studio .live-row__meta .dests > span[data-tip]::after { left: auto; right: 0; transform: translateY(2px); }
body.studio .live-row__meta .dests > span[data-tip]:hover::after,
body.studio .live-row__meta .dests > span[data-tip]:focus-visible::after { transform: translateY(0); }
body.studio .live-row__actions { display: flex; align-items: center; gap: 8px; }
body.studio .live-row__actions .btn { padding: 9px 18px; }
body.studio .live-row__actions .btn--icon { padding: 0; }

/* the clip count, on the title line */
body.studio .clipbadge {
  display: inline-flex; align-items: center; gap: 6px; padding: 2px 9px; border-radius: 999px;
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.02em;
  border: var(--hair-w) solid var(--border-strong); color: var(--muted);
}
body.studio .clipbadge .lucide { width: 13px; height: 13px; }
body.studio .clipbadge--some { color: var(--gold); border-color: color-mix(in srgb, var(--gold) 55%, transparent); }
body.studio .clipbadge--none { color: var(--dim); border-style: dashed; }

/* the encore's shelf state, on the thumbnail. Scoped to the frame on purpose:
   the tooltip rule below sets `position: relative` on every [data-tip], and at
   equal specificity the later rule would win and drop this pill into the flow
   (it did, and the pill rode the bottom edge of the poster). */
body.studio .live-row__thumb .encore-pill {
  position: absolute; top: 8px; left: 8px; padding: 2px 8px; border-radius: 999px;
  font-family: var(--display); font-weight: 800; font-size: 0.66rem; letter-spacing: 0.14em;
  text-transform: uppercase; --lh: 1.5; line-height: var(--lh-snap); border: 1px solid transparent; z-index: 1;
}
body.studio .encore-pill--on { background: var(--gold); color: var(--gold-ink); }
body.studio .encore-pill--off { background: rgb(20 20 20 / 0.85); color: #8a8a8a; border-color: #3a3a3a; }

/* the room's own mark is gold, everything else it plays to is grey — the
   prototype's r4-ref-8 rule, and the token law's (gold is the house). */
body.studio .live-row__meta .brand--bsp { color: var(--gold); }
body.studio .live-row__meta .brand--bsp:hover { color: var(--gold-bright); }

/* LIVE, said once, on the row and nowhere else on the page */
body.studio .pill-live {
  display: inline-flex; align-items: center; gap: 5px; padding: 1px 8px; border-radius: 999px;
  background: var(--live); color: #fff; font-family: var(--display); font-weight: 800;
  font-size: 0.68rem; letter-spacing: 0.14em; text-transform: uppercase; --lh: 1.5; line-height: var(--lh-snap);
}
body.studio .pill-live::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%; background: #fff;
  animation: pill-live-pulse 1.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  body.studio .pill-live::before { animation: none; }
}
@keyframes pill-live-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.45; transform: scale(0.72); }
}

/* The prototype's one filled action per page: gold, and it is the same gold
   the studio masthead's GO LIVE pill already wears (#409). Money and the Pass
   still own gold everywhere a FAN can see it — this is the desk. */
body.studio .btn--gold {
  background: var(--gold); border-color: var(--gold); color: var(--gold-ink); font-weight: 700;
}
body.studio .btn--gold:hover { background: var(--gold-bright); border-color: var(--gold-bright); }

/* the row that is on air NOW */
body.studio .live-row--now { background: color-mix(in srgb, var(--gold) 7%, transparent); box-shadow: inset 3px 0 0 var(--gold); }
body.studio .live-row--now:hover { background: color-mix(in srgb, var(--gold) 11%, transparent); }
body.studio .live-row--now .live-row__thumb { border-color: color-mix(in srgb, var(--gold) 50%, transparent); }

@media (max-width: 899px) {
  body.studio .live-row { grid-template-columns: 110px minmax(0, 1fr); align-items: start; }
  body.studio .live-row__actions { grid-column: 1 / -1; justify-content: flex-end; }
}

/* ---------- THE BOARD'S SECOND SHAPE (Maaz 2026-09-08, mock 2) -------------
   Same nights, same figures, laid out as cards. Grid is the DEFAULT shape, so
   these rules carry the board more often than the row's do.

   The switch sits at the right end of the tab rail. It is a pair of links, so
   the whole thing works with JavaScript off; ONE stroke on the GROUP with the
   picked answer as a FILL is the segmented control's own law (r8) — never a
   second ring inside the first. */
body.studio .lives-bar { display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; }
body.studio .viewswitch { display: inline-flex; align-items: center; flex: none;
  border: var(--hair-w) solid var(--border-strong); border-radius: 999px; padding: 3px; }
body.studio .viewswitch__btn { display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 30px; border-radius: 999px; color: var(--dim);
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease); }
body.studio .viewswitch__btn:hover { color: var(--text); }
body.studio .viewswitch__btn--on { background: var(--surface-3); color: var(--text); }
body.studio .viewswitch__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Three across a 1280px desk, which is the mock's density — the shared card
   floor of 300px gave four and squeezed the actions row onto two lines. */
body.studio .grid--lives { display: grid; gap: 16px; margin-top: 22px;
  grid-template-columns: repeat(auto-fill, minmax(min(330px, 100%), 1fr)); }

body.studio .live-card { position: relative; display: flex; flex-direction: column;
  min-width: 0; cursor: pointer; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); padding: 12px;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease),
    transform var(--dur) var(--ease); }
body.studio .live-card:hover { border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  background: var(--surface-2); transform: translateY(-2px); }
body.studio .live-card:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
body.studio .live-card:hover .live-card__title a { color: var(--accent-soft); }
/* an open kebab is taller than its card */
body.studio .live-card:has(details.menu[open]) { z-index: 40; }

body.studio .live-card__frame { position: relative; aspect-ratio: 16 / 9; overflow: hidden;
  border-radius: var(--radius-sm); background: var(--surface-3); }
body.studio .live-card__frame img { width: 100%; height: 100%; object-fit: cover; display: block;
  filter: saturate(0.8); }
/* a live that never happened keeps its picture, greyed — the row's own
   treatment, and the only place this sheet desaturates a photograph. Called
   off OR missed since 2026-09-15: one rule, one predicate. */
body.studio .live-card__frame--off img { filter: grayscale(1) brightness(0.6); }

/* the tab's own word, on the picture: a dot and a label over a dark plate, so
   it reads on any cover */
body.studio .live-card__state { position: absolute; top: 10px; left: 10px; z-index: 1;
  display: inline-flex; align-items: center; gap: 7px; padding: 4px 10px; border-radius: 999px;
  font-size: 0.74rem; font-weight: 500; --lh: 1.5; line-height: var(--lh-snap);
  color: var(--text); background: rgb(11 10 9 / 0.72); backdrop-filter: blur(4px);
  border: var(--hair-w) solid rgb(255 255 255 / 0.1); }
body.studio .live-card__dot { width: 6px; height: 6px; border-radius: 50%; background: var(--dim); flex: none; }
body.studio .live-card__state--soon .live-card__dot { background: var(--gold); }
body.studio .live-card__state--now { color: #fff; }
body.studio .live-card__state--now .live-card__dot { background: var(--live);
  animation: pill-live-pulse 1.4s ease-in-out infinite; }
body.studio .live-card__state--off .live-card__dot { background: var(--live); }
/* MISSED: the doors passed with nothing on the wire (Maaz 2026-09-10). The dot
   is the base's own dim, declared rather than inherited so this state has a
   rule of its own — and it is deliberately NOT gold (that is a live still
   ahead) and NOT live-red (that is on air, or called off). A non-event reads
   as one. */
body.studio .live-card__state--missed .live-card__dot { background: var(--dim); }
@media (prefers-reduced-motion: reduce) {
  body.studio .live-card__state--now .live-card__dot { animation: none; }
}
/* the encore's shelf state rides the OPPOSITE corner, so the two pills never
   collide. Same absolute rule the row needs for the same reason: the tooltip
   rule sets `position: relative` on every [data-tip] and would otherwise win
   on source order and drop the pill into the flow. */
body.studio .live-card__frame .encore-pill { position: absolute; top: 10px; right: 10px;
  padding: 2px 8px; border-radius: 999px; font-family: var(--display); font-weight: 800;
  font-size: 0.66rem; letter-spacing: 0.14em; text-transform: uppercase;
  --lh: 1.5; line-height: var(--lh-snap); border: 1px solid transparent; z-index: 1; }
body.studio .live-card__frame .encore-pill[data-tip]::after { left: auto; right: 0; transform: translateY(2px); }
body.studio .live-card__frame .encore-pill[data-tip]:hover::after,
body.studio .live-card__frame .encore-pill[data-tip]:focus-visible::after { transform: translateY(0); }

/* the body FILLS the card, so `margin-top: auto` on the actions row lands
   every card's buttons on one line across the grid — a two-line meta on one
   tile used to stagger its neighbours' buttons up by a row's height */
body.studio .live-card__body { flex: 1 1 auto; padding: 13px 4px 2px; display: flex; flex-direction: column; }
body.studio .live-card__title-line { display: flex; align-items: center; gap: 8px; min-width: 0; }
/* ONE line and an ellipsis (mock 2's own titles) — the whole name is on the
   element's `title`, so the hover the clamp took away is given back. */
body.studio .live-card__title { flex: 1 1 auto; min-width: 0; font-size: 1rem; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.studio .live-card__title a { color: inherit; text-decoration: none; transition: color 150ms ease; }
body.studio .live-card__title-line .clipbadge { flex: none; }
body.studio .live-card__when { margin-top: 4px; color: var(--muted); font-size: 0.88rem; }
body.studio .live-card__meta { display: flex; align-items: center; gap: 12px; margin-top: 10px;
  color: var(--muted); font-size: 0.85rem; flex-wrap: wrap; }
body.studio .live-card__meta > span { display: inline-flex; align-items: center; gap: 6px; }
body.studio .live-card__meta .lucide { flex: none; color: var(--dim); }
body.studio .live-card__meta .dests { display: inline-flex; align-items: center; gap: 8px; color: var(--dim); }
/* same baseline-overhang fix as the row (r8): a baseline-aligned SVG overhangs
   its line box by the font's descender and makes the whole row taller */
body.studio .live-card__meta .dests > span { display: inline-flex; align-items: center; }
body.studio .live-card__meta .brand--bsp { color: var(--gold); }
/* the fold: "+2" for the marks that did not fit, never a second line of icons */
body.studio .live-card__meta .dests__more { font-size: 0.76rem; font-weight: 600; color: var(--muted); }

/* A SENTENCE-LENGTH TIP ON A 300px CARD IS AN OVERFLOW BUG (the post-tray
   class, r13). `[data-tip]::after` is `left: 50%; white-space: nowrap` against
   its own element, so a tip on a mark near the card's right edge lays out a
   box past the grid — with no element's own rect outside the viewport, which
   is why a rect-only probe cannot see it. The containing block MOVES: each row
   of the card body is the positioned ancestor, the control inside it is
   static, and the tip resolves against a box that is inside the card by
   construction at any width. */
body.studio .live-card__title-line,
body.studio .live-card__meta,
body.studio .live-card__acts { position: relative; }
body.studio .live-card__title-line [data-tip],
body.studio .live-card__meta [data-tip],
body.studio .live-card__acts [data-tip] { position: static; }
body.studio .live-card__title-line [data-tip]::after,
body.studio .live-card__meta [data-tip]::after,
body.studio .live-card__acts [data-tip]::after {
  left: 0; right: auto; transform: translateY(2px);
  white-space: normal; width: max-content; max-width: 100%; }
body.studio .live-card__title-line [data-tip]:hover::after,
body.studio .live-card__title-line [data-tip]:focus-visible::after,
body.studio .live-card__meta [data-tip]:hover::after,
body.studio .live-card__meta [data-tip]:focus-visible::after,
body.studio .live-card__acts [data-tip]:hover::after,
body.studio .live-card__acts [data-tip]:focus-visible::after { transform: translateY(0); }

/* Control room · Share · ⋯ across the foot (mock 2), the primary taking the
   width the two glyph buttons leave. `margin-top: auto` keeps the row on the
   card's floor, so a two-line title never staggers the buttons down the grid. */
body.studio .live-card__acts { display: flex; align-items: center; gap: 8px;
  justify-content: flex-end; margin-top: auto; padding-top: 14px; }
body.studio .live-card__acts .btn { padding: 9px 16px; }
body.studio .live-card__acts .btn--icon { padding: 0; flex: none; }
body.studio .live-card__go { flex: 1 1 auto; justify-content: center; }
/* the card that is on air NOW, or whose fifteen minutes have opened */
body.studio .live-card--now { border-color: color-mix(in srgb, var(--gold) 50%, transparent);
  background: color-mix(in srgb, var(--gold) 6%, var(--surface)); }

/* ---------- the live detail page (bsp-proto/studio/live-detail.html) -------
   Five tabs over one night. The rail is the prototype's .ld-tab (an underline,
   deliberately NOT the pill above: the pill is "which list am I looking at",
   the underline is "which part of this one thing"), and every panel below is
   either a real surface re-homed into it or an honest empty state. */
/* the night's breadcrumb slot (r2 item 7 — it replaced the "‹ Lives" back
   link): the trail above the header, with the header's own breathing room */
body.studio .ld5-crumbs { padding: 26px 0 0; margin-bottom: 18px; }
body.studio .ld5-crumbs .crumbs { margin: 0; }
body.studio .ld5-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; flex-wrap: wrap; margin-top: 4px; }
body.studio .ld5-head__status {
  font-family: var(--sans); font-size: 0.72rem; font-weight: 600; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--muted);
}
body.studio .ld5-head__status--live { color: var(--live); }
body.studio .ld5-head h1 { font-size: clamp(1.8rem, 3.4vw, 2.5rem); letter-spacing: -0.02em; --lh: 1.1; line-height: var(--lh-snap); margin-top: 4px; font-weight: 600; }
body.studio .ld5-head__when { margin-top: 8px; color: var(--muted); font-size: 1rem; }
body.studio .ld5-head__right { display: flex; align-items: center; gap: 10px; padding-bottom: 4px; }
body.studio .ld5-public { color: var(--muted); font-size: 0.98rem; }
body.studio .ld5-public:hover { color: var(--text); }
/* GO TO PUBLIC PAGE + a stroke COPY LINK (Maaz r12): a ghost button, the same
   weight as Revoke/Export and never the gold Save. The confirmation is a glyph+
   word swap in CSS off the copy controller's `.is-copied`, no toast. */
body.studio .ld5-copy { display: inline-flex; align-items: center; gap: 6px; }
body.studio .ld5-copy__done { display: none; align-items: center; gap: 6px; }
body.studio .ld5-copy.is-copied .ld5-copy__idle { display: none; }
body.studio .ld5-copy.is-copied .ld5-copy__done { display: inline-flex; }

body.studio .ld-tabs { display: flex; gap: 4px; margin-top: 30px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
body.studio .ld-tab {
  padding: 12px 16px; background: none; border: 0; border-bottom: 2px solid transparent;
  margin-bottom: -1px; color: var(--muted); font-size: 0.95rem; font-weight: 500;
  cursor: pointer; display: inline-flex; align-items: center; gap: 8px; text-transform: none; letter-spacing: 0;
}
body.studio .ld-tab:hover { color: var(--text); }
body.studio .ld-tab[aria-selected="true"] { color: var(--text); border-bottom-color: var(--gold); }
body.studio .ld-tab .count { color: var(--dim); font-size: 0.8rem; }
body.studio .ld-panel { margin-top: 26px; }
body.studio .ld-empty { padding: 72px 24px; text-align: center; border: 1px dashed var(--border-strong); border-radius: var(--radius); }
/* THE EMPTY BOARD'S HERO (live-flow r4 item 35, Maaz's mock): a solid dark
   card — never the dashed frame — one gold GO LIVE, and a three-block strip
   of the creator's real numbers with gold small-caps labels. */
body.studio .lv-hero {
  padding: 46px 26px 26px; text-align: center;
  border: var(--hair-w) solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface);
}
body.studio .lv-hero__head { margin: 0; font-size: clamp(1.5rem, 3.2vw, 2rem); }
body.studio .lv-hero__sub { margin: 10px 0 0; color: var(--muted); }
/* THE BUTTON IS THE HERO (Maaz 2026-09-10): the mock's GO LIVE is a wide
   gold slab, not the masthead pill. `.btn` is 0.8rem / 11px padding and
   all-small-caps — this selector has to beat it or the sell reads as a
   toolbar chip. Integer min-height: a round gold fill still hairlines. */
body.studio .lv-hero .lv-hero__go.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 16px;
  margin-top: 28px; padding: 22px 64px; min-height: 76px; min-width: 22rem;
  font-family: var(--display); font-weight: 800; font-size: clamp(1.45rem, 3.2vw, 1.85rem);
  font-variant-caps: normal; text-transform: uppercase; letter-spacing: 0.14em;
  border-radius: 16px; line-height: 1;
}
@media (max-width: 520px) {
  body.studio .lv-hero .lv-hero__go.btn { min-width: 0; width: 100%; padding: 20px 28px; }
}
body.studio .lv-hero__facts {
  display: grid; grid-template-columns: repeat(3, 1fr);
  margin-top: 32px; border: var(--hair-w) solid var(--border);
  border-radius: var(--radius); background: var(--surface-2);
}
body.studio .lv-hero__fact {
  display: grid; gap: 8px; justify-items: center; align-content: center;
  padding: 24px 12px; min-width: 0;
}
body.studio .lv-hero__fact + .lv-hero__fact { border-left: var(--hair-w) solid var(--border); }
body.studio .lv-hero__fact b { font-size: 1.9rem; line-height: 1; }
body.studio .lv-hero__label {
  color: var(--accent); font-size: 0.7rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
}
body.studio .lv-hero__dests { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
body.studio .lv-hero__dest {
  display: inline-grid; place-items: center; width: 34px; height: 34px;
  border-radius: 999px; border: var(--hair-w) solid var(--border); color: var(--dim);
}
body.studio .lv-hero__dest--on { border-color: var(--hair-gold); color: var(--accent); }
@media (max-width: 720px) {
  body.studio .lv-hero__facts { grid-template-columns: 1fr; }
  body.studio .lv-hero__fact + .lv-hero__fact { border-left: 0; border-top: var(--hair-w) solid var(--border); }
}
body.studio .ld-empty .lucide { display: block; margin: 0 auto 14px; color: var(--dim); }
body.studio .ld-empty p { font-size: 1.05rem; color: var(--muted); max-width: 40ch; margin: 0 auto; }
body.studio .ld-empty .small { display: block; margin-top: 8px; color: var(--dim); }

/* Details — the go-live form's own furniture, on a night that already exists */
body.studio .ld5-form { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); gap: 26px; align-items: start; }
body.studio .ld5-form .form-card > section { padding: 26px 30px; }
body.studio .ld5-show-title { font-size: clamp(1.5rem, 2.8vw, 2rem); font-weight: 500; letter-spacing: -0.015em; --lh: 1.15; line-height: var(--lh-snap); margin-top: 18px; }
body.studio .ld5-slug { margin-top: 6px; font-family: var(--mono); font-size: 0.8rem; color: var(--dim); overflow-wrap: anywhere; }
body.studio .ld5-soft { margin-top: 14px; color: var(--muted); font-size: 0.88rem; }
body.studio .ld5-save { margin-top: 22px; }
body.studio .ld5-rsvp { margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--border); }
body.studio .ld5-rsvp .eyebrow { display: block; margin-bottom: 8px; }
/* r9 C1: the two ways into what the room said — the sheet and the file — plus
   the honest line beside them. Declared ONCE: this class was already here for
   the inline panel, and two declarations 130 lines apart is the r8 bug. */
body.studio .ld5-said { margin-top: 20px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
body.studio .ld5-said__line { color: var(--muted); font-size: 0.86rem; }
/* the questions a night asked, on a night that already happened */
body.studio .rq-record { list-style: none; }
body.studio .rq-record > li { display: flex; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--border); }
body.studio .rq-record > li:last-child { border-bottom: 0; }
body.studio .rq-record .num { color: var(--dim); }
body.studio .rq-record .dim { display: block; margin-top: 3px; }
body.studio .ld5-form .summary { position: static; }
/* THE NIGHT'S COVER CARD (r7-ref-7): the composer's own .summary/.cover
   furniture, plus the two controls a night that already exists needs — the
   trash beside the label, and the word over a picture you can replace. */
body.studio .summary__trash { display: inline-flex; align-items: center; cursor: pointer; padding: 4px; color: var(--muted); background: none; border: 0; }
/* display:inline-flex outranks the UA's [hidden] rule — the composer's trash
   ships hidden and is revealed when there is something to take off. */
body.studio .summary__trash[hidden] { display: none; }
/* DRAWN AND HONEST (r9 item 5). The trash is on the header row from the first
   paint; with nothing attached and nothing picked there is nothing to take off,
   so it is disabled, dimmer, and carries its own reason as the tip. It must not
   take the hover red either — a control that colours like it will act is the
   thing the disabled state exists to stop saying. */
body.studio .summary__trash:disabled { color: var(--dim); opacity: 0.55; cursor: default; }
body.studio .summary__trash:disabled:hover { color: var(--dim); }
/* THE TIP ANCHORS RIGHT ON THIS ONE (r9). [data-tip]::after is centred on its
   control — `left: 50%` with a translateX(-50%) — and `white-space: nowrap`, so
   a wide tip on a 24px control sitting at the right edge of the card lays out a
   box that runs past the viewport. That is a real horizontal scroll, not a
   paint artifact: measured at 390px, the document's scrollWidth went 108px past
   its clientWidth the moment this control started being drawn at rest. Nothing
   else in the studio wears a tip at a container's right edge, so the fix is
   here rather than in the tooltip. The hover rule has to be overridden too —
   it re-declares the transform. */
body.studio .summary__trash[data-tip]::after { left: auto; right: 0; transform: translateY(2px); }
body.studio .summary__trash[data-tip]:hover::after,
body.studio .summary__trash[data-tip]:focus-visible::after { transform: translateY(0); }
body.studio .summary__trash input { position: absolute; opacity: 0; width: 0; height: 0; }
body.studio .summary__trash:hover { color: var(--live); }
body.studio .summary__trash:has(input:checked) { color: var(--live); }
body.studio .summary__trash:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
/* REPLACE, centred over the picture on hover (r8-ref-8). A word, not a
   control: the whole frame is the picker's label, and this is what pointing at
   it means. The scrim is on the ::after so it dims the picture and not the
   word. */
body.studio .cover__replace {
  position: absolute; z-index: 3; inset: 0; margin: auto; width: max-content; height: max-content;
  padding: 8px 18px; border-radius: 999px; border: var(--hair-w) solid rgb(244 237 225 / 0.5);
  background: rgb(11 10 9 / 0.6); color: var(--text); font-size: 0.86rem; font-weight: 500;
  opacity: 0; transition: opacity 150ms ease;
}
body.studio .cover__replace[hidden] { display: none; }
body.studio .cover:hover .cover__replace, body.studio .cover:focus-within .cover__replace { opacity: 1; }
body.studio .cover:has(.cover__replace:not([hidden]))::after {
  content: ""; position: absolute; z-index: 2; inset: 0;
  background: rgb(6 5 4 / 0); transition: background 150ms ease;
}
body.studio .cover:hover:has(.cover__replace:not([hidden]))::after { background: rgb(6 5 4 / 0.38); }
body.studio .ld-readonly__note {
  margin-bottom: 18px; padding: 14px 18px; border: 1px solid var(--border-strong);
  border-radius: var(--radius); background: var(--surface); color: var(--muted); font-size: 0.92rem;
}
body.studio .ld-readonly .form-card { opacity: 0.86; }

body.studio .form-card { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; }
body.studio .form-card > section { padding: 24px 22px; border-top: 1px solid var(--border); }
body.studio .form-card > section:first-child { border-top: 0; }
body.studio .form-card .eyebrow { display: block; margin-bottom: 14px; }
body.studio .field-label { display: block; margin: 24px 0 12px; font-size: 0.92rem; color: var(--muted); }
body.studio .textarea {
  width: 100%; min-height: 80px; resize: vertical; padding: 14px 16px; background: var(--input-bg);
  border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font: inherit; line-height: var(--lh-snap); font-size: 0.92rem;
}
/* `.gl-sched` and `.gl-tz` are declared ONCE, up there (r10 item 7): this
   block used to re-declare both with a different font-size, and both applied.
   The two hosts render one partial (studio/_when_fields) now. */
body.studio .gl-dur { margin-top: 26px; }
body.studio .gl-dur .dur-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
body.studio .gl-dur .dur-head .eyebrow { margin: 0; }
body.studio .gl-who { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
body.studio .gl-who .eyebrow { margin: 0; }
body.studio .gl-passes { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 18px; }
body.studio .gl-adv { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
body.studio .gl-adv .eyebrow { margin-bottom: 6px; }
body.studio .gl-adv .sum { display: block; color: var(--muted); font-size: 0.98rem; }

/* The pill row, the segmented control and the destination circles are the GO
   LIVE page's own furniture and are declared ONCE, up there (product owner
   2026-09-03, item 6). This panel used to re-declare all three with the stroke
   on a different element — the label instead of its span — so both rules
   applied and every chip and every picked answer drew one box inside another.
   The markup here posts the same shape the composer's does (a .sr-only input
   and a <span> for the words), which is what lets one rule set serve both. */

/* Greenroom — the booth's seats, re-homed from the control room */
body.studio .gr5-top { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; padding: 8px 0 22px; }
body.studio .gr5-top p { color: var(--muted); font-size: 1rem; }
body.studio .gr5-h { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-top: 18px; }
body.studio .gr5-h h2 { font-size: 1.35rem; font-weight: 600; letter-spacing: -0.01em; }
body.studio .gr5-h span { color: var(--muted); font-size: 0.98rem; }
body.studio .gr5-label { margin-top: 40px; color: var(--muted); font-size: 1rem; }

@media (max-width: 899px) {
  body.studio .ld5-form { grid-template-columns: minmax(0, 1fr); }
  body.studio .gl-sched { grid-template-columns: minmax(0, 1fr); }
}

/* the tooltip the prototype hangs on every bare number */
body.studio [data-tip] { position: relative; }
body.studio [data-tip]::after {
  content: attr(data-tip); position: absolute; left: 50%; bottom: calc(100% + 8px);
  transform: translateX(-50%) translateY(2px); white-space: nowrap; padding: 6px 10px;
  border-radius: var(--radius-sm); font-size: 0.76rem; font-weight: 500; letter-spacing: 0;
  text-transform: none; color: var(--text); background: var(--surface-3);
  border: 1px solid var(--border-strong); box-shadow: 0 10px 24px rgb(0 0 0 / 0.4);
  opacity: 0; pointer-events: none; transition: opacity 120ms ease, transform 120ms ease; z-index: 20;
}
body.studio [data-tip]:hover::after, body.studio [data-tip]:focus-visible::after { opacity: 1; transform: translateX(-50%) translateY(0); }
/* BELOW THE TRIGGER (product owner, r12 items 1 and 6a). The base tip hangs
   ABOVE its control, which is right for a number in the middle of a row and
   wrong for the two places this class is used: the FIRST card in the Advanced
   sheet, where the tip landed on the "Who hears about it" label and covered
   the words it was explaining, and the head's primary, where up is off the top
   of the viewport. It also CLAMPS — `normal` wrapping and a max-width inside
   the host — because a sentence-length tip is what these two carry, and
   `nowrap` is what let the sheet's reason run past the modal's own edge.
   Both transform states are re-declared: the base rule's hover carries a
   translateX(-50%) that pairs with `left: 50%` and must survive. */
body.studio .tip-below[data-tip]::after {
  top: calc(100% + 6px); bottom: auto; z-index: 60;
  white-space: normal; width: max-content; max-width: 320px;
  /* IT HAS TO READ AS A LAYER, which is the other half of "legibility" (r12
     item 6a). `--surface-3` is exactly what the sheet's PICKED choice card is
     filled with, so a tip landing on one was the same colour as the thing it
     was covering — the complaint's real cause, not the position alone. The
     near-black ring outside the hairline is what separates it from whatever it
     lands on, and the drop lifts it; no glow (the design system bans one). */
  background: var(--surface-3); border-color: var(--border-strong);
  box-shadow: 0 0 0 var(--hair-w) var(--bg), 0 12px 28px rgb(0 0 0 / 0.6);
}
/* CLAMPED INSIDE THE MODAL, and ANCHORED RIGHT. The sheet's own tips are the
   reason this class exists and its cards are full-width, so the containing
   block IS the modal's inner width — clamping to it is what keeps a longer
   future sentence inside the sheet rather than over its edge.
     Right, not centred, because of what is UNDER it: these cards are a stack,
   so "below the trigger" lands on the next card, and that card's words are all
   on its LEFT (a title, a line under it) with only the check on its right. A
   centred box covers the title; a right-anchored one covers the empty half. */
body.studio .adv .tip-below[data-tip]::after {
  max-width: calc(100% - 24px); left: auto; right: 0; transform: translateY(2px);
}
body.studio .adv .tip-below[data-tip]:hover::after,
body.studio .adv .tip-below[data-tip]:focus-visible::after { transform: translateY(0); }
/* A CTA AT A RIGHT EDGE ANCHORS RIGHT (the .summary__trash rule's reasoning):
   both hosts of `.cta-gate` sit hard right — the composer's head and the
   live's own Save, in a 328px column — so a box centred on a 150px button
   would lay out past the edge it is against. */
body.studio .cta-gate[data-tip]::after { left: auto; right: 0; transform: translateY(2px); }
body.studio .cta-gate[data-tip]:hover::after,
body.studio .cta-gate[data-tip]:focus-visible::after { transform: translateY(0); }
/* The share tiles carry no [data-tip] at all (journey r3 item 1: no hover
   text on a social glyph), so the old left-anchored tile-tip rule is gone. */

/* the icon button (share, the kebab's twin) */
body.studio .btn--icon {
  width: 40px; height: 40px; padding: 0; color: var(--muted);
  display: inline-flex; align-items: center; justify-content: center;
}
/* A BARE `.btn--icon` IS THE UA'S OWN GREY BUTTON (r8). This class sets the
   box and the colour and says nothing about the chrome, so the two hosts that
   do NOT pair it with `.btn` — both sheet closes on the Followers page — were
   drawing a grey rounded square behind the ×, which is the button Maaz's mock
   4 is showing as the don't and his mocks 5–6 draw as a plain glyph.

   `:not(.btn)` is what keeps the reset off the two hosts that DO pair them
   (the Post and Generate sheets' own ×): they want the ghost's stroke, and
   this rule ships later in the file at equal weight, so it would otherwise
   strip it. */
body.studio .btn--icon:not(.btn) { background: none; border: 0; font: inherit; cursor: pointer; }
body.studio .btn--icon:hover { color: var(--text); }
body.studio .btn--icon .lucide { flex: none; }
body.studio .gl-adv:hover .gear { color: var(--text); border-color: var(--text); }
body.studio .gl-adv:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

/* ADVANCED SETTINGS, the sheet. A native <dialog> inside the composer's form,
   so its fields post with everything else and there is no second save. */
body.studio .adv {
  border: 1px solid var(--border-strong); border-radius: 16px; padding: 0;
  background: var(--surface); color: var(--text);
  width: min(640px, calc(100vw - 40px)); max-height: calc(100vh - 60px); overflow: auto;
  box-shadow: 0 30px 80px rgb(0 0 0 / 0.6);
}
body.studio .adv::backdrop { background: rgb(0 0 0 / 0.7); }
/* WHAT THEY SAID, the sheet (r9 C1). The same box as the Advanced sheet and
   the same furniture inside it (.adv__in / __head / __x / __foot are named by
   class, not by parent, so this is reuse and not a second look) — but its OWN
   name, because "a read-only night has no dialog.adv" is an assertion about
   the EDITOR, and a record of what the room said belongs on a night that
   already happened most of all. */
body.studio .said-sheet {
  border: 1px solid var(--border-strong); border-radius: 16px; padding: 0;
  background: var(--surface); color: var(--text);
  width: min(640px, calc(100vw - 40px)); max-height: calc(100vh - 60px); overflow: auto;
  box-shadow: 0 30px 80px rgb(0 0 0 / 0.6);
}
body.studio .said-sheet::backdrop { background: rgb(0 0 0 / 0.7); }

/* ---------- THE SHARE SHEET (product owner, r12 item 5) --------------------
   The same box and the same furniture as the two sheets above it (.adv__in /
   __head / __x are named by class, not by parent), with its OWN name and its
   own body: a preview card, four named places, and one link.

   r12-ref-7 is the STRUCTURE — that reference is another product's light-mode
   modal, so nothing about its skin came over. Fenced: .share-mount, .share-
   sheet*, .share-card*, .share-on*. */
body.studio .share-sheet {
  border: 1px solid var(--border-strong); border-radius: 16px; padding: 0;
  background: var(--surface); color: var(--text);
  width: min(480px, calc(100vw - 32px)); max-height: calc(100vh - 60px); overflow: auto;
  box-shadow: 0 30px 80px rgb(0 0 0 / 0.6);
}
body.studio .share-sheet::backdrop { background: rgb(0 0 0 / 0.7); }
body.studio .share-sheet .adv__in { padding: 26px 26px 28px; }
body.studio .share-sheet .adv__head { margin-bottom: 10px; }
body.studio .share-sheet .adv__head h2 { font-size: 1.4rem; }
body.studio .share-sheet__deck { margin: 0 0 22px; font-size: 0.9rem; max-width: 40ch; }
body.studio .share-sheet__eyebrow { display: block; margin-bottom: 10px; }
/* The mount is the row's own control plus this sheet; a closed <dialog>
   generates no box, so it adds nothing to the actions cluster. */
body.studio .share-mount { display: inline-flex; }

/* THE PREVIEW CARD. Its one job is "this is what somebody who taps your link
   sees", so its parts are the fan page's parts: the cover, the title, the
   host, the announced clock and the door's own CTA. */
body.studio .share-card {
  border: var(--hair-w) solid var(--border); border-radius: 14px;
  background: var(--surface-2); overflow: hidden;
}
body.studio .share-card__top { display: grid; grid-template-columns: 148px minmax(0, 1fr); gap: 14px; padding: 14px; }
body.studio .share-card__cover { border-radius: 9px; overflow: hidden; aspect-ratio: 16 / 9; background: var(--surface-3); }
body.studio .share-card__cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
body.studio .share-card__id { min-width: 0; display: flex; flex-direction: column; gap: 10px; justify-content: center; }
body.studio .share-card__id > b { font-weight: 500; font-size: 1rem; --lh: 1.3; line-height: var(--lh-snap); }
body.studio .share-card__host { display: flex; align-items: center; gap: 9px; font-size: 0.82rem; color: var(--muted); }
body.studio .share-card__host .avatar { width: 26px; height: 26px; font-size: 0.62rem; }
body.studio .share-card__host b { color: var(--text); font-weight: 500; display: block; }
body.studio .share-card__foot {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 12px 14px; border-top: var(--hair-w) solid var(--border);
}
body.studio .share-card__when { margin: 0; font-size: 0.85rem; color: var(--muted); }
/* INERT, and it looks like the button it is a picture of. `.share-sheet__
   eyebrow` above the card is what says so — never a disabled-looking chip,
   which would read as a door that is shut. */
body.studio .share-card__cta {
  flex: none; background: var(--gold); color: var(--gold-ink);
  border-radius: 999px; padding: 8px 16px; font-size: 0.8rem; font-weight: 700;
  letter-spacing: 0.04em; white-space: nowrap;
}

/* FOUR NAMED PLACES. Three open their own composer with the link in it;
   Instagram publishes no intent at all, so its tile copies one — the tip says
   why, and the caption says it happened. */
body.studio .share-sheet__on {
  margin: 24px 0 12px; text-align: center; font-size: 0.9rem; font-weight: 500;
}
body.studio .share-on { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
body.studio .share-on__t {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  background: none; border: 0; padding: 10px 4px; border-radius: 12px;
  color: var(--muted); font: inherit; line-height: var(--lh-snap);
  font-size: 0.76rem; text-decoration: none; cursor: pointer;
  transition: background-color 150ms ease, color 150ms ease;
}
body.studio .share-on__t:hover { background: var(--surface-2); color: var(--text); }
body.studio .share-on__t:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* THE MARK STANDS ALONE — no ring and no plate (r8's one-stroke rule). Three
   of these four marks ARE a filled shape of their own (Facebook is a circle,
   LinkedIn a rounded square, Instagram a stroked rounded square), so a
   bordered circle around them draws a second stroke 1px off the first — the
   hand-drawn look the avatar ring round was about. The tile's own hover fill
   is the hit state instead. */
body.studio .share-on__mark {
  width: 40px; height: 40px; display: inline-flex;
  align-items: center; justify-content: center;
}
body.studio .share-on__label { white-space: nowrap; }
/* Instagram's countdown line (journey r3 item 1): "Link copied, redirecting
   you to Instagram in 3" — a status row under the tiles, hidden at rest. */
body.studio .share-sheet__ig { margin: 10px 0 0; text-align: center; font-size: 0.82rem; color: var(--gold); }
body.studio .share-sheet__ig[hidden] { display: none; }

/* THE PASS'S SHARE SHEET (Maaz's QA mock, 2026-09-09 23:20) — the same box and
   the same furniture, dressed the way he drew it: a gold wash across the top,
   the house ticket on a disc, and the heading centred under it. The wash is a
   BACKGROUND on the box, not a glow shadow on a component (the A2 ban), and it
   is pointer-transparent by construction because it is paint. */
body.studio .share-sheet--pass { position: relative; overflow: hidden auto; }
body.studio .share-sheet--pass::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 210px; pointer-events: none;
  background: radial-gradient(120% 100% at 50% 0%,
    color-mix(in srgb, var(--gold) 26%, transparent), transparent 72%);
}
body.studio .share-sheet--pass .adv__in { position: relative; text-align: center; }
body.studio .share-sheet--pass h2 { font-size: 1.5rem; }
/* ONE LINE (Maaz 2026-09-13). The deck used to inherit the base's 40ch max and
   wrapped to two lines; the sheet is wide enough to hold this sentence whole,
   and the mock draws it on a single line. */
body.studio .share-sheet--pass .share-sheet__deck {
  margin-inline: auto; max-width: none; white-space: nowrap;
}
body.studio .share-sheet--pass .share-card { text-align: left; }
/* INSET FROM THE CORNER (Maaz 2026-09-13) — the close used to kiss the top-right
   edge of the card; it sits inside it now, its own paddingless margin cleared so
   the offset is exactly what these two numbers say. */
body.studio .share-sheet__x { position: absolute; top: 14px; right: 14px; margin: 0; }
body.studio .share-sheet__crest {
  display: grid; place-items: center; width: 66px; height: 66px; margin: 6px auto 20px;
  border-radius: 999px; color: var(--gold);
  background: color-mix(in srgb, var(--gold) 20%, transparent);
}
/* The Pass's own cover in the preview: the metal is a CSS card with its own
   radius, so the frame it sits in owns the crop and it owns none. */
body.studio .share-card__cover .pass-cover { width: 100%; height: 100%;
  border-radius: 0; border: 0; aspect-ratio: auto; padding: 10px; }
body.studio .share-card__cover .pass-cover__title { font-size: 0.8rem; }
body.studio .share-card__cover .pass-card__bare { position: static; display: grid;
  place-items: center; width: 100%; height: 100%; color: var(--dim); }

body.studio .share-sheet__foot { margin-top: 22px; text-align: center; }
body.studio .share-sheet__later { margin-top: 14px; }
/* The card the panel is drawn as would double the sheet's own stroke — one
   element, one stroke (r8). */
body.studio .said-sheet .rsvp-said { border: 0; padding: 0; background: none; }
body.studio .adv__in { padding: 30px 32px 34px; }
body.studio .adv__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 26px; }
body.studio .adv__head h2 { font-size: 1.7rem; letter-spacing: -0.02em; }
body.studio .adv__x { background: none; border: 0; color: var(--muted); cursor: pointer; padding: 4px; margin: -4px -4px 0 0; }
body.studio .adv__x:hover { color: var(--text); }
body.studio .adv .eyebrow { display: block; margin-bottom: 14px; }
body.studio .adv__rule { height: 1px; background: var(--border); margin: 26px 0; }
body.studio .adv__rsvp-note { color: var(--muted); font-size: 0.92rem; margin-bottom: 18px; }

/* THE RSVP QUESTION EDITOR (r7-ref-4). One editor, two hosts — the composer's
   Advanced modal and the night's Details panel — so it is styled once and
   inherits whichever surface it is standing on. Every slot is in the markup and
   the empty ones are [hidden]; nothing here is built in JavaScript. */
body.studio .rq { padding: 16px 0; border-top: 1px solid var(--border); }
body.studio .rq:first-child { border-top: 0; padding-top: 0; }
body.studio .rq[hidden] { display: none; }
body.studio .rq__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
body.studio .rq__head .eyebrow { margin: 0 0 10px; }
body.studio .rq__x {
  background: none; border: 0; color: var(--dim); cursor: pointer; padding: 4px; margin: -4px -4px 6px 0;
  transition: color 150ms ease;
}
body.studio .rq__x:hover { color: var(--live); }
body.studio .rq__count { margin-top: 6px; text-align: right; font-size: 0.78rem; color: var(--dim); }
body.studio .rq__kinds { margin-top: 4px; }
body.studio .rq__options { margin-top: 14px; padding-left: 14px; border-left: 1px solid var(--border); }
body.studio .rq__options[hidden] { display: none; }
body.studio .rq__opts-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
body.studio .rq__opts-head .field-label { margin: 0 0 10px; }
/* An option row is a stroked field plus its own delete. The delete carries no
   stroke of its own on purpose — one element, one stroke (r8) — and the field
   is what sets the row's height, so nothing here lands on a half pixel. */
body.studio .rq__opt { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
body.studio .rq__opt[hidden] { display: none; }
body.studio .rq__opt input { flex: 1; min-width: 0; }
body.studio .rq__opt-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; flex: 0 0 auto;
  background: none; border: 0; border-radius: var(--radius-sm);
  color: var(--dim); cursor: pointer; transition: color 150ms ease;
}
body.studio .rq__opt-x:hover:not(:disabled) { color: var(--live); }
/* THE TWO-OPTION FLOOR, said with a control instead of a sentence (r9 A3):
   the delete goes dim and unclickable rather than taking away the second
   option and leaving a poll that is not a question. */
body.studio .rq__opt-x:disabled { opacity: 0.3; cursor: not-allowed; }
body.studio .rq__opt-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .rq__add-opt { margin: 4px 0 10px; }
body.studio .rq__add-opt:disabled { opacity: 0.45; cursor: not-allowed; }

/* THE ZERO STATE (r9 A2) — what a night that asks nothing says, instead of
   drawing Question 1's whole editor at a creator who never wanted one. It sits
   after the rows so `.rq:first-child` still names the first question.
   ONE LINE since r10 item 5: it used to hold its own "Add questions" button
   under the message while "Add question" sat above it, so the section had two
   controls for one act. The button below it is the section's only one. */
body.studio .rqs__zero { margin: 0 0 10px; }
body.studio .rqs__zero[hidden] { display: none; }
/* THE CEILING LINE (product owner, r14 item 5) takes the "Add question"
   button's place at the third question, so it wears the button's own top
   margin and never sits beside it. `[hidden]` is re-declared for the same
   reason `.rqs__zero`'s is: the studio fence is full of rules that give a
   block a display, and one of them landing here would out-rank the UA. */
body.studio .rqs__ceiling { margin: 0; }
body.studio .rqs__ceiling[hidden] { display: none; }
/* WHY THE EDITOR IS OFF (Maaz 2026-09-10), on a live going out right now. The
   reason hangs on this WRAPPER rather than on "Add question" itself, because
   `.btn[disabled]` sets `pointer-events: none` and a tip on the control would
   never show — the `.cta-gate` rule, and the one who-hears already keeps on a
   label. It hugs the button (inline-block), so the hover area is the control
   and not the whole empty row.
     ANCHORED LEFT, unlike `.adv .tip-below`'s cards. That rule is written for
   the sheet's full-width choices, where `right: 0` and `max-width: 100%` both
   resolve against the modal's own inner width; here the containing block is a
   ~140px button at the sheet's LEFT edge, so a right anchor would lay the box
   out from that button's right edge and clamp a sentence to its width. Left,
   with a px clamp, grows into the 576px of content the sheet has — and what
   it lands on is the NEXT section's eyebrow, not the words it is explaining,
   which is the thing r12 item 6a was actually about. Keeping the wrapper
   tight to the button is the other half: a full-width one would anchor right
   over the empty half, and hand every blank pixel of that row a tooltip. */
body.studio .rqs__off { display: inline-block; }
/* `hidden` stays on the BUTTON (the `.cta-gate:has(.btn[hidden])` rule, and
   what every existing test asserts): an inline-block wrapping nothing still
   generates a line box, which would leave a blank line where the ceiling line
   is supposed to take the button's place. */
body.studio .rqs__off:has(.btn[hidden]) { display: none; }
body.studio .rqs__off[data-tip]::after {
  top: calc(100% + 6px); bottom: auto; left: 0; right: auto; z-index: 60;
  transform: translateY(2px);
  white-space: normal; width: max-content; max-width: 320px;
  /* The near-black ring `.tip-below` wears, for the same reason: `--surface-3`
     is the sheet's own picked-card fill, so a tip landing on one would be the
     colour of the thing it covers. */
  box-shadow: 0 0 0 var(--hair-w) var(--bg), 0 12px 28px rgb(0 0 0 / 0.6);
}
body.studio .rqs__off[data-tip]:hover::after,
body.studio .rqs__off[data-tip]:focus-visible::after { transform: translateY(0); }

/* RSVP RESPONSES (Maaz 2026-09-10, his mock). Not scoped to the studio shell:
   the control room and the night's Details sheet both render it, and it is a
   read either way — a share per option, or the typed lines themselves.
   ONE INNER CARD PER QUESTION. It was a flat list whose questions ran into
   each other, so the eye had to find where one ended; a card per question is
   what makes "1 response · Multiple choice" belong to the bars under it. */
.rsvp-said__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  flex-wrap: wrap;
}
.rsvp-said__intro { min-width: 0; }
.rsvp-said__deck { margin: 4px 0 0; color: var(--muted); font-size: 0.88rem; }
.rsvp-said__count { flex: none; white-space: nowrap; }
.rsvp-said__block {
  margin-top: 16px; padding: 16px;
  border: var(--hair-w) solid var(--border); border-radius: var(--radius);
}
.rsvp-said__meta { margin: 4px 0 0; }
/* THE ROWS. Label, a full-width track, then the figure — one grid, so the
   bars start on the same x in every card and the eye reads down them. The
   label column is a share of the row rather than its own content's width, or
   two cards with different longest labels would draw two different charts. */
.rsvp-said__opts { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.rsvp-said__opts > li {
  display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 12fr) auto;
  align-items: center; gap: 14px;
}
.rsvp-said__label { font-size: 0.92rem; overflow-wrap: anywhere; }
.rsvp-said__bar { height: 10px; border-radius: 999px; background: var(--surface-3); overflow: hidden; }
.rsvp-said__bar > span { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.rsvp-said__n {
  text-align: right; white-space: nowrap; font-size: 0.86rem; color: var(--muted);
  font-variant-numeric: tabular-nums lining-nums;
}
.rsvp-said__lines { margin-top: 12px; }
/* THE TRACK IS THE FIRST THING TO GO ON A PHONE (`.tally`'s own rule, and this
   panel is on the 390px no-scroll walk): a 10px bar between a wrapping label
   and a figure is unreadable long before it overflows, and the count and the
   share say the whole thing without it. */
@media (max-width: 520px) {
  .rsvp-said__opts > li { grid-template-columns: minmax(0, 1fr) auto; }
  .rsvp-said__bar { display: none; }
}
.rsvp-said__q { margin: 0; font-size: 0.98rem; }
.tally { display: flex; flex-direction: column; gap: 8px; }
.tally > li { display: grid; grid-template-columns: minmax(0, 1fr) 90px 2.5rem; align-items: center; gap: 12px; }
.tally__label { font-size: 0.92rem; overflow-wrap: anywhere; }
.tally__bar { height: 6px; border-radius: 999px; background: var(--surface-3); overflow: hidden; }
.tally__bar > span { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.tally__n { text-align: right; font-variant-numeric: tabular-nums; }
@media (max-width: 520px) {
  .tally > li { grid-template-columns: minmax(0, 1fr) 2.5rem; }
  .tally__bar { display: none; }
}
/* THE ADVANCED SHEET'S FOOTER (product owner, r12 item 6b): the state of the
   creator's own defaults on the left, the two acts on the right. It was one
   button, hard right. */
body.studio .adv__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin-top: 26px; flex-wrap: wrap;
}
body.studio .adv__foot-acts { display: flex; align-items: center; gap: 14px; margin-left: auto; }
/* THE SAVE SAYS SO, IN THE SHEET (product lead, live-flow r2 item 4). A toast
   would paint BEHIND the <dialog> (the top layer is why this whole footer
   answers a turbo-stream instead of a redirect), so the confirmation is a line
   on the footer's own row, left of the two buttons. It is the only gold on the
   row: it marks a positive state, which is the one thing gold is for. */
body.studio .adv__foot-said {
  display: inline-flex; align-items: center; gap: 7px; margin: 0;
  color: var(--accent); font-size: 0.82rem;
}
/* AN INLINE-FLEX BOX OUTRANKS THE UA'S `[hidden]` RULE (r14 item 5, third
   time). The line is always in the markup now (live-flow r3 item 2) and
   `hidden` is what decides whether anything is being claimed — without this it
   would be on screen from the first paint. */
body.studio .adv__foot-said[hidden] { display: none; }
body.studio .adv__foot-said .lucide { flex: none; }
/* THE (i) BESIDE THE PAIR (product owner, r14 item 6b). It replaced a tooltip
   ON "Set as default" — hovering one button to learn what the OTHER one does
   is the wrong place for the answer, because the sentence is about the pair.
     It anchors RIGHT, for the .cta-gate arithmetic: this row is right-aligned
   and `.tip-i`'s own 236px box hangs LEFT-anchored off its glyph, which from
   here lays out past the modal's edge. And it wraps at the sheet's own width,
   because the sentence is two clauses. The base rule already renders it ABOVE
   its trigger, which is what the footer needs — it is the last row in the
   sheet and there is nothing below it to land on. */
body.studio .adv__foot .tip-i[data-tip]::after {
  left: auto; right: 0; width: max-content; max-width: 260px;
  transform: translateX(0) translateY(2px);
  /* The near-black ring the sheet's other tips wear (.tip-below's reasoning):
     --surface-3 is what a picked choice card is filled with, so a tip landing
     on one needs an edge of its own to read as a layer. */
  box-shadow: 0 0 0 var(--hair-w) var(--bg), 0 12px 28px rgb(0 0 0 / 0.6);
}
body.studio .adv__foot .tip-i[data-tip]:hover::after,
body.studio .adv__foot .tip-i[data-tip]:focus-visible::after { transform: translateX(0) translateY(0); }

/* the cards: one question, N answers, the chosen one lit and checked */
body.studio .gl-choices { display: block; }
body.studio .choice {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
  width: 100%; padding: 16px 18px; border: 1px solid var(--border); border-radius: var(--radius);
  background: transparent; text-align: left; cursor: pointer;
  transition: border-color 150ms ease, background-color 150ms ease;
}
body.studio .choice + .choice { margin-top: 10px; }
body.studio .choice b { display: block; font-weight: 500; font-size: 1.05rem; }
body.studio .choice span span { display: block; margin-top: 3px; color: var(--muted); font-size: 0.9rem; }
body.studio .choice:hover { border-color: var(--border-strong); }
body.studio .choice:has(input:checked) { background: var(--surface-3); border-color: var(--border-strong); }
body.studio .choice:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .choice__check { flex: none; color: var(--accent); opacity: 0; margin-top: 3px; }
body.studio .choice:has(input:checked) .choice__check { opacity: 1; }
body.studio .notify-line { margin-top: 18px; color: var(--muted); font-size: 0.95rem; }
/* THE REACH SENTENCE IS SUBTEXT (product owner, r10 item 2). It used to wear
   `.core` — a clamp up to 1.3rem — inside a sheet whose every other line is
   0.9rem, which made a consequence read as the headline over the two decisions
   that cause it. The FIGURES keep the house light: the number is the copy. */
body.studio .notify-line__said { margin: 0; font-size: 0.95rem; --lh: 1.55; line-height: var(--lh-snap); }
body.studio .notify-line__said .num { font-weight: 600; color: var(--accent); }
/* A CHOICE THAT IS NOT AN ANSWER (product owner, r10 item 4). On a free door
   "Only people who can get in" is the same list as "Everyone who follows you",
   so it is disabled and carries its reason. Dimmed, not hidden: a control that
   vanishes reads as a control that broke, and the reason is the point. */
body.studio .choice--off { cursor: default; opacity: 0.5; }
body.studio .choice--off:hover { border-color: var(--border); }
/* A <button> WEARS THIS TOO since r12 ("Restore default" on the Advanced
   sheet, "Maybe later" on the share sheet), so the UA's button box has to come
   off here rather than in two places: without it Chrome draws its own grey
   fill and its own font, which is what "Maybe later" shipped as. font-family
   and letter-spacing only — never the `font` shorthand, which would take this
   rule's own font-size with it. */
/* `[hidden]` HAS TO WIN here too — the same arithmetic as `.btn[hidden]`
   (r14 item 5/6a): a rule that gives an element a display out-ranks the UA's
   own `[hidden] { display: none }`, so "Restore default" carried the attribute
   and stayed on the row. */
body.studio .link-u[hidden] { display: none; }
body.studio .link-u {
  display: inline-block; color: var(--muted); font-size: 0.82rem;
  text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--border-strong);
  appearance: none; background: none; border: 0; padding: 0; cursor: pointer;
  font-family: inherit; letter-spacing: inherit;
}
body.studio .link-u:hover { color: var(--text); }

/* a switch over a checkbox: the state is the input's, and :has drives it */
body.studio .toggle-row { display: flex; align-items: center; justify-content: space-between; gap: 20px; cursor: pointer; }
body.studio .toggle-row b { display: block; font-weight: 500; font-size: 1.05rem; }
body.studio .toggle-row span:not(.switch) { display: block; margin-top: 3px; color: var(--muted); font-size: 0.92rem; }
body.studio .switch {
  flex: none; width: 50px; height: 28px; border-radius: 999px; position: relative;
  border: 1px solid var(--border-strong); background: var(--surface-3); cursor: pointer;
  transition: background 150ms ease, border-color 150ms ease;
}
body.studio .switch::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px;
  border-radius: 50%; background: var(--dim);
  transition: transform 150ms var(--ease), background 150ms ease;
}
body.studio .toggle-row:has(input:checked) .switch { background: var(--text); border-color: var(--text); }
body.studio .toggle-row:has(input:checked) .switch::after { background: var(--bg); transform: translateX(22px); }
body.studio .toggle-row:has(input:focus-visible) .switch { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The destinations board's Default column uses the same switch as a button_to
   submit, where the state is the server's answer in aria-checked. */
body.studio button.switch { appearance: none; padding: 0; }
body.studio .switch[aria-checked="true"] { background: var(--gold); border-color: var(--gold); }
body.studio .switch[aria-checked="true"]::after { background: var(--gold-ink); transform: translateX(22px); }
/* A LOCKED SWITCH IS A GOLD PILL WITH A LOCK, AND IT HAS NO THUMB (Maaz
   2026-09-14). `[aria-checked="true"]` already fills the track; the thumb is
   the affordance of a control that MOVES, and drawing its 20px gold-ink knob
   on that gold at the far right read as a dark blob half off the pill (his
   screenshot of the Automations page). `content: none` is what stops the
   pseudo being GENERATED, and it ties with `.switch::after`'s own
   `content: ""` (0,2,2 both) — so it wins on SOURCE ORDER and this pair has
   to ship after the base rules above. It is deliberately NOT scoped to one
   page's rows: a locked switch has no thumb wherever it lands, and fencing
   the fix to `.au-row` is how the next host inherits the blob. The dim the
   locked cells used to wear came OFF with it — the lock is what says the
   control cannot move, and a 50%-opacity dark glyph on a 50%-opacity gold
   track is the one reading of "locked" nobody can actually see. */
body.studio .switch--locked {
  display: flex; align-items: center; justify-content: center;
  color: var(--gold-ink); cursor: default;
}
body.studio .switch--locked::after { content: none; }

/* ---------- BOX OFFICE (Maaz, 2026-09-12) ----------
   Fenced: `.bo-*` is this screen's own furniture and no other screen wears it.
   It reuses the declared vocabulary on purpose — `.form-card` and its
   sections, `.title-input`, `.field-label`, `.pill-row`/`.pill`,
   `.toggle-row`/`.switch`, `.summary`, `.tabs`/`.tab` and `.roster` — because
   two near-identical blocks is how two screens start disagreeing about which
   element carries a stroke (the r8 hazard). The only NEW boxes here are the
   two-column grid, the money field's adornment and the lede's rule. */

/* THE LEDE SITS UNDER THE TAB RAIL (the mock's own placement), which is also
   where the mock's hairline is: `.tabs` carries `border-bottom: 0`, so the
   rule belongs to the block under it. */
/* THE LEDE ROW CARRIES THE PAGE'S ONE BUTTON ON THE RIGHT (Maaz 2026-09-13):
   New pass / New drop sits beside the sentence, not a row below it. */
body.studio .bo-lede { display: flex; align-items: center; justify-content: space-between;
  gap: 24px; flex-wrap: wrap; margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border); }
body.studio .bo-lede .page-head__deck { margin-top: 0; flex: 1 1 auto; min-width: 0; }
body.studio .bo-lede__act { flex: none; }

/* THE EMPTY CARD IS THE CREATE ACTION (Maaz 2026-09-14) — his blue mocks 1 & 5
   restyled into our gold/dark: a two-column card, the sell + one gold CTA on
   the left, a GENERIC preview on the right (no fake price, no blue button, no
   lock-mocked product claim). It stacks on a narrow desk. While it shows, the
   intro row + New pass/New drop are hidden, so this holds the page's one CTA. */
body.studio .bo-empty {
  margin-top: 22px; padding: 34px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface);
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 300px); gap: 34px; align-items: center;
}
body.studio .bo-empty__main { min-width: 0; }
body.studio .bo-empty__head { margin: 0; font-family: var(--display); font-size: 1.45rem; font-weight: 800; --lh: 1.1; line-height: var(--lh-snap); }
body.studio .bo-empty__body { margin: 12px 0 0; max-width: 46ch; color: var(--muted); font-size: 0.95rem; }
body.studio .bo-empty__points { list-style: none; margin: 18px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
body.studio .bo-empty__point { display: flex; align-items: center; gap: 10px; font-size: 0.9rem; color: var(--text); }
body.studio .bo-empty__point svg { flex: none; color: var(--gold); }
body.studio .bo-empty__cta { margin-top: 24px; }
body.studio .bo-empty__aside { min-width: 0; }
body.studio .bo-empty__preview {
  display: flex; flex-direction: column; gap: 8px;
  padding: 18px; border: 1px solid var(--hair-gold); border-radius: var(--radius);
  background: color-mix(in srgb, var(--gold) 6%, var(--surface-2));
}
body.studio .bo-empty__preview--pass { gap: 6px; }
body.studio .bo-empty__preview--drop { gap: 10px; }
body.studio .bo-empty__eyebrow {
  font-family: var(--mono); font-size: 0.66rem; font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--gold);
}
body.studio .bo-empty__pvtitle { font-weight: 700; font-size: 1rem; color: var(--text); }
body.studio .bo-empty__pvsub { font-size: 0.82rem; color: var(--dim); }
body.studio .bo-empty__pvrule { height: 1px; margin: 4px 0; background: var(--border); }
body.studio .bo-empty__pvrow { display: flex; align-items: center; gap: 8px; font-size: 0.86rem; color: var(--muted); }
body.studio .bo-empty__pvrow svg { flex: none; color: var(--dim); }
body.studio .bo-empty__pincard {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 14px; border: 1px solid var(--hair-gold); border-radius: var(--radius-sm);
  background: var(--surface);
}
body.studio .bo-empty__buy {
  flex: none; padding: 6px 16px; border-radius: 999px;
  background: var(--gold); color: var(--gold-ink); font-weight: 700; font-size: 0.82rem;
}
@media (max-width: 720px) {
  body.studio .bo-empty { grid-template-columns: 1fr; gap: 24px; }
}

/* The page's one gold action, hard right, above the table (mock 01). */
body.studio .bo-act { display: flex; justify-content: flex-end; margin-top: 26px; }

/* The questions and the card, the composer's own two-column shape. A sticky
   panel in ONE column parks itself over the form, which is why the preview
   stands still below 900px. */
body.studio .bo-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) 328px;
  gap: 30px; margin-top: 22px; align-items: start;
}
body.studio .bo-grid .form-card > section > * + * { margin-top: 12px; }
body.studio .bo-grid .field-label { margin-bottom: 0; }
body.studio .bo-preview .drop-pin__card { margin-bottom: 0; }
/* THE PREVIEW'S HEAD, both hosts (Maaz 2026-09-15): the eyebrow left, what
   the frame is for hard right. It WRAPS, because in a 276px tray the two
   together do not fit one line — and the sentence is the half that gives. */
body.studio .bo-preview__head { flex-wrap: wrap; gap: 4px 10px; margin-bottom: 10px; }
body.studio .bo-preview__said { color: var(--dim); font-size: 0.78rem; }

/* A select dressed as the house's own field — same box as `.title-input`, so
   the stack reads as one column of controls. */
/* THE FORM'S ORDINARY FIELD BOX — deliberately NOT `.title-input`, which is
   the composer's MARQUEE title line (a 2rem borderless display field): worn by
   a title and a one-line blurb it made both of them enormous, which is a thing
   only a screenshot could say. */
body.studio .bo-in, body.studio .bo-select, body.studio .bo-money__in {
  width: 100%; background-color: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
  padding: 11px 14px; font: inherit; line-height: normal;
}
/* THE SELECT KEEPS THE HOUSE CHEVRON (Maaz 2026-09-13). The shared rule above
   sets a background COLOR, not the `background` shorthand, so the global
   `select`'s chevron background-image survives — this only leaves room for it,
   so "A new offer" reads as a dropdown and not a text field. */
body.studio .bo-select { padding-right: 32px; }
body.studio .bo-select--sm { width: auto; flex: none; }
body.studio .bo-row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
/* TITLE + THE OFFER PICKER ON ONE ROW: the title takes the width, the compact
   select sits at its natural size on the right. */
body.studio .bo-row--title .bo-in { flex: 1 1 auto; width: auto; min-width: 0; }
/* THE ⓘ BESIDE A LABEL — display only, so it says the one fact the deleted
   essay held, on hover/focus. `[data-tip]` is the house tooltip; the label is
   inline-flex so the glyph sits on the baseline beside the words. */
body.studio .bo-field__label { display: inline-flex; align-items: center; gap: 6px; }
body.studio .bo-info { display: inline-flex; color: var(--muted); cursor: help; }
body.studio .bo-info:hover, body.studio .bo-info:focus-visible { color: var(--gold); outline: none; }
/* CONFIRMATION: A REAL CHECKBOX ON THE LEFT (Maaz 2026-09-13), one line, no
   toggle. The box keeps the UA's own control; the row is a plain flex row. */
body.studio .bo-check { display: flex; align-items: center; gap: 10px; margin-top: 20px;
  cursor: pointer; font-size: 0.92rem; }
body.studio .bo-check__box { flex: none; width: 18px; height: 18px; margin: 0; accent-color: var(--gold); cursor: pointer; }

/* THE PRICE WEARS ITS UNIT (his item 6). The sign is inside the field's own
   box rather than beside it, so the two read as one control; the input keeps
   the border so focus lands where a creator can see it. */
body.studio .bo-money { position: relative; display: inline-flex; align-items: center; min-width: 0; }
body.studio .bo-money__sign {
  position: absolute; left: 14px; color: var(--muted); font-size: 0.95rem; pointer-events: none;
}
body.studio .bo-money__sign--after { left: auto; right: 14px; }
body.studio .bo-money__in { width: 9ch; padding-left: 28px; }
body.studio .bo-money__sign--after ~ .bo-money__in,
body.studio .bo-money:has(.bo-money__sign--after) .bo-money__in { padding-left: 14px; padding-right: 28px; }

body.studio .bo-field { display: block; }
body.studio .bo-field__label { display: block; margin-bottom: 8px; font-size: 0.92rem; color: var(--muted); }
body.studio .bo-field__said { display: block; margin-top: 6px; color: var(--dim); font-size: 0.82rem; }
body.studio .bo-said { margin-top: 10px; }
body.studio .bo-said[hidden] { display: none; }

/* The picked chip takes the GOLD stroke the mock draws — one override on THIS
   row, never a second pill recipe (the composer's own duration chips keep the
   house's surface-3 answer). */
body.studio .bo-clock .pill:has(input:checked) > span { border-color: var(--gold); }
/* Custom is a field wearing a pill, so the box loses its own chrome and the
   label's span carries the stroke.
   AND IT HAS TO CLIMB OUT OF `.pill`'s OWN RULE: `body.studio .pill
   input:not(.sr-only)` is `position: absolute; opacity: 0; width: 0` — written
   to hide a radio, and it hid this number box completely (found in a browser,
   invisible to every assert_select, because the markup was right). The radio
   beside it is still `.sr-only` and still hidden by that same rule. */
body.studio .bo-clock__custom > span { padding: 0; }
/* THE WIDTH RIDES THIS RULE AND NOT THE ONE BELOW, which is arithmetic and
   not taste: `.pill input:not(.sr-only)` is (0,3,2) and says `width: 0`, so a
   `width` on the plain `.bo-clock__in` selector (0,2,1) loses to it and the
   box measures nothing however visible it is. */
body.studio .pill input.bo-clock__in {
  /* WIDE ENOUGH FOR ITS OWN PLACEHOLDER. `box-sizing: border-box` is the
     house's global reset, so the 18px-a-side padding below comes OUT of this
     width — at 8.5ch the word "Custom" was clipped to "Cu" (seen in a
     screenshot, which is the only place a clipped placeholder exists). */
  position: static; opacity: 1; width: 8.5em; height: auto;
}
body.studio .bo-clock__in {
  background: none; border: 0; color: var(--text);
  padding: 9px 18px; font: inherit; line-height: normal; text-align: center;
}
body.studio .bo-clock__in:focus { outline: none; }
body.studio .bo-in::placeholder { color: var(--muted); }
body.studio .bo-seats { max-width: 240px; }

/* DROP-FORM QA (2026-09-13). The mock's own layout: a wider price field, the
   Countdown label inline with its chips, and the Discount-to-show + Seat-limit
   row. Each class is also declared here because the #415 guard requires every
   class these views render to be styled. */
/* THE PRICE IS WIDER THAN THE % BOX (his own note): a pass-filled `$2.5` was
   clipping its last digit at 9ch. Same specificity as `.bo-money__in` and
   later in the sheet, so this width wins; the discount % keeps the narrow one. */
body.studio .bo-price { width: 13ch; }
/* COUNTDOWN LABEL INLINE WITH THE CHIP ROW: the label's stacked margins are
   dropped so it sits on the chips' own baseline, and the pill row takes the
   rest of the line. */
body.studio .bo-clock-row { align-items: center; gap: 14px; }
body.studio .bo-clock-row__label { margin: 0; flex: none; }
body.studio .bo-clock-row .pill-row { flex: 1 1 auto; }
/* ── THE FOUR FIELDS ARE TWO EQUAL HALVES, ON BOTH HOSTS (Maaz's launch QA,
   2026-09-16) ──────────────────────────────────────────────────────
   "Price and the frequency dropdown equal width on the same row so they
   align. Discount to show and Seat limit: stretch both, equal width, the
   same left/right edges as the row above."

   MEASURED BEFORE IT (the desk, 1512): a 112px price box beside a 117px
   select, the pair huddled in the left third of a 708px row; under them a
   77px % box at the row's left edge and a 200px seat field hard right with
   194px of hole between the two groups. Four controls, four widths, and no
   two edges shared — which is the screenshot.

   IT IS THE TRAY'S OWN r3 CONSTRUCTION, UNSCOPED. Two grid TRACKS rather
   than two flex bases, and that is arithmetic and not taste: a
   `flex-basis: 0` under the house's `box-sizing: border-box` reset is clamped
   up to the item's own padding, and the select carries 14+32 of it against
   the money box's 0, so the flex pair came back 121 and 169 (r3 measured it).
   Two tracks are equal without either item's padding entering into it.

   `.bo-row--price` and `.bo-row--discount` are why those rows carry
   modifiers: four rows on this form wear `.bo-row` (title, price, countdown,
   discount), and a grid on the bare class would have halved the title field
   and the chip row too.

   AND THE LABEL STACKS OVER ITS FIELD, which is the half that makes "same
   left/right edges" possible at all: a label INLINE to the field's left can
   only ever start the field after the words, and the two labels are
   different lengths ("Discount to show ⓘ" measured 133px against "Seat
   limit ⓘ"'s 80) — so two EQUAL fields with inline labels would begin at
   two different x. Stacked, all four controls share the row's own two edges.
   The label keeps its inline-flex box so the ⓘ stays centred on the words
   (a baseline-aligned SVG overhangs its line box by the font's descender —
   r8), and it keeps sitting OUTSIDE the field, which is what Maaz's
   2026-09-13 ruling asked of it. */
body.studio .bo-row--price,
body.studio .bo-row--discount {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
/* The groups top-align: if one label wraps at a narrow width the other must
   not slide down half a line to meet it (`.bo-row`'s own `align-items:
   center`, which is right for the price row and wrong for a stacked pair). */
body.studio .bo-row--discount { align-items: start; }
/* Each half fills its own track. `.bo-money` is inline-flex (blockified as a
   grid item, so the `$` stays inside the field's own box), and the price
   field, the % field and the seat field each carry a width of their own
   further up the sheet. */
body.studio .bo-row--price > .bo-money,
body.studio .bo-row--price > .bo-select--sm {
  width: auto; min-width: 0; max-width: none;
}
body.studio .bo-row--price .bo-price { width: 100%; }
/* THE DISCOUNT LABEL KEEPS ITS INLINE-FLEX BOX (its ⓘ aligned with the
   words), overriding the block `.bo-field__label`; later in the sheet at
   equal specificity. */
body.studio .bo-field__label--inline { display: inline-flex; align-items: center; gap: 6px; margin-bottom: 0; }
/* LABEL OVER FIELD, one shape on both sides of the row (Maaz 2026-09-13's
   "the same pattern on both sides", stacked). */
body.studio .bo-discount,
body.studio .bo-seatlimit { display: grid; gap: 8px; }
body.studio .bo-discount > .bo-money { width: auto; min-width: 0; }
body.studio .bo-discount .bo-money__in { width: 100%; }
body.studio .bo-row--discount .bo-seats { width: 100%; max-width: none; }
/* AND THE TWO ⓘ TIPS ANCHOR TO THEIR OWN GROUP, ON BOTH HOSTS. The base tip
   is `left: 50%`, `translateX(-50%)` and `nowrap`, so a sentence hanging off a
   14px glyph is the hidden-horizontal-scroll class this house has already hit
   on a chip, a circle, a tile and a table header: a width clamp cannot fix
   where the box STARTS, so the containing block moves instead.

   IT IS NOT A TIDY-UP — IT IS THIS ROUND'S OWN BUG, MEASURED. Stacking the
   labels moved the seat ⓘ into the row's RIGHT track, and at a true 390 its
   nowrap sentence put 108px of scroll on the document with NO element's
   bounding box outside the viewport (the tell for this class: an absolutely
   positioned pseudo is part of scrollable overflow). The tray already carried
   these three rules for the same reason at its own width; unscoped, the tip
   can never leave the form at any width on either host.

   Both transform states are re-declared — the base hover carries the
   translateX that pairs with the `left: 50%` this replaces. */
body.studio .bo-discount,
body.studio .bo-seatlimit { position: relative; }
body.studio .bo-info[data-tip] { position: static; }
body.studio .bo-info[data-tip]::after {
  /* AND `box-sizing: border-box` ON THE PSEUDO, which is not the same thing
     as the sheet's universal reset — `*` does not match `::after`. Without it
     the tip's own 10px-a-side padding and its hairline sit OUTSIDE the
     `max-width: 100%`, and the box lands exactly 22px past the container it
     was just clamped to (measured in the tray, and the same 22px the
     analytics tiles paid in r15). */
  box-sizing: border-box;
  left: 0; right: auto; transform: translateY(2px);
  width: max-content; max-width: 100%; white-space: normal; text-align: left;
}
body.studio .bo-info[data-tip]:hover::after,
body.studio .bo-info[data-tip]:focus-visible::after { transform: translateY(0); }
body.studio .summary__head .eyebrow { margin: 0; }
body.studio .bo-mail { margin-top: 20px; }
body.studio .bo-submit { display: flex; gap: 10px; align-items: center; margin-top: 22px; flex-wrap: wrap; }

/* The shelf. `.roster` carries no min-width, which is what lets it squeeze to
   390px on its own — so there is no scroll container here and the document's
   own no-horizontal-scroll fence stays absolute. */
body.studio .bo-table { margin-top: 26px; }
body.studio .bo-table th[scope="row"] .chip { margin-left: 8px; }
/* THE KEBAB COLUMN, and its open panel wins over the rows under it (the Lives
   board's own rule): an open menu is taller than its row, so the row lifts on a
   stacking context while it is open. */
body.studio .bo-table tr:has(details.menu[open]) { position: relative; z-index: 40; }

@media (max-width: 899px) {
  body.studio .bo-grid { grid-template-columns: minmax(0, 1fr); }
  body.studio .bo-preview { position: static; }
}

/* THE SUMMARY COLUMN */
/* AND UNDER THE COMPOSER'S OWN BAR (Maaz 2026-09-13). 58px is the house's
   approximation of the MASTHEAD, and this page no longer draws one — its
   chrome is `gl-bar`, whose height is the same `--gl-bar-h` this reads, so the
   two sticky boxes cannot overlap whatever that number becomes. Scoped to the
   composer's shell: `.summary` has four hosts and the other three keep the
   masthead. */
body.studio .golive-shell .summary { top: calc(var(--gl-bar-h) + 20px); }
body.studio .summary {
  position: sticky; top: calc(58px + 20px); min-width: 0;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 18px 18px 22px;
}
body.studio .summary__head { display: flex; align-items: center; justify-content: space-between; color: var(--muted); font-size: 0.95rem; }
body.studio .summary__head button { background: none; border: 0; color: var(--muted); cursor: pointer; padding: 4px; }
body.studio .summary__head button:hover { color: var(--text); }
body.studio .summary__head button[hidden] { display: none; }
/* THE COVER FRAME (product owner 2026-09-03). The picture is a REAL IMAGE now
   — the creator's own, or the house's card (app/assets/images/default-cover.*)
   — so the stack of radial gradients and the text lockup that used to stand in
   for one are gone. A CSS placeholder only ever existed inside body.studio,
   which is exactly why no fan surface ever showed it.

   One frame, three states: the picture, REPLACE over it on hover, and the
   empty state the trash leads to. */
body.studio .cover {
  display: flex; align-items: center; justify-content: center; text-align: center;
  position: relative; overflow: hidden; cursor: pointer;
  margin-top: 14px; aspect-ratio: 16 / 9; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface-3);
}
body.studio .cover:hover { border-color: var(--border-strong); }
/* The keyboard's way in: the input is clipped, so the FRAME wears the ring. */
body.studio .cover:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .cover__img { position: relative; z-index: 1; width: 100%; height: 100%; object-fit: cover; }
body.studio .cover__img[hidden] { display: none; }
/* The invitation, and it is only ever reached by taking a picture off. */
body.studio .cover__empty { position: relative; z-index: 1; padding: 14px; color: var(--muted); }
body.studio .cover__empty[hidden] { display: none; }
body.studio .cover__empty .lucide { display: block; margin: 0 auto 8px; color: var(--dim); }
body.studio .cover__empty b { display: block; font-size: 0.98rem; font-weight: 500; color: var(--text); }
body.studio .cover__empty span { display: block; margin-top: 4px; font-size: 0.8rem; }
/* The file input stays in the form and out of the flow — clipped, not
   display:none, because a keyboard cannot focus what isn't rendered. */
body.studio .cover__pick {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
body.studio .cover__error { margin-top: 10px; color: var(--live); font-size: 0.84rem; }
body.studio .cover__error[hidden] { display: none; }
body.studio .summary__rule { height: 1px; background: var(--border); margin: 18px 0 16px; }
body.studio .summary h3 { font-size: 1.2rem; font-weight: 500; }
body.studio .summary__line { margin-top: 6px; color: var(--muted); font-size: 0.92rem; }
body.studio .summary__line span[hidden] { display: none; }
body.studio .summary .chip { margin-top: 10px; }
body.studio .summary .chip[hidden] { display: none; }
body.studio .summary__passes { margin-top: 6px; color: var(--muted); font-size: 0.86rem; }
body.studio .summary__passes[hidden] { display: none; }
body.studio .summary__dests { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
/* EVERY MARK ON THIS CARD IS LIT (product owner, r9 item 5). A circle only
   reaches this row once it is PICKED — the composer's summarizeStages hides the
   unpicked ones, and the live's own card reads its armed rows — so the row is a
   list of destinations that ARE on. Drawing them in --dim (the picker's
   not-chosen colour) said the opposite of what the row means. Same gold ring
   and gold mark the house's own always-on circle wears, and it needs no :has()
   because there is no input here to check. */
body.studio .summary__dests .dest {
  width: 28px; height: 28px; cursor: default;
  color: var(--gold); border-color: var(--hair-gold);
}
body.studio .summary__dests .dest svg { width: 13px; height: 13px; }
body.studio .summary__label { color: var(--muted); font-size: 0.95rem; }
/* THE (i) BESIDE "NOTIFYING" (product owner, r10 item 2) — what the two
   figures under it count. The tip is a SENTENCE, so this one wraps and anchors
   LEFT: the base [data-tip]::after is `nowrap` and centred on its control,
   which for 140 characters hung on a 20px glyph in a 328px column lays out a
   box past both edges. Both transform states are re-declared, because the base
   rule's hover state carries the translateX(-50%) this one must not inherit. */
body.studio .tip-i {
  appearance: none; background: none; border: 0; padding: 0; margin-left: 6px;
  color: var(--dim); cursor: help; display: inline-flex; vertical-align: -2px;
}
body.studio .tip-i:hover, body.studio .tip-i:focus-visible { color: var(--muted); }
body.studio .tip-i[data-tip]::after {
  white-space: normal; width: 236px; left: 0; text-align: left;
  transform: translateX(0) translateY(2px);
}
body.studio .tip-i[data-tip]:hover::after,
body.studio .tip-i[data-tip]:focus-visible::after { transform: translateX(0) translateY(0); }
/* The reach figures. GOLD here is the product owner's own call on this card
   (r4-ref-6) and it is the one figure block in the studio that wears it: the
   number is what the tap is about to spend on somebody's attention. */
body.studio .notify-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px 20px; margin-top: 12px; }
body.studio .notify-grid b {
  display: block; font-weight: 500; font-size: 1.45rem; --lh: 1.1; line-height: var(--lh-snap); color: var(--gold);
  font-variant-numeric: tabular-nums lining-nums;
}
body.studio .notify-grid span { display: block; font-size: 0.8rem; color: var(--muted); margin-top: 2px; }

@media (max-width: 899px) {
  /* One column: the summary lands under the questions and stops being sticky —
     a sticky panel in a single column parks itself over the form. */
  body.studio .golive-grid { grid-template-columns: minmax(0, 1fr); }
  body.studio .summary { position: static; }
  /* THE BAR KEEPS ITS SHAPE and simply wraps: the name and the arrow on one
     row, the said line and the one tap on the next, hard right (the cluster's
     own `justify-content`). Nothing here restyles the acts — the r3 rule that
     they never wrap INTERNALLY holds at every width. */
  body.studio .gl-bar { align-items: flex-start; }
  body.studio .gl-bar__acts { flex: 1 1 100%; }
}

/* ---------- THE DESTINATIONS BOARD (prototype r5-ref-7) --------------------
   Six rows, one order, and since 2026-09-10 THREE columns: Destination, then
   a LIVE cell and a CLIPS cell, each with its own state and its own one
   control. It was four (Destination / Status / Default / Actions) beside a
   separate "Clips post to" rowlist; the Default switch moved into Manage to
   pay for the Clips column, because four state columns on a table a phone has
   to read is how a column ends up hidden at a breakpoint.
   Fenced: .dest5-* and .st5* are this screen's own furniture.
   `.page-title` and `.page-head__id` are the studio's
   own page heading — both prototype screens landed the same rule the same
   night, so they live ONCE, in the board's block above. */
/* r3: .dest5-head/.dest5-sum are gone — the board wears the shared
   studio/_page_header now, and its count line folded into the header's row. */

/* The one gold-bordered control on the page. Gold because a sound check is
   the money question asked early — "will tonight's feeds actually carry?" —
   and it is bordered, never filled: the filled primary belongs to GO LIVE. */
body.studio .btn--gold-outline {
  border-color: color-mix(in srgb, var(--gold) 65%, transparent); color: var(--gold);
  font-weight: 500; padding: 12px 20px; font-size: 1rem;
  display: inline-flex; align-items: center; gap: 10px;
}
body.studio .btn--gold-outline:hover { border-color: var(--gold); background: color-mix(in srgb, var(--gold) 8%, transparent); }

/* THE SCROLL IS KEYED TO THE MARKER, NOT THE CLASS (Followers r2): the wrap
   only becomes a scroll region where the view asks by name, and the house
   fence's DOCUMENT check is untouched. .card--flush is `overflow: hidden` and
   overflow-x wins on its own axis — and an overflow box clips its content
   whether or not it scrolls, with no z-index escaping an ancestor's clip (the
   Team round) — so the containment STANDS DOWN while a Manage menu is open.
   Both halves are needed: the card's own hidden overflow is what clipped the
   old kebab on every viewport. */
body.studio .dest5-card { margin-top: 26px; overflow: visible; }
body.studio .dest5-tablewrap { overflow: visible; }
body.studio .dest5-tablewrap[data-hscroll] { overflow-x: auto;
  overscroll-behavior-x: contain; scrollbar-color: var(--border-strong) transparent; }
body.studio .dest5-tablewrap[data-hscroll]:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
body.studio .dest5-card:has(details.menu[open]) .dest5-tablewrap[data-hscroll] { overflow: visible; }
body.studio .dest5-note { margin-top: 10px; }

/* TABLE-LAYOUT: FIXED, three columns at ~40/30/30 (Maaz 2026-09-10). The old
   `width: 1px` auto-layout trick let column one (Destination) eat every pixel
   the other two didn't claim — at 1400 that parked the Live header in the
   card's right third, because the name column's min-content floor was the
   ONLY floor in play. Fixed layout gives every column an actual share instead
   of a leftover: the widths below are taken from the header row's own cells
   (fixed layout reads the FIRST row — thead here, or tbody on the headerless
   "Also connected" table below, which carries the same nth-child rule).
   `min-width` on the table is the honest floor now instead of no floor at
   all: at 390 the wrap scrolls (data-hscroll, the owner's own opt-in) rather
   than squeezing three real columns into a phone. */
body.studio .dest5-table {
  width: 100%; min-width: 760px; table-layout: fixed; border-collapse: collapse; font-size: 1rem;
}
/* Percentages of the table, not of leftover space — Live and Clips Posting
   stay nowrap (their pill + one control never wraps mid-row) and the name
   column truncates instead of stretching past its own 40%. */
body.studio .dest5-table th:first-child, body.studio .dest5-table td:first-child { width: 40%; }
body.studio .dest5-table th:nth-child(2), body.studio .dest5-table th:nth-child(3),
body.studio .dest5-table td:nth-child(2), body.studio .dest5-table td:nth-child(3) {
  width: 30%; white-space: nowrap;
}
body.studio .dest5-table th {
  text-align: left; padding: 18px 22px; font-weight: 400; color: var(--muted);
  border-bottom: 1px solid var(--border);
}
body.studio .dest5-table td { padding: 20px 22px; border-bottom: 1px solid var(--border); vertical-align: middle; }
body.studio .dest5-table tr:last-child td { border-bottom: 0; }
body.studio .dest5-table .menu__panel { right: 0; left: auto; min-width: 200px; }
body.studio .dest5-table .btn--gold-outline { padding: 10px 20px; font-size: 0.95rem; }
body.studio .dest5-id { display: flex; align-items: center; gap: 18px; min-width: 0; }
/* The mark, centred in its own circle — grid place-items, so a mark whose
   artboard isn't square (X) still sits on the middle of the plate. */
body.studio .logo {
  width: 56px; height: 56px; flex: none; border-radius: 50%;
  background: var(--surface-3); border: 1px solid var(--border);
  display: grid; place-items: center;
}
body.studio .logo svg { display: block; overflow: visible; }
/* The name truncates now that its column is a fixed 40% rather than a
   stretch-to-fit: an ellipsis on the label, never "YouTu / be" wrapped down
   three letters. The subtitle spans below keep `overflow-wrap: anywhere` — a
   channel URL still has to break somewhere. */
body.studio .dest5-id b {
  display: block; font-weight: 500; font-size: 1.05rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* THE TEXT COLUMN'S spans, and only those (r7, measured). This used to read
   `.dest5-id span`, which also matched the plate itself — `<span class="logo">`
   is a span inside .dest5-id, and at (0,2,2) that rule outranked the (0,2,1)
   one above it. The plate lost its `display: grid` to `display: block`, so
   every mark sat on the content-box top instead of the middle: 15px high in
   all six rows, verified in the browser at 1512x982 before and after. It also
   took `margin-top: 3px` (the whole plate nudged down) and `color: var(--muted)`
   — which is currentColor for the X mark, so X was drawn in the caption grey
   rather than the row's own ink. Scoping to the text div fixes all three. */
body.studio .dest5-id > div span { display: block; color: var(--muted); font-size: 0.92rem; margin-top: 3px; --lh: 1.5; line-height: var(--lh-snap); }
body.studio .dest5-id > div { min-width: 0; overflow-wrap: anywhere; }

/* The state, as a dot and a phrase. Three tones, and the words are the sound
   check's own (§103) so a feed reads identically on both screens. */
body.studio .st5 { display: inline-flex; align-items: center; gap: 8px; padding: 8px 16px; border-radius: 999px; font-size: 0.92rem; }
body.studio .st5::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
body.studio .st5--ok { color: var(--ok); background: color-mix(in srgb, var(--ok) 12%, transparent); }
body.studio .st5--warn { color: var(--gold); background: color-mix(in srgb, var(--gold) 12%, transparent); }
body.studio .st5--bad { color: var(--live); background: color-mix(in srgb, var(--live) 12%, transparent); }
body.studio .st5-again { display: inline-block; margin-left: 16px; color: var(--gold); text-decoration: underline; text-underline-offset: 3px; font-size: 0.92rem; }
body.studio .dest5-dash { color: var(--dim); }
/* ONE LEG'S CELL: the state, then the one control that state allows. nowrap
   because both cells are sized to their content — a pill that wraps under its
   own Manage button reads as two rows in one cell. LEFT-ALIGNED (Maaz
   2026-09-10): `th` is `text-align: left`, so a `flex-end` cluster packed
   itself against the cell's right edge while "Live"/"Clips Posting" sat over
   the cell's LEFT padding — the header named a column the row's own content
   never sat under. The cluster starts at the same left edge as its header
   now; the column is still `white-space: nowrap`, so nothing re-stretches. */
body.studio .dest5-leg {
  display: flex; align-items: center; gap: 14px; flex-wrap: nowrap; justify-content: flex-start;
}
body.studio .dest5-leg__state { display: inline-flex; align-items: center; gap: 12px; flex-wrap: wrap; }
body.studio .dest5-leg__who {
  color: var(--muted); font-size: 0.92rem;
  white-space: normal; max-width: 16ch; overflow-wrap: anywhere;
}
body.studio .dest5-leg form, body.studio .dest5-leg__form { display: inline-flex; margin: 0; flex: none; }
body.studio .dest5-leg .btn { flex: none; }
/* A leg this platform hasn't got. Two words and no [data-tip] — a
   sentence-length tip is nowrap and centred on its own element, so on the last
   column it lays out a box past the card and pushes the document sideways. The
   reason is said once, in prose, under the rows that need it. */
body.studio .dest5-na { color: var(--dim); white-space: nowrap; }

/* MANAGE, and it must actually win. `.menu > summary` is (0,1,1) and a bare
   `.dest5-manage` is (0,1,0), so the shared 30x28 trigger box outranked the
   old `.dest5-dots` circle wholesale — the doubled-stroke lesson
   (`.topbar__me-menu > summary.topbar__me`) on the other axis. Naming the
   ELEMENT is what beats it, at (0,2,2). It is deliberately not written
   `body.studio .menu > summary…`: the scope law reads the first class after
   the fence, so that spelling would register `.menu` as studio furniture and
   strip the styling off the bell, the masthead and the follow disclosure. */
body.studio .dest5-menu { flex: none; }
body.studio summary.dest5-manage {
  width: auto; height: 40px; padding: 0 18px; border-radius: 999px;
  border: var(--hair-w) solid var(--border-strong); background: var(--surface-3);
  color: var(--text); font-size: 0.92rem; white-space: nowrap;
}
body.studio summary.dest5-manage:hover { border-color: var(--text); }
/* The default toggle lives in the panel now (2026-09-10): the item names what
   it is, the check states whether it is on, the tap toggles it. */
body.studio .dest5-default { justify-content: space-between; gap: 10px; }
body.studio .dest5-table tr:has(details.menu[open]) td { position: relative; z-index: 40; }

@media (max-width: 899px) {
  /* NO COLUMN IS HIDDEN ANY MORE. The trailing one is CLIPS — the half of the
     page this round exists to surface — so the table keeps all three and the
     wrap scrolls instead (data-hscroll, the owner's own opt-in).
     The legs stack their control under their state here, which is what keeps
     the scroll short: side by side, each cell demands pill + Manage; stacked,
     it demands the wider of the two, and the name column gets the difference. */
  body.studio .dest5-leg { flex-direction: column; align-items: flex-start; gap: 8px; }
  body.studio .dest5-table th, body.studio .dest5-table td { padding: 14px 12px; }
  body.studio .logo { width: 44px; height: 44px; }
  body.studio .st5-again { margin-left: 0; margin-top: 6px; }
}

/* The brand marks (PlatformLogosHelper): one colour on a control's face,
   full colour on the board. Never sized by the SVG's own attributes alone —
   the circle owns the geometry. */
/* inline-flex, not inline (r8): an SVG glyph on the BASELINE overhangs its line
   box by the font's descender, so the span it sits in came out 23.5px tall and
   every row that carries a destination glyph landed its bottom hairline on a
   half pixel. A flex box has no baseline to overhang. */
.brand { flex: none; display: inline-flex; align-items: center; }
.brand--stroke { fill: none; }
/* AN SVG ROOT CLIPS TO ITS OWN VIEWPORT unless told not to, and YouTube's mark
   is scaled past the 24-unit box on purpose so it stops reading as the small
   one in a row of six (PlatformLogosHelper::OPTICAL_SCALE). The element's box
   is unchanged — every glyph in a row is still one size — so this only lets
   the ~1px a side of ink paint. */
.brand--wide { overflow: visible; }
.brand--letter { font-family: var(--display); font-weight: 700; font-size: 1rem; --lh: 1; line-height: var(--lh-snap); }
/* The fix, in the row that needs it. Never live-red unless the check actually
   failed: an expiring sign-in is a today problem, not an emergency. */
body.studio .dest5-fix { max-width: 52ch; }
body.studio .dest5-fix--bad { color: var(--live); }
/* WIRE 15: the room's own line, above the board. Not a table row — it is not
   one of the six, and it never needs a status, a default or an action. */
body.studio .dest5-yours {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 20px;
}
body.studio .dest5-yours__label { font-weight: 600; }
body.studio .dest5-yours .num { color: var(--muted); }
body.studio .dest5-yours .chip { color: var(--accent); }

/* ---------- THE LOBBY'S TWO SHEETS (r9 B4/B5) ----------
   Not fenced: these are FAN surfaces, so nothing here may be written against
   body.studio (the studio-scope law's second invariant) — and `.gen-sheet` and
   `.adv` are both studio furniture, which is why the lobby gets its own two
   names rather than borrowing either.

   `.csheet` is the follow confirm — one sentence and two answers. `.rsvpq` is
   the creator's questions, which used to be a block welded to the bottom of
   the greenroom card and is a <dialog> now: the RSVP opens it, the button
   beside the clock re-opens it, and closing it costs nothing. */
.csheet::backdrop, .rsvpq::backdrop { background: rgb(0 0 0 / 0.7); backdrop-filter: blur(3px); }
.csheet, .rsvpq {
  padding: 0; border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text);
  max-height: calc(100vh - 48px); overflow: auto;
}
.csheet[open], .rsvpq[open] { display: block; }
.csheet { width: min(420px, calc(100vw - 32px)); }
.rsvpq { width: min(520px, calc(100vw - 32px)); text-align: left; }
.csheet__in { padding: 22px; }
.rsvpq__in { padding: 22px; }
.csheet__head, .rsvpq__head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 14px;
}
.csheet__head h2 { font-size: 1.02rem; --lh: 1.4; line-height: var(--lh-snap); }
.rsvpq__head h2 { font-size: 1.1rem; }
.rsvpq__head p { margin-top: 4px; }
.csheet__x, .rsvpq__x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: 0 0 auto; margin: -4px -4px 0 0;
  padding: 0; background: none; border: 0; border-radius: var(--radius-sm);
  color: var(--dim); cursor: pointer; transition: color 150ms ease;
}
.csheet__x:hover, .rsvpq__x:hover { color: var(--text); }
.csheet__x:focus-visible, .rsvpq__x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Two answers, and only one of them writes anything. `button_to` wraps its
   button in a form, so the form is what has to sit in the row. */
.csheet__foot { display: flex; align-items: center; gap: 10px; margin-top: 20px; }
.csheet__foot .btn { width: 100%; }
.csheet__go { flex: 1; }
.rsvpq__form { margin-top: 18px; }
.rsvpq__foot { margin-top: 18px; }
.rsvpq__foot .btn { width: 100%; }
.rsvpq__q { display: block; margin-bottom: 12px; }
.rsvpq__q > span { display: block; margin-bottom: 6px; font-size: 0.9rem; }
/* "You're in ✓" — the CTA's own shape, holding a STATE. Deliberately not
   tappable: the RSVP is done, and a button that answers nothing is worse than
   a sentence (r9 B3). */
.greenroom__in {
  border: 1px solid var(--border-strong); background: transparent; color: var(--muted);
  cursor: default; margin: 18px auto 0;
}
.greenroom__meta { gap: 10px; }
/* THE STATUS LINE above the CTA (r9, owner's ruling 3): the announced clock
   until the doors are opening, then what is actually happening. Empty on a
   night with no announced time, and an empty <p> must take no room. */
.greenroom__status { margin-top: 16px; color: var(--muted); font-size: 0.9rem; }
/* A poll or a multi-select, answered in the greenroom. The options are the
   creator's own words and the fan posts the position, never the label. */
.rsvpq__q--pick { border: 0; padding: 0; margin: 0 0 14px; }
.rsvpq__q--pick > legend { padding: 0; margin-bottom: 8px; font-size: 0.9rem; }
.rsvpq__opt {
  display: flex; align-items: center; gap: 10px; padding: 9px 12px; margin-bottom: 6px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); cursor: pointer;
  font-size: 0.92rem;
}
.rsvpq__opt:hover { border-color: var(--border-strong); }
.rsvpq__opt:has(input:checked) { border-color: var(--accent); }
.rsvpq__opt:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
/* THE ROOM'S OWN ANSWER, on the fan's own sheet (Maaz 2026-09-10). The same
   numbers the creator reads (RsvpQuestions.report), presented as a SHARE of
   the people who answered that one question.

   THE BAR IS A FILL BEHIND THE ROW, not a track beside it: the row is already
   the control, and a third column would squeeze the creator's own words at
   390px (which is why `.tally__bar` has to drop its bar on a phone and this
   one never does). The fill is the label's FIRST child and neither it nor the
   content carries a z-index — two positioned siblings paint in DOM order, so
   the words are on top by construction rather than by a stacking guess. */
.rsvpq__opt--result { position: relative; overflow: hidden; }
/* `:not()` and not source order: `> *` and `.rsvpq__fill` are the same
   specificity, so without it the fill's own `position: absolute` survives only
   as long as nobody moves a rule. */
.rsvpq__opt--result > *:not(.rsvpq__fill) { position: relative; }
.rsvpq__fill {
  position: absolute; inset: 0 auto 0 0;
  background: color-mix(in srgb, var(--text) 9%, transparent); pointer-events: none;
}
/* YOURS IS GOLD (Maaz's own ruling on the reference: their green and yellow
   are theirs). The house accent, so it reads in all three moods — and it is
   the one positive state on this sheet, which is the rule gold answers to.
   It rides the CHECKED INPUT — the same selector the stroke above already
   uses — so the mark follows the tap instead of the last save: a fill on the
   saved answer beside a stroke on the new pick is two golds on one sheet with
   nothing to tell them apart, and the button is what says which is stored. */
.rsvpq__opt:has(input:checked) .rsvpq__fill { background: color-mix(in srgb, var(--accent) 20%, transparent); }
.rsvpq__opt:has(input:checked) .rsvpq__opt-pct { color: var(--accent); }
.rsvpq__opt-word { flex: 1; overflow-wrap: anywhere; }
.rsvpq__opt-pct { flex: 0 0 auto; color: var(--muted); } /* the figure wears `.num` */
/* An option nobody can pick any more: drawn because it is inside the
   denominator, inert because there is nothing to tick. It resolves no pointer
   events of its own, so its hover must not claim to be a control. */
.rsvpq__opt--gone { cursor: default; color: var(--muted); }
.rsvpq__opt--gone:hover { border-color: var(--border); }
.rsvpq__votes { margin-top: 8px; }
/* The receipt, where the fan is actually looking — a toast about this write
   would sit behind the dialog's own backdrop. */
.rsvpq__said {
  margin-top: 14px; padding: 9px 12px; font-size: 0.9rem; color: var(--accent);
  border: 1px solid var(--hair-gold); border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
/* The sheet's close and its Done both take the house focus ring rather than
   the UA's — a dialog is exactly where keyboard focus has to be visible. */
body.studio .adv__x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }

/* ---------- THE DOOR CLOSE (r6 item 4, Maaz 2026-09-11) --------------------
   A blocked person's wall, on the creator's profile and on any of their
   lives or encores. UNFENCED on purpose: these are FAN surfaces, and a
   `body.studio` class on one renders bare with nothing said (the
   studio_scope_law law-2 trap). One mark, no new palette — the same live-red
   the refusal toast and the form-errors band already carry, at text weight
   (a red FILL is a live badge or the cut, and this is neither). */
.blocked-wall__mark { display: inline-flex; align-items: center; justify-content: center;
  width: 52px; height: 52px; border-radius: 999px;
  border: var(--hair-w) solid var(--hair-live); color: var(--live); }

/* ---------- STAR RATINGS (2026-09-03) ----------------------------------------
   Not fenced by a body class: the fan's widget lives on a fan page and the
   creator's summary lives in the studio AND on the fan page's owner bar, so
   both are written against the tokens every mood defines.

   ONE STROKE PER CONTROL (the r8 law): the CARD carries the border and the
   stars carry none — five bordered boxes inside a bordered box is the exact
   thing that made the masthead avatar read as hand-drawn. Integer heights on
   the star buttons and the word line, so nothing lands on a half pixel. */
.rating {
  /* NOT inline-flex, and that is the whole-pixel rule (r8): a shrink-to-fit box
     is as wide as its widest text, which measured 287.53px — so the card's own
     rounded 1px hairline painted its right edge across two columns at half
     coverage, which is what "rough, not premium" looked like. A block card with
     a whole max-width lands on the grid, and it is the shape every other .card
     in this product already has. */
  display: flex; flex-direction: column; gap: 6px; align-items: flex-start;
  width: 100%; max-width: 320px;
  padding: 14px 16px; background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: var(--radius);
}
/* AND HIDDEN HAS TO MEAN INVISIBLE (Maaz's screen recording, 2026-09-10). A
   member watching an encore before the beat gets this card `hidden`, so the
   player can reveal it on the beat without a reload — and the rule above gives
   it a `display`, which OUTRANKS the UA stylesheet's `[hidden] { display: none }`
   (`.lobby__starting` and `.cap-cards` learned the same thing; a rule that hands
   an element a display has to hand back the hidden state itself). What he
   watched was the ask standing over a tape paused at 0:00, painting Good and
   Amazing under the cursor, on a rating the server was always going to refuse.
   Every request test passed: the markup was right the whole way through, and
   `assert_select ".rating[hidden]"` cannot see paint (#415), which is why the
   fence for this one is a browser reading `visible: true`. */
.rating[hidden] { display: none; }
.rating__ask {
  margin: 0; font-family: var(--display); font-weight: 700; font-size: 1.15rem;
  text-transform: uppercase; letter-spacing: 0.02em; --lh: 1.1; line-height: var(--lh-snap);
}
.rating__stars { display: flex; gap: 2px; }
.rating__star {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0; margin: 0;
  background: none; border: 0; cursor: pointer; color: var(--dim);
  transition: color 120ms var(--ease);
}
.rating__star:hover, .rating__star.is-on { color: var(--accent); }
/* GONE ONCE GIVEN (product lead, encore addendum E2, 2026-09-07). live-flow
   item 28 left the given stars on screen as a read-only record (`.rating--said`,
   whose three rules are deleted here along with the markup they styled — a rule
   with no markup is the #415 trap in reverse). On the encore that record is an
   overlay parked on the picture, so what is left is the RECEIPT: one line, in
   the card's own shape so nothing shifts under the cursor that just tapped,
   which `rating-gone` then removes along with its frame — and it needs no rule
   of its own: `.rating` + `.rating__said` already draw exactly that. */

/* ...AND THE READOUT COMES BACK ON HOVER (addendum 2 / E7): "Only on hover over
   that region: 'you rated' + their stars. No hover = nothing."

   TWO DECISIONS, and the first one buys the second. `pointer-events: none` all
   the way down means this element cannot be the target of anything: no
   invisible hit area over the picture, no patch of video where play/pause
   stops working, and no 160×50 hole in the scrubber. What is left to trigger
   the reveal is an ANCESTOR's hover — and a `:hover` on an ancestor still
   applies when the pointer is over a pointer-events:none descendant, so the
   stage's own hover is the switch.

   Hanging it on `.lobby__stage:hover` rather than on the readout itself also
   makes it behave like the player's own chrome: it fades in with the native
   control bar (which Chrome shows on pointer movement and hides on idle) and
   leaves with it, instead of being a thing parked on the tape. The cost, said
   out loud: a device with no hover never paints it. The text is still in the
   DOM and `opacity` is not `display`, so a screen reader reads the member's
   own answer either way.

   It is NOT `.rating`: no border, no fill, no padding — a card is what E2
   removed. */
.rating-said {
  display: flex; align-items: center; gap: 8px; pointer-events: none;
  opacity: 0; transition: opacity var(--dur) var(--ease);
  font-size: 0.8rem; font-weight: 600; color: #EFE7D6;
  text-shadow: 0 1px 3px rgb(3 3 3 / 0.85);
}
.lobby__stage:hover .rating-said { opacity: 1; }
/* AND THE SLOT ITSELF STEPS OUT OF THE WAY. Two measured facts, both about the
   bottom-left of the frame:

   (1) `.lobby__overlay` is a positioning shell with no pointer rule of its
       own, so it was the thing resolving the pointer over the readout —
       `document.elementFromPoint` at the readout's own centre answered
       `div.lobby__overlay`, not the video. Invisible and clickable is the one
       combination worse than the card E2 removed, so in THIS state (the only
       one where the slot holds nothing tappable) the shell is transparent too.
   (2) The band it sits in belongs to the PLAYER's own chrome. Chrome draws its
       control bar across the bottom ~48px of the frame plus its inset, Safari
       ~50, Firefox ~40 — and it appears on pointer movement, i.e. on the same
       gesture that reveals this readout. Two things in one place on one
       gesture is a collision, so the readout clears the band. 64px is a
       clearance allowance for the widest of the three, not a measured
       constant: no browser publishes that height and none is queryable from
       script (the panel is closed shadow DOM; a hit test at those
       coordinates answers `VIDEO`).

   The phone branch above turns this slot static, where `bottom` does nothing —
   there the readout is a row under the frame and collides with neither. */
.lobby__overlay:has(.rating-said) { pointer-events: none; bottom: 64px; }
/* AND WHERE THE SLOT GOES STATIC, IT DOES NOT EXIST. Measured at a true 390
   (CDP device metrics, not a resized window — the r12 lesson): the phone
   branch above turns this slot into a static ROW, so an unrevealable 20px
   readout plus the slot's own 10px margin opened a 44px gap between the frame
   and the status line where an unrated night has 14px. Thirty pixels of dead
   space on every phone encore a member has rated.

   THE GATE IS THE BREAKPOINT THAT CAUSES IT, and that is a correction: this
   was `@media (hover: none)` first, which read better ("no hover = nothing",
   literally) and was wrong twice. A headless Chrome on Linux reports
   `hover: none` — CI collapsed the slot, the readout's rect came back all
   zeros, and the desktop browser test measuring its position failed with every
   assertion before it passing, which is the signature of a zero rect. And a
   touch LAPTOP at desk width would have lost a readout that costs it nothing
   there, because the slot is absolute above 640px.
   `max-width: 640px` is exactly coincident with the problem: it is the one
   place the slot holds layout it cannot use. Nothing is lost below it — with
   no pointer, `:hover` never fires, so the readout never painted there
   anyway; what goes is the space it was holding. The trade on touch is
   therefore only the screen-reader announcement, which is consistent (nothing
   on that page shows a phone reader their own rating either way) and withholds
   no action, since the rating is one-shot. */
@media (max-width: 640px) {
  .lobby__overlay:has(.rating-said) { display: none; }
}
/* The stars are the ask's own markup, so the fill rules already govern them;
   only the box shrinks to a readout's scale. */
.rating-said .rating__star { width: 16px; height: 16px; cursor: default; }
.rating-said .rating__stars { gap: 1px; }
@media (prefers-reduced-motion: reduce) {
  .rating-said { transition: none; }
}
/* The filled star is a CSS rule, never the SVG's own `fill` attribute: a
   presentation attribute loses to a stylesheet, so the attribute could only
   ever half-work (the #424 brand-mark lesson, the other way round). */
.rating__star .lucide-star { fill: none; }
.rating__star.is-on .lucide-star { fill: currentColor; }
.rating__star:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
/* Reserved height: the word appears on hover, and a card that grows under a
   cursor moves the stars out from under the finger that is choosing. */
.rating__said {
  margin: 0; min-height: 18px; font-family: var(--sans); font-weight: 700;
  font-size: 0.86rem; --lh: 1.3; line-height: var(--lh-snap); color: var(--accent);
}
.rating__note { margin: 0; font-size: 0.74rem; --lh: 1.4; line-height: var(--lh-snap); color: var(--dim); }

/* The creator's read of all of them — the score, its count, and the way to the
   rungs. Nothing renders at zero.

   THE DISTRIBUTION'S OWN RULES ARE GONE (Maaz 2026-09-10): the `<details>`
   histogram came off the fan-preview bar, and Analytics -> Encore is the one
   surface that draws it (`.an-bar*`). Deleting `.rating-sum__row/__rung/
   __track/__fill/__count` with the markup keeps the sheet from carrying rules
   for a shape nothing renders — the class-name-with-no-style bug facing the
   other way (#415). */
.rating-sum { display: flex; flex-direction: column; gap: 10px; }
/* The OWNER BAR's shape: the score, the count and the breakdown ask read as
   one line across a slim bar rather than as a stacked card. */
.rating-sum--bar { flex-direction: row; align-items: center; gap: 14px; flex-wrap: wrap; }
.rating-sum__head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.rating-sum__score {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--display); font-weight: 800; font-size: 1.8rem;
  --lh: 1.05; line-height: var(--lh-snap); color: var(--accent);
  font-variant-numeric: tabular-nums lining-nums;
}
.rating-sum__score .lucide-star { fill: currentColor; }
/* The ask and its button on one baseline. `inline-flex` because the words and
   the control are one statement and must wrap together. */
.rating-sum__more { display: inline-flex; align-items: center; gap: 10px; color: var(--muted); }
/* THE FAN PREVIEW BAR'S DIVIDER (Maaz 2026-09-10) — between what the bar is
   and what the room said. It is drawn by the score's own presence: a night
   nobody rated has nothing to divide from the label, and a hairline hanging in
   the bar beside nothing is furniture with no job. */
.lobby__owner-rule { display: none; align-self: stretch; width: var(--hair-w); background: var(--border-strong); }
.lobby__owner .cluster:has(.rating-sum) > .lobby__owner-rule { display: block; }

/* ══════════════════════════════════════════════════════════════════════════
   THE CONTROL ROOM (r10; five tabs and a full-bleed top row since r12 —
   People · Destinations · Drops · Moderation)
   cr-ref-1/3/5 + r12-ref-1/2/3/5/6. Everything under the program monitor lives
   in one of five panels; the rail itself is body.studio's own .tabs/.tab pills
   (the control room is a studio screen), so nothing here re-declares a tab.

   The panels are CLIENT-SIDE, not addresses, and that is forced: this page
   holds a live media session in a [data-turbo-permanent] iframe, and any URL
   change is a Turbo Drive VISIT, which TRANSFERS the permanent node into a new
   body — a re-parented iframe reloads its document and the call dies (the same
   fact that made the greenroom and the control room one page). Every panel is
   in the DOM at all times and the closed ones carry [hidden]; a morph resets
   that attribute from the server HTML, so control_tabs_controller re-applies
   the creator's choice on turbo:morph. Without JavaScript all four render,
   stacked — a degradation, never a dead control. */
/* THE TOP ROW (r12). This page carries no masthead above it, so its own first
   row is the whole chrome: the way back, the live's title, and the one GO LIVE.
   min-height on a row of stroked controls, and an integer one — an even 34px
   ring in an odd row is a half pixel, which is the r8 hairline law. */
body.studio .cr-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; min-height: 56px; padding: 26px 0 14px;
}
/* THE CHEVRON SITS ON THE TITLE'S OWN LINE (product lead, live-flow r2 item
   2). The column stacked a back LINE above the title; it is one row now —
   chevron, then the live's name — which is why the direction flips and the
   glyph gets a real 40x40 hit box of its own (the r12 hit-area rule: the
   BUTTON is the event target, so the glyph takes no pointer events). */
body.studio .cr-top__who { display: flex; flex-direction: row; align-items: center; gap: 4px; min-width: 0; }
body.studio .cr-top__title { margin: 0; font-size: 1.3rem; min-width: 0; }
body.studio .cr-top__acts { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* The only way out of a full-bleed page, so it is a real control and not a
   whisper: muted, but it takes the text colour on hover like every other link
   in the studio. inline-flex, because a baseline-aligned SVG overhangs its
   line box by the font's descender (r8). */
body.studio .cr-back {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--muted); font-size: 0.82rem; text-decoration: none;
}
body.studio .cr-back:hover { color: var(--text); }
body.studio .cr-back .lucide { flex: none; pointer-events: none; }
/* THE WORDLESS VARIANT: a glyph with no words needs the house's 40x40 box
   around it or it is a 20px target beside a heading. Integer size and
   place-items, per the whole-pixel rule.
   ONE DECLARATION, TWO HOSTS — the control room's hand-written chevron, and
   `studio_back_link ..., words: false` (the composer's bar) which asks for it
   by name. Two near-identical blocks is how one host's stroke drifts off the
   other's (r8). */
body.studio .cr-top__who > .cr-back,
body.studio .cr-back--icon {
  display: inline-grid; place-items: center; width: 40px; height: 40px;
  margin-left: -10px; border-radius: 8px; flex: none;
}
body.studio .cr-top__who > .cr-back:hover,
body.studio .cr-back--icon:hover { background: var(--surface-2); }

/* WHERE YOU ARE, AS THE ROOM'S OWN TITLE (Maaz 2026-09-10, his mock).
   `.cr-prog__important` is retired with the markup that rendered it — a class
   nothing renders is the #415 trap in reverse — and the "**Important:**"
   label it carried is gone with it: it was the notice's own type, hard right,
   warning a creator about a page they opened on purpose. This is a heading. */
body.studio .cr-prog__h {
  margin: 0; font-size: 1.5rem; font-weight: 600; color: var(--text);
  --lh: 1.2; line-height: var(--lh-snap);
}
body.studio .cr-prog__h + .cr-prog__said { margin-top: 4px; }

body.studio .cr-panel__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 16px;
}
body.studio .cr-panel__head h3 { margin: 0; font-size: 1.05rem; }
body.studio .cr-panel__head p { margin: 4px 0 0; }
body.studio .cr-sub { margin: 18px 0 10px; }
/* ONE COLUMN, GROUPED (product lead, live-flow r2 item 14). The Moderation tab
   was a header link, an empty state, and then — under the empty state — the
   one control a creator can use before the show. A group is a titled block
   with its own rule above it, so "the setting" and "what happened" read as two
   things rather than as a page with a stray form at the bottom. */
body.studio .cr-group + .cr-group {
  margin-top: 26px; padding-top: 22px; border-top: var(--hair-w) solid var(--border);
}
/* A group's own empty state sits right under its title — .cr-empty's 18px top
   padding is for a panel, not for a titled block inside one. */
body.studio .cr-group > .cr-empty:last-child,
body.studio .cr-group .cr-empty { padding-top: 4px; }
/* A settings row: label, control, Save — on one line on a desk, stacked on a
   phone, and never a `.cluster` (that box centres a row of chips and left the
   select and its Save reading as two unrelated controls). */
body.studio .cr-set { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
body.studio .cr-set label { margin: 0; }
/* The house form rule is `width: 100%` on every select (16px, so iOS does not
   zoom the page on focus) — inside a flex row that made the control eat the
   line and stack the label above it and Save below, which is the "weirdly
   placed" shape this item is fixing. `width: auto` and a real minimum, so the
   three read as one setting. */
body.studio .cr-set select { width: auto; min-width: 200px; flex: 0 1 auto; }

/* the search field — a pill, the one in cr-ref-1. An integer height, because a
   999px radius is all arc and an arc on a fractional box antialiases (r8/r9). */
body.studio .cr-search {
  height: 40px; min-width: 240px; flex: 1; max-width: 340px; padding: 0 16px;
  border-radius: 999px; border: var(--hair-w) solid var(--border-strong);
  background: transparent; color: inherit; font: inherit; --lh: 1.4; line-height: var(--lh-snap);
}
body.studio .cr-search::placeholder { color: var(--dim); }

/* the roster rows */
body.studio .cr-person { display: flex; align-items: center; gap: 12px; min-height: 52px; }
body.studio .cr-person[hidden] { display: none; }
/* ONE LINE, ELLIPSIS, NEVER OVER THE PILL (Maaz 2026-09-10, screenshot 6).
   `flex: 1; min-width: 0` shrinks the BOX below its content and, with no
   `overflow`, the text paints straight out of it — so in a squeezed tray
   "maaz" was laid over the FOLLOWER pill beside it. A name is one line here:
   this row's other two items are a label and a menu, and a name wrapping to
   two lines pushes a 52px row into 70 and the list stops scanning. */
body.studio .cr-person__name {
  flex: 1; min-width: 0; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.studio .cr-person__acts { display: flex; align-items: center; gap: 12px; margin-left: auto; }
body.studio .cr-person__acts form { display: contents; }
/* THE ROW MENU IS WIDER (product lead, live-flow r2 item 20). Five items, each
   with a sentence under it, inside the house's 280px panel read as a thin
   column of wrapped text — "it looks too thin" — so this one menu gets a
   width its own contents ask for. Still bounded by the viewport, so a phone
   keeps the house behaviour. */
body.studio .cr-person__acts .menu__panel {
  min-width: 300px; max-width: min(380px, calc(100vw - 32px));
}
/* live-flow r4 item 15: inside the tray the panel's containing block is the
   ROW (position moves, width cannot fix where a box landed — the post-tray
   rule), so the menu is exactly as wide as the tray and its notes wrap. The
   scroller's clip stands down while a menu is open (an overflow box clips
   whether or not it scrolls, and no z-index escapes an ancestor's clip). */
body.studio .cr-tray .cr-person { position: relative; }
body.studio .cr-tray .cr-person__acts,
body.studio .cr-tray .cr-person__acts .menu { position: static; }
body.studio .cr-tray .cr-person__acts .menu__panel {
  left: 0; right: 0; min-width: 0; max-width: none;
}
body.studio .cr-tray__scroll:has(details.menu[open]) { overflow: visible; }
/* ── THE PILL COMES OFF A SQUEEZED ROW (Maaz 2026-09-10, screenshot 6) ──────
   "If the pill would collide with the name or the ⋯, HIDE the pill."
   IT HAS TO BE A CONTAINER QUERY, and that is a fact about this page rather
   than a preference: the tray's width is the CREATOR'S — `.cr-split` drags it
   and `--cr-tray-w` stores it — so it is independent of the viewport and no
   media query can see it. The row is the container.
   THE NUMBER IS MEASURED, not taste. Everything on this row but the name is a
   fixed width — the row's padding, a 34px avatar, three 12px gaps, the pill
   (FOLLOWER is the widest tag this roster mints: 81.5px) and a 40px kebab —
   and in a browser that furniture comes to 209.5px at every tray width, so
   the name is `row - 209.5`. The tray's own DEFAULT gives a 300px row and a
   90px name, which is the resting state and stays as it is; 275 is the row at
   which the name would drop under 66px, i.e. under about seven characters
   before the ellipsis, and past that the label is worth less than the
   person's name. The tag is still on the row's own menu one tap away.
   AND THE OPEN ROW HAS TO BE RAISED. `container-type` applies layout
   containment, which makes the row a STACKING CONTEXT — so without this the
   kebab's panel (z-index 30) is trapped at row N's level and every row after
   it paints on top of it. It is the house's own answer to exactly this, worn
   already by .lives-row, .live-row, .live-card, .enc-row and a Destinations
   table row. */
body.studio .cr-tray .cr-person { container-type: inline-size; }
body.studio .cr-tray .cr-person:has(details.menu[open]) { z-index: 40; }
@container (max-width: 275px) {
  body.studio .cr-person__tag { display: none; }
}
/* When it IS drawn it is one line: a chip carries `overflow-wrap: anywhere`
   for free text in a wrapping cluster, and here that wrapped FOLLOWER into a
   two-line circle rather than letting the row decide. */
body.studio .cr-person__tag { white-space: nowrap; overflow-wrap: normal; flex: none; }
/* THE WHOLE BUTTON IS ONE HIT AREA (r12 item 7, widened to every control in
   the product by live-flow r2 item 22): a 40x40 box around a 15px glyph is
   ~86% padding, and if the glyph is what the pointer lands on then the box
   reads as the only live part of the control. The rule that makes the BUTTON
   the event target lives ONCE now, on `.lucide` near the top of this file —
   this used to be one of three copies, and the other ~18 glyph-bearing
   controls in the studio had none.
   (The Speaker/Grid view toggle in r12-ref-2 is NOT one of these controls — it
   is Daily Prebuilt's own chrome inside the iframe, where no stylesheet of
   ours reaches; see the theming research doc, "Icon set, button shapes, radii:
   not themeable".) */
/* A popover inside a control-room panel has to raise the PANEL: main.shell > *
   gets a house-fade animation, which makes every direct child of the shell a
   stacking context while it runs, so a z-index on the menu alone is trapped
   inside its own row (the Encores round's lesson). */
body.studio .cr-tray:has(details.menu[open]) { position: relative; z-index: 5; }
body.studio .cr-act {
  background: none; border: 0; padding: 0; cursor: pointer; color: var(--muted);
  font: inherit; font-size: 0.82rem; text-decoration: underline;
  text-underline-offset: 3px; text-decoration-color: var(--border-strong);
}
body.studio .cr-act:hover { color: var(--text); }
body.studio .cr-act:disabled { color: var(--dim); cursor: not-allowed; }
/* "Invite sent." — the ask's own receipt on the row (journey r3 item 9). */
body.studio .cr-sent { color: var(--ok); }
body.studio .cr-act--warn { color: var(--live); }
body.studio .cr-act--warn:hover { color: var(--live); }
body.studio .cr-empty { padding: 18px 0; color: var(--dim); font-size: 0.9rem; }

/* THE HOST'S TWO CHAT VERBS, AS GLYPHS (product lead, live-flow item 26,
   2026-09-06). "Pin"/"Unpin" were words inside a line of somebody's chat and
   the delete was a bare "x", which everywhere else in this house means close.

   A 28px box around a 15px glyph, so the tap target is a target on a phone.
   The glyph takes no pointer events from the ONE `.lucide` rule at the top of
   this file (live-flow r2 item 22 collapsed three copies into it), so the
   BUTTON is what elementFromPoint answers across the whole box rather than the
   15px SVG in the middle of it. Integer size, per the whole-pixel rule. */
/* ── THE HELD-LINK QUEUE (control-room QA item 6) ────────────────────────────
   Cards above the scrollback, one per message waiting on the host. Empty, the
   container is an empty box — no heading, no chrome, nothing that tells a host
   about a feature instead of about their room. It is `flex: none` because it
   sits in the chat's own column beside a `flex: 1` scrollback: the queue takes
   what it needs and the scrollback keeps the rest. */
body.studio .cr-held { flex: none; display: grid; gap: 8px; }
body.studio .cr-held:not(:empty) { padding-bottom: 12px; }
body.studio .cr-held__card {
  display: grid; gap: 6px; padding: 10px 12px;
  border: var(--hair-w) solid var(--hair-gold); border-radius: 10px;
  background: var(--surface-2);
}
body.studio .cr-held__who { margin: 0; font-size: 0.78rem; font-weight: 600; color: var(--gold); }
body.studio .cr-held__who .dim { font-weight: 400; }
body.studio .cr-held__body { margin: 0; font-size: 0.86rem; overflow-wrap: anywhere; }
body.studio .cr-held__acts { display: flex; gap: 8px; }

/* THE HOVER LABEL STAYS INSIDE THE ROW (journey r3 item 6): the base tip is
   centred + nowrap, which on a 28px control hard against the scroller's right
   edge clipped "Delete" to "Dele". Right-anchored, the tip can only grow INTO
   the row, so the scroll box never cuts it. Both transform states re-declared
   — the base hover carries a translateX(-50%) that pairs with left: 50%. */
body.studio .cr-msg-act[data-tip]::after { left: auto; right: 0; transform: translateY(2px); }
body.studio .cr-msg-act[data-tip]:hover::after,
body.studio .cr-msg-act[data-tip]:focus-visible::after { transform: translateY(0); }
/* live-flow r4 item 8: pin/delete ride EVERY render of rooms/_chat_message
   (the broadcast copy included), so the base rule is unfenced display:none —
   a fan page renders them invisible by construction (studio_scope_law's law
   2) — and only the control room's tray shows them. */
.cr-msg-act { display: none; }
.cr-msg-act--warn { display: none; }
body.studio .cr-tray .cr-msg-act {
  flex: none; display: inline-grid; place-items: center;
  width: 28px; height: 28px; padding: 0;
  background: none; border: 0; border-radius: 6px;
  color: var(--muted); cursor: pointer;
  vertical-align: middle;
}
body.studio .cr-msg-act:hover { color: var(--text); background: var(--surface-2); }
body.studio .cr-msg-act--warn:hover { color: var(--live); }

/* DESTINATIONS — the "also playing on" rows carry a brand plate, and the plate
   is a GRID (r7: scope descendant-span rules to the text column, or the plate
   itself matches and loses its display). */
body.studio .cr-dest { display: flex; align-items: center; gap: 12px; min-height: 52px; }
body.studio .cr-dest__mark {
  display: grid; place-items: center; width: 26px; height: 26px; flex: none; color: var(--text);
}
body.studio .cr-dest__name { flex: 1; min-width: 0; font-weight: 600; }
body.studio .cr-dest--off { opacity: 0.55; }
/* THE ZERO STATE (r12 item 5, r12-ref-5): the sell, not a status line. ONE
   SENTENCE, and the loud tap under it is gone (2026-09-11) — `.cr-zero`,
   `.cr-zero__go` and their rules went out with the gold ADD DESTINATIONS
   primary, because a control room does not invite a creator to leave it.
   Where a new platform comes from is two quiet lines at the foot of the
   list. A rule with nothing rendering it is the #415 trap in reverse, so
   only the lede is left.
   Measure: 56ch computed to 487px in a 1240px panel and wrapped an
   83-character sentence (item 7). */
body.studio .cr-zero__lede { margin: 0; max-width: min(96ch, 100%); font-size: 0.95rem; }
/* `.cr-card-quote` WAS HERE and went out with its markup (Maaz 2026-09-12).
   It was a hand-built picture of the cut card — the profile plate, the live's
   name, the §6.1 peg, the door line — and since KB's ticket the card the
   socials carry is a PNG this app renders, which the preview below now shows
   directly. A class named after retired markup is how a new surface inherits
   a box by reaching for a familiar name (the `.gl-side` P0), so it is deleted
   rather than left standing. */
/* ── THE PROFILE QR (control-room QA item 8) ─────────────────────────────────
   UNFENCED, and NOT called `.qr-card`: that name belonged to the app-download
   screen's own code when this shipped — the `.gen-sheet`-not-`.sheet` hazard
   this file keeps a warning about. (That screen and its class are gone as of
   2026-09-16, so the name is free; these two keep theirs, because renaming a
   shipped class is a sweep through this sheet and two browser tests to buy a
   tidier word.) `.doorqr` is the plate, `.doorcode` is the
   card around it, and both live outside the studio fence because their
   partials do (`app/views/shared/_profile_qr*`): law 2 of the fence is that a
   non-studio view wearing a fenced class renders bare, silently.

   THE PLATE IS WHITE AND THAT IS NOT A THEME SLIP (see PROFILE_QR_GOLD). A QR
   is read by binarizing relative luminance, so the quiet zone has to BE the
   light half — a gold code on the near-black canvas is a code no scanner is
   looking for. `QrCode#to_svg` paints its own quiet-zone rect, so this box
   only rounds the corners and holds the size. */
.doorqr {
  display: block; flex: none; width: 74px; height: 74px;
  border-radius: 8px; overflow: hidden; background: #fff;
}
/* THE DIRECT CHILD, and the `>` is load-bearing (Maaz 2026-09-11). The code's
   own SVG now NESTS one — the house mark in the centre quiet zone — and a
   descendant rule set that inner viewport to 100% of the plate, which is CSS
   beating its own width/height attributes and a glyph the size of the whole
   code. A rasterizer cannot see it (rsvg applies none of this file), so it was
   a browser that found it. */
.doorqr > svg { display: block; width: 100%; height: 100%; }

/* The creator's own door as something they can hold up: the code, who it
   belongs to, the house's name. One artifact — the control room's cut preview
   renders the same plate from the same encoder. */
.doorcode {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  width: max-content; max-width: 100%; padding: 18px;
  border: var(--hair-w) solid var(--border); border-radius: 16px; background: var(--surface-2);
}
.doorcode .doorqr { width: 196px; height: 196px; border-radius: 12px; }
.doorcode__who { display: flex; align-items: center; gap: 10px; }
.doorcode__names { display: grid; }
.doorcode__name { font-weight: 600; }
.doorcode__handle { color: var(--muted); font-size: 0.84rem; }
/* The stub. Gold, and the one place on this card that is.

   IT IS A SENTENCE NOW, NOT A LABEL (Maaz 2026-09-11: "Join me on
   BackstagePass", not a bare BACKSTAGEPASS) — so the uppercase and the tracked
   letter-spacing came off with the wordmark they were setting. */
.doorcode__mark {
  display: flex; align-items: center; gap: 7px; margin: 0;
  padding-top: 12px; border-top: var(--hair-w) solid var(--border); width: 100%;
  justify-content: center; color: var(--gold); font-size: 0.92rem;
}
.doorcode__mark .brand { flex: none; }

/* STAGE — the seats (cr-ref-3). A fixed 4-up grid that folds, so an empty seat
   is visibly a seat and not a gap. */
body.studio .cr-seats {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px;
}
body.studio .cr-seat {
  display: flex; flex-direction: column; gap: 12px; min-height: 148px;
  padding: 16px; border: 1px solid var(--border); border-radius: 12px;
}
body.studio .cr-seat--empty {
  border-style: dashed; align-items: center; justify-content: center; text-align: center;
}
/* LIT IF THEY ARE IN THE ROOM (Maaz, 2026-09-06). The Crew block reads at a
   glance — who is actually here and who isn't — so the whole card carries it,
   not just the state line. Driven by measured stage presence and nothing else:
   a seat we have not seen arrive stays dim, which is the honest answer. */
body.studio .cr-seat--on { border-color: var(--hair-gold); background: var(--surface-2); }
/* IN THE GREEN ROOM is lit, but it is NOT the gold of on-camera
   (GREEN-ROOM-INFRA 2026-09-07). Gold marks the positive state in this house
   and "on camera in front of an audience" is the only positive state a seat
   card has; a guest who has turned up backstage is present, which is a
   different claim and reads one step quieter. A seat can never wear both:
   admit! ejects them from the green room on its way to the stage. */
body.studio .cr-seat--green { border-color: var(--border-strong); background: var(--surface-2); }
/* the RECORD's seat (an ended night's greenroom tab, r2 item 10): who + what
   happened, no controls — so no working-seat minimum height to fill */
body.studio .cr-seat--record { min-height: 0; }
body.studio .cr-seat__field {
  width: 100%; height: 38px; padding: 0 12px; border-radius: 8px;
  border: var(--hair-w) solid var(--border-strong); background: transparent;
  color: inherit; font: inherit; text-align: center; --lh: 1.4; line-height: var(--lh-snap);
}
body.studio .cr-seat__field::placeholder { color: var(--dim); }
body.studio .cr-seat__who { display: flex; align-items: center; gap: 10px; }
body.studio .cr-seat__name { font-weight: 600; }
body.studio .cr-seat__role { display: block; color: var(--muted); font-size: 0.82rem; }
body.studio .cr-seat__state {
  font-family: var(--mono); font-size: 0.68rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--dim);
}
body.studio .cr-seat__state--on { color: var(--accent); }
body.studio .cr-seat__foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: auto;
}
body.studio .cr-seat__foot .cr-seat__acts { display: flex; align-items: center; gap: 12px; margin-left: auto; }
body.studio .cr-seat__foot form { display: contents; }
/* mic + camera. HIDDEN until the host's own call reports the track state —
   there is no camera state in Daily's REST surface (docs, room-presence), so
   the server can never honestly draw one. crew_call_controller writes
   data-mic/data-cam off the frame's participant events, in the host's own
   browser, which is the only place the truth exists. */
body.studio .cr-io { display: flex; gap: 8px; }
body.studio .cr-io__pip {
  display: none; place-items: center; width: 30px; height: 30px; border-radius: 999px;
  border: var(--hair-w) solid var(--border-strong); color: var(--muted);
}
/* Three card shapes read the same attributes, because one controller writes
   them: the live's own People tab (.cr-seat), the Greenroom tray (.cr-seat2)
   and the greenroom page's tiles (.gr-seat). */
body.studio [data-mic] .cr-io__pip--mic,
body.studio [data-cam] .cr-io__pip--cam { display: grid; }
body.studio [data-mic="off"] .cr-io__pip--mic,
body.studio [data-cam="off"] .cr-io__pip--cam { color: var(--dim); border-color: var(--border); }
body.studio [data-mic="on"] .cr-io__pip--mic,
body.studio [data-cam="on"] .cr-io__pip--cam { color: var(--accent); border-color: var(--accent); }
/* Two glyphs per pip, one shown — a slashed mic is what "muted" looks like,
   and colour alone would be a claim nobody can read at a glance. */
body.studio .cr-io__pip > span { display: none; }
body.studio .cr-seat[data-mic="on"] .cr-io__pip--mic > .io-on,
body.studio .cr-seat[data-mic="off"] .cr-io__pip--mic > .io-off,
body.studio .cr-seat[data-cam="on"] .cr-io__pip--cam > .io-on,
body.studio .cr-seat[data-cam="off"] .cr-io__pip--cam > .io-off { display: inline-flex; }


/* Preview the widget (r10; rebuilt 2026-09-12): the real cut PNG over the
   host's own camera, on the Destinations tab. 16:9 because that is the
   composition the socials receive. */
body.studio .cr-preview > summary { display: inline-block; cursor: pointer; list-style: none; }
body.studio .cr-preview > summary::-webkit-details-marker { display: none; }
/* TWO LAYERS IN ONE 16:9 WINDOW, AND NO SCALING LEFT.
   The old box held an iframe at a fixed 1280x720 scaled by `--pv-scale`,
   because a 1920x1080 LAYOUT asked to lay itself out at the tray's own ~300px
   became a different card (control-room QA item 8 — it clipped its own door
   line). An image has no layout to reflow, so 16:9 into a 16:9 box is the same
   card at any size and the transform, the variable and the ResizeObserver that
   set it all went with the frame.
   THE HATCH IS THE HONEST STILL: the camera-check tile's own texture (a plane
   with no stage room draws it at full size), so an empty box reads as "no
   picture" rather than as a black one. The camera paints over it when there is
   one; the card paints over both. */
body.studio .cr-preview__box {
  position: relative; overflow: hidden;
  width: 100%; max-width: 640px; aspect-ratio: 16 / 9; margin-top: 12px;
  border: var(--hair-w) solid var(--border); border-radius: 10px; background: #030303;
  background-image: repeating-linear-gradient(45deg,
    rgb(255 255 255 / 0.015) 0 16px, rgb(255 255 255 / 0.045) 16px 32px);
}
/* `object-fit: cover` on the camera: a 4:3 webcam in a 16:9 window is what the
   compositor does to it too, and letterboxing a preview of a full-bleed card
   would show a border the feeds never carry. */
body.studio .cr-preview__cam {
  position: absolute; inset: 0; display: block; width: 100%; height: 100%;
  object-fit: cover; background: transparent;
}
/* THE CARD IS ON TOP, FULL-BLEED, AND ITS ALPHA IS THE POINT — KB's ticket has
   transparent corners and two cut-out notches, so the camera shows through
   around it exactly as the live picture does on a social feed. `pointer-events:
   none` because nothing in it is a control. */
body.studio .cr-preview__card {
  position: absolute; inset: 0; display: block; width: 100%; height: 100%;
  object-fit: cover; pointer-events: none;
}
/* NO SRC, NO IMAGE. The controller sets the src on open (~840 KB a panel
   nobody opened must not spend), and an `<img>` carrying an alt and no src
   paints its alt TEXT into the box while the panel opens. */
body.studio .cr-preview__card:not([src]) { display: none; }
body.studio .cr-preview__said { margin: 8px 0 0; }

/* ── the live's Analytics tab: two views over one live (r11, an-ref-1/2) ──────
   Nothing here is a new palette or a new radius: the pills are body.studio's
   own .tab (integer height, one stroke, --hair-w on the arc — the r8/r9 laws),
   the cards are .card's surface + hairline at the same 10px radius, and the
   bars are the rating summary's own track/fill in gold. What is new is the
   ARRANGEMENT the mock asks for: a labeled figure row inside a card, and a
   measured bar whose words and numbers share one line above the track. */
body.studio .an-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; margin-top: 22px;
}
body.studio .an-row .tabs { margin-top: 0; }
/* THE PEOPLE TAB'S RECORD ROW (Maaz 2026-09-10) reuses this shape — the
   figures on the left, the file hard right, the same place the Analytics tab
   hands over its download. The figures are a wrapping flex row of their own,
   and a flex ITEM needs `min-width: 0` before it is allowed to wrap at all:
   without it three figures and a pill hold a 390px viewport open, which is
   the class `studio_no_horizontal_scroll_test` exists to refuse. */
body.studio .an-row--record { margin-top: 0; align-items: flex-end; }
body.studio .an-row--record > .stat-row { flex: 1 1 auto; min-width: 0; margin-top: 0; }

/* A pill that is a filled ACTION rather than a state — the same gold the
   studio's one filled action already wears (.btn--gold, #409). */
body.studio .tab--gold {
  background: var(--gold); border-color: var(--gold); color: var(--gold-ink); font-weight: 600;
}
body.studio .tab--gold:hover { background: var(--gold-bright); border-color: var(--gold-bright); color: var(--gold-ink); }
/* A pill naming a view this live does not have: drawn, dimmed, and not a link
   (a control that cannot be tapped should not look tappable). */
body.studio .tab--off { color: var(--dim); border-color: var(--border); cursor: default; }
body.studio .tab--off:hover { color: var(--dim); }
/* the in-card download — an INTEGER height, like every other pill here */
body.studio .tab--sm { height: 34px; padding: 0 14px; font-size: 0.88rem; }

/* the figures, label over value over caption (the mock's order) */
/* Four across on a desk, and the minimum track is what decides that: the
   panel's content box is ~762px at 1100px of viewport, so 170px tracks plus
   28px gutters came to 764 and the fourth figure wrapped onto its own row.
   148 + 24 fits with room to spare and still wraps honestly on a phone. */
body.studio .an-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: 22px 24px; margin-top: 18px;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius); padding: 24px;
}
body.studio .an-stat .meta { display: block; }
body.studio .an-stat b {
  display: block; margin-top: 12px; font-family: var(--sans); font-weight: 500; font-size: 2rem;
  --lh: 1.05; line-height: var(--lh-snap); letter-spacing: -0.02em; color: var(--text);
  font-variant-numeric: tabular-nums lining-nums;
}
body.studio .an-stat .small { display: block; margin-top: 8px; color: var(--muted); }

body.studio .an-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 18px; margin-top: 18px;
}
body.studio .an-card {
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius); padding: 24px; min-width: 0;
}
body.studio .an-card__head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
}
body.studio .an-card__head h3 { margin: 0; }

/* the full-width card under the two-up grid (Sales), in all three of its
   shapes — one declaration instead of the same inline style three times */
body.studio .an-card--full { margin-top: 18px; }

/* a measured bar: the words left, the numbers right, the track under both */
body.studio .an-bars { list-style: none; margin: 20px 0 0; padding: 0; }
body.studio .an-bar + .an-bar { margin-top: 20px; }
body.studio .an-bar__line {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
}
body.studio .an-bar__what { color: var(--muted); }
body.studio .an-bar__num { font-variant-numeric: tabular-nums lining-nums; }
body.studio .an-bar__track {
  display: block; height: 6px; margin-top: 10px;
  background: var(--surface-3); border-radius: 3px; overflow: hidden;
}
body.studio .an-bar__fill { display: block; height: 6px; background: var(--gold); border-radius: 3px; }

/* what the room said, totals only */
body.studio .an-asked { margin-top: 20px; }
body.studio .an-asked__q { margin: 0 0 10px; }
body.studio .an-opt {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 14px; padding: 7px 0;
}
body.studio .an-opt__count { font-variant-numeric: tabular-nums lining-nums; }

/* sales in this session */
body.studio .an-total {
  display: block; margin: 18px 0 6px;
  font-family: var(--sans); font-weight: 500; font-size: 2.4rem;
  --lh: 1.05; line-height: var(--lh-snap); letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums lining-nums;
}
body.studio .an-sale {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; padding: 15px 0; border-top: 1px solid var(--border);
}
body.studio .an-sale__gross { font-variant-numeric: tabular-nums lining-nums; }
/* THE TWO BUCKETS (Maaz 2026-09-10): what they bought to get in, and what they
   bought once they were in. The heading carries the bucket's own subtotal on
   the same baseline, so a creator reads the split without adding the lines up
   themselves — and the FIRST bucket's head owns the rule under the grand
   total, which is why the head carries the border and `.an-sale` keeps its
   own (a line under a heading, a line between lines). */
body.studio .an-bucket__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; padding: 16px 0 8px; border-top: 1px solid var(--border-strong);
}
body.studio .an-bucket__head .eyebrow { margin: 0; }
body.studio .an-bucket__sum { font-size: 1.05rem; font-variant-numeric: tabular-nums lining-nums; }
/* The first line inside a bucket sits under its own heading, so it does not
   need a second rule 8px below the first. */
body.studio .an-bucket__head + .an-sale { border-top: 0; padding-top: 4px; }

body.studio .an-note { margin-top: 18px; color: var(--muted); }
body.studio .an-note + .an-note { margin-top: 6px; }
/* under the $0, where the total's own 6px bottom margin has already spaced it */
body.studio .an-note--tight { margin-top: 0; }

/* ---- the Live view's three always-on cards (Maaz 2026-09-07) ----
   A card that is drawn with nothing in it has to say WHICH nothing it is, or
   it is the blank page again in a smaller frame. Three pieces of furniture,
   every class DECLARED (#415: a class nothing styles passes every
   assert_select) and every SHAPE measured in a browser
   (test/system/night_analytics_test.rb), because a circle, a ghost row and a
   three-column row are exactly what a request test cannot see. */

/* the block inside the card: a badge, the headline, the line under it. No
   border of its own — the card's stroke is already the box, and a second one
   inside it reads as a hole. */
body.studio .an-zero { padding: 26px 0 22px; text-align: center; }
body.studio .an-zero__mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 52px; height: 52px; border-radius: 999px;
  background: var(--surface-3); color: var(--muted);
}
body.studio .an-zero__said {
  margin: 16px auto 0; max-width: 34ch; font-weight: 500; color: var(--text);
}
body.studio .an-zero__sub { margin: 6px auto 0; max-width: 40ch; font-size: 0.88rem; color: var(--dim); }

/* the rows the answers will land in — a SHAPE, not a count of anything */
body.studio .an-ghosts { display: grid; gap: 8px; margin-top: 4px; }
/* --surface-3 and not --surface-2, measured against the mock: a ghost row has
   to be as far off the card as the bar TRACK beside it is (+11 in this mood,
   which is the step Maaz's own render carries), and --surface-2 is half that —
   four rows nobody can see is a card that reads as truncated. */
body.studio .an-ghost {
  display: flex; align-items: center; gap: 14px;
  height: 30px; padding: 0 14px;
  background: var(--surface-3); border-radius: 8px;
}
body.studio .an-ghost__dot {
  flex: none; width: 12px; height: 12px; border-radius: 999px; background: var(--border);
}
body.studio .an-ghost__line { width: 38%; height: 5px; border-radius: 3px; background: var(--border); }

/* REACH IS THREE COLUMNS on this card (the mock): the rail, its track, its
   figure. Every column is placed EXPLICITLY, because a row with a real send
   and no receipt back draws no track at all and auto-placement would slide its
   figure into the empty middle and mis-align it against the three rows beside
   it. The ratings distribution keeps the stacked `.an-bar` — same class, and
   only this modifier moves. */
body.studio .an-bars--reach .an-bar + .an-bar { margin-top: 14px; }
body.studio .an-bar--rail {
  display: grid; grid-template-columns: minmax(0, auto) minmax(40px, 1fr) auto;
  align-items: center; column-gap: 16px; row-gap: 8px;
}
body.studio .an-bar--rail .an-bar__what { grid-area: 1 / 1; }
body.studio .an-bar--rail .an-bar__track { grid-area: 1 / 2; margin-top: 0; }
body.studio .an-bar--rail .an-bar__num { grid-area: 1 / 3; text-align: right; }
/* On a phone the rail's own words and its figure need the whole width, so the
   track drops under both — the shape this card had before it grew a column.
   EVERY ROW IS PINNED, here and above: auto-placement runs in DOM order and
   is sparse, so a full-width track as the SECOND child pushes itself onto row
   2 and then pushes the figure onto row 3 — measured at 390px, the label on
   its own line with its number two lines below it. */
@media (max-width: 560px) {
  body.studio .an-bar--rail { grid-template-columns: minmax(0, 1fr) auto; }
  body.studio .an-bar--rail .an-bar__num { grid-area: 1 / 2; }
  body.studio .an-bar--rail .an-bar__track { grid-area: 2 / 1 / 3 / 3; }
}

/* SALES AT REST: what it was, and beside it the rails it would have arrived
   on. The hairline between them is the card's own, so at one column it comes
   off and the rows become a section under the sentence. */
body.studio .an-split {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0 32px; align-items: start;
}
body.studio .an-split__rows { padding-left: 32px; border-left: 1px solid var(--border); }
body.studio .an-preview {
  display: grid; grid-template-columns: auto minmax(0, auto) minmax(40px, 1fr) auto;
  align-items: center; column-gap: 14px; padding: 14px 0;
}
body.studio .an-preview + .an-preview { border-top: 1px solid var(--border); }
body.studio .an-preview .lucide { color: var(--dim); }
body.studio .an-preview__what { color: var(--muted); }
body.studio .an-preview .an-bar__track { margin-top: 0; }
body.studio .an-preview__num { font-weight: 500; }
@media (max-width: 899px) {
  body.studio .an-split { grid-template-columns: minmax(0, 1fr); gap: 20px; }
  body.studio .an-split__rows { padding-left: 0; padding-top: 4px; border-left: 0; border-top: 1px solid var(--border); }
}

/* ---------- Analytics, four pills (r15, Maaz's mocks 2026-09-10) ----------
   Live · Encore · Money · People, each an address. Server-rendered bars (the
   density-band idiom), CSS streak bars and two inline SVG polylines — no
   chart library. One stroke per control, whole-pixel heights, and every class
   here is DECLARED (the #415 lesson: a class nothing styles passes every
   assert_select). The pills are the house .tab pill (the item's hair stroke,
   gold border when on — exactly the mock's selected ring). */
body.studio .anx-pills { margin-top: 26px; }
body.studio .anx-sec { margin-top: 56px; scroll-margin-top: 80px; }
body.studio .anx-sec__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
}
body.studio .anx-sec__title {
  margin: 0; font-family: var(--sans); font-weight: 600; font-size: 1.5rem;
  letter-spacing: -0.01em;
}

/* The range switch: the house .tabs rail worn as the mock's segmented control.
   The GROUP carries the one stroke (r8 — never a bordered box inside a
   bordered box); the picked answer is a FILL. Fully round, so the stroke
   spends --hair-w (r9). */
body.studio .tabs--rng {
  display: inline-flex; gap: 2px; margin: 0; padding: 3px; flex-wrap: nowrap;
  border: var(--hair-w) solid var(--border-strong); border-radius: 999px;
  background: var(--input-bg);
}
body.studio .tabs--rng .tab {
  height: 30px; padding: 0 13px; border: 0; border-radius: 999px;
  font-size: 0.82rem; font-weight: 600; color: var(--muted);
}
body.studio .tabs--rng .tab:hover { color: var(--text); }
body.studio .tabs--rng .tab--on { background: var(--surface-3); color: var(--text); border: 0; }

/* THE STREAK (Live pill only): 52 weekly bars, CSS, no chart lib. A week with
   no live is the CANVAS — a slot, never a grey zero chip — so the row's shape
   holds still while only the lived weeks paint. Once = the house gold; 2+ =
   the brighter gold, SHINING: the glow is the mock's own design for exactly
   this state (a justified one — the house ban is on unjustified glows). */
body.studio .anx-streak { margin-top: 34px; }
body.studio .anx-streak__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px 24px; flex-wrap: wrap;
}
body.studio .anx-streak__title {
  display: flex; align-items: center; gap: 10px; margin: 0;
  font-size: 1.6rem; font-weight: 600; letter-spacing: -0.01em;
  --lh: 1.2; line-height: var(--lh-snap);
}
body.studio .anx-streak__title .lucide { color: var(--gold); flex: none; }
body.studio .anx-streak__sub { margin: 6px 0 0; color: var(--muted); font-size: 0.92rem; }
body.studio .anx-streak__legend {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  color: var(--muted); font-size: 0.82rem; padding-top: 8px;
}
body.studio .anx-streak__swatch {
  display: inline-block; width: 12px; height: 16px; border-radius: 3px;
  background: var(--surface-2); margin-right: 6px; vertical-align: -3px;
}
body.studio .anx-streak__swatch--once { background: var(--gold); }
body.studio .anx-streak__swatch--multi {
  background: var(--gold-bright); box-shadow: 0 0 8px rgba(240, 213, 154, 0.55);
}
body.studio .anx-streak__bars {
  display: flex; align-items: stretch; gap: 4px; height: 44px; margin-top: 22px;
}
/* EVERY ONE OF THE 52 SLOTS IS A TABLET (r15 polish, Maaz 2026-09-10: "empty
   tablets ... for the entire year till the end"). The empty state used to be
   `transparent`, so a row that starts at the join week painted a handful of
   gold bars against nothing — the year ahead read as missing rather than as
   not-yet. It is the dim well the legend's own "No live" swatch has always
   promised, and it is deliberately the SAME well for a past week with no live
   and a week that has not happened: the x-labels carry that difference, and a
   second shade here would be a grey zero chip scoring a week nobody has
   lived. */
body.studio .anx-streak__bar { flex: 1 1 0; min-width: 0; border-radius: 4px; background: var(--surface-2); }
body.studio .anx-streak__bar--once { background: var(--gold); }
body.studio .anx-streak__bar--multi {
  background: var(--gold-bright); box-shadow: 0 0 10px rgba(240, 213, 154, 0.5);
}
/* The x-labels are the row's meaning: "{join date} · you started" on the left
   (the bars run FORWARD from there) and "365 days from start" on the right.
   They wrap rather than collide on a phone, and the right one stays right. */
body.studio .anx-streak__x {
  display: flex; justify-content: space-between; gap: 8px 16px; flex-wrap: wrap;
  margin-top: 8px; color: var(--dim); font-size: 0.82rem;
}
body.studio .anx-streak__x span:last-child { margin-left: auto; text-align: right; }
@media (max-width: 640px) { body.studio .anx-streak__bars { gap: 2px; } }

/* THE TILES (Live + Encore pills): uppercase letter-spaced caption, the big
   numeral, the sub, the vs-previous line — the mock's card, four abreast at
   the desk. */
body.studio .anx-tiles {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 28px clamp(18px, 3vw, 44px); margin-top: 26px;
}
@media (max-width: 900px) { body.studio .anx-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 480px) { body.studio .anx-tiles { grid-template-columns: 1fr; } }
/* position: relative is load-bearing — the (i)'s tip anchors to the TILE (the
   .cap-cost recipe), because a 236px box anchored on a glyph near the grid's
   right edge is the sentence-length-[data-tip] overflow class. */
body.studio .anx-tile { position: relative; min-width: 0; }
body.studio .anx-tile__cap {
  display: block; color: var(--muted); font-size: 0.78rem; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase;
}
body.studio .anx-tile__num {
  display: block; margin-top: 10px; font-family: var(--sans); font-weight: 500;
  font-size: 2.1rem; --lh: 1.05; line-height: var(--lh-snap); letter-spacing: -0.02em;
  color: var(--text); font-variant-numeric: tabular-nums lining-nums;
}
body.studio .anx-tile__sub { display: block; margin-top: 4px; color: var(--muted); font-size: 0.92rem; }
body.studio .anx-tile .tip-i[data-tip] { position: static; }
/* box-sizing is load-bearing: the universal border-box reset does not reach a
   pseudo-element, so a content-box max-width:100% plus the tip's own padding
   and hairline overflowed every tile by 22px — 2px of DOCUMENT scroll at
   390px (measured). */
body.studio .anx-tile .tip-i[data-tip]::after {
  box-sizing: border-box; width: max-content; max-width: 100%; left: 0; right: auto;
}

/* The vs-previous line: red down, gold up (the mock); an exact zero is flat
   and dim — an arrow on "0%" would claim a direction nothing moved in. */
body.studio .anx-cmp { display: block; margin-top: 8px; font-size: 0.88rem; color: var(--muted); }
body.studio .anx-cmp b { font-weight: 600; }
body.studio .anx-cmp__word { color: var(--dim); }
body.studio .anx-cmp--up b, body.studio .anx-cmp--up .anx-cmp__dir { color: var(--gold); }
body.studio .anx-cmp--down b, body.studio .anx-cmp--down .anx-cmp__dir { color: var(--live); }
body.studio .anx-cmp--flat b { color: var(--muted); }

/* The tile rows on this page hold four abreast at the desk — a grid, because
   the flex row wrapped its fourth tile under a long honest sub-line and left
   half the row empty. Sub-lines wrap inside their own column. */
body.studio .anx .stat-row--labeled {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 28px clamp(20px, 3vw, 56px);
}
body.studio .anx .stat-row--labeled .stat .small { max-width: 30ch; }

/* Every live / Every Encore — a table, because the numbers are read down a
   column. The CARD is the scroll region (Maaz r15: "add a scroll from right
   to left" — the trailing columns exist to be read, so they scroll into reach
   instead of being hidden), marked data-hscroll BY NAME for the fence, and
   the DOCUMENT still never scrolls anywhere. */
body.studio .anx-tablewrap[data-hscroll] { overflow-x: auto; margin-top: 14px; padding: 4px 20px; }
body.studio .anx-tablewrap .anx-table { min-width: 780px; margin-top: 0; }
body.studio .anx-pager {
  display: flex; justify-content: flex-end; align-items: baseline; gap: 16px;
  margin-top: 10px; color: var(--muted); font-size: 0.9rem;
  font-variant-numeric: tabular-nums lining-nums;
}
body.studio .anx-pager__link { color: var(--text); text-decoration: none; font-weight: 600; }
body.studio .anx-pager__link:hover { color: var(--accent); }
body.studio .anx-table { width: 100%; margin-top: 14px; border-collapse: collapse; }
/* The header words sit UNDER the card's own top line, so they need room above
   them — the wrap gives 4px and the row gave none, which left more space
   below "LIVE ELIGIBLE JOINED…" than above it (Maaz r15 polish). */
body.studio .anx-table th {
  padding: 14px 12px 10px 0; text-align: left; color: var(--dim);
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
}
body.studio .anx-table th.num, body.studio .anx-table td.num { text-align: right; }
body.studio .anx-table td {
  padding: 12px 12px 12px 0; border-top: 1px solid var(--border);
  font-variant-numeric: tabular-nums lining-nums; vertical-align: baseline;
}
body.studio .anx-table th:last-child, body.studio .anx-table td:last-child { padding-right: 0; }
body.studio .anx-table__live { display: block; color: var(--text); text-decoration: none; font-weight: 600; }
body.studio .anx-table__live:hover { color: var(--accent); }

/* Horizontal bars: Best channel, By product, Where they came from. */
body.studio .anx-bars { margin-top: 14px; display: grid; gap: 12px; }
body.studio .anx-bar { display: flex; align-items: center; gap: 14px; }
body.studio .anx-bar__label {
  flex: 0 0 clamp(110px, 18vw, 190px); min-width: 0; color: var(--muted); font-size: 0.9rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.studio .anx-bar__track {
  flex: 1; display: block; height: 8px; border-radius: 4px;
  background: var(--surface-3); overflow: hidden;
}
body.studio .anx-bar__fill { display: block; height: 8px; border-radius: 4px; background: var(--accent); }
body.studio .anx-bar__num { flex: none; text-align: right; min-width: 44px;
  font-variant-numeric: tabular-nums lining-nums; }
body.studio .anx-bar__num .dim { font-weight: 400; margin-left: 6px; }

/* ── r16: THE PEOPLE PILL'S FIVE AUDIENCE TILES AND ITS THREE ACTIVITY CARDS
   (Maaz 2026-09-13) ─────────────────────────────────────────────────────────

   FIVE tiles, not four: two windowed movement figures and the three rails'
   reach. The row narrows in two steps rather than one, because five 176px
   columns is already tight at the shell's 980px and three is the honest floor
   before the reach group would break across rows on its own. */
body.studio .anx-tiles--five { grid-template-columns: repeat(5, minmax(0, 1fr)); }
@media (max-width: 1000px) { body.studio .anx-tiles--five { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 620px) { body.studio .anx-tiles--five { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 420px) { body.studio .anx-tiles--five { grid-template-columns: 1fr; } }

/* The rail's mark inside a caption. inline-flex because a baseline-aligned SVG
   overhangs its line box by the font's descender and would grow the caption's
   row (the r8 hairline lesson, on a 14px glyph). */
body.studio .anx-tile__glyph {
  display: inline-flex; align-items: center; margin-left: 6px;
  vertical-align: middle; color: var(--muted);
}

/* Unfollowed is the one figure on this page that is bad news as it grows, and
   live-red is what the house spends on a loss (`.anx-cmp--down` already does).
   The caption stays muted: the NUMERAL is the claim. */
body.studio .anx-tile__num--loss { color: var(--live); }

/* PEAK ACTIVITY HOURS — 24 wells, CSS and nothing else (no chart library, the
   streak's own rule). Every slot is drawn, always: an hour nobody arrived in
   is a REAL ZERO, so it reads as an empty well rather than as a gap in the
   row. The gold grows from the bottom, which is why the slot is a flex column
   ending at its foot. */
body.studio .anx-hours { margin-top: 16px; }
body.studio .anx-hours__bars {
  display: flex; align-items: flex-end; gap: 3px; height: 132px;
}
/* position: relative CONTAINS the .sr-only label inside each slot — .sr-only is
   position: absolute, and with no positioned ancestor it lands at its static
   position inside the ICB and pushes the document sideways (the FOLLOWERS r2
   lesson, measured at 1,816px on a 1,512px viewport). */
body.studio .anx-hours__slot {
  position: relative; flex: 1; min-width: 0; height: 100%;
  display: flex; align-items: flex-end;
  background: var(--surface-2); border-radius: 3px; overflow: hidden;
}
body.studio .anx-hours__fill { display: block; width: 100%; background: var(--gold); border-radius: 3px; }
/* EIGHT COLUMNS, NOT `space-between` — a label has to stand under the bar it
   names. space-between spreads eight labels over the FULL width (label i at
   i/7), while the bar each one marks is at i/8 of it, so "9pm" ended up flush
   right under the 11pm well and the drift grew to ~117px on a 940px row
   (measured). Eight equal columns put label i at exactly i/8, which lands
   within ~3px of slot 3i's own left edge once the bars' 3px gaps are counted.
   Left-aligned on purpose: the label marks where that hour STARTS. */
body.studio .anx-hours__x {
  display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); margin-top: 8px;
}

/* The Devices and Top cities lists: the "Where they came from" bar, in half a
   column. The label gives up its wide basis (there are two of these side by
   side) and the row gains the share of the measured set. */
body.studio .anx-bars--split .anx-bar__label { flex: 0 0 clamp(88px, 10vw, 132px); }
body.studio .anx-bar__pct { flex: 0 0 44px; text-align: right; color: var(--muted); font-size: 0.9rem; }

/* The two-up grid. Its sparkline and drop lines came off with the sections
   Maaz killed on the 2026-09-13 earnings split (a class named after retired
   markup is how the next surface inherits a box, the .gl-side rule) — the GRID
   itself survived that cull because r16's Devices | Top cities pair renders
   it, which is its one host now. */
body.studio .anx-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(20px, 4vw, 48px); }
@media (max-width: 760px) { body.studio .anx-grid2 { grid-template-columns: 1fr; } }
body.studio .anx-big {
  display: block; margin-top: 8px; font-family: var(--sans); font-weight: 500;
  font-size: 1.9rem; --lh: 1.1; line-height: var(--lh-snap); letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums lining-nums;
}

/* The follower line: y min/max, the window's two ends, one polyline.
   (r16 deleted `.anx-movement`, `.anx-movement__word`, `.anx-big--count` and
   `.anx-quiet` with the markup they dressed — the hero, its movement line and
   the going-quiet link. A class named after retired markup is how the next
   surface inherits a box by reaching for a familiar name.) */
body.studio .anx-chart { display: flex; gap: 12px; align-items: stretch; }
body.studio .anx-chart__y {
  display: flex; flex-direction: column; justify-content: space-between;
  color: var(--dim); font-size: 0.8rem; text-align: right; padding: 2px 0 22px;
  font-variant-numeric: tabular-nums lining-nums;
}
body.studio .anx-chart__frame { flex: 1; min-width: 0; }
body.studio .anx-chart__frame svg {
  display: block; width: 100%; height: 150px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
body.studio .anx-chart__frame polyline { stroke: var(--accent); stroke-width: 1.5px; }
body.studio .anx-chart__x { display: flex; justify-content: space-between; margin-top: 6px; }

/* (r15: the Every-live table's phone answer is the data-hscroll card above —
   Maaz asked for the scroll, so the old drop-columns rule came off.) */
@media (max-width: 560px) {
  body.studio .anx-bar { flex-wrap: wrap; row-gap: 4px; }
  body.studio .anx-bar__label { flex-basis: 100%; }
}

/* ═══ THE CONTROL ROOM SHELL (Maaz's ten mocks, 2026-09-07) ═══════════════════
   The four tabs under the monitor are gone. This is an app shell: the live's
   top bar, the program at whatever width the trays leave it, one tray at a
   time, and a permanent right-hand icon rail.

   THE PROGRAM AND THE TRAY ARE SIBLINGS IN FLOW, not an overlay. The mocks
   measure it: with the chat open the program's own frame ends at x=1378 and
   the overline's right-hand count moves with it; closed, the frame runs to
   1840. So the tray takes width from the program rather than covering it —
   which is also what makes "closed = the program uses the width" a real state
   rather than a z-index.

   Colours are OUR tokens, not the mock's greys (the brief's own instruction):
   near-black canvas, one gold accent, live-red for the two controls that stop
   something. Every hairline is `--hair-w` and every stroked pill has an
   integer height — the r8/r9 laws (Chrome cannot snap an arc, so a
   999px-radius box on a fractional line reads as grit). */
body.studio .shell--room {
  max-width: none; padding: 0 22px 0; height: 100dvh;
  display: flex; flex-direction: column; overflow: hidden;
}
/* THE TOP BAR (mock 01): the live's name, then one cluster on the right. This
   page carries no masthead above it, so this row is the whole chrome — an
   integer min-height, because a row of stroked controls on a fractional line
   is the r8 hairline bug. */
body.studio .cr-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; min-height: 64px; flex: none;
}
body.studio .cr-copy { display: inline-flex; align-items: center; gap: 8px; }
body.studio .cr-copy .lucide { flex: none; }

/* ── THE RING OF FEEDS ───────────────────────────────────────────────────────
   One pill holding what this live is ready on and a circle per place it plays
   (mock 01). The GROUP carries the stroke and each circle carries its own —
   they are not a segmented control (where the group strokes and the answer
   fills): every circle here is a separate thing with its own state, which is
   the r8 "a pill row strokes the item" half of that rule. */
/* TWO ROWS NOW (control-room QA item 2): the pill, and — only when a feed has
   actually FAULTED — a line under it. The pill keeps its own height and stroke;
   the outer box is a column so the fault line sits UNDER the circles in the
   flow instead of floating over the page from the viewport's top edge. */
body.studio .cr-feeds { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
body.studio .cr-feeds__row {
  display: flex; align-items: center; gap: 12px;
  height: 44px; padding: 0 6px 0 16px;
  border: var(--hair-w) solid var(--border); border-radius: 999px;
}
body.studio .cr-feeds__said { color: var(--muted); font-size: 0.86rem; white-space: nowrap; }
body.studio .cr-feeds__ring { display: flex; align-items: center; gap: 5px; }
/* THE FAULT LINE. Live-red, because a feed that is not going out is a thing
   that STOPPED — the two controls that stop something are the only other
   live-red on this page. It is drawn only where a row says so
   (`control_room_feed_alerts`); "not connected" is not a fault and never
   appears here. */
body.studio .cr-feeds__alert {
  display: grid; gap: 3px; justify-items: end;
  margin: 0; max-width: 46ch; text-align: right;
  color: var(--live); font-size: 0.8rem;
}
body.studio .cr-feeds__alert-said {
  display: inline-flex; align-items: flex-start; gap: 7px; text-align: left;
}
body.studio .cr-feeds__alert .lucide { flex: none; margin-top: 2px; }
body.studio .cr-feeds__alert-go { color: var(--muted); font-size: 0.8rem; }
/* AND THE ROW ALIGNS TO THE PILL WHEN THERE IS A FAULT UNDER IT. `.cr-top__acts`
   centres its children, so a two-row cluster pushed Copy invite link to the
   middle of the taller box and out of line with the pill it sits beside
   (measured: 38px of drop). Only the faulted state pays for it. */
body.studio .cr-top__acts:has(.cr-feeds__alert) { align-items: flex-start; }
body.studio .cr-top__acts:has(.cr-feeds__alert) > .btn { height: 44px; }
body.studio .cr-feed {
  display: inline-grid; place-items: center; flex: none;
  width: 32px; height: 32px; padding: 0; border-radius: 999px;
  border: var(--hair-w) solid var(--border); background: transparent;
  color: var(--text); cursor: pointer; text-decoration: none;
}
/* GOLD MEANS GOING OUT TONIGHT and nothing else wears it — the room (always
   on) and every armed leg. */
body.studio .cr-feed--room,
body.studio .cr-feed--on {
  border-color: var(--hair-gold); color: var(--gold);
  background: color-mix(in srgb, var(--gold) 14%, transparent);
}
/* Connected, not picked: present and plain. */
body.studio .cr-feed--connected { color: var(--muted); }
/* `.cr-feed--absent` is deleted with its markup (control-room QA item 2: only
   connected accounts are pills), and `.cr-feed--add` with its own
   (2026-09-11: there is no "+" on the ring — connecting is not a
   control-room act). A rule with nothing rendering it is the #415 trap in
   reverse — it reads like a live state. */
body.studio .cr-feed:hover { border-color: var(--border-strong); }
/* ── THE RED DOT ON A PICKED LEG THAT HAS NO KEY (Maaz 2026-09-15, r20) ─────
   Live-red, because the two other live-red things on this page are the controls
   that STOP something and this is a feed that will not start.

   IT IS A GRID ITEM, NOT AN ABSOLUTE ONE, and that is forced rather than
   preferred: the rule directly below makes every `.cr-feed[data-tip]` in the
   ring `position: static` (so a sentence-length tooltip resolves against the
   PILL — the 84px-of-horizontal-scroll fix), which means an absolutely
   positioned child in here would take the PILL as its containing block and land
   nowhere near its own circle. `.cr-feed` is already `inline-grid`, so both
   children share one cell and the dot takes the corner with `place-self`; the
   glyph keeps the `place-items: center` default. No positioning, no containing
   block, nothing that can inflate the ring's scroll width.

   The 2px ring in the page's own colour is what separates it from the glyph's
   top-right corner and from the circle's gold hairline. */
body.studio .cr-feed > * { grid-area: 1 / 1; }
body.studio .cr-feed__dot {
  place-self: start end; width: 8px; height: 8px; border-radius: 999px;
  background: var(--live); box-shadow: 0 0 0 2px var(--bg);
}
/* ── THE TIP'S CONTAINING BLOCK IS THE PILL, NOT THE CIRCLE ─────────────────
   `[data-tip]::after` is `position: absolute; left: 50%; white-space: nowrap`
   against its own [data-tip] element, which the base rule makes `relative`. On
   a 32px circle a sentence-length tip is a ~180px box hanging off both sides,
   and its right overflow inflates the circle's scrollWidth → the ring's → the
   pill's → the document's. MEASURED at a true 390px: 84px of horizontal
   scroll, with NO element's bounding box outside the viewport, which is why
   a rect-only probe found nothing. (Same class as r9 item 6 and the r12
   destination row: clamping the width cannot fix it, because CSS cannot know
   where the control landed — the containing block has to move.)

   So the circles go `static` and the PILL is `relative`: the tip's `left`/
   `max-width: 100%` now resolve against a box that is inside the viewport by
   construction, at any width. It also wraps, because these are sentences. */
body.studio .cr-feeds__row { position: relative; }
body.studio .cr-feeds .cr-feed[data-tip] { position: static; }
/* AND IT HANGS BELOW (control-room QA item 2). The base tip is
   `bottom: calc(100% + 8px)` — ABOVE its control — and this pill's top edge is
   ~24px from the top of the document, so every tip in the ring painted at a
   negative y and was CLIPPED BY THE BROWSER'S OWN CHROME. That is the
   half-cut "YouTube — not connected" box in his screenshot: not a flash, not a
   toast, the tooltip. Nothing that belongs to this row may paint upward. */
/* AND IT WRAPS RATHER THAN RUNNING WIDE (Maaz 2026-09-15: his replacement is
   "Not ready — fresh key not added before Live", "two lines if needed so the
   tip isn't too wide"). `box-sizing: border-box` is what makes that happen and
   it is the r15 tile-tip lesson on this pseudo: a `::after` DOES NOT INHERIT
   the universal border-box reset, so `max-width: 100%` was capping the CONTENT
   box and the tip's own 10px of padding and 1px hairline either side were laid
   on top of it — measured at 1512, a 242px sentence painting a 264px border
   box inside a 251px client box, i.e. 13px of overflow inside `.cr-feeds__row`
   itself. With the box counted properly the same sentence breaks over two
   lines (52px against a 19px line, padding and border included) and can never
   be wider than the row it is anchored to, at any width. */
body.studio .cr-feeds .cr-feed[data-tip]::after {
  left: 0; right: auto; width: max-content; max-width: 100%; box-sizing: border-box;
  top: calc(100% + 8px); bottom: auto;
  white-space: normal; text-align: left; text-transform: none; letter-spacing: 0;
  transform: translateY(-2px);
}
body.studio .cr-feeds .cr-feed[data-tip]:hover::after,
body.studio .cr-feeds .cr-feed[data-tip]:focus-visible::after { transform: translateY(0); }

/* ── THE BODY: program | tray | rail ─────────────────────────────────────── */
body.studio .cr-body {
  flex: 1; min-height: 0; display: flex; gap: 0;
}
body.studio .cr-prog {
  flex: 1; min-width: 0; display: flex; flex-direction: column;
  padding: 10px 0 18px; overflow-y: auto;
}
/* The overline: the tense on the left, the room's own count on the right —
   above the picture, never over it (live-flow r2 item 19: anything we lay over
   Daily's iframe lands on Prebuilt's own chrome and we cannot know what it
   covers). */
body.studio .cr-prog__over {
  display: flex; align-items: center; gap: 12px; min-height: 30px; flex: none;
}
body.studio .cr-prog__over-end { margin-left: auto; display: flex; align-items: center; gap: 12px; }
body.studio .cr-tense {
  display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-family: var(--mono); font-size: 0.7rem; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--dim); font-weight: 700;
}
/* Maaz 2026-09-10: NOT LIVE YET is the tense (bold); the Join line is a
   sentence, not a second overline — so it drops the tracking and the caps. */
body.studio .cr-tense__said {
  font-family: inherit; font-size: 0.86rem; font-weight: 400;
  letter-spacing: 0; text-transform: none; color: var(--muted);
}
/* LIVE is live-red and it is spoken off Stream#live? and off nothing else. */
body.studio .cr-tense--live { color: var(--live); }
/* The elapsed clock. A tabular figure, or the row twitches every second as the
   digits change width. */
body.studio .cr-elapsed {
  color: var(--muted); font-size: 0.86rem; font-variant-numeric: tabular-nums;
}
/* The host's own counts. Plain text, not a pill: the mocks read "142 waiting"
   as part of the overline, and a chip there would compete with the tense. */
body.studio .cr-count { color: var(--muted); font-size: 0.86rem; }

/* THE FRAME IS CAPPED BY WIDTH AND SHAPED BY aspect-ratio, and that pairing is
   the whole rule. Two ways of getting here are wrong and both were measured:

     `width: 100%` alone — a 16:9 box is 815px tall on a 1450px column, which
       pushed GO LIVE and the nothing-is-broadcast sentence off a 982px screen
       with the trays closed. It was the first thing the rendered frames showed.
     `height: <cap>` + `width: auto` — inside a flex COLUMN the box then
       shrinks (flex-shrink defaults to 1) and its height drops below the value
       while the width holds, so the monitor stops being 16:9 at all: measured
       1.69 against 1.778 on a 757px viewport. `flex: none` plus a max-WIDTH
       derived from the height cap is the shape that holds in both directions —
       wide column, the cap binds and the height is the cap; narrow column, the
       width binds and the height follows. Either way it is 16:9, which a
       browser test measures. */
/* THE CAP MOVED UP WITH ITEM 1'S DELETION (control-room QA, Maaz 2026-09-07:
   "give the freed height to the program/video. Expand it."). The `.cr-brief`
   box under the chrome was ~90px of card — 14px margin, 28px of padding, two
   lines of text — and that is what 58dvh→66dvh / 640→720px hands back, so the
   page's total height is unchanged and all of the gain is picture. The pairing
   below is still the whole rule; only the numbers moved. */
body.studio .cr-prog__frame {
  position: relative; flex: none; margin: 8px auto 0;
  width: 100%; max-width: calc(min(66dvh, 720px) * 16 / 9); aspect-ratio: 16 / 9;
  border: var(--hair-w) solid var(--border); border-radius: 14px; overflow: hidden;
  background: var(--surface-2);
}
body.studio .cr-prog__frame > video { display: block; width: 100%; height: 100%; object-fit: cover; }
/* THE CALL FILLS THE FRAME, AND `height: 100%` IS WHAT MAKES THAT TRUE (Maaz
   2026-09-08: "the program card has a dead brown/black slab under Daily's own
   tray … collapse it. Video fills the frame").

   The comment above these two selectors has claimed "the call fills the frame"
   since the shell round and the stylesheet never said so: the width and the
   border were reset here and the HEIGHT was left to `.crew-call--lobby` /
   `--incall`'s own desk caps (min(72vh,620px) / min(56vh,520px)). Those are
   sized for a page where the call is the whole column, and inside a frame
   capped at min(66dvh,720px) they are SHORTER than the box — so the card's own
   `background: var(--surface-2)` (#1c1712, the warm near-black) painted a slab
   under Daily's tray: 128px of it at 982px of viewport in call, 200px at 1200.
   That is the brown patch, and it is ours, not Daily's. It never showed in a
   test because the demo plane draws a `<video>` in this slot (which does carry
   `height: 100%`) — the real branch only renders where a stage room answers,
   which is the "write the real branch into the DOM before concluding" lesson.

   The iframe is already `width/height: 100%` of this box (crew_call's own
   `iframeStyle`), so one declaration makes the card and Daily's frame the same
   rectangle in both states — no band under the tray at any viewport, and no
   overflow either: the pre-join card used to be TALLER than the frame at desk
   heights under ~910px and was hard-clipped by `overflow: hidden`. Capping it
   at the frame hands that case back to Prebuilt's own scrolling, which is the
   lesser of the two (r2 item 19's trap is a short box, and a clipped box is a
   short box with no way to reach the rest). `.cr.is-focus` used to re-declare
   exactly this pair — one rule now, in one place. */
body.studio .cr-prog__frame .crew-call--lobby,
body.studio .cr-prog__frame .crew-call--incall {
  width: 100%; height: 100%; margin-inline: 0; border: 0; border-radius: 0;
}
body.studio .cr-prog__note {
  margin: 8px 0 0; color: var(--dim); font-size: 0.82rem; flex: none;
}
/* THE CHROME. Daily owns camera / mic / share (they are inside its iframe, on
   top of the video); this row is ours and holds only what is ours. */
body.studio .cr-prog__acts {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 14px; flex: none;
}
body.studio .cr-prog__gap { flex: 1; }
/* THE PRE-GO BRIEF RIDES THE CHROME ROW (live-flow r4 item 3): it takes the
   spacer's flex so the sentences sit on GO LIVE's own row. min-width: 0 lets
   them wrap inside their own box instead of shoving the primary onto a second
   row (the documented flex hazard on this page).
   LEFT-ALIGNED since 2026-09-10: it is a heading over its own subline, and a
   right-ragged title beside a button reads as a caption for the button. */
body.studio .cr-prog__brief { flex: 1; min-width: 0; text-align: left; }
body.studio .cr-prog__brief .cr-prog__said { margin: 4px 0 0; }
/* AND THE ROW KEEPS ITS DISTANCE (Maaz 2026-09-10: "more margin below the GO
   LIVE row so RSVP responses never kiss the button"). The RSVP card is the
   next thing in this column and it sat 14px under a 48px gold primary. */
body.studio .cr-prog__acts { margin-bottom: 12px; }
body.studio .cr-prog__acts + .card { margin-top: 20px; }
/* The mark and the words are one control, so the glyph never takes the tap
   (the house `.lucide { pointer-events: none }` rule) and never wraps off it. */
body.studio .cr-go { display: inline-flex; align-items: center; gap: 9px; white-space: nowrap; }
/* THE SAME TIP RULE for the chrome's own refused controls (the disabled cut,
   the pre-live drop): a sentence-length nowrap box on a control in a wrapping
   row is the overflow class above. The ROW is the containing block. */
body.studio .cr-prog__acts { position: relative; }
body.studio .cr-prog__acts .btn[data-tip] { position: static; }
body.studio .cr-prog__acts .btn[data-tip]::after {
  left: 0; right: auto; width: max-content; max-width: 100%;
  white-space: normal; text-align: left; text-transform: none; letter-spacing: 0;
}
body.studio .cr-focus-go { display: inline-flex; align-items: center; gap: 7px; }
body.studio .cr-prog__said { margin: 14px 0 0; color: var(--muted); font-size: 0.88rem; }
/* live-flow r4 item 25: "you · unmuted" / the lock phrases never wrap
   mid-label in a narrow tray. */
body.studio .cr-log__act { white-space: nowrap; }

/* ── THE TRAY ───────────────────────────────────────────────────────────────
   TWENTY PERCENT NARROWER, AND THAT IS ALSO THE CEILING (control-room QA item
   3, Maaz 2026-09-07: "default tray width 20% narrower than today; that new
   default is also the maximum; drag only narrower"). The old clamp was
   320/30vw/460; every number here is 0.8 of that.

   THE CEILING IS ENFORCED IN CSS, NOT IN THE DRAG. `min(var(--cr-tray-w),
   var(--cr-tray-max))` means the stored width can only ever make the tray
   NARROWER — a forged localStorage value, a stale one from a wider screen, a
   bug in the controller, all resolve to the max. The default is the same
   expression with no override: a percentage flex-basis resolves against the
   flex container's own width, so `min(100%, max)` IS max.

   `--cr-tray-w` is written as an inline custom property on the shell by
   control_shell_controller (and re-applied on turbo:morph, which strips it) —
   never on the tray, because there are six of them and the width is one
   decision. */
body.studio .cr { --cr-tray-max: clamp(256px, 24vw, 368px); }
/* THE FLOOR IS IN THE CSS TOO (journey r3 item 5): the drag already clamps at
   220px in JS, but a stored width from any other writer must never let the
   gear or the copy paint over the rail — clamp() holds the floor whatever the
   inline value says, so the composer and Send keep a box they both fit in. */
body.studio .cr-tray {
  flex: 0 0 clamp(220px, var(--cr-tray-w, 100%), var(--cr-tray-max)); min-width: 0;
  display: flex; flex-direction: column; min-height: 0;
  padding: 10px 18px 18px;
}
/* ── THE VERTICAL RULE IS THE HANDLE (item 3) ───────────────────────────────
   It used to be the tray's own `border-left`, which is why the rule and the
   spacing move here: a border cannot be dragged. The box is a real 11px
   target with the hairline drawn down its middle, and it is only in the layout
   while a tray is open (`.cr.is-open`) — with scripting off that class is
   never set, and the trays stack, where a vertical rule would be nonsense.

   It is a `separator` with `tabindex`, per the ARIA window-splitter pattern:
   the arrow keys narrow and widen it in 24px steps and Home restores the
   default, so the width is reachable without a pointer. */
body.studio .cr-split { display: none; }
body.studio .cr.is-open .cr-split {
  display: block; flex: none; width: 11px; margin-left: 12px;
  align-self: stretch; cursor: col-resize; background: none; border: 0; padding: 0;
  position: relative;
}
body.studio .cr-split::before {
  content: ""; position: absolute; inset: 0 5px;
  background: var(--border);
}
body.studio .cr-split:hover::before,
body.studio .cr-split:focus-visible::before { background: var(--hair-gold); inset: 0 4px; }
body.studio .cr-split:focus-visible { outline: none; }
/* While the pointer is down the whole shell keeps the resize cursor and stops
   selecting text under the drag. */
body.studio .cr.is-resizing { cursor: col-resize; user-select: none; }
body.studio .cr.is-resizing .cr-split::before { background: var(--hair-gold); inset: 0 4px; }
/* DECLARED, not assumed: a rule that gives an element a display out-ranks the
   UA's [hidden] rule, and that has bitten this codebase four times
   (.cap-cards, .rqs__ceiling, .btn, .link-u). Here it is load-bearing — the
   flex box above would keep every closed tray on screen. */
body.studio .cr-tray[hidden] { display: none; }
body.studio .cr-tray__head { flex: none; min-width: 0; }
/* IN THE CHAT BAR THE HEAD SHRINKS AND ITS WORDS WRAP (journey r3 item 5).
   `flex: none` in that ROW was the overlap: an unshrinkable head is as wide
   as its longest sentence on one line, which pushed the gear out of a
   narrowed tray and onto the rail. The tray itself is a column, where the
   head keeps `flex: none` (there it means "don't grow tall"). */
body.studio .chat__bar .cr-tray__head { flex: 1 1 0; }
body.studio .cr-tray__title { margin: 0; font-size: 1.02rem; }
body.studio .cr-tray__said { margin: 4px 0 0; color: var(--muted); font-size: 0.82rem; overflow-wrap: anywhere; }
body.studio .cr-tray__line { margin: 14px 0 0; color: var(--text); font-size: 0.88rem; }
body.studio .cr-tray__scroll { flex: 1; min-height: 0; overflow-y: auto; padding-top: 14px; }
body.studio .cr-tray--list .cr-tray__search {
  display: flex; align-items: center; gap: 10px; margin-bottom: 8px;
}
body.studio .cr-tray--list .cr-search { min-width: 0; height: 38px; }
body.studio .cr-tray__foot {
  flex: none; padding-top: 14px; margin-top: 14px;
  border-top: var(--hair-w) solid var(--border);
}
body.studio .cr-tray__done { margin-top: 12px; }
/* A tray is a narrow column, so every sentence-length tip in one anchors to
   the TRAY rather than to its own control (the overflow class above). */
body.studio .cr-tray { position: relative; }
body.studio .cr-tray .btn[data-tip],
body.studio .cr-tray .dim[data-tip] { position: static; }
body.studio .cr-tray .btn[data-tip]::after,
body.studio .cr-tray .dim[data-tip]::after {
  left: 0; right: auto; width: max-content; max-width: 100%;
  white-space: normal; text-align: left; text-transform: none; letter-spacing: 0;
}
body.studio .cr-tray__scope { margin-top: 16px; }
/* The chat tray holds a full column: bar, scrollback, composer. */
body.studio .cr-tray .chat { min-height: 0; }
/* 44px of top padding, not 14 (live-flow r4 item 10): the pin/delete tips
   hang ABOVE their control, and on the FIRST row that put them outside the
   scroller's own clip — "Pin"/"Delete" cut off from above. The padding is
   inside the scrollport, so the tip has room to paint. */
body.studio .cr-tray .chat__scroll { padding: 44px 0 14px; }
body.studio .cr-tray .chat__form { padding: 12px 0 0; }
body.studio .cr-tray .chat__bar { padding: 0 0 12px; align-items: flex-start; }

/* A titled block inside a tray — the tray's own sections (Crew, Guests,
   Questions waiting, Paid to get in). */
body.studio .cr-block + .cr-block {
  margin-top: 24px; padding-top: 20px; border-top: var(--hair-w) solid var(--border);
}
body.studio .cr-block__title { margin: 0 0 4px; font-size: 0.98rem; }
body.studio .cr-block__said { margin: 0 0 12px; color: var(--muted); font-size: 0.82rem; }

/* ── THE RAIL (mock 01) ──────────────────────────────────────────────────────
   Always visible, six destinations, a glyph over its own word. Glyph-only was
   never an option on the one screen a creator runs a show from — the studio
   rail's own narrow-mode rule ("a glyph-only sheet is a puzzle"). */
body.studio .cr-rail {
  flex: none; width: 84px; display: flex; flex-direction: column; align-items: stretch;
  gap: 2px; padding: 8px 6px 18px; margin-left: 14px; overflow-y: auto;
  border-left: var(--hair-w) solid var(--border);
}
body.studio .cr-chev {
  align-self: flex-end; display: inline-grid; place-items: center;
  width: 32px; height: 32px; padding: 0; margin-bottom: 6px;
  background: none; border: 0; border-radius: 8px; color: var(--muted); cursor: pointer;
}
body.studio .cr-chev:hover { color: var(--text); background: var(--surface-2); }
/* The chevron points at what the tap DOES: left to open, right to close. */
body.studio .cr-chev[aria-expanded="true"] .lucide { transform: scaleX(-1); }
body.studio .cr-railbtn {
  display: grid; justify-items: center; gap: 5px;
  padding: 10px 4px; background: none; border: 0; border-radius: 10px;
  color: var(--muted); cursor: pointer; text-align: center;
}
body.studio .cr-railbtn:hover { color: var(--text); background: var(--surface-2); }
body.studio .cr-railbtn__word { font-size: 0.72rem; line-height: 1.2; }
body.studio .cr-railbtn__num { font-size: 0.7rem; color: var(--dim); }
/* CHAT'S FIGURE IS THE CLIENT'S, so its span is in the DOM at all times and
   [hidden] at zero (Maaz 2026-09-12). The declaration is belt and braces —
   this class sets no `display`, so the UA's own [hidden] rule already wins —
   and it is written because the day somebody gives this class a `display` the
   rail grows an empty box under Chat, silently. The trap that has bitten
   .cap-cards, .rqs__ceiling, .btn, .link-u and .doorsheet__act in this file. */
body.studio .cr-railbtn__num[hidden] { display: none; }
/* THE OPEN ONE IS GOLD. Gold marks the positive state in this house, and here
   the positive state is "this is the panel you are looking at". */
body.studio .cr-railbtn[aria-selected="true"] {
  color: var(--gold); background: var(--surface-2);
}
body.studio .cr-railbtn[aria-selected="true"] .cr-railbtn__num { color: var(--gold); }

/* ── FOCUS (the addendum) ────────────────────────────────────────────────────
   The program takes the viewport: the top bar, the rail and the tray come off.
   It is a CLASS and never a navigation, for the same reason the trays are
   client-side — a URL change re-parents the permanent iframe and the call
   dies. The one control that survives it is the way out, which ships in the
   same breath (the r12 rule about the back link and the masthead exemption).
   [hidden]-style overrides are declared, per the law above. */
/* ONE SLOT, TWO STATES: [hidden] is declared because .btn gives itself
   `display: inline-flex` and out-ranks the UA's own [hidden] rule — the trap
   that has bitten .cap-cards, .rqs__ceiling, .btn and .link-u in this file. */
body.studio .cr-focus-go,
body.studio .cr-unfocus { display: inline-flex; align-items: center; gap: 7px; }
body.studio .cr-unfocus { display: none; }
body.studio .cr.is-focus .cr-focus-go { display: none; }
body.studio .cr.is-focus .cr-unfocus { display: inline-flex; }
body.studio .cr.is-focus .cr-top,
body.studio .cr.is-focus .cr-rail,
body.studio .cr.is-focus .cr-tray { display: none; }
body.studio .cr.is-focus .cr-prog { padding-top: 14px; }
/* The frame takes everything the chrome does not need. `height: auto` releases
   the desk cap; min-height: 0 lets it shrink inside the flex column. */
/* `max-width: none` is load-bearing: the desk cap is what keeps the monitor
   from filling a 1450px column, and in focus the whole point is that it does. */
body.studio .cr.is-focus .cr-prog__frame {
  flex: 1; min-height: 0; aspect-ratio: auto; width: 100%; max-width: none;
}

/* ── A DESTINATION ROW (mocks 06 / 10) ───────────────────────────────────────
   A card per feed, and the picked ones carry the gold hairline the masthead's
   circles carry — one colour for one claim across two surfaces. */
body.studio .cr-feedrow {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 12px 14px; margin-bottom: 8px;
  border: var(--hair-w) solid var(--border); border-radius: 12px;
}
body.studio .cr-feedrow--on { border-color: var(--hair-gold); }
body.studio .cr-feedrow--off { opacity: 0.55; }
body.studio .cr-feedrow__who { flex: 1; min-width: 0; display: grid; }
body.studio .cr-feedrow__name { font-weight: 600; }
body.studio .cr-feedrow__handle { color: var(--dim); font-size: 0.8rem; }
body.studio .cr-feedrow__state { color: var(--muted); font-size: 0.82rem; }
/* The ring's dot, on the row that says the same thing in words (r20). Inline,
   because this row is a flex line and there is nothing here to position
   against; the words carry the claim and the dot is the glance. */
body.studio .cr-feedrow__state--needs {
  display: inline-flex; align-items: center; gap: 6px; color: var(--live);
}
body.studio .cr-feedrow__dot {
  flex: none; width: 8px; height: 8px; border-radius: 999px; background: var(--live);
}
body.studio .cr-feedrow__none { color: var(--dim); }
body.studio .cr-feedrow__key { display: flex; gap: 8px; flex-wrap: wrap; width: 100%; }
body.studio .cr-feedrow__key input { flex: 1; min-width: 160px; }
/* The row verb, in gold where it ADDS and live-red where it stops something —
   the same colour rule the chrome's own two controls follow. */
body.studio .cr-act--gold { color: var(--gold); }
body.studio .cr-act--gold:hover { color: var(--gold-bright); }
/* The cut at the foot of the tray: full width, so it reads as the panel's one
   conclusion rather than as another row verb. */
body.studio .cr-cut--block { display: flex; width: 100%; justify-content: center; }
body.studio .cr-tray__foot form { display: block; }

/* ── A GREENROOM SEAT (mocks 04 / 05) ────────────────────────────────────── */
body.studio .cr-seat2 {
  display: grid; gap: 10px; padding: 12px 14px; margin-bottom: 8px;
  border: var(--hair-w) solid var(--border); border-radius: 12px;
}
/* Lit when they are actually there, and driven by measured presence alone: a
   seat we have not seen arrive stays dim, which is the honest answer. */
body.studio .cr-seat2--here { border-color: var(--border-strong); background: var(--surface-2); }
body.studio .cr-seat2__row { display: flex; align-items: center; gap: 10px; }
body.studio .cr-seat2__who { flex: 1; min-width: 0; display: grid; }
body.studio .cr-seat2__name { font-weight: 600; }
body.studio .cr-seat2__role { color: var(--dim); font-size: 0.8rem; }
body.studio .cr-seat2__acts { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
body.studio .cr-seat2__acts form { display: contents; }
body.studio .cr-seat2__link {
  width: 100%; padding: 6px 10px; font-size: 0.74rem;
  background: transparent; color: inherit;
  border: var(--hair-w) solid var(--border-strong); border-radius: 6px;
}
/* THE SLOT. A 16:9 block whose centre is the one tap — exactly what the mock
   draws. It carries no video element and no play affordance, because the green
   room's CALL is on its own page: a tile in a 460px tray that looked like a
   feed would be claiming a picture we are not showing. */
body.studio .cr-tile {
  display: grid; place-items: center; aspect-ratio: 16 / 9;
  border-radius: 10px; background: var(--surface-3);
}
body.studio .cr-tile__go { pointer-events: auto; }

/* THE BADGE, and the colour is the claim: gold for an invite nobody has
   claimed (the one state the host can act on), green for MEASURED presence,
   dim for "we have not seen you". A seat can never wear green and gold at
   once — admit! ejects them from the green room on the way to the stage. */
body.studio .cr-badge {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  height: 26px; padding: 0 11px; border-radius: 999px;
  border: var(--hair-w) solid var(--border-strong);
  font-size: 0.76rem; color: var(--muted); white-space: nowrap;
}
body.studio .cr-badge__dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
body.studio .cr-badge--green { border-color: var(--hair-ok); color: #6ee7b7; }
body.studio .cr-badge--on { border-color: var(--hair-gold); color: var(--gold); }
body.studio .cr-badge--invited { border-color: var(--hair-gold); color: var(--gold); }
body.studio .cr-badge--off { color: var(--dim); border-color: var(--border); }

/* THE DOOR into the green room (mock 05): gold-outlined, at the foot of the
   tray, two lines. */
body.studio .cr-door {
  display: grid; gap: 3px; margin-top: 14px; padding: 12px 14px;
  border: var(--hair-w) solid var(--hair-gold); border-radius: 12px;
  color: var(--text); text-decoration: none; flex: none;
}
body.studio .cr-door:hover { border-color: var(--gold); background: var(--surface-2); }
body.studio .cr-door__row { display: flex; align-items: center; gap: 9px; color: var(--gold); }
body.studio .cr-door__row .lucide { flex: none; }
body.studio .cr-door__row b { color: var(--text); }
body.studio .cr-door__said { color: var(--dim); font-size: 0.8rem; }

/* A disclosure that holds a form (Invite a guest, New drop) — the mock's own
   resting state is one row. */
body.studio .cr-invite { margin-top: 10px; }
/* `.cr-invite__open` IS ON EVERY ONE OF THESE SUMMARIES and was declared by
   nothing until 2026-09-14 — the rule dressed the element structurally
   (`> summary`) and the name the markup asks for styled air. Inert, because
   the structural selector still matched; found by the Drops tray's own #415
   guard, and named here so the class means what the markup says it does. */
body.studio .cr-invite > summary,
body.studio .cr-invite__open {
  display: inline-flex; align-items: center; gap: 7px;
  cursor: pointer; list-style: none; color: var(--muted); font-size: 0.86rem;
}
body.studio .cr-invite > summary::-webkit-details-marker,
body.studio .cr-invite__open::-webkit-details-marker { display: none; }
body.studio .cr-invite > summary:hover,
body.studio .cr-invite__open:hover { color: var(--text); }
body.studio .cr-invite__form { display: grid; gap: 8px; margin-top: 10px; }
body.studio .cr-invite__pair { display: flex; gap: 8px; }
body.studio .cr-invite__pair input { flex: 1; min-width: 0; }

/* ── A DROP CARD (mock 07) ──────────────────────────────────────────────── */
body.studio .cr-drop {
  display: grid; gap: 8px; justify-items: start;
  padding: 14px; margin-bottom: 10px;
  border: var(--hair-w) solid var(--border); border-radius: 12px;
}
body.studio .cr-drop--up { border-color: var(--hair-gold); }
body.studio .cr-drop__title { margin: 0; font-size: 1rem; }
body.studio .cr-drop__figs { margin: 0; color: var(--muted); font-size: 0.86rem; }
body.studio .cr-drop__said { margin: 0; color: var(--dim); font-size: 0.8rem; }
body.studio .cr-drop form { display: contents; }
/* THE LIVE DROP'S CLOCK AND ITS TWO EXTEND SHAPES (Maaz 2026-09-13). The
   remaining time reads like the fan pin's, a queued extra is a dim chip beside
   it, and the extend form is its OWN flex row — the card's `display: contents`
   on plain button forms would otherwise collapse this one too. */
body.studio .cr-drop__clock { margin: 0; font-size: 0.95rem; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
body.studio .cr-drop form.cr-extend { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
body.studio .cr-extend__label { color: var(--muted); font-size: 0.86rem; }
body.studio .cr-extend__min { flex: none; width: auto; }
body.studio .cr-extend__go { flex: none; }
/* `.cr-off` IS DELETED (Maaz 2026-09-15, r3). The live card took one field —
   the discount, the only thing on the form that is display only — and his call
   is that a card already up does not have its price changed. The rules went
   out WITH the markup and the write went with them (`tray_update` refuses a
   row that is not staged, by name): a class named after retired furniture is
   how the next surface inherits a box by reaching for a familiar name. */
/* ── A STAGED ROW (Maaz 2026-09-15, screen 5) ───────────────────────────────
   The card became a ROW: a name, its figures, one gold verb, and two quiet
   controls that appear under the cursor. Tighter box, because the tray lists
   every staged drop this creator owns now and six of the old cards was a
   column nobody could read. */
body.studio .cr-drop--row { gap: 5px; padding: 11px 12px; }
/* `.cr-drop` is `justify-items: start`, so every full-width child of it has to
   say so — the same reason the Edit disclosure carried `justify-self` before
   it moved into the acts row. */
body.studio .cr-drop__line,
body.studio .cr-drop__acts,
body.studio .cr-drop__quiet { justify-self: stretch; width: 100%; min-width: 0; }
body.studio .cr-drop__line { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; }
/* The origin chip is GONE (Maaz 2026-09-15, r3 — "remove Made for Live with …
   entirely"), so its `nowrap` override went with it: a rule for markup nothing
   renders is the other half of the `.gl-side` P0. `.cr-drop__line` stays as
   the row's title line, which is what keeps a long title inside the grid
   track (`.cr-drop` is `justify-items: start`, where a bare heading sizes to
   max-content). */
/* The list's one sentence sits UNDER the rows rather than inside each of them,
   so it is a child of the scroller and takes its own spacing. */
body.studio .cr-drop__said--list { margin: 2px 0 12px; }
/* THE ACTS LINE. `.cr-drop form { display: contents }` is what makes the Fire
   and Delete forms' own BUTTONS the flex items here rather than their forms. */
body.studio .cr-drop__acts { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 3px; }
/* THE ROW'S FIRE IS A TIGHTER BOX, and only its box: `.btn` is 11/20 padding
   at 0.8rem, which in a 276px tray is a 137x44 gold slab — half the row's
   area, times every drop on the shelf. It is still the ONE gold recipe (the
   r12 rule: `.btn--gilt` was deleted rather than joined by a third), and the
   dimmed pre-GO button is the same box so the row does not change height when
   a show goes live. MEASURED: 158 -> 144px per row.
   `.cr-drop--row .btn` is (0,3,0) against `.btn`'s (0,1,0). */
body.studio .cr-drop--row .btn { padding: 8px 15px; font-size: 0.76rem; }
/* AND THE TWO QUIET CONTROLS HAVE A LINE OF THEIR OWN, for a measured reason:
   beside the gold verb they overflow a 276px tray (Fire 137 + Delete 48 +
   both summaries ≈ 143), so they wrapped — and because the reveal is an
   opacity rather than a display, the line they wrapped to held ~90px of dead
   space under every row nobody was hovering. One row, one height, always. */
body.studio .cr-drop__quiet { display: flex; align-items: center; flex-wrap: wrap; gap: 14px; margin-top: 2px; }
/* Each disclosure is one flex item at rest — its summary, nothing more — and
   the WHOLE LINE when it opens (the `[open]` rule below). `min-width: 0` and
   NEVER `width: 100%`: a width resolves this item's flex basis to the whole
   line, so a CLOSED summary wraps onto a row of its own and the compact
   resting row is gone. */
body.studio .cr-drop__peek,
body.studio .cr-drop__edit { min-width: 0; }
/* A DISCLOSURE TAKES THE WHOLE LINE WHEN IT OPENS, so its
   panel is UNDER the row rather than squeezed into a flex item beside Fire.
   It is in flow, so nothing about it can be clipped by the tray's own
   scrollport (the r6 lesson: an overflow box clips its content whether or not
   it scrolls, and no z-index escapes an ancestor's clip).

   MEASURED, AND IT IS A GUARANTEE RATHER THAN THE CAUSE TODAY: with this rule
   removed both panels still land on their own line, because a fan card and a
   form each have a min-content width wider than the space Fire leaves, so the
   flex item wraps on its own. That is the layout depending on what the panel
   happens to CONTAIN — a narrow one, or a future one, would sit beside the
   gold button — so the basis states the intent and the browser test measures
   the result. */
body.studio .cr-drop__quiet > details[open] { flex: 1 1 100%; }
/* PREVIEW AND EDIT ARE REVEALED ON HOVER and they never move the row: opacity,
   so both summaries hold their slot at rest and the list cannot jump under the
   cursor. Focus reveals them for a keyboard, and an OPEN one stays lit however
   the pointer wanders — `:focus-within` alone loses them the moment a click
   lands on the panel's own field.

   NO `@media (hover: none)` GATE, deliberately, and it is the two shipped
   precedents' own trade (`.clip-card__play`, `.enc-thumb`): headless Linux
   Chrome REPORTS `hover: none`, so a media query here makes the rest state
   untestable in a browser — and opacity does not affect hit testing, so a tap
   still finds the control on a device with no pointer.

   NOTE FOR A BROWSER TEST: the W3C "element displayed" check DOES read an
   `opacity: 0` element as not displayed, so Capybara cannot click either
   summary until the row is hovered. That is the reveal reporting itself, and
   it is why both click tests hover first. */
body.studio .cr-drop__more { opacity: 0; transition: opacity var(--dur) var(--ease); }
body.studio .cr-drop--row:hover .cr-drop__more,
body.studio .cr-drop--row:focus-within .cr-drop__more,
body.studio .cr-drop__quiet > details[open] .cr-drop__more { opacity: 1; }
body.studio .cr-drop__more:focus-visible { opacity: 1; }
/* The preview panel opens under the row. `.drop-pin__card` is UNFENCED house
   vocabulary (the fan's own room wears it), so this scopes its one studio
   override by an ancestor rather than re-declaring the class. */
body.studio .cr-drop__card { margin-top: 10px; }
body.studio .cr-drop__card .drop-pin__card { margin-bottom: 0; }

/* ── THIS LIVE'S TRANSACTIONS (Maaz 2026-09-15, r3) ─────────────────────────
   Under Create New Drop: a hairline, a row per sale, and the pair that adds
   them up. Drawn only where there ARE sales (the partial's own guard), so the
   rule never paints a border over nothing.

   THE ROW IS A GRID AND THE NAME IS THE TRACK THAT GIVES
   (`minmax(0, 1fr) auto auto`), because a name is the only unbounded thing on
   it: in a 276px tray a long one on a flex row pushes the money and the clock
   out of the box, which is the overlap the brief names. `minmax(0, …)` and not
   `1fr` — a bare `1fr` is `minmax(auto, 1fr)`, whose min is MIN-CONTENT, so
   the ellipsis below could never engage and the row would widen the tray
   instead (the Followers roster's own lesson). */
body.studio .cr-ledger {
  margin-top: 18px; padding-top: 14px;
  border-top: var(--hair-w) solid var(--border);
}
body.studio .cr-ledger__head {
  display: flex; align-items: baseline; justify-content: space-between;
  flex-wrap: wrap; gap: 4px 10px;
}
body.studio .cr-ledger__sum { flex: none; color: var(--gold); font-size: 0.84rem; font-weight: 700; }
body.studio .cr-ledger__rows { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 7px; }
body.studio .cr-ledger__row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: baseline; gap: 10px; font-size: 0.86rem;
}
body.studio .cr-ledger__who { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
body.studio .cr-ledger__amt { font-weight: 600; }
body.studio .cr-ledger__at { color: var(--dim); font-size: 0.76rem; }

/* ── BOX OFFICE'S CREATE FORM, IN THE TRAY (Maaz 2026-09-14) ─────────────────
   `studio/drops/_form` is one partial with two hosts now. Nothing about the
   fields moved; this is the tray's own dressing of them, and every rule here
   exists because a 260–330px column is not the 1120px one the form was drawn
   for. It is keyed to `.bo-grid--tray` (the host says which shape it wants)
   and NOT to a media query: the tray is narrow at a wide viewport, so no
   @media can see it — the r7 rule.

   THE DISPLAY RULE HAS TO OUT-RANK `body.studio .cr-drop form` (0,2,2), which
   is `display: contents` and would dissolve the form's own box the moment Edit
   opened inside a row. `.cr-tray .bo-grid--tray` is (0,3,1) and wins on class
   count — the same fix `.cr-extend` already carries one rule up. */
body.studio .cr-tray .bo-grid--tray { display: block; margin-top: 10px; }
/* THE PREVIEW GOES UNDER THE QUESTIONS, and it stands still: `.summary` is
   sticky at the masthead's own offset, which inside the tray's own scrollport
   is a card that parks itself over the form it is previewing. */
body.studio .cr-tray .bo-preview--tray { position: static; margin-top: 16px; padding: 14px; }
/* The tray IS the panel, so the form-card's surface, stroke and 22px gutters
   would be a card inside a card with 44px of the column spent on nothing. */
body.studio .cr-tray .bo-grid--tray .form-card { background: none; border: 0; border-radius: 0; }
body.studio .cr-tray .bo-grid--tray .form-card > section { padding: 0; border-top: 0; }
/* The two selects shrink rather than standing at their longest option's width
   — a pass named "The Saturday Room · $99/mo" is wider than the whole tray. */
body.studio .cr-tray .bo-grid--tray .bo-select--sm {
  flex: 1 1 140px; width: auto; min-width: 0; max-width: 100%;
}
/* ── THE TWO PAIRS ARE EQUAL HALVES — AND THE CONSTRUCTION IS THE BASE
   BLOCK'S NOW (Maaz 2026-09-15 r3, then his launch QA 2026-09-16) ─────────
   r3 asked the tray for "discount and seat limit: wider, equal width; price
   and billing interval: wider, equal width, same row" and built it here,
   `.cr-tray`-scoped, because the tray is narrow at a WIDE viewport and no
   @media can see it (the r7 rule). The launch QA asked the DESK for exactly
   the same thing, so the two equal tracks, the label-over-field groups and
   the ⓘ tip's containing block all moved UP THE SHEET where both hosts read
   one set of rules — there is nothing left here for the two to keep in step
   about, and the arithmetic (why a grid rather than two flex bases) is
   recorded beside them.

   WHAT STAYS THE TRAY'S OWN is the pair of rows the desk lays side by side
   and this column cannot: at ~260px of content two half-tray fields under a
   wrapping "Discount to show ⓘ" are narrower than either field was, and the
   countdown's five chips want the whole width rather than what is left after
   the word Countdown. ONE track, so each of those two fields is the tray's
   own width — wider than r3's 46% and still equal to the other by
   construction. */
body.studio .cr-tray .bo-grid--tray .bo-clock-row { flex-direction: column; align-items: stretch; gap: 8px; }
body.studio .cr-tray .bo-grid--tray .bo-row--discount { grid-template-columns: minmax(0, 1fr); gap: 12px; }

/* ── THE BANNER TRAY (Maaz 2026-09-10) ───────────────────────────────────────
   The drops tray's own card shape: a row per banner, and the SHOWN one wears
   the gold hairline — gold means "going out tonight" here exactly as it does
   on the feeds ring. */
body.studio .cr-banner {
  display: grid; gap: 8px; justify-items: stretch;
  padding: 14px; margin-bottom: 10px;
  border: var(--hair-w) solid var(--border); border-radius: 12px;
}
body.studio .cr-banner--on { border-color: var(--hair-gold); background: var(--surface-2); }
body.studio .cr-banner__body { margin: 0; font-size: 0.92rem; overflow-wrap: anywhere; }
body.studio .cr-banner__acts { display: flex; align-items: center; gap: 8px; }
body.studio .cr-banner__gap { flex: 1; }
body.studio .cr-banner form { display: contents; }
body.studio .cr-banner__count { margin: 0; color: var(--dim); font-size: 0.78rem; text-align: right; }
body.studio .cr-banner__edit { margin-top: 0; }
body.studio .cr-banner__edit > summary { display: inline-flex; align-items: center; gap: 6px; }

/* The six MVP marks under "Broadcasting": lit where this live's lineup carries
   one — the feeds ring's own claim at a smaller size, and never a button (the
   arming lives in Destinations). */
/* AND THE BLOCK IS SEPARATED FROM THE SHELF ABOVE IT (Maaz 2026-09-15, r2
   item 1: "more space under + Create a banner / above READY — BROADCASTS AT
   GO"). It measured ZERO — `.cr-block + .cr-block` carries the house's own
   24/20/hairline for exactly this relationship, and its selector cannot reach
   this one because what sits above it is the create `<details>` rather than
   another block. The same three numbers, so the tray reads like every other
   one; the hairline is what says the shelf has ended and this is a different
   thing. MEASURED: the create disclosure's bottom and this block's top were
   both at y=326, i.e. 0px of air; they are 24px apart now, with the rule and
   20px of padding between it and the eyebrow. */
body.studio .cr-banner-live {
  display: grid; gap: 10px; justify-items: start;
  margin-top: 24px; padding-top: 20px;
  border-top: var(--hair-w) solid var(--border);
}
body.studio .cr-banner-live__feeds { display: inline-flex; gap: 6px; }
body.studio .cr-banner-feed {
  display: inline-grid; place-items: center; flex: none;
  width: 26px; height: 26px; border-radius: 999px;
  border: var(--hair-w) solid var(--border); color: var(--dim);
}
body.studio .cr-banner-feed--on {
  border-color: var(--hair-gold); color: var(--gold);
  background: color-mix(in srgb, var(--gold) 14%, transparent);
}

/* THE TWO SWATCHES (Maaz 2026-09-12): the creator picks the bar's colour and
   the words'. Native `<input type="color">`, so the row is a label and a
   platform picker and nothing else — the box is the browser's and we only
   size it and give it the house hairline. */
body.studio .cr-banner__colors { display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center; }
body.studio .cr-banner__swatch {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--muted); font-size: 0.8rem;
}
body.studio .cr-banner__swatch input[type="color"] {
  width: 34px; height: 26px; padding: 2px; flex: none;
  background: var(--surface-2); cursor: pointer;
  border: var(--hair-w) solid var(--border); border-radius: 7px;
}

/* THE SOCIAL PREVIEW — the creator's OWN camera at 16:9 with the bar
   composited bottom-left in the colours they picked. Deliberately never the
   left program iframe (the room's picture never wears the banner) and never a
   second Daily join (an instance, a prejoin and a participant-minute spent on
   a preview). The dark plate is what shows through in every state that has no
   picture — and the BAR is drawn in all of them, because a creator with their
   camera off still gets to see where the line sits. */
/* `container-type: size` is what makes `cqh` on the bar mean THIS PLATE'S
   HEIGHT, which is the whole conversion the bar's geometry needs (see its own
   rule). It is legal here because the box's size is its own — `width: 100%`
   plus `aspect-ratio` gives a definite height with no help from the contents,
   every one of which is absolutely positioned anyway. It also makes the plate
   a stacking context, which costs nothing: `overflow: hidden` already clips
   everything in it (the `.cr-person` container-query lesson, on a box that
   wants the containment). */
body.studio .cr-banner-preview {
  position: relative; width: 100%; aspect-ratio: 16 / 9; overflow: hidden;
  container-type: size;
  border-radius: 10px; border: var(--hair-w) solid var(--border);
  background: linear-gradient(160deg, #171310 0%, #0a0806 60%, #060504 100%);
}
body.studio .cr-banner-preview__tag {
  position: absolute; top: 8px; left: 10px;
  font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.16em;
  color: var(--dim); z-index: 2;
}
/* The camera fills the plate; `cover` because the tape is 16:9 and a webcam
   usually is not, and a letterboxed preview would misreport where the bar
   sits relative to the picture's own edges. */
body.studio .cr-banner-preview__cam {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: none;
}
body.studio .cr-banner-preview[data-preview="live"] .cr-banner-preview__cam { display: block; }
/* ONE SENTENCE PER STATE, all of them rendered by the ERB and revealed here
   (no copy in the JavaScript). `display: none` on the base is what makes the
   reveal a reveal — and every state that is not `live` has one. */
body.studio .cr-banner-preview__said {
  position: absolute; left: 10%; right: 10%; top: 50%; transform: translateY(-50%);
  margin: 0; display: none; z-index: 2;
  color: var(--dim); font-size: 0.78rem; text-align: center; line-height: 1.45;
}
body.studio .cr-banner-preview[data-preview="waiting"] .cr-banner-preview__said[data-when="waiting"],
body.studio .cr-banner-preview[data-preview="off"] .cr-banner-preview__said[data-when="off"],
body.studio .cr-banner-preview[data-preview="refused"] .cr-banner-preview__said[data-when="refused"] {
  display: block;
}
/* The bar itself. Its two colours are inline (they are data — the
   `.rsvpq__fill` precedent), so nothing here may set a colour: a token in this
   rule would be a second answer to a question the creator already answered.

   THE GEOMETRY IS THE THREE `banner.*` PARAMS WE SEND, CONVERTED (r2, Maaz
   2026-09-15 item 4). `CreatorBanner::SHAPE_GU` is the one definition — it
   carries the vendor verification and why the x inset follows from the
   rounding — and `test/views/banner_shape_test.rb` pins these three numbers to
   it, so the preview and the feeds cannot drift. The conversion is VCS's own
   `pixelsPerGridUnit = min(w, h) / 36`: on a landscape frame one grid unit is
   1/36 of the HEIGHT, i.e. 2.778cqh here.

     margin_x_gu 2       → left 5.556cqh
     margin_y_gu 2       → bottom 5.556cqh   (Maaz: "not flush to the bottom")
     cornerRadius_gu 1   → border-radius 2.778cqh
     maxW_pct_default 65 → max-width 65%     (unchanged, the vendor's default)

   THE TYPE IS THE CONTROL ROOM'S NOTICE (item 3), and the honest half of that
   is that the FAMILY already matched: both resolve to Archivo (`--sans`) —
   measured, `Archivo, "Helvetica Neue", Arial, sans-serif` on both. What
   differed was the WEIGHT, 500 against `.cr-prog__h`'s 600, which at 13px is
   what reads as a different face. The family is named here anyway rather than
   inherited, so the decision is local and assertable (the `.prof-name` leading
   precedent). The weight is deliberately NOT mirrored onto the wire: Daily's `banner.text.fontFamily` is eleven faces and Archivo is not
   one of them, so the type is the vendor's on the feed and the house's here,
   which is Maaz's own ruling.

   AND THE DIAGONAL IS OURS ALONE. There is no per-edge clip anywhere in the
   baseline composition (`cornerRadius_px` goes through `roundRect`, and
   `rotation_deg` turns the whole box, words included), so the feeds carry a
   straight right edge and this cut is the one shape fact the two surfaces do
   not share. It is deliberately that one: it decorates the edge the bar's own
   text length decides and says nothing about where the bar sits, which is the
   question this plate exists to answer. `--bar-cut` is in `em` so the slant
   holds its scale against the words rather than against a plate width nothing
   here can read, and the right padding CARRIES it — without that the cut
   clips the last word off the bottom line.

   THE LEADING IS SPELLED OUT LOCALLY, and `--lh: 1.3` alone is NOT that.
   `--lh-snap` is declared once on `:root`, where a custom property's value is
   computed — so the 1.55 fallback is already frozen into what every element
   inherits and every `--lh: N` in this sheet is inert (the `.prof-name`
   finding). Writing the pair here read as a tidy-up of the bare `1.3` this
   rule carried and MEASURED as a regression: 17px of leading became 21. The
   `round(calc(1em * var(--lh)), 1px)` form resolves against this element and
   keeps r8's whole-pixel rule at the same 17. */
body.studio .cr-banner-preview__bar {
  --bar-cut: 0.9em;
  /* THE WORDS' OWN CLEARANCE IS THE VENDOR'S (`CreatorBanner::VENDOR_PADDING_GU`
     — `banner.padding_gu`, default 2, which we deliberately do not send). Since
     the bar is flush to the frame's edge, this is the number that keeps the
     words off it, so converting Daily's own is what makes the clearance a
     creator reads here the clearance the feed has — never a pixel of ours. */
  --bar-pad: 5.556cqh;
  position: absolute; left: 0cqh; bottom: 5.556cqh; max-width: 65%; z-index: 2;
  padding: 5px calc(var(--bar-pad) + var(--bar-cut)) 5px var(--bar-pad);
  border-radius: 2.778cqh;
  clip-path: polygon(0 0, 100% 0, calc(100% - var(--bar-cut)) 100%, 0 100%);
  font-family: var(--sans); font-weight: 600; font-size: 0.84rem;
  --lh: 1.3; line-height: round(calc(1em * var(--lh)), 1px);
  overflow-wrap: anywhere;
  /* NO TEXT SHADOW AND NO STROKE (Maaz 2026-09-16 00:14 item 3: "unclean").
     There was none here; the halo he read is the VENDOR's — `banner.text`'s
     own `stroke_gu` defaults to 0.5, which index.jsx turns into a fifteen
     pixel stroke at 1080p — and the fix is in the adapter, which now states
     that width 0. This declaration is the fence: a shadow added here later
     would be a claim the feeds no longer make. */
  text-shadow: none;
}

/* ── THE TICKER IS A DIFFERENT SHAPE, HONESTLY DRAWN ────────────────────────
   A static bar is Daily's content-sized lower third: inset off the bottom,
   rounded, hugging its words. A TICKER is our own full-bleed strip — the
   WebFrame's box IS the bar (`TICKER_STRIP`'s `margin_gu` is 0), so it spans
   the frame and sits flush to its edge. Drawing them the same would be this
   round's own lie facing the other way.

   THE HEIGHT IS THE CONVERTED `height_gu` WITH A LEGIBILITY FLOOR. 3gu is
   8.333cqh, which on the tray's own plate (184–332px wide, so 103–187 tall) is
   9–16px — and a strip that thin cannot hold a readable word at the tray's
   220px floor. `max()` keeps the proportion wherever it fits and stops
   shrinking past 15px, which is the same compromise the static bar's own fixed
   `font-size` already makes about type. The EDGE, the DIRECTION and the SPEED —
   the three things a creator is actually deciding — are exact. */
body.studio .cr-banner-preview__bar--ticker {
  left: 0; right: 0; bottom: 0; max-width: none;
  height: max(8.333cqh, 15px);
  padding: 0; border-radius: 0; clip-path: none;
  display: flex; align-items: center; overflow: hidden;
  font-size: 0.6rem; --lh: 1; line-height: 1;
}
body.studio .cr-banner-preview__bar--top { top: 0; bottom: auto; }
/* The static bar's `--top` has to clear the plate's own SOCIAL PREVIEW tag,
   which sits at the top-left; the strip runs under it full-bleed and does not. */
body.studio .cr-banner-preview__bar--top:not(.cr-banner-preview__bar--ticker) { top: 5.556cqh; }

/* FOUR COPIES AND A `-50%` TRANSLATE: half the track has to be identical to
   the other half or the loop seams. The ticker PAGE measures its own line and
   is exact; a plate cannot read its own text width in CSS, so `--crawl` is the
   row's seconds-per-screen doubled — which makes the three answers correctly
   ORDERED against each other, which is what a creator is reading here.

   `will-change` is deliberately absent: this box is 15px tall and promoting it
   to its own layer would snap the hairlines around it (the masthead's own r9
   finding) for a preview nobody is measuring. */
body.studio .cr-banner-preview__crawl {
  display: flex; flex: none;
  animation: cr-banner-crawl var(--crawl, 50s) linear infinite;
}
body.studio .cr-banner-preview__crawl > span { white-space: nowrap; flex: none; padding-right: 2em; }
@keyframes cr-banner-crawl { from { transform: translateX(0); } to { transform: translateX(-50%); } }
/* IT HOLDS STILL WHERE THE CREATOR ASKED THEIR MACHINE TO. The ticker PAGE
   deliberately does not honour this — its reader is a video encoder, and the
   fan watching on Instagram has no way to ask it for less motion — but this is
   a UI on the creator's own desk. */
@media (prefers-reduced-motion: reduce) {
  body.studio .cr-banner-preview__crawl { animation: none; }
}

/* ── THE TICKER CHIP (mock 2: ← Ticker ←) ───────────────────────────────────
   Which SHAPE a row is, because two rows with the same words are two different
   overlays and the words cannot tell them apart. A static row wears nothing:
   static is the product, and a chip on every row would say nothing. */
body.studio .cr-banner__chip {
  display: inline-flex; align-items: center; gap: 5px; margin: 0;
  align-self: start; padding: 2px 8px;
  border: var(--hair-w) solid var(--border-strong); border-radius: 999px;
  color: var(--muted); font-size: 0.68rem; letter-spacing: 0.1em; text-transform: uppercase;
}

/* ── THE FOUR SETTINGS (Maaz 2026-09-15 23:50) ──────────────────────────────
   Every row is a label with its controls WRAPPING underneath rather than beside
   them: `--cr-tray-w` goes to 220px, which leaves 184px of content, and three
   pills on one line cannot fit in that — a nowrap row would scroll the card
   sideways (the drop card's own 27px lesson). */
body.studio .cr-bset { display: grid; gap: 10px; }
body.studio .cr-bset__check { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 0.86rem; }
body.studio .cr-bset__row { display: grid; gap: 6px; }
/* LOAD-BEARING: the rule above hands this row a `display`, which out-ranks the
   UA stylesheet's own `[hidden] { display: none }` — so without this line the
   speed pills paint for a static banner with the markup perfectly correct (the
   r14 trap, now seven times over in this sheet). */
body.studio .cr-bset__row[hidden] { display: none; }
body.studio .cr-bset__label {
  color: var(--dim); font-size: 0.68rem; letter-spacing: 0.12em; text-transform: uppercase;
}
body.studio .cr-bset__pills { display: flex; flex-wrap: wrap; gap: 6px; }
/* COMPACT, AND KEYED TO THIS BLOCK. `.pill`'s own 9px/18px is the house size on
   six other screens; narrowing it here rather than globally is the
   `data-hscroll` rule — the decision travels with the markup that asked for
   it. */
body.studio .cr-bset .pill > span { padding: 6px 11px; font-size: 0.8rem; display: flex; align-items: center; gap: 4px; }
body.studio .cr-bset__acts { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

/* ── THE STANCE CARDS (mock 08) ──────────────────────────────────────────────
   A card per answer and the picked one carries the gold hairline. It is NOT a
   segmented control (where the group strokes and the answer fills): these are
   three cards, each with its own sentence, which is the shape the mock draws
   and the shape that lets each one explain itself. */
body.studio .cr-stance {
  display: grid; gap: 3px; width: 100%; text-align: left;
  padding: 12px 14px; margin-bottom: 8px;
  border: var(--hair-w) solid var(--border); border-radius: 12px;
  background: transparent; color: var(--text); font: inherit; cursor: pointer;
}
body.studio .cr-stance:hover { border-color: var(--border-strong); }
body.studio .cr-stance--on { border-color: var(--hair-gold); background: var(--surface-2); }
body.studio .cr-stance__name { font-weight: 600; }
body.studio .cr-stance__said { color: var(--muted); font-size: 0.82rem; }
body.studio .cr-tray form:has(> .cr-stance) { display: contents; }

/* THE LINK HOLD — a switch, drawn as one. */
body.studio .cr-switch {
  display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  padding: 12px 14px; margin: 14px 0 0;
  border: var(--hair-w) solid var(--border); border-radius: 12px;
  background: transparent; color: var(--text); font: inherit; cursor: pointer;
}
body.studio .cr-switch__label { flex: 1; min-width: 0; font-size: 0.9rem; }
body.studio .cr-switch__track {
  flex: none; display: block; width: 40px; height: 24px; border-radius: 999px;
  border: var(--hair-w) solid var(--border-strong); background: var(--surface-3);
  position: relative; transition: background 120ms ease, border-color 120ms ease;
}
body.studio .cr-switch__knob {
  position: absolute; top: 3px; left: 3px; width: 16px; height: 16px;
  border-radius: 50%; background: var(--muted); transition: transform 120ms ease, background 120ms ease;
}
body.studio .cr-switch--on .cr-switch__track { background: var(--gold); border-color: var(--gold); }
body.studio .cr-switch--on .cr-switch__knob { transform: translateX(16px); background: var(--gold-ink); }
body.studio .cr-tray form:has(> .cr-switch) { display: contents; }

/* THE HOUSE RULE — label, field, save. */
/* `.cr-rule` is deleted with the House rule field it dressed (control-room QA
   item 5). A rule with no markup reads like a live control. */

/* ═══ THE GREENROOM DECK ON THE LIVE'S OWN PAGE (journey r3 item 11) ═════════
   Tiles of who's backstage + the corridor as a side column — mock 09's shape,
   unfenced because half the people who belong in it are fans. The chat keeps
   its own .bs furniture below; only the frame around it is new. */
.bs-deck {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(280px, 380px);
  gap: 22px; align-items: start; margin-bottom: 18px;
  padding: 18px; border: 1px solid var(--border-strong); border-radius: var(--radius);
}
/* live-flow r4 item 20: a green-room guest gets the corridor ALONE — one
   column, no who-grid, and the panel loses the second column's divider. */
.bs-deck--solo { grid-template-columns: minmax(0, 1fr); }
.bs-deck--solo .bs { border-left: 0; padding-left: 0;
  background: var(--surface);
}
.bs-deck .bs { border-left: 1px solid var(--border); padding-left: 18px; min-height: 240px; max-height: 420px; }
.bs-deck__who { min-width: 0; }
.bs-deck__title { margin: 0; font-size: 1.02rem; }
.bs-deck__said { margin: 4px 0 14px; color: var(--muted); font-size: 0.82rem; }
.bs-deck__tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; }
.bs-seat {
  display: flex; align-items: center; gap: 10px; min-width: 0;
  padding: 12px; border: 1px solid var(--border); border-radius: 12px;
}
.bs-seat__who { flex: 1; min-width: 0; display: grid; }
.bs-seat__name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bs-seat__role { color: var(--dim); font-size: 0.8rem; }
.bs-seat__state {
  flex: none; font-family: var(--mono); font-size: 0.64rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--muted);
}
@media (max-width: 900px) {
  .bs-deck { display: block; }
  .bs-deck .bs { border-left: 0; padding-left: 0; margin-top: 16px; border-top: 1px solid var(--border); padding-top: 14px; }
}

/* ═══ THE BACKSTAGE CHAT (mock 09) ═══════════════════════════════════════════
   The crew-and-guests corridor. Its own table, its own channel, its own
   composer — and its own column on the greenroom page. */
.bs { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.bs__head { flex: none; padding-bottom: 12px; border-bottom: var(--hair-w) solid var(--border); }
.bs__title { margin: 0; font-size: 1.02rem; }
.bs__said { margin: 4px 0 0; color: var(--muted); font-size: 0.82rem; }
.bs__scroll {
  flex: 1; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 10px; padding: 14px 0;
}
.bs-msg { margin: 0; font-size: 0.88rem; --lh: 1.4; line-height: var(--lh-snap); overflow-wrap: anywhere; }
/* Name dim, words bright — the room's own chat shape, so a host reading both
   does not have to learn two. */
.bs-msg__who { color: var(--muted); font-weight: 600; margin-right: 6px; }
.bs-msg__who--me { color: var(--accent); }
.bs__oops { margin: 0; color: var(--live); font-size: 0.8rem; }
.bs__oops:empty { display: none; }
.bs__form { display: flex; gap: 8px; flex: none; padding-top: 12px; border-top: var(--hair-w) solid var(--border); }
.bs__form input[type="text"] { flex: 1; min-width: 0; }
.bs__send { padding: 9px 14px; }

/* ═══ THE GREENROOM PAGE (mock 09) ═══════════════════════════════════════════
   One promise at the top, the call and the seats on the left, the corridor on
   the right. */
body.studio .gr { padding-bottom: 0; }
body.studio .gr-top {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  min-height: 64px; flex: none;
}
body.studio .gr-top__gap { flex: 1; }
body.studio .gr-pill {
  display: inline-flex; align-items: center; height: 30px; padding: 0 14px;
  border: var(--hair-w) solid var(--hair-gold); border-radius: 999px;
  color: var(--gold); font-family: var(--mono); font-size: 0.7rem;
  letter-spacing: 0.18em; text-transform: uppercase; flex: none;
}
body.studio .gr-said { margin: 0; color: var(--muted); font-size: 0.9rem; }
body.studio .gr-body { flex: 1; min-height: 0; display: flex; gap: 0; }
body.studio .gr-stage {
  flex: 1; min-width: 0; display: flex; flex-direction: column;
  padding: 6px 0 22px; overflow-y: auto;
}
body.studio .gr-stage .crew-call--lobby,
body.studio .gr-stage .crew-call--incall { width: 100%; margin-inline: 0; }
body.studio .gr-mute { margin: 10px 0 0; color: var(--muted); font-size: 0.84rem; }
body.studio .gr-mute--warn { color: var(--live); }
body.studio .gr-seats {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 10px; margin-top: 16px;
}
body.studio .gr-seat {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px 14px; border: var(--hair-w) solid var(--border); border-radius: 12px;
}
body.studio .gr-seat__who { flex: 1; min-width: 0; display: grid; }
body.studio .gr-seat__name { font-weight: 600; }
body.studio .gr-seat__role { color: var(--dim); font-size: 0.8rem; }
body.studio .gr-seat form { display: contents; }
body.studio .gr-waiting { margin: 14px 0 0; color: var(--dim); font-size: 0.84rem; }
body.studio .gr-body .bs {
  flex: 0 0 clamp(320px, 28vw, 420px);
  padding: 6px 0 22px 18px; margin-left: 18px;
  border-left: var(--hair-w) solid var(--border);
}

/* ── THE SHELL ON A SMALL SCREEN ─────────────────────────────────────────────
   Creators run shows from phones, so this is not an afterthought. Below the
   desk breakpoint the three columns stack: the program keeps the top of the
   screen, the rail becomes a horizontal strip under it (still six
   destinations, still worded), and the open tray takes the width below that.
   Nothing is hidden and nothing becomes a glyph-only puzzle. */
@media (max-width: 1080px) {
  body.studio .shell--room { height: auto; overflow: visible; padding-bottom: 40px; }
  body.studio .cr-body { display: block; }
  body.studio .cr-prog { overflow: visible; padding-bottom: 8px; }
  /* THE FRAME GOES BACK TO WIDTH-DRIVEN on a phone: the desk rule sizes it by
     height, and a 620px-tall 16:9 box is 1102px wide — which on a 390px column
     is the horizontal-scroll bug `assert_no_horizontal_scroll` exists to
     catch. `max-width: 100%` alone would letterbox it into a sliver. */
  body.studio .cr-prog__frame { height: auto; width: 100%; }
  body.studio .cr-tray {
    flex: none; margin-left: 0; padding: 14px 0 0;
    border-left: 0; border-top: var(--hair-w) solid var(--border);
  }
  body.studio .cr-tray__scroll { overflow: visible; }
  body.studio .cr-rail {
    width: auto; flex-direction: row; flex-wrap: wrap; align-items: center;
    gap: 4px; margin-left: 0; padding: 12px 0 0;
    border-left: 0; border-top: var(--hair-w) solid var(--border); overflow: visible;
  }
  body.studio .cr-chev { align-self: center; margin: 0 4px 0 0; }
  body.studio .cr-chev .lucide { transform: rotate(90deg); }
  body.studio .cr-chev[aria-expanded="true"] .lucide { transform: rotate(-90deg); }
  body.studio .cr-railbtn { padding: 8px 10px; }
  /* THE RING WRAPS ON A PHONE. Eight 32px circles plus their gaps plus "Ready
     on 3" is ~440px, which is 80px past a 390px column — measured, and it is
     exactly the horizontal-scroll bug assert_no_horizontal_scroll exists to
     catch. Nothing is hidden: the pill goes full width and the circles wrap
     onto a second line, because a feed a creator cannot see is a feed they
     cannot check. */
  body.studio .cr-top { padding-bottom: 4px; }
  body.studio .cr-top__acts { width: 100%; }
  body.studio .cr-feeds {
    flex-wrap: wrap; height: auto; width: 100%;
    padding: 10px 12px; border-radius: 14px; row-gap: 8px;
  }
  body.studio .cr-feeds__ring { flex-wrap: wrap; row-gap: 6px; }
  body.studio .gr-body { display: block; }
  body.studio .gr-stage { overflow: visible; }
  body.studio .gr-body .bs {
    flex: none; padding: 14px 0 0; margin-left: 0;
    border-left: 0; border-top: var(--hair-w) solid var(--border);
  }
  body.studio .gr-body .bs__scroll { max-height: 46vh; }
}

/* ── THE ACCOUNT PAGE (Maaz's mocks, 2026-09-07) ─────────────────────────────
   Four cards on one column, and ONE row idiom under all of them: the label on
   the left, the control on the right (.acct-row). That is what makes Profile,
   Sign in & support and Timezone read as one screen rather than three panels
   that happen to be stacked.

   Studio furniture, so every rule here is fenced — a non-studio view wearing
   .acct-row would render bare, which is law 2 of the fence. The exceptions are
   house vocabulary and live below this block: .avatar--xl is an avatar size and
   .btn--warn-outline/.btn--off are .btn modifiers, both of which stay unfenced
   on purpose (a fan surface must keep working the day it uses one). */
body.studio .shell--acct { max-width: 900px; }
body.studio .acct-card { margin-top: 16px; padding: 26px 28px; }
body.studio .acct-card__title { margin: 0; font-size: 1.15rem; }
/* The one primary per card, hard right — the mock's Save sits under the fields
   it writes, not under the whole page. */
body.studio .acct-card__act { display: flex; justify-content: flex-end; margin-top: 20px; }
body.studio .acct-card__said { margin: 14px 0 0; }

/* PROFILE — the picture, then the words. The photo column is a fixed track so
   the field rows all start on one line whatever the avatar is. */
body.studio .acct-profile { display: grid; grid-template-columns: 200px 1fr; gap: 30px; }
body.studio .acct-fields { display: flex; flex-direction: column; gap: 4px; min-width: 0; }

body.studio .acct-photo {
  display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center;
}
/* ONE <label> for the file input, and ONE opener in it: the avatar with the
   camera badge on it. "Change photo" was a second label for the same input
   directly under the same badge (Maaz's QA, 2026-09-16) — `.acct-photo__word`
   went out of this stylesheet with the markup, because a class named after
   retired markup is how the next surface inherits a box by reaching for a
   familiar name. */
body.studio .acct-photo__pick {
  position: relative; display: flex; flex-direction: column; align-items: center;
  gap: 12px; cursor: pointer;
}
body.studio .acct-photo__pick .ring { border-radius: 999px; }
body.studio .acct-photo__cam {
  position: absolute; right: 4px; top: 84px;
  display: grid; place-items: center; width: 34px; height: 34px;
  border-radius: 999px; background: var(--surface-3); color: var(--text);
  border: var(--hair-w) solid var(--border-strong);
}
body.studio .acct-photo__pick:hover .acct-photo__cam { background: var(--surface-2); }
/* CLIPPED, never display:none — a keyboard cannot focus what isn't rendered,
   and Capybara's make_visible cannot undo a clip-path either, so tests attach
   with visible: false (the cover-field rule). */
body.studio .acct-photo__file {
  position: absolute; width: 1px; height: 1px; opacity: 0;
  clip-path: inset(50%); pointer-events: none;
}
body.studio .acct-photo__pick:focus-within .acct-photo__cam {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
body.studio .acct-photo__hint { margin: 0; line-height: 1.4; }
body.studio .acct-photo__circle { display: block; }
body.studio .acct-photo__error { margin: 0; color: var(--live); font-size: 0.85rem; max-width: 190px; }
body.studio .acct-photo__error[hidden] { display: none; }

/* THE PHOTO PREVIEW (Maaz, r2 item 1). The same box as the Advanced sheet and
   the share sheet — .acct-crop__in is its own body because nothing else in the
   product frames a picture inside a circle you can drag.

   The STAGE is a square, and the circle over it is a MASK and not a clip: the
   file that leaves here is the square (every avatar surface draws the circle
   itself, `.avatar--photo` inside `.ring`), so the corners have to stay
   visible — dimmed — or a creator would position a picture against a boundary
   the crop does not use. */
body.studio .acct-crop {
  border: 1px solid var(--border-strong); border-radius: 16px; padding: 0;
  background: var(--surface); color: var(--text);
  width: min(420px, calc(100vw - 32px)); max-height: calc(100vh - 60px); overflow: auto;
  box-shadow: 0 30px 80px rgb(0 0 0 / 0.6);
}
body.studio .acct-crop::backdrop { background: rgb(0 0 0 / 0.7); }
body.studio .acct-crop__in { padding: 22px 24px 24px; }
body.studio .acct-crop__title { margin: 0; font-size: 1.4rem; }
body.studio .acct-crop__said { margin: 6px 0 18px; }
body.studio .acct-crop__stage {
  position: relative; width: 100%; aspect-ratio: 1; overflow: hidden;
  border-radius: 14px; background: var(--bg); cursor: grab;
  touch-action: none; /* the drag is ours; a phone must not pan the sheet */
}
body.studio .acct-crop__stage:active { cursor: grabbing; }
/* NATURAL SIZE IS LOAD-BEARING: the controller's scale factor is stage pixels
   per SOURCE pixel, so a stylesheet that shrank this to fit (the house has
   several `img { width: 100% }` rules for framed pictures) would make every
   crop land somewhere other than where it was positioned. */
body.studio .acct-crop__img {
  position: absolute; left: 50%; top: 50%;
  width: auto; height: auto; max-width: none; max-height: none;
  transform-origin: center; transform: translate(-50%, -50%);
  user-select: none; -webkit-user-drag: none;
}
/* One element, one stroke: the ring is drawn by this mask's own inset shadow
   and the darkening outside the circle by its border-radius trick — nothing
   here takes the pointer, so the drag underneath it is uninterrupted. */
body.studio .acct-crop__mask {
  position: absolute; inset: 0; pointer-events: none; border-radius: 14px;
  box-shadow: inset 0 0 0 2000px rgb(3 3 3 / 0.55);
  /* `closest-side` is the whole reason this reads as the avatar's own circle:
     a radial-gradient sizes to the farthest CORNER by default, so 50% would
     draw a circle ~1.41× too wide and the mask would fall off the stage. */
  -webkit-mask-image: radial-gradient(circle closest-side at 50% 50%, transparent 0 99%, #000 100%);
  mask-image: radial-gradient(circle closest-side at 50% 50%, transparent 0 99%, #000 100%);
}
body.studio .acct-crop__zoom { display: flex; align-items: center; gap: 12px; margin-top: 18px; }
body.studio .acct-crop__range { flex: 1; min-width: 0; accent-color: var(--gold); }
body.studio .acct-crop__acts { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }

/* THE ROW. 168px of label is enough for "Support email" on one line at this
   type size, which is what keeps every control's left edge aligned. */
body.studio .acct-row {
  display: grid; grid-template-columns: 168px 1fr; gap: 18px;
  align-items: start; padding: 10px 0;
}
body.studio .acct-row__label {
  color: var(--muted); font-size: 1rem; padding-top: 13px; margin: 0;
}
body.studio .acct-row__ctl { min-width: 0; }
/* EVERY CONTROL IN A ROW IS FULL WIDTH — and the reason this is worth a
   comment is that `input` here also matched an `<input type="submit">`. The
   support-email row used to hold one, so the button kept its 100% width (it
   was `flex: none`, i.e. shrink 0) and squeezed the address field beside it
   down to a 44px square: the exact frame in Maaz's r2 screenshot 2. There is
   no submit inside a row on this page any more — the page has ONE Save, in
   the card's own action slot — and a new one must not be put in a row. */
body.studio .acct-row__ctl input,
body.studio .acct-row__ctl select,
body.studio .acct-row__ctl textarea { width: 100%; }
body.studio .acct-row__ctl textarea { resize: vertical; }
body.studio .acct-row__said { margin: 8px 0 0; }
/* A READ-ONLY VALUE wears the box its neighbours wear, so the card reads as one
   form — but it is a <span>, not a disabled input, because the value is not
   editable HERE and a greyed field reads as a control that broke. Its trailing
   control is the way to change it. */
body.studio .acct-row__ctl--ro {
  display: flex; align-items: center; gap: 12px; min-height: 48px;
  padding: 0 14px; border: var(--hair-w) solid var(--border);
  border-radius: 10px; background: var(--surface-2);
}
body.studio .acct-ro { flex: 1; min-width: 0; overflow-wrap: anywhere; }
body.studio .acct-ro--none { font-style: normal; }
/* The pencil (mock 3): a 32px box around a 16px glyph, and the glyph never
   takes the pointer — the link is the event target across the whole box. */
body.studio .acct-ro__edit {
  flex: none; display: grid; place-items: center; width: 32px; height: 32px;
  border-radius: 8px; color: var(--muted); position: relative;
}
body.studio .acct-ro__edit:hover { color: var(--text); background: var(--surface-3); }
body.studio .acct-ro__act { flex: none; color: var(--muted); font-size: 0.92rem; }
body.studio .acct-ro__act:hover { color: var(--text); }
/* VERIFY sits IN the field (mock 4) — an unproven number is the one row on this
   card with something to finish, so the control is on the row and not below it.
   Integer height on a stroked pill. */
body.studio .acct-ro__verify {
  flex: none; height: 32px; display: inline-flex; align-items: center;
  padding: 0 14px; border: var(--hair-w) solid var(--border-strong);
  border-radius: 8px; background: var(--surface-3);
  color: var(--text); font-size: 0.9rem;
}
body.studio .acct-ro__verify:hover { border-color: var(--gold); color: var(--gold); }

/* THE CONTACT CHANGE, IN THE ROW (Maaz 2026-09-11). The change used to be a
   page; it is these two states of a row now — a field asking for the new
   value, then the code. No box of its own: the field and the code input wear
   the row's own controls, so the card still reads as one form.

   Fenced `body.studio` on purpose. The SAME partial renders on the fan
   /settings/contact page, where these classes are absent and the row falls
   back to the plain shell the page has always had — which is why nothing here
   is load-bearing for the flow, only for how it sits in this card. */
body.studio .acct-otp { display: block; }
body.studio .acct-otp__row { display: flex; align-items: center; gap: 8px; }
/* The field takes the slack and the button hugs the right — never the other
   way round (the r4 toolbar rule, on a row). `width: auto` is what stops
   `.acct-row__ctl input { width: 100% }` resolving against the flex container
   and pushing the button out of the row. */
body.studio .acct-otp__row .acct-otp__field {
  flex: 1 1 auto; min-width: 0; width: auto;
}
body.studio .acct-otp__row .btn { flex: none; }
body.studio .acct-otp__said { margin: 0 0 8px; }
body.studio .acct-otp__said .num { color: var(--text); }
/* One line under the control: what happens next, and the way out of it. */
body.studio .acct-otp__foot {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 12px; margin: 8px 0 0;
}
/* `button_to` is a FORM, so its own box would break the row — it is a text
   control here, the shape the Cancel link beside it already has. */
body.studio .acct-otp__foot form { display: contents; }
body.studio .acct-otp__link {
  color: var(--muted); font-size: 0.92rem; background: none; border: 0; padding: 0;
}
body.studio .acct-otp__link:hover:not([disabled]) { color: var(--text); }
body.studio .acct-otp__link[disabled] { color: var(--dim); cursor: default; }
/* THE FRAME CARRIES ITS OWN FLASH, because a frame has no layout to render one
   in — a mistyped code would otherwise re-draw the same field saying nothing.

   UNFENCED, and that is the fence's own law 2 rather than an oversight: this
   one slot is rendered by a LAYOUT (layouts/contact_frame), which is not a
   studio view, so fencing it would leave it bare. Same shape and same reason
   as `.door-sheet__said`, which is the other frame layout in the product. The
   name is this card's, so there is nothing for it to leak onto. */
.acct-otp__flash { margin-bottom: 12px; }
.acct-otp__flash .toast { margin: 0 0 8px; width: auto; }

/* A SECTION HEAD is a marked square, the title, and one sentence under it. */
body.studio .acct-sec { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 12px; }
body.studio .acct-sec__mark {
  flex: none; display: grid; place-items: center; width: 40px; height: 40px;
  border-radius: 11px; background: color-mix(in srgb, var(--gold) 12%, var(--surface-2));
  color: var(--gold); border: var(--hair-w) solid var(--hair-gold);
}
body.studio .acct-sec__mark--warn {
  background: color-mix(in srgb, var(--live) 12%, var(--surface-2));
  color: var(--live); border-color: var(--hair-live);
}
body.studio .acct-sec__sub { margin: 4px 0 0; }


/* THE EXITS. The card carries the warm stroke so the whole block reads as the
   one place on this page where something is lost. */
body.studio .acct-danger {
  margin-top: 30px; margin-bottom: 44px; padding: 26px 28px;
  border-color: var(--hair-live);
}
body.studio .acct-danger__cols { display: grid; grid-template-columns: 1fr 1fr; gap: 26px; }
/* The divider is the second column's own edge, so there is no third element
   holding a line. */
body.studio .acct-danger__cols > .acct-danger__col + .acct-danger__col {
  padding-left: 26px; border-left: var(--hair-w) solid var(--border);
}
body.studio .acct-danger__col { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; }
body.studio .acct-danger__col p { margin: 0; }
body.studio .acct-danger__col form { margin: 0; }

/* The two reversible states this page can be in, said above everything it can
   change. */
body.studio .acct-state {
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
}
body.studio .acct-state h3 { margin: 0 0 4px; }
body.studio .acct-state p { margin: 0; }
body.studio .acct-state form { margin: 0; flex: none; }

/* THE SHARE SHEET (mock 5; Maaz's popup QA 2026-09-11). The plate, then the
   three things you can do with it. It rides .share-sheet's own dialog box and
   only narrows it — the card is a portrait, so a 560px sheet would strand it
   in the middle.

   THE HEAD IS A CLOSE AND NOTHING ELSE. The "Backstage" title came off (the
   sheet holds one thing and that thing is a picture of itself), so the chevron
   went with it: with no title beside it a chevron reads as a Back, and the
   studio is not a native stack. The negative margins are what let a proper
   40px target sit in the corner without spending 40px of the screen above the
   card — which is the whole of "the QR sits higher". */
body.studio .doorsheet { max-width: 420px; }
body.studio .doorsheet .adv__in { padding: 18px 22px 26px; }
body.studio .doorsheet__head { display: flex; justify-content: flex-end; margin: -6px -8px 2px 0; }
body.studio .doorsheet__x {
  flex: none; display: grid; place-items: center; width: 40px; height: 40px;
  padding: 0; border: 0; border-radius: 10px;
  background: none; color: var(--muted); cursor: pointer;
}
body.studio .doorsheet__x:hover { background: var(--surface-3); color: var(--text); }
body.studio .doorsheet .doorcode { width: 100%; margin: 0 auto; }
body.studio .doorsheet .doorcode .doorqr { width: 100%; height: auto; aspect-ratio: 1; }
/* FLEX, NOT THREE FIXED COLUMNS: Copy QR is revealed only where the platform
   can put an image on the clipboard, and an empty grid track would leave a
   third of the row as a hole on every browser that cannot. */
body.studio .doorsheet__acts { display: flex; gap: 10px; margin-top: 18px; }
body.studio .doorsheet__act {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; min-height: 78px; padding: 12px 6px;
  border: var(--hair-w) solid var(--border); border-radius: 12px;
  background: var(--surface-2); color: var(--text);
  font: inherit; line-height: var(--lh-snap); font-size: 0.92rem;
  text-align: center; cursor: pointer;
}
/* A rule that hands an element a display out-ranks the UA's [hidden] (r14's
   own trap, and this is the class the copy control is drawn hidden in). */
body.studio .doorsheet__act[hidden] { display: none; }
body.studio .doorsheet__act:hover { border-color: var(--border-strong); background: var(--surface-3); }

@media (max-width: 760px) {
  body.studio .acct-profile { grid-template-columns: 1fr; }
  body.studio .acct-row { grid-template-columns: 1fr; gap: 8px; }
  body.studio .acct-row__label { padding-top: 0; }
  body.studio .acct-danger__cols { grid-template-columns: 1fr; }
  /* The divider was a column edge; stacked, it becomes a rule above. */
  body.studio .acct-danger__cols > .acct-danger__col + .acct-danger__col {
    padding-left: 0; padding-top: 22px;
    border-left: 0; border-top: var(--hair-w) solid var(--border);
  }
  body.studio .acct-state { flex-direction: column; align-items: flex-start; }
}

/* House vocabulary, deliberately UNFENCED (see the fence's own header): an
   avatar size and two .btn modifiers. A fan surface must keep working the day
   it wears one. */
.avatar--xl { width: 116px; height: 116px; font-size: 2.4rem; }
/* The exits' outlined red. It is the ONE red control shape in the product that
   is not live-red-means-on-air: a stroke and the word, never a filled button,
   because a filled red primary invites the tap this card exists to slow down. */
.btn--warn-outline {
  border-color: var(--hair-live); color: var(--live); background: none;
}
.btn--warn-outline:hover {
  border-color: var(--live); background: color-mix(in srgb, var(--live) 10%, transparent);
}
/* DRAWN AND REFUSED (the Download-report idiom, r13): a control whose action
   would refuse is dimmed with its reason on it, never hidden and never a
   primary that redirects with an alert. */
.btn--off { color: var(--dim); border-color: var(--border); cursor: default; }
.btn--off:hover { color: var(--dim); border-color: var(--border); background: none; }

/* A DISABLED CONTROL HAS TO LOOK DISABLED. Currency is read-only because no
   creators.currency exists (see the view), and a select that is refused but
   drawn at full strength is a control a creator will try to use. The house
   dims it and takes the pointer, so the sentence under it is the answer. */
body.studio .acct-pair__f select[disabled] {
  /* --muted, not --dim: this control STATES the currency, so it has to read as
     a value. At --dim it read as an empty field with a placeholder in it. */
  color: var(--muted); background: var(--surface); cursor: default; opacity: 1;
}
/* The support email's Save is a control beside a field, not a block — flex
   would otherwise stretch it across a third of the row. */
body.studio .acct-ro__row .btn { flex: none; }

/* ══ THE TIMEZONE PICKER (Maaz 2026-09-10) ══════════════════════════════════
   A searchable control over the <select> this page has always posted. It is
   the Pass form's dropdown furniture (`pf-drop`) with a search field in the
   panel, and it follows that picker's one structural decision: the panel is IN
   FLOW under the face, so it pushes the card down rather than floating — on a
   page of stacked cards nothing can clip it and no z-index is involved.

   Every class here is a studio class, fenced accordingly (the studio fence's
   own law), and each `[hidden]` rule below is load-bearing: a rule that hands
   an element a display OUT-RANKS the UA's own `[hidden]`, which is how a
   controller-revealed element ends up on screen before its controller has
   said so (#536). */
body.studio .tzp { display: block; }
/* Hidden LAST by the controller, and hidden by nothing else: with no
   JavaScript this is the control. */
body.studio .tzp__native[hidden] { display: none; }
body.studio .tzp__drop[hidden] { display: none; }
body.studio .tzp__drop {
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--input-bg);
}
/* The face wears the height of every other control in a row on this page, so
   the card still reads as one form. */
body.studio .tzp__face {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  min-height: 48px; padding: 0 14px; cursor: pointer; list-style: none;
  color: var(--text); font-size: 0.95rem;
}
body.studio .tzp__face::-webkit-details-marker { display: none; }
body.studio .tzp__said { min-width: 0; overflow-wrap: anywhere; }
body.studio .tzp__chev { flex: none; color: var(--muted); transition: transform 150ms var(--ease); }
body.studio .tzp__drop[open] .tzp__chev { transform: rotate(180deg); }
body.studio .tzp__drop[open] > .tzp__face { border-bottom: 1px solid var(--border); }
body.studio .tzp__drop:has(> summary:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

/* THE FIELD STAYS PUT WHILE THE LIST SCROLLS — the scroll box is the list, not
   the panel, because a search field that scrolls away is a search field you
   have to go back for. */
body.studio .tzp__find {
  display: flex; align-items: center; gap: 8px; padding: 10px 12px;
  border-bottom: 1px solid var(--border);
}
body.studio .tzp__mag { flex: none; color: var(--muted); }
body.studio .tzp__query {
  flex: 1; min-width: 0; border: 0; background: none; padding: 0;
  color: var(--text); font: inherit; line-height: var(--lh-snap);
}
body.studio .tzp__query:focus { outline: none; }
body.studio .tzp__query::-webkit-search-cancel-button { filter: grayscale(1) opacity(0.6); }
/* `position: relative` is load-bearing, not decoration: the controller scrolls
   the current row into view with `offsetTop`, which is measured against the
   nearest POSITIONED ancestor. With none, the list opened scrolled to a row
   somewhere else entirely — the same containing-block class as the Team
   sheet's 12,627px of blank box. */
body.studio .tzp__list {
  position: relative; list-style: none; margin: 0; padding: 6px;
  max-height: 300px; overflow-y: auto;
}
body.studio .tzp__row {
  display: block; width: 100%; text-align: left; border: 0; background: none;
  padding: 8px 10px; border-radius: var(--radius-sm); cursor: pointer;
  color: var(--muted); font: inherit; line-height: var(--lh-snap);
}
body.studio .tzp__row:hover { color: var(--text); background: var(--surface-3); }
body.studio .tzp__row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The row a creator is ON. Not gold: gold marks a positive state in this house,
   and this is a readout of the current value — the face above already says it. */
body.studio .tzp__row.is-on { color: var(--text); background: var(--surface-3); font-weight: 600; }
body.studio .tzp__zero { margin: 10px; }
body.studio .tzp__zero[hidden] { display: none; }
body.studio .tzp__stamped[hidden] { display: none; }

/* ══ THE TEAM (Maaz's five Team screens, 2026-09-08) ════════════════════════
   STANDING TEAMMATES: who can touch the studio, and what they can touch. All
   of it fenced under body.studio — the studio fence's own law — except
   nothing: every class here is a studio class, and studio_scope_law_test is
   what holds that.

   The table is the house `.roster` (one table look in the studio, not a second
   one); these rules only add what a roster row of PEOPLE needs — an avatar
   beside two lines of identity, and a kebab that does not take a column
   heading. */
body.studio .team-top {
  display: flex; align-items: center; gap: 12px; margin: 22px 0 18px; flex-wrap: wrap;
}
body.studio .team-top .btn { flex: none; }
/* The search box: the glyph inside the field, the field taking the row. It is
   the same shape as the rail's own find box, one level larger because it is
   the page's control and not a 248px column's. */
body.studio .team-find {
  position: relative; display: flex; align-items: center; gap: 10px;
  flex: 1 1 260px; min-width: 0;
  padding: 0 16px; height: 52px;
  background: var(--input-bg); border: 1px solid var(--border); border-radius: var(--radius);
  color: var(--dim);
}
body.studio .team-find:focus-within { border-color: var(--border-strong); }
body.studio .team-find input {
  flex: 1; min-width: 0; margin: 0; padding: 0; border: 0; background: none;
  color: var(--text); font: inherit; line-height: var(--lh-snap);
}
body.studio .team-find input:focus { outline: none; }
body.studio .team-find .lucide { flex: none; }

/* INSURANCE, not a layout: `.roster` squeezes (nothing in it carries a
   min-width and only its head is nowrap), so at 390px this table fits and the
   scroller never engages — measured. It is here for the day a column grows.
   NO min-width: one would force the scroll and then clip the kebab's own
   popover against it, which is a worse answer than a narrow column.

   …AND IT CLIPPED THE KEBAB ANYWAY (Maaz's screenshot 2, his live QA
   2026-09-08 — the gold hairline under the ⋯ is the clipped panel). Two facts
   the note above missed: an `overflow` box clips its content WHETHER OR NOT it
   scrolls, and `overflow-y: visible` beside `overflow-x: auto` computes to
   `auto`, so this rule was clipping on both axes on every viewport. So the
   containment stands down while a menu is open. It has to be the OVERFLOW and
   not a z-index: the house's "raise the row, don't clip the popover" rule
   (`.enc-row:has(details[open])`) works one level down from here, and no
   stacking order escapes an ancestor's clip. */
body.studio .team-scroll { overflow-x: auto; overscroll-behavior-x: contain; }
body.studio .team-scroll:has(details.menu[open]) { overflow: visible; }
body.studio .team-table tr:has(details.menu[open]) td { position: relative; z-index: 40; }
body.studio .team-table th[scope="row"] { padding-right: 20px; }
body.studio .team-who { display: flex; align-items: center; gap: 12px; min-width: 0; }
body.studio .team-who__id { display: flex; flex-direction: column; min-width: 0; }
/* The name and its badge on one line: the chip is a state, so it rides beside
   the name rather than under it. */
body.studio .team-who__name {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 0.95rem; font-weight: 600;
}
body.studio .team-who__mail { font-weight: 400; }
/* The kebab column is as wide as the control and no wider. */
body.studio .team-act { width: 56px; text-align: right; }
body.studio .team-none { margin: 16px 0 0; }

/* SCREEN 4 — "You're running solo.", and the three rungs BESIDE it. Two equal
   columns, stretched, so the ask and the choice it is asking about are one
   screen: "Choose how much they can manage" over three cards a creator can
   already read is a decision, and the same sentence over nothing is a leap.
   They stack under 900px, the ask first — it is the only control here. */
body.studio .team-zero {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 20px; margin-top: 28px;
}
@media (max-width: 900px) {
  body.studio .team-zero { grid-template-columns: minmax(0, 1fr); }
}
body.studio .team-solo {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 56px 28px; text-align: center;
}
/* The mark in its own circle: the same plate the studio's other empty states
   wear, at the size the mock draws. */
body.studio .team-solo__mark {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 68px; height: 68px; border-radius: 999px;
  background: var(--surface-2); color: var(--muted);
}
body.studio .team-solo__head {
  margin: 26px 0 0; font-size: clamp(1.7rem, 3.2vw, 2.4rem);
  letter-spacing: -0.02em; font-weight: 500;
}
body.studio .team-solo__said {
  margin: 14px auto 26px; max-width: 34ch; color: var(--muted);
  font-size: 1rem; --lh: 1.5; line-height: var(--lh-snap);
}
body.studio .team-solo .btn { flex: none; }

/* THE THREE RUNGS, widest first, each one a card with its own mark. The
   sentences are `AdminGrant::ROLE_BLURBS` — the add sheet's role cards and the
   invite mail print the same three, so there is one description of a rung in
   the product. */
body.studio .team-rungs { display: flex; flex-direction: column; gap: 20px; }
body.studio .team-rung {
  flex: 1; display: flex; align-items: center; gap: 18px; padding: 22px 24px;
}
body.studio .team-rung__mark {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; border-radius: 999px;
  background: var(--surface-2); color: var(--gold);
}
body.studio .team-rung__id { min-width: 0; }
body.studio .team-rung__name { margin: 0; font-size: 1.05rem; font-weight: 500; }
body.studio .team-rung__said {
  margin: 4px 0 0; color: var(--muted); font-size: 0.92rem;
  --lh: 1.5; line-height: var(--lh-snap);
}

/* ── THE ADD SHEET ──────────────────────────────────────────────────────────
   The Advanced sheet's own box, one size wider than the door sheet's: two
   fields side by side need the room. */
body.studio .team-sheet {
  border: 1px solid var(--border-strong); border-radius: 16px; padding: 0;
  background: var(--surface); color: var(--text);
  width: min(620px, calc(100vw - 40px)); max-height: calc(100vh - 60px); overflow: auto;
  box-shadow: 0 30px 80px rgb(0 0 0 / 0.6);
}
body.studio .team-sheet::backdrop { background: rgb(0 0 0 / 0.7); }
body.studio .team-sheet .adv__in { padding: 26px 28px 28px; }
body.studio .team-sheet .adv__head { margin-bottom: 20px; }
body.studio .team-sheet .adv__head h2 { font-size: 1.7rem; letter-spacing: -0.02em; margin: 0; }
body.studio .team-sheet .adv__foot { margin-top: 24px; }

/* The two tabs are a PILL ROW (each box carries its own stroke, the picked one
   is gold) and NOT the studio's segmented control — that idiom puts the stroke
   on the GROUP and fills the answer, which would draw a bordered box inside a
   bordered box here (r8's one-stroke rule). Same markup shape as .pill, so the
   picked state is the same `:has(input:checked)` question. */
body.studio .team-tabs {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px;
}
/* POSITIONED, and this is the fix for Maaz's screenshot 3 — the sheet with
   ~850px of empty box under its own footer. `.sr-only` is
   `position: absolute` with NO offsets, so it lands at its STATIC POSITION
   inside its nearest POSITIONED ancestor — and `overflow` does not make one,
   so 250 follower checkboxes were laid out against the modal DIALOG at their
   in-flow offsets deep inside an unclipped list: the sheet's scrollable
   overflow measured 12,627px against a 705px client height (measured before
   the fix; 705px after). Nothing manufactured that space and no min-height was
   involved — it was one missing `position`, multiplied by the roster. The r9
   item 6 `.sr-only` lesson, on the other axis. Any new label here that hides
   its own input needs this line. */
body.studio .team-tab { position: relative; margin: 0; border: 0; padding: 0; background: none; }
body.studio .team-tab > span {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  min-height: 56px; padding: 0 14px; cursor: pointer;
  border: var(--hair-w) solid var(--border); border-radius: var(--radius);
  color: var(--muted); font-size: 0.95rem;
  transition: color 150ms ease, border-color 150ms ease, background-color 150ms ease;
}
body.studio .team-tab:hover > span { color: var(--text); }
body.studio .team-tab:has(input:checked) > span {
  color: var(--text); border-color: var(--hair-gold);
  background: color-mix(in srgb, var(--gold) 7%, transparent);
}
body.studio .team-tab:has(input:focus-visible) > span { outline: 2px solid var(--accent); outline-offset: 2px; }

body.studio .team-fields {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin-top: 16px;
}
body.studio .team-fields[hidden] { display: none; }
body.studio .team-fields input {
  width: 100%; min-height: 56px; padding: 0 16px;
  background: var(--input-bg); border: 1px solid var(--border); border-radius: var(--radius);
  color: var(--text); font: inherit; line-height: var(--lh-snap); font-size: 0.98rem;
}
body.studio .team-fields input:focus { outline: none; border-color: var(--border-strong); }
@media (max-width: 560px) {
  body.studio .team-fields, body.studio .team-tabs { grid-template-columns: minmax(0, 1fr); }
}

body.studio .team-followers { margin-top: 16px; }
body.studio .team-followers[hidden] { display: none; }
body.studio .team-followers__more { margin: 12px 0 0; }
body.studio .team-picks__none { margin: 0; }
body.studio .team-picks { margin-top: 14px; max-height: 320px; overflow-y: auto; }
/* A pick row: avatar, two lines of identity, and the box hard right. The whole
   row is the label, so the hit area is the row and not the 22px box. */
body.studio .team-pick {
  position: relative;
  display: flex; align-items: center; gap: 12px; margin: 0;
  padding: 9px 10px; border-radius: var(--radius); cursor: pointer;
  transition: background-color 150ms ease;
}
body.studio .team-pick[hidden] { display: none; }
body.studio .team-pick:hover { background: var(--surface-2); }
body.studio .team-pick:has(input:checked) { background: var(--surface-3); }
body.studio .team-pick__id { display: flex; flex-direction: column; min-width: 0; flex: 1; }
body.studio .team-pick__name { font-size: 0.95rem; font-weight: 500; }
body.studio .team-pick__box,
body.studio .team-pass__box {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: 7px;
  border: 1px solid var(--border-strong); color: transparent;
  transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
}
body.studio .team-pick:has(input:checked) .team-pick__box,
body.studio .team-pass:has(input:checked) .team-pass__box {
  background: var(--gold); border-color: var(--gold); color: var(--gold-ink);
}
body.studio .team-pick:has(input:focus-visible) .team-pick__box,
body.studio .team-pass:has(input:focus-visible) .team-pass__box {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

/* The name chips over the role cards: who this submit is about. */
body.studio .team-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
body.studio .team-chips:empty { display: none; }

/* THE PICKED RUNG IS GOLD. `.choice` is the house's own card (the composer's
   Advanced sheet), and its picked state is a tungsten stroke — right there,
   where the answer is one of several equal options. Here the picked card is
   the one thing on the sheet that decides what somebody can touch, and gold is
   this house's mark for the state that is ON. */
body.studio .team-roles { margin-top: 4px; }
body.studio .team-roles .choice:has(input:checked) {
  border-color: var(--hair-gold);
  background: color-mix(in srgb, var(--gold) 6%, transparent);
}
body.studio .team-label {
  margin: 24px 0 8px; color: var(--muted); font-size: 0.9rem;
}
/* The pass picker's trigger reads as a select — a full-width box with a
   chevron — so `.menu > summary`'s little inline trigger has to lose. The
   selector is `details.team-pick-menu > summary` (0,1,2), which beats
   `.menu > summary` (0,1,1) whatever the source order does. */
body.studio .team-pick-menu { display: block; }
body.studio details.team-pick-menu > summary {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; min-height: 56px; padding: 0 16px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--input-bg); color: var(--text); font-size: 0.98rem;
  cursor: pointer; list-style: none;
}
body.studio details.team-pick-menu > summary::-webkit-details-marker { display: none; }
body.studio details.team-pick-menu > summary:hover { border-color: var(--border-strong); }
/* IT OPENS UPWARD, and that is the whole fix for Maaz's clipped picker (his
   screenshot 1, live QA 2026-09-08). The sheet is a scroll container
   (`max-height` + `overflow: auto`) and therefore CLIPS its
   absolutely-positioned descendants, scrolling or not — so a panel hung below
   a trigger that sits just above the footer was cut off at the dialog's own
   edge, and the two-item list inside it read as a list somebody had to scroll.
   The room on this sheet is ABOVE the trigger (three role cards' worth), which
   is where a native <select> would put it too, so the panel never leaves the
   box and nothing has to clip it. */
body.studio .team-pick-menu .team-pass-panel {
  top: auto; bottom: calc(100% + 6px);
  left: 0; right: 0; min-width: 0; padding: 8px; max-height: 280px; overflow-y: auto;
}
body.studio .team-passes-block[hidden] { display: none; }
body.studio .team-pass {
  position: relative;
  display: flex; align-items: center; gap: 12px; margin: 0;
  padding: 9px 10px; border-radius: var(--radius); cursor: pointer;
}
body.studio .team-pass:hover { background: var(--surface-2); }
body.studio .team-pass__name { flex: 1; font-size: 0.95rem; }
body.studio .team-pass__note { flex: none; }

/* ── WHOSE STUDIO (the acting banner) ───────────────────────────────────────
   One row above the shell, so a teammate always knows which account a screen
   is about and always has the way home. Not live-red and not gold: it is a
   statement of fact, not a warning and not a positive state. */
body.studio .acting-bar {
  display: flex; align-items: center; justify-content: center; gap: 14px; flex-wrap: wrap;
  padding: 10px 20px; background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  font-size: 0.88rem; color: var(--muted);
}
body.studio .acting-bar__who { display: inline-flex; align-items: center; gap: 8px; }
body.studio .acting-bar__who b { color: var(--text); font-weight: 600; }
body.studio .acting-bar__out {
  margin: 0; padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--muted); font: inherit; line-height: var(--lh-snap);
  text-decoration: underline; text-underline-offset: 3px;
}
body.studio .acting-bar__out:hover { color: var(--text); }

/* THE OWNER-ALONE PENCIL (2026-09-08). A super admin opens the Account page and
   the handle is not theirs to change, so the control is drawn dimmed with its
   reason rather than hidden (the Download-report idiom) — and it resolves no
   pointer, because a cursor that says "clickable" over a refusal is the lie the
   whole idiom exists to stop. `position: relative` is what keeps its
   sentence-length tip anchored to the control instead of the page (the
   84px-of-scroll class). */
body.studio .acct-ro__edit--off {
  position: relative; color: var(--dim); cursor: default;
}
body.studio .acct-ro__edit--off:hover { color: var(--dim); background: none; }

/* ── THE FOLLOWERS PAGE (Maaz 2026-09-09, r2) ───────────────────────────────
   The members table rebuilt: hero count + gold delta + sparkline, five
   filter-scoped cards, a 16-column roster, and native <dialog> sheets
   (Filters / Add people / Update access / a row's own acts) that ride the top
   layer so no table stacking context can clip them. All fenced body.studio.

   WIDTH IS ANSWERED BY A PINNED FIRST COLUMN AND A SIDEWAYS SCROLL (r2, and
   it reverses this page's own first answer). Column hiding put Reachable,
   Open rate, Last attended, Last sign-in, Joined, Source and Tags behind an
   expander at desk width — Maaz's screenshots show them as COLUMNS, because
   a column is what can be read down, sorted and filtered. So the roster's
   container scrolls, the person's name stays pinned to the left edge, and the
   container carries data-hscroll: the house fence
   (studio_no_horizontal_scroll_test) still refuses a DOCUMENT that overflows
   anywhere and now skips the one region whose owner asked for it. */
/* THE LEDE IS ONE LINE (r9 item 1, Maaz's screenshot 1). `.page-head__deck`
   is capped at the house's 62ch prose measure — 532.6px, measured — against a
   sentence that wants ~585, so it wrapped and broke after "when they", leaving
   "show up." alone on a 60px second line. The cap comes off for THIS deck
   only (the `.ov-focus__deck` precedent — a one-line caption is not prose, and
   a class records the decision where the markup asked for it rather than
   re-leading every studio deck in the product), and the shell hands it 1152px
   at desk width. `__hold` is the half that holds at EVERY width: under ~625px
   of viewport the sentence wraps whatever the cap says, and the tail is never
   the orphan. */
body.studio .fol-deck { max-width: none; }
body.studio .fol-deck__hold { white-space: nowrap; }

body.studio .fol-hero { margin-top: 26px; }
body.studio .fol-hero__row { display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; }
body.studio .fol-hero__count { font-size: clamp(2.6rem, 6vw, 4.4rem); font-weight: 650;
  letter-spacing: -0.02em; --lh: 1.04; line-height: var(--lh-snap); }
body.studio .fol-hero__delta { color: var(--gold); font-size: 0.95rem; }
body.studio .fol-spark { display: block; width: min(100%, 1080px); height: 36px; margin-top: 10px; }
body.studio .fol-spark polyline { stroke: var(--gold); stroke-width: 1.5;
  vector-effect: non-scaling-stroke; opacity: 0.85; }

body.studio .fol-toolbar { display: flex; gap: 10px; align-items: center; margin-top: 24px; flex-wrap: wrap; }
/* Search · Filters · Add people · Revoke · Export (mocks 2-3). The field
   carries its own magnifier inside — there is no Search button, because the
   typing is the search.

   AND IT EATS THE LEFTOVER (r4). It was capped at 420px, so on a desk the row
   stopped at the Filters cluster and left 346px of nothing after Export
   (measured at 1512) — his screenshot 3. The buttons never grew and still do
   not: `.cluster` is the flex default `0 1 auto`, so the field is the only
   thing that takes the slack and the four acts hug the right edge. The
   cluster keeps its SHRINK, which is what wraps it under the field at 390px
   instead of pushing the document sideways. */
body.studio .fol-search { position: relative; display: flex; margin: 0; flex: 1 1 260px; }
body.studio .fol-search__glyph { position: absolute; left: 13px; top: 50%;
  transform: translateY(-50%); display: inline-flex; color: var(--muted);
  pointer-events: none; }
body.studio .fol-search input[type="search"] { flex: 1; min-width: 0; height: 40px;
  padding: 0 14px 0 38px; font-size: 0.9rem; color: var(--text); background: transparent;
  border: var(--hair-w) solid var(--border-strong); border-radius: 999px;
  -webkit-appearance: none; appearance: none; }
body.studio .fol-search input[type="search"]::placeholder { color: var(--muted); }
body.studio .fol-search input[type="search"]:focus-visible { outline: none; border-color: var(--text); }
body.studio .fol-btn--lit { border-color: var(--text); color: var(--text); }
/* The Pass studio's Holders tab borrowed this toolbar's ORIGINAL search shape
   — a field plus a Search button — and it is still that shape, so the rule
   stays declared for it. (A class a view renders and nothing styles is #415's
   own lesson; deleting this with the Followers markup would have left that
   tab's field unstyled and passed every assert_select.) */
body.studio .fol-toolbar__search { display: flex; flex: 1; min-width: 240px; gap: 8px; margin: 0; }
body.studio .fol-toolbar__search input[type="text"] { flex: 1; }

/* THE SELECTION BAR (mocks 4-6). Born hidden — `display: flex` beats the
   hidden attribute's UA rule, so the [hidden] line is load-bearing (#377's
   trap). Count on the left with the every-page link beside it, the two acts
   on the right: gold Revoke access, then Cancel as a plain way out. */
body.studio .fol-select-bar { display: flex; align-items: center; gap: 16px;
  flex-wrap: wrap; margin-top: 12px; padding: 14px 18px;
  border: var(--hair-w) solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); }
body.studio .fol-select-bar[hidden] { display: none; }
body.studio .fol-select-bar__n { font-size: 0.9rem; color: var(--text); }
/* it is a SENTENCE in a row, so it wraps and it reads left — a nowrap
   sentence beside two acts is what pushes a phone sideways (the post-tray
   class), and a <button> inherits neither `text-align` nor a sane
   `white-space` from its row. */
body.studio .fol-select-bar__all { border: 0; background: transparent; padding: 0;
  font: inherit; font-size: 0.9rem; color: var(--gold); text-decoration: underline;
  text-underline-offset: 3px; cursor: pointer; text-align: left; white-space: normal; }
body.studio .fol-select-bar__all[hidden] { display: none; }
body.studio .fol-select-bar__act { display: flex; align-items: center; gap: 16px;
  margin-left: auto; }
body.studio .fol-select-bar__form { margin: 0; }
/* TWO ACTS, ONE BAR (r3): only the one whose button opened the picker is
   drawn, so its own display:none has to beat the flex box it is given. */
body.studio .fol-select-bar__act > span { display: inline-flex; align-items: center; }
body.studio .fol-select-bar__act > span[hidden] { display: none; }
body.studio .fol-select-bar__cancel { border: 0; background: transparent; padding: 0;
  font: inherit; font-size: 0.9rem; color: var(--muted); text-decoration: underline;
  text-underline-offset: 3px; cursor: pointer; }
body.studio .fol-select-bar__cancel:hover { color: var(--text); }

/* The empty-list secondary: a gold text link that opens the same Add people
   sheet the toolbar carries (one dialog, two triggers). */
body.studio .fol-empty__link { border: 0; background: transparent; padding: 0;
  font: inherit; font-size: 0.88rem; color: var(--gold); text-decoration: underline;
  text-underline-offset: 3px; cursor: pointer; }
body.studio .fol-empty__link:hover { color: var(--gold-bright); }

/* ── REVOKE & EXPORT, ON THE HOLDERS TAB (Maaz 2026-09-09; export 2026-09-13)
   A MODE, NOT A PAGE: Revoke or Export reveals a bar and a column of ticks,
   Cancel puts them away, and nothing navigates. The ticks EXIST ONLY IN A
   MODE (revoke or export) — the column is hidden with no data-mode at all, so
   an accidental submit from anywhere else can carry no member with it. */
body.studio .hol:not([data-mode]) .hol-tick { display: none; }
/* The toolbar's Revoke is IDLE until it is tapped, and lit while the mode is
   on — the same `.fol-btn--lit` the Followers toolbar already uses. */
body.studio .hol-enter { flex: none; }
/* EXPIRES ON, AND THE MARK BESIDE IT (Maaz 2026-09-13). One row, so the date
   and the mark share a baseline; `nowrap` because the cell's text is BOUNDED
   (a date, or "Never") and there is nothing here to protect the viewport from
   — the r4 rule, which is also why no `overflow-wrap` is set. */
/* THE ACCESS CHIP DOES NOT BREAK ITS OWN WORD, and this is the r4 defect on a
   second table: `.chip` carries `overflow-wrap: anywhere` so a chip holding
   free text can shrink inside a wrapping `.cluster` rather than widen the page
   — right where it was written, wrong in a table cell. This chip's text is
   BOUNDED (Active, Ending, Lifetime, Revoked, Past due, Paused), so there is
   nothing here to protect the viewport from, and "LIFETIM / E" on two lines is
   all it bought.

   AND `nowrap` IS THE WHOLE FIX HERE, unlike r4's own — no `min-width` beside
   it. This table is `table-layout: auto`, which sizes a column to at least its
   MIN-CONTENT, and a nowrap chip's min-content is the whole chip: the column
   self-sized to 100px (chip 72 + the cell's padding), where the 124px
   reservation copied from r4 was ADDING 24px to a ten-column table that had
   only 8px of slack left. Measured both ways. */
body.studio .pass-holders .chip { white-space: nowrap; overflow-wrap: normal; }

/* HOLDERS SCROLLS SIDEWAYS, HOLDER IS FROZEN (r17). The table grew a Current
   plan column and outran its flush card — Watch time's own (i) tip was clipped
   with nowhere to scroll. The wrapper is a [data-hscroll] scroll region now
   (its `overflow-x: auto` is declared once, generically, on
   `.fol-tablewrap[data-hscroll]`), and these rules give the pass table what
   the Followers `.fol-table` already had: a min-width so it actually overflows
   on a desk, a header that docks under the masthead (fol-stickyhead writes
   `--fol-dock`), and a Holder column pinned left so a name never scrolls away
   from the figure being read. */
body.studio .pass-holders { min-width: 940px; }
body.studio .fol-tablewrap[data-hscroll] .pass-holders thead th {
  position: sticky; top: var(--fol-dock, 0px); background: var(--surface); z-index: 1; }
body.studio .fol-tablewrap[data-hscroll] .pass-holders tbody th[scope="row"],
body.studio .fol-tablewrap[data-hscroll] .pass-holders thead th.pass-holders__who {
  position: sticky; left: 0; z-index: 2; background: var(--surface);
  box-shadow: var(--hair-w) 0 0 var(--border); }
body.studio .fol-tablewrap[data-hscroll] .pass-holders thead th.pass-holders__who { z-index: 3; }
body.studio .pass-holders tbody tr:hover th[scope="row"] { background: var(--surface-2); }
/* the tick column pins ahead of Holder while a revoke/export selection is open */
body.studio .hol[data-mode] .fol-tablewrap[data-hscroll] .pass-holders .hol-tick {
  position: sticky; left: 0; z-index: 2; background: var(--surface); width: 44px; }
body.studio .hol[data-mode] .fol-tablewrap[data-hscroll] .pass-holders thead .hol-tick { z-index: 3; }
body.studio .hol[data-mode] .fol-tablewrap[data-hscroll] .pass-holders tbody th[scope="row"],
body.studio .hol[data-mode] .fol-tablewrap[data-hscroll] .pass-holders thead th.pass-holders__who { left: 44px; }

body.studio .hol-exp { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
/* GREEN IS "something out there will charge again"; the off state is the same
   mark with the house's disabled weight, never a second glyph. `--ok` is the
   house's own connected colour, so this mark and a live destination's pip read
   as one idea. `--dim` is ALREADY 45% alpha and is the house's own disabled
   weight — stacking an `opacity` on it lands the mark near 25%, which is the
   contrast floor `--dim` is already sitting on (the E3 measurement). */
body.studio .hol-renew { display: inline-flex; align-items: center; }
body.studio .hol-renew--on { color: var(--ok); }
body.studio .hol-renew--off { color: var(--dim); }
body.studio .hol-tick { width: 1%; padding-right: 0 !important; }
body.studio .hol-tick input { width: 16px; height: 16px; accent-color: var(--gold); cursor: pointer; }
body.studio .hol-form { margin: 0; }
body.studio .hol-bar {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  margin-top: 14px; padding: 16px 20px;
  border: var(--hair-w) solid var(--border); border-radius: var(--radius);
  background: var(--surface);
}
body.studio .hol-bar[hidden] { display: none; }
body.studio .hol-bar__said { font-size: 0.95rem; }
body.studio .hol-bar__said [hidden] { display: none; }
body.studio .hol-bar__all { background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; line-height: var(--lh-snap); font-size: 0.92rem; color: var(--gold);
  text-decoration: underline; text-underline-offset: 3px; }
body.studio .hol-bar__all:hover { color: var(--gold-bright); }
body.studio .hol-bar__acts { display: flex; align-items: center; gap: 18px; margin-left: auto; }
/* A FILTER IN A COLUMN HEAD — the Pass holders' Access header, and since
   2026-09-13 the Delivery log's Channel header too. A disclosure inside a
   <th>, so the label keeps the header's own type and only the chevron is
   added. TWO CLASSES ON ONE DECLARATION rather than one surface borrowing the
   other's name: `.hol-filter` is the holders' and `.chn-filter` is Channels',
   and the shape they share is written once so a change reaches both. */
body.studio .hol-filter .menu > summary,
body.studio .chn-filter .menu > summary { display: inline-flex; align-items: center; gap: 4px;
  border: 0; background: none; padding: 0; font: inherit; letter-spacing: inherit;
  text-transform: inherit; color: inherit; width: auto; height: auto; }
body.studio .hol-filter .menu > summary:hover,
body.studio .chn-filter .menu > summary:hover { color: var(--text); border-color: transparent; }
body.studio .hol-filter .menu__panel,
body.studio .chn-filter .menu__panel { min-width: 150px; }
body.studio .hol-filter .menu__item,
body.studio .chn-filter .menu__item { display: flex; align-items: center; justify-content: space-between; gap: 10px; }

/* ── ADD PEOPLE: THE VALIDITY PICKER (Maaz 2026-09-13) ────────────────────
   Only drawn when a Pass offers more than one way in; the creator picks how
   long the comp lasts. A plain radio row, the sheet's own small type. */
body.studio .ap-plans { display: flex; flex-wrap: wrap; gap: 10px 18px; margin-top: 10px; }
body.studio .ap-plan { display: inline-flex; align-items: center; gap: 8px; font-size: 0.92rem; cursor: pointer; }
body.studio .ap-plan input { accent-color: var(--gold); cursor: pointer; }


body.studio .fol-cards { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin-top: 14px; }
@media (max-width: 1100px) { body.studio .fol-cards { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 700px) { body.studio .fol-cards { grid-template-columns: repeat(2, 1fr); } }
body.studio .fol-card { border: 1px solid var(--border); border-radius: var(--radius);
  padding: 14px 16px; background: var(--surface); display: grid; gap: 2px; align-content: start; }
body.studio .fol-card__label { font-size: 0.64rem; font-weight: 700; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--dim); }
body.studio .fol-card b { font-size: 1.45rem; font-weight: 650; }

/* THE SCROLL BEHAVIOUR IS KEYED TO THE MARKER, NOT THE CLASS. The Pass
   studio's Holders tab renders `.fol-tablewrap` too, and its width is its own
   round's decision — so `[data-hscroll]` (the same marker the house fence
   reads) is what turns a wrap into a scroll region. .card--flush is
   `overflow: hidden`; overflow-x wins on its own axis and the y stays hidden,
   which is why every popover in a row is a top-layer <dialog> — an overflow
   box clips its content whether or not it scrolls, and no z-index escapes an
   ancestor's clip (the Team round). The scrollbar is NOT hidden: a region
   that scrolls should say so. */
body.studio .fol-tablewrap { margin-top: 14px; }
body.studio .fol-tablewrap[data-hscroll] { overflow-x: auto;
  overscroll-behavior-x: contain; scrollbar-color: var(--border-strong) transparent; }
body.studio .fol-tablewrap[data-hscroll]:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
/* the roster is denser than the house .roster — sixteen columns live here */
body.studio .fol-table th, body.studio .fol-table td { padding: 10px 8px; }
body.studio .fol-table { font-size: 0.82rem; }

/* THE COLUMN HEADER DOCKS UNDER THE MASTHEAD (r5), and it cannot be the plain
   `thead th { position: sticky; top: 58px }` that sentence sounds like —
   because the sideways scroll r2 asked for is exactly what takes it away.
   `overflow-x: auto` makes this wrap a SCROLL CONTAINER ON BOTH AXES (the pair
   auto/visible is impossible: visible computes to auto, and clip computes to
   hidden — measured here as `overflow-y: hidden`), so the header's sticky view
   rectangle is the WRAP'S scrollport and not the viewport. Measured in Chrome:
   with `top: 58px` the header scrolled away with the table and docked nowhere,
   because a scrollport with no vertical scroll range never triggers the
   constraint at all.
   What DOES work is the same sticky pointed at the same scrollport, with the
   distance translated into that scrollport's own coordinates: the header's
   natural top IS the scrollport's top edge, so `top: N` puts it exactly N
   below that edge — and `fol-stickyhead` writes N = (the masthead's bottom −
   the scrollport's top) on every scroll frame. Two things then come free and
   are deliberately not re-implemented in JavaScript: the BOTTOM clamp is the
   browser's own (measured: at `top: 5000px` the header stops on the table's
   last row, 2760px = the table's 2797 less the header's 37), and the corner
   cell is sticky on BOTH axes from one mechanism — `left: 0` and its z-index 3
   come from the pinned-column rule below, so it still covers the cells that
   slide under it.
   The fill is the card's own, so nothing moves at rest (the header cells were
   TRANSPARENT — measured rgba(0,0,0,0) — and rows would read straight through
   a docked header without it). With no JavaScript the variable is never
   written, `top` resolves to 0, and the header sits where it always sat.
   Keyed to [data-hscroll], never to the bare class (the r2 rule): outside a
   scroll container this same declaration would pin the header to the VIEWPORT
   at top: 0, i.e. behind the masthead, on whatever screen borrowed the name. */
body.studio .fol-tablewrap[data-hscroll] .fol-table thead th {
  position: sticky; top: var(--fol-dock, 0px); background: var(--surface);
}
/* COLUMN ONE IS PINNED. It needs an opaque fill of its own (the scrolling
   cells would otherwise read straight through it) and a fill that follows the
   row's own states, or the seam shows on hover and on a picked row. */
body.studio .fol-table .fol-col--who { position: sticky; left: 0; z-index: 2;
  background: var(--surface); box-shadow: var(--hair-w) 0 0 var(--border); }
body.studio .fol-table thead .fol-col--who { z-index: 3; background: var(--surface); }
body.studio .fol-table tbody tr:hover .fol-col--who { background: var(--surface-2); }
body.studio .fol-table tbody:has(.fol-check input:checked) > tr > * { background: var(--surface-2); }
body.studio .fol-expandrow > td { background: var(--surface-2); }

/* THE PICKER'S COLUMN. Always in the DOM (a frame swap re-renders it from a
   server that cannot know we are picking) and zero-wide until fol-select puts
   .fol-picking on <main> — never `display: none`, because a column that comes
   and goes changes the table's own column count under a colspan. */
body.studio .fol-table .fol-check { width: 0; padding: 0; }
body.studio .fol-check__box { display: none; }
body.studio .fol-picking .fol-table .fol-check { width: 40px; padding: 10px 0 10px 14px;
  position: sticky; left: 0; z-index: 2; background: var(--surface); }
body.studio .fol-picking .fol-table thead .fol-check { z-index: 3; }
body.studio .fol-picking .fol-table .fol-col--who { left: 40px; }
body.studio .fol-picking .fol-check__box { display: inline-flex; align-items: center; }
/* the same gold tick the Encores shelf picks with — one checkbox in the house */
body.studio .fol-check__box input { appearance: none; -webkit-appearance: none;
  width: 18px; height: 18px; margin: 0; border: var(--hair-w) solid var(--border-strong);
  border-radius: 5px; background: transparent; cursor: pointer; }
body.studio .fol-check__box input:checked {
  background-color: var(--accent); border-color: var(--accent);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23111111' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-size: 12px 12px; background-position: center; background-repeat: no-repeat; }
/* half a page picked: a gold dash, drawn from the token rather than a hex
   baked into a data URI (a gradient resolves var(); an inline SVG cannot) */
body.studio .fol-check__box input:indeterminate {
  background-color: transparent; border-color: var(--accent);
  background-image: linear-gradient(var(--accent), var(--accent));
  background-size: 10px 2px; background-position: center; background-repeat: no-repeat; }
body.studio .fol-check__box input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .fol-who { display: inline-flex; align-items: center; gap: 8px; }
/* a name never wraps letter-by-letter in a squeezed cell — it truncates */
body.studio .fol-who__name { font-weight: 600; white-space: nowrap; max-width: 150px;
  overflow: hidden; text-overflow: ellipsis; display: inline-block; vertical-align: middle; }
/* the + is the row's opener; open, it reads as a close (the plus rotates to an ×) */
body.studio .fol-expand { flex: none; width: 24px; height: 24px; display: inline-flex;
  align-items: center; justify-content: center; border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); background: transparent; color: var(--muted); cursor: pointer;
  transition: transform 150ms ease, color 150ms ease; }
body.studio .fol-expand:hover { color: var(--text); border-color: var(--text); }
body.studio tbody.is-open .fol-expand { transform: rotate(45deg); color: var(--text); }
body.studio .fol-contact { display: flex; align-items: center; gap: 6px; }
body.studio .fol-copy { flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border: 0; background: transparent; color: var(--dim); cursor: pointer;
  border-radius: var(--radius-sm); }
body.studio .fol-copy:hover { color: var(--text); }
body.studio .fol-copy__done { display: none; color: var(--gold); }
body.studio .fol-copy.is-copied .fol-copy__done { display: inline; }
body.studio .fol-copy.is-copied .lucide-copy { display: none; }
body.studio .fol-trunc { max-width: 180px; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; display: inline-block; vertical-align: bottom; color: var(--muted); }
/* THE ACCESS CHIP IS ONE LINE, ALWAYS (r4, his screenshot 1: a 73px pill two
   lines tall reading `Follow / er`). `.chip` carries `overflow-wrap: anywhere`
   so a chip holding free text — a stream title, a pass name — can always
   shrink inside a wrapping .cluster rather than widen the page past the
   viewport. Right there, wrong in a table cell: this chip's text is BOUNDED
   (`Follower`, `Revoked`, `1p · 2t · 1d`), so there is nothing to protect the
   viewport from and every break it took was a break it never needed. It takes
   the width of its own words, and .fol-col--access reserves that width. */
body.studio .fol-chipbtn { cursor: pointer; background: transparent; font: inherit;
  white-space: nowrap; overflow-wrap: normal; }
body.studio .fol-sub { display: block; font-size: 0.72rem; margin-top: 2px; font-weight: 400; }
body.studio .fol-sub .lucide { vertical-align: -1px; }
/* lit = a send can land right now — text + fill, never gold (A16.4's own lit) */
body.studio .fol-reach { display: inline-flex; gap: 4px; }
body.studio .fol-reach > span { width: 22px; height: 22px; display: inline-flex; align-items: center;
  justify-content: center; border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--dim); opacity: 0.55; }
body.studio .fol-reach > span.fol-reach--on { color: var(--text); border-color: var(--border-strong);
  background: var(--surface-3); opacity: 1; }
body.studio .fol-openbtn { background: transparent; border: 0; font: inherit; cursor: pointer;
  color: var(--text); padding: 0; text-align: right; }
/* A SORTABLE HEAD wears the header's own type and says which way it orders.
   One direction per key (the roster's own ORDER BY), so the arrow states the
   order rather than offering to flip it into a clause the whitelist has not
   got. r3 made every one of them a TOGGLE, so the arrow states the order it is
   running and the tap flips it. */
body.studio .fol-sort { display: inline-flex; align-items: center; gap: 4px;
  color: inherit; text-decoration: none; }
body.studio .fol-sort:hover { color: var(--text); }
body.studio .fol-sort--on { color: var(--text); }
/* THE MARK IS THE STATE (r3): the head that is RUNNING wears one arrow at full
   strength, every other sortable head wears the double chevron dim — "this can
   be tapped" — and a head only lights on hover so a table of eight of them is
   not eight competing arrows. `inline-flex` on the span because a
   baseline-aligned SVG overhangs its line box by the font's descender (r8). */
body.studio .fol-sort__way { display: inline-flex; align-items: center; opacity: 0.32; }
body.studio .fol-sort:hover .fol-sort__way { opacity: 0.7; }
body.studio .fol-sort--on .fol-sort__way { opacity: 1; }
/* A HEADER TIP RENDERS DOWNWARD AND LEFT-ANCHORED, because the header row is
   the FIRST row inside a box whose overflow is hidden on the y axis: the
   house default hangs a tip ABOVE its trigger, which from row one is off the
   top edge and clipped away entirely. Below, it lands on the rows — inside
   the box — and it grows into the table rather than off the right of it. */
body.studio .fol-table .tip-i[data-tip]::after {
  left: 0; right: auto; transform: translateX(0) translateY(2px);
  text-transform: none; letter-spacing: 0; }
body.studio .fol-table .tip-i[data-tip]:hover::after,
body.studio .fol-table .tip-i[data-tip]:focus-visible::after { transform: translateX(0) translateY(0); }
/* THE ROW'S ⋯ IS THE HOUSE DISCLOSURE (r6 item 2, Maaz: "use the studio
   details.menu pattern, same as other kebabs"). It was a centred modal
   <dialog> with a name heading and a CLOSE button — his screenshot 3, and the
   bug. `.fol-sheet--acts` / `.fol-acts*` went out with the markup: a class
   named after a retired control is how the next surface inherits a box by
   reaching for a familiar name.

   The summary keeps this borderless 28px box — it is (0,2,0) against
   `.menu > summary`'s (0,1,1), so the house trigger's border and 30px width
   lose and only its list-style/marker reset applies, which is the half that
   is wanted. */
body.studio .fol-kebab { display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border: 0; background: transparent; color: var(--muted);
  border-radius: var(--radius-sm); cursor: pointer; }
body.studio .fol-kebab:hover { color: var(--text); background: var(--surface-3); }
/* The menu is drawn at the row's right edge and its panel is placed in
   viewport coordinates (see .menu--anchored), so the only thing left for the
   wrapper to decide is where it sits in the cell: `.menu` is inline-block,
   which lands on the text baseline and hangs the kebab below the row's other
   cells by the font's descender. Middle is the row's own centre line. */
body.studio .fol-rowmenu { vertical-align: middle; }

/* the expand layer — [hidden] guarded per the r14 lesson (the UA's table-row
   display must never outrank it) */
body.studio .fol-expandrow[hidden] { display: none; }
body.studio .fol-open { display: grid; grid-template-columns: 1.2fr 1fr; gap: 10px 28px; padding: 6px 4px; }
@media (max-width: 900px) { body.studio .fol-open { grid-template-columns: 1fr; } }
/* Both halves are grid children of a fractional track, so each needs its own
   floor: `min-width: auto` on a grid item sizes it to its CONTENT, and one
   long holding name (or an email) would push the track past the roster's
   width — which inside a scroll container is silent extra scroll. Declared,
   rather than left as marker classes nothing styles (#415). */
body.studio .fol-open__has, body.studio .fol-open__opens { min-width: 0; }
body.studio .fol-holdings { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 8px; }
body.studio .fol-holdings li { display: flex; gap: 6px 14px; align-items: baseline;
  justify-content: space-between; flex-wrap: wrap; border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 8px 12px; background: var(--surface); }
body.studio .fol-holdings__name { font-weight: 600; }
body.studio .fol-openlist { display: grid; gap: 4px; margin: 8px 0; }
body.studio .fol-openlist > div { display: flex; justify-content: space-between; gap: 12px; }
body.studio .fol-openlist dt { color: var(--muted); }
body.studio .fol-openlist dd { margin: 0; }
/* r3: `.fol-facts` came off with the five columns the expand used to repeat. */

/* the sheets — native <dialog>, top layer */
body.studio .fol-sheet { border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text); padding: 20px;
  width: min(480px, calc(100vw - 32px)); max-height: min(84vh, 760px); overflow-y: auto; }
body.studio .fol-sheet::backdrop { background: rgb(0 0 0 / 0.6); }
body.studio .fol-sheet--wide { width: min(640px, calc(100vw - 32px)); }
body.studio .fol-sheet--filters { width: min(700px, calc(100vw - 32px)); }
body.studio .fol-sheet__form { margin: 0; }
body.studio .fol-filter-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; margin-top: 14px; }
@media (max-width: 640px) { body.studio .fol-filter-grid { grid-template-columns: 1fr; } }
/* A CELL OF THIS GRID, and `>` is load-bearing (r7). `.fol-filter-grid label`
   is (0,2,2) and `.pf-pick` is (0,2,1), so the moment a cell contained a
   picker — Tags is the first one that does — this rule reached INTO its rows
   and turned each `label.pf-pick` from a flex row into a two-row grid: the
   tick box drawn ABOVE the word it belongs to, on screen, with the markup
   perfectly correct (caught in a browser, which is the only place it exists).
   The rule means "a cell", so it says so.

   `.fol-filter-cell` is a cell that is NOT a <label>: Tags is a <details>, and
   a label wrapping one makes every click on its face a toggle of something
   else. Named rather than `> div`, so the decision is recorded and assertable. */
body.studio .fol-filter-grid > label,
body.studio .fol-filter-grid > .fol-filter-cell { display: grid; gap: 6px; align-content: start; }
/* THE SHEET'S TWO REVEALS (r3) — Select passes under Access = pass holders,
   and the sign-in date pair under Custom. Each carries its own display:none so
   [hidden] wins over the grid it is given (the r14 rule: a rule that gives an
   element a display outranks the UA's [hidden]), and the sheet's <noscript>
   block turns both on for a host with no scripting. */
body.studio .fol-filter-reveal { display: grid; gap: 6px; margin-top: 14px; }
body.studio .fol-filter-reveal[hidden] { display: none; }
/* Select passes reuses the Pass form's own dropdown furniture (pf-drop /
   pf-pick), so there is one answer in this studio to "tick several of my
   passes" — and its panel being IN FLOW is what keeps it un-clippable inside
   a scrolling <dialog>. */
body.studio .fol-addrow { display: grid; grid-template-columns: 1fr 1.3fr auto 1fr; gap: 8px; }
@media (max-width: 640px) { body.studio .fol-addrow { grid-template-columns: 1fr; } }

/* ── ADD PEOPLE (r8, Maaz's mocks 5–6) ──────────────────────────────────────
   Two tabs, then sections divided by one rule. The rail is CLIENT-SIDE and has
   to be (it is inside a <dialog>, and a URL change re-renders the page under
   it — the control room's own reasoning), so every box it flips carries its own
   display:none for [hidden] to win over (the r14 rule: a rule that gives an
   element a display outranks the UA's own [hidden]). */
body.studio .ap-head { align-items: flex-start; }
body.studio .ap-tabs { display: flex; gap: 10px; margin: 16px 0 0;
  padding-bottom: 16px; border-bottom: var(--hair-w) solid var(--border); }
/* AND THE RAIL IS DRAWN HIDDEN AND REVEALED BY ITS CONTROLLER (Maaz's r3 item
   4, 2026-09-13), which is why it needs this line: the rule above hands it a
   `display`, so without one of its own the UA's `[hidden]` loses and a rail
   nothing can work would be drawn over two stacked panes. It replaced a
   `<noscript><style>` that inverted these three boxes — see the panel partial
   for what that construct actually did on a turbo-frame render. */
body.studio .ap-tabs[hidden] { display: none; }
body.studio .ap-tab { display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 14px; min-height: 42px; border: var(--hair-w) solid var(--border-strong);
  border-radius: var(--radius-sm); background: none; color: var(--muted);
  font-family: inherit; font-size: 0.95rem; cursor: pointer; }
body.studio .ap-tab:hover { color: var(--text); }
/* The active tab wears the gold hairline (his mock) and the body colour — one
   accent, on the thing that is on. */
body.studio .ap-tab--on { border-color: var(--hair-gold); color: var(--text); }
body.studio .ap-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .ap-pane { margin-top: 18px; }
body.studio .ap-pane[hidden] { display: none; }
body.studio .ap-rule { margin: 20px 0 0; border: 0;
  border-top: var(--hair-w) solid var(--border); }
body.studio .ap-h { margin: 20px 0 4px; font-size: 1.02rem; font-weight: 600;
  letter-spacing: 0; text-transform: none; }
body.studio .ap-deck { margin: 0 0 12px; color: var(--muted); font-size: 0.92rem; }
/* A refusal, in the sheet, above the buttons that caused it. */
body.studio .ap-said { margin: 14px 0 0; padding: 10px 12px; color: var(--live);
  border: var(--hair-w) solid var(--hair-live); border-radius: var(--radius-sm);
  font-size: 0.92rem; }
/* CHOOSE VALIDITY's label, and the word a single-way Pass prints instead of
   asking (Maaz 2026-09-13). The label is an eyebrow rather than an `.ap-h`:
   `.ap-h` is a section HEADING and this sits INSIDE Pass access, under its
   deck — a second 1.02rem heading there would read as a new section. */
body.studio .ap-sublabel { margin: 12px 0 0; font-size: 0.78rem; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
body.studio .ap-validity { margin: 10px 0 0; font-size: 0.92rem; color: var(--text); }
/* THE ATTESTATION IS ONE ROW (Maaz 2026-09-13): the sentence starts to the
   RIGHT of the checkbox and stays there. It is deliberately NOT `.cluster`,
   which is `flex-wrap: wrap` — a sentence this long cannot fit on the box's own
   line, so the whole ITEM wrapped under it. `nowrap` plus a shrinkable span is
   what makes the TEXT wrap inside its own item instead; `flex: none` keeps the
   box its own size, and the top margin sits it on the first line of type. */
body.studio .ap-attest { display: flex; flex-wrap: nowrap; align-items: flex-start;
  gap: 10px; cursor: pointer; }
body.studio .ap-attest input { flex: none; margin-top: 3px; accent-color: var(--gold); cursor: pointer; }
body.studio .ap-attest span { min-width: 0; }
body.studio .ap-more[hidden] { display: none; }
body.studio .ap-capped { margin: 8px 0 0; }
body.studio .ap-capped[hidden] { display: none; }
/* The picker and its "No file chosen" in one field (his mock 6). `min-width:
   0` on the input is what lets it shrink below its intrinsic width, which is
   the 390px hazard the file-picker note at the top of this file describes. */
body.studio .ap-file { display: flex; align-items: center;
  border: var(--hair-w) solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--input-bg); padding: 8px 10px; min-width: 0; }
body.studio .ap-file input[type="file"] { flex: 1 1 auto; min-width: 0; margin: 0; }
body.studio .ap-sample, body.studio .ap-link { display: inline-flex;
  align-items: center; gap: 6px; }
body.studio .ap-sample { text-decoration: underline; }
/* THE PICKED PASSES, as chips with a × (his mock). Every chip is rendered by
   the server and hidden, so the controller flips `hidden` and no copy lives in
   the JavaScript; `justify-self` is not in play because this is a flex row. */
body.studio .ap-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
body.studio .ap-chip { display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 6px 6px 12px; min-height: 32px;
  border: var(--hair-w) solid var(--border-strong); border-radius: 999px;
  font-size: 0.9rem; white-space: nowrap; overflow-wrap: normal; }
body.studio .ap-chip[hidden] { display: none; }
body.studio .ap-chip__x { display: inline-flex; align-items: center;
  justify-content: center; width: 22px; height: 22px; border: 0; padding: 0;
  border-radius: 999px; background: none; color: var(--muted); cursor: pointer; }
body.studio .ap-chip__x:hover { color: var(--text); background: var(--surface-3); }
body.studio .ap-chip__x:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* "Importing in progress, do not exit." — the request IS the import, so the
   sheet's own wait is where it belongs. */
body.studio .ap-progress { display: flex; align-items: center; gap: 8px;
  margin: 18px 0 0; color: var(--gold); font-size: 0.92rem; }
body.studio .ap-progress[hidden] { display: none; }
/* `encore-starting` is the house's one rotation keyframe (`to { rotate(360deg)
   }`, declared for the encore's own spinner) — a second identical one would be
   two definitions of a full turn. */
body.studio .ap-progress .lucide { animation: encore-starting 900ms linear infinite; }
@media (prefers-reduced-motion: reduce) {
  body.studio .ap-progress .lucide { animation: none; }
}
/* THE RESULT'S TWO NUMBERS. Both are drawn, zero included: this is a measured
   count of rows the creator just handed us, and #283 forbids a fabricated zero
   rather than a real one. */
body.studio .ap-figs { display: flex; gap: 32px; }
body.studio .ap-fig b { display: block; font-size: 1.9rem; line-height: 1.1; }
body.studio .ap-fig span { display: block; margin-top: 2px; color: var(--muted);
  font-size: 0.8rem; letter-spacing: 0.08em; text-transform: uppercase; }
/* THE SIGN-IN DATE PAIR (r7): two of the house's own pickers with the word
   between them, each in its own column so an open calendar pushes its own side
   down and nothing else. `align-items: start` is what keeps the second field
   at the top of the row while the first one's calendar is open; the word takes
   a padding rather than `center`, because centring it against a 350px-tall
   open calendar parks it halfway down the sheet. Below 520px the pair stacks:
   a 155px calendar is seven 20px columns, and this sheet is 358px wide on a
   phone. */
body.studio .fol-when { display: grid; grid-template-columns: 1fr auto 1fr;
  gap: 10px; align-items: start; }
body.studio .fol-when__to { padding-top: 15px; }
@media (max-width: 520px) {
  body.studio .fol-when { grid-template-columns: 1fr; gap: 6px; }
  body.studio .fol-when__to { padding-top: 0; }
}
/* the GIVE rows: one stroke on the row, gold when picked, +/− swap in CSS.
   position: relative because the row hides its own checkbox (.sr-only is
   position: absolute — the Team sheet's 12,627px lesson). */
body.studio .fol-give { position: relative; display: flex; align-items: center;
  justify-content: space-between; gap: 12px; border: 1px solid var(--border-strong);
  border-radius: var(--radius); padding: 10px 14px; cursor: pointer; }
body.studio .fol-give:has(input:checked) { border-color: var(--gold); }
/* r6: every listed Pass is a live row now, held or not — the locked variant
   (a paid pass drawn inert, pointing at a bulk tool) is gone with its markup.
   What replaced its sentence is one line under the group, and only when this
   person actually holds a paid membership. */
body.studio .fol-give__said { margin: 8px 2px 0; }
body.studio .fol-give span > .small { display: block; }
body.studio .fol-give__mark { color: var(--muted); display: inline-flex; }
body.studio .fol-give__mark .lucide-minus { display: none; }
body.studio .fol-give:has(input:checked) .fol-give__mark { color: var(--gold); }
body.studio .fol-give:has(input:checked) .fol-give__mark .lucide-plus { display: none; }
body.studio .fol-give:has(input:checked) .fol-give__mark .lucide-minus { display: inline; }
/* the "N of M" line under the toolbar — its spacing is a rule now, not an
   inline style on the one element that carries it */
body.studio .fol-of { margin: 10px 0 0; }
/* the Update access sheet's tag block: the chips wrap, the field sits under
   them, and the whole thing keeps off the row above */
body.studio .fol-tags { display: grid; gap: 2px; }
body.studio .fol-tagchip { display: inline-flex; align-items: center; gap: 6px; }
body.studio .fol-tagchip button { display: inline-flex; border: 0; background: transparent;
  color: var(--dim); cursor: pointer; padding: 0; }
body.studio .fol-tagchip button:hover { color: var(--text); }
body.studio .fol-block { display: flex; gap: 10px; align-items: center;
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  padding: 10px 14px; margin-top: 16px; cursor: pointer; }
body.studio .fol-block .lucide { flex: none; vertical-align: -2px; }

/* NO COLUMN HIDING (r2, and this reverses the page's first answer). Maaz's
   screenshots draw every fact as a column, because a column is what can be
   read down, sorted and filtered; the expand layer went back to being the
   access/open-rate DETAIL rather than a place to put columns. The width is
   answered above: the region scrolls and column one is pinned.

   So each column carries its OWN width instead of a magic total on the table
   — the roster is as wide as its content honestly needs, and a class rendered
   here has a rule to its name (a marker class nothing styles is #415's own
   failure, and it is what the Pass studio's css-declared fence caught when
   the hiding rules were deleted). */
body.studio .fol-col--who { min-width: 190px; }
body.studio .fol-col--contact { min-width: 190px; }
/* Wide enough for the chip's own longest word with the cell's padding around
   it — the other half of the r4 fix. `white-space: nowrap` alone stops the
   break; without the reservation the cell is still sized by the rest of the
   table and the pill overflows its own box (the text would run through the
   stroke, which is the same defect wearing a different shape). */
body.studio .fol-col--access { min-width: 112px; }
body.studio .fol-col--showed { min-width: 92px; }
body.studio .fol-col--encore { min-width: 100px; }
body.studio .fol-col--watch { min-width: 124px; }
body.studio .fol-col--reach { min-width: 92px; }
body.studio .fol-col--open { min-width: 100px; }
body.studio .fol-col--lastatt { min-width: 112px; }
body.studio .fol-col--signin { min-width: 104px; white-space: nowrap; }
body.studio .fol-col--joined { min-width: 96px; white-space: nowrap; }
body.studio .fol-col--source { min-width: 100px; }
body.studio .fol-col--tags { min-width: 112px; }
body.studio .fol-col--kebab { width: 44px; }
/* The Pass studio's Holders tab renders .fol-col--contact too, and its phone
   answer is its OWN round's (it hides; it does not scroll). Named by its
   table so r2's reversal stays inside r2. */
@media (max-width: 720px) {
  body.studio .pass-holders .fol-col--contact { display: none; }
}

/* ---------- THE PASS'S COVER (Maaz 2026-09-09) -----------------------------
   DELIBERATELY UNFENCED. A metal is what the Pass WEARS, so it is drawn
   wherever a Pass is drawn — the studio's preview and shelf, and the public
   card on a creator's profile. Fencing it behind body.studio would leave every
   fan surface rendering the class bare (studio_scope_law_test's law 2).

   It is a CSS card and not five files: the column holds one word, so a share
   link that names the Pass looks like that Pass without a second image host —
   and og:image stays absent, because nothing here rasterises to a file and the
   house never invents a picture it does not have. */
.pass-cover {
  display: flex; align-items: center; justify-content: center; text-align: center;
  aspect-ratio: 1.91 / 1; border-radius: var(--radius); padding: 18px;
  border: 1px solid var(--border); overflow: hidden;
}
.pass-cover__title {
  font-family: var(--display, var(--sans)); font-weight: 700;
  font-size: clamp(1.05rem, 3.2vw, 1.6rem); letter-spacing: 0.02em;
  text-transform: uppercase; text-wrap: balance;
}
/* Five metals, each a real surface: a dark plate, a diagonal sheen, and an ink
   that clears AA on it. The sheen is a gradient and never an image. */
.pass-cover--bronze, .pf-metal--bronze {
  background: linear-gradient(135deg, #3a2214 0%, #a9662f 42%, #e0a266 58%, #55321c 100%);
  border-color: #a9662f;
}
.pass-cover--bronze .pass-cover__title, .pf-metal--bronze .pf-metal__title { color: #24140a; }
.pass-cover--silver, .pf-metal--silver {
  background: linear-gradient(135deg, #3f4247 0%, #9aa0a8 40%, #e6e9ed 58%, #5a5f66 100%);
  border-color: #9aa0a8;
}
.pass-cover--silver .pass-cover__title, .pf-metal--silver .pf-metal__title { color: #1d2024; }
.pass-cover--gold, .pf-metal--gold {
  background: linear-gradient(135deg, #4a3208 0%, #c69a3c 40%, #f4dc9a 58%, #6b4a10 100%);
  border-color: var(--gold, #c69a3c);
}
.pass-cover--gold .pass-cover__title, .pf-metal--gold .pf-metal__title { color: #241900; }
.pass-cover--diamond, .pf-metal--diamond {
  background: linear-gradient(135deg, #27414c 0%, #8fc4d6 38%, #eaf7fb 56%, #3c6473 100%);
  border-color: #8fc4d6;
}
.pass-cover--diamond .pass-cover__title, .pf-metal--diamond .pf-metal__title { color: #10262f; }
.pass-cover--platinum, .pf-metal--platinum {
  background: linear-gradient(135deg, #45484d 0%, #c8ccd2 40%, #f6f8fa 56%, #676c73 100%);
  border-color: #c8ccd2;
}
.pass-cover--platinum .pass-cover__title, .pf-metal--platinum .pf-metal__title { color: #1b1d20; }

/* ---------- THE PASS CHECKOUT POPUP (Maaz r19, 2026-09-13) ------------------
   A popup on the profile, not a second page. Two panes: the Pass as a fan sees
   it on the LEFT, and the one door (signed out) or the checkout (signed in) on
   the RIGHT. UNFENCED on purpose — this is a fan surface, and a studio-fenced
   class here renders bare (studio_scope_law's law 2). */
.pass-modal {
  border: 1px solid var(--border-strong); border-radius: 18px; padding: 0;
  background: var(--surface); color: var(--text);
  width: min(940px, calc(100vw - 28px)); max-width: min(940px, calc(100vw - 28px));
  max-height: calc(100vh - 48px); overflow: hidden auto;
  box-shadow: 0 30px 90px rgb(0 0 0 / 0.62); position: relative;
}
.pass-modal::backdrop { background: rgb(0 0 0 / 0.72); }
.pass-modal__x {
  position: absolute; top: 14px; right: 14px; z-index: 2;
  display: grid; place-items: center; width: 34px; height: 34px;
  border: 0; border-radius: 999px; cursor: pointer;
  background: color-mix(in srgb, var(--surface-3) 80%, transparent);
  color: var(--muted);
}
.pass-modal__x:hover { color: var(--text); background: var(--surface-3); }
.pass-modal__x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pass-modal__loading { text-align: center; padding: 60px 20px; }

/* The two panes. LEFT is the preview on its own faint surface; RIGHT is the
   door or the checkout. They stack on a narrow screen — the preview first, so
   the fan sees what they are buying before the door. */
.pass-co { display: grid; grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr); }
.pass-co__left {
  padding: 30px 26px; background: var(--surface-2);
  border-right: 1px solid var(--border);
}
.pass-co__right { padding: 32px 30px; min-width: 0; }
@media (max-width: 720px) {
  .pass-co { grid-template-columns: 1fr; }
  .pass-co__left { border-right: 0; border-bottom: 1px solid var(--border); }
}

/* THE PREVIEW — the shelf card's own parts, stacked. */
.pass-preview { display: flex; flex-direction: column; gap: 6px; }
.pass-preview__cover { border-radius: var(--radius); overflow: hidden; margin-bottom: 10px; }
.pass-preview__cover .pass-cover { width: 100%; }
/* Its own unfenced class — .pass-card__bare is studio furniture (scope-law 2),
   and this preview is a fan surface. */
.pass-preview__bare {
  display: grid; place-items: center; aspect-ratio: 1.91 / 1;
  background: var(--surface-3); color: var(--dim); border-radius: var(--radius);
}
.pass-preview__name { font-size: 1.35rem; letter-spacing: -0.01em; margin: 2px 0 0; }
.pass-preview__by { margin: 0 0 6px; }
.pass-preview__by b { color: var(--text); font-weight: 500; }
.pass-preview__price { margin: 2px 0 8px; font-size: 1.15rem; font-weight: 700; }
.pass-preview__blurb { margin: 4px 0 8px; font-size: 0.92rem; }
.pass-preview__line { margin: 3px 0 0; }

/* THE RIGHT PANE. The checkout eyebrow, the buy buttons (shared/pass_buy), and
   the legal line; or, signed out, the creator's value and the embedded door. */
.pass-co__eyebrow { margin-bottom: 14px; }
.pass-co__pay > .btn { margin-top: 10px; }
.pass-co__pay > .btn:first-of-type { margin-top: 0; }
.pass-co__legal { margin-top: 16px; }
.pass-co__door { text-align: center; }
.pass-co__door .ring { margin: 0 auto; }
.pass-co__name { font-size: 1.4rem; margin: 12px 0 2px; }
.pass-co__joined { margin: 0 0 10px; }
.pass-co__pitch { margin: 0 0 20px; color: var(--muted); font-size: 0.98rem; }
/* The embedded door sits in the pane's own width, left-aligned like the door
   itself — the frame carries the field, the code screen and the flash. */
.pass-co__door turbo-frame { display: block; text-align: left; }
/* THE DOOR, SCOPED TO THE MODAL (the .door-sheet idiom). The frame renders
   in-sheet mode (in_a_sheet? is true — no "see who's live", no legal footer),
   so its body is a plain .shell div; the base .shell adds 96px of bottom
   padding and a page-sized heading, neither of which belongs in a pane. The
   heading STAYS (the code step's "We sent a 6-digit code to …" lives in it),
   just sized to the pane. */
.pass-modal .door-body { padding: 6px 0 4px; max-width: none; margin: 0; }
.pass-modal .door-head { text-align: left; margin-bottom: 20px; }
.pass-modal .door-head h1 { font-size: 1.5rem; letter-spacing: -0.01em; }
.pass-modal .door-sheet__said { margin-bottom: 14px; }

/* ---------- THE PASS FORM (Maaz 2026-09-09) --------------------------------
   Fenced: .pf-* is this screen's own furniture and no other screen wears it.
   It reuses the composer's declared vocabulary on purpose — .form-card, its
   sections, .title-input, .textarea, .field-label, .choice, .toggle-row,
   .switch, .summary and .cover — because two near-identical blocks 380 lines
   apart is how two screens start disagreeing about which element carries a
   stroke (the r8 hazard, three times over). */
body.studio .pf-shell { max-width: 1120px; padding-bottom: 120px; }
/* THE FULL-SCREEN TOP BAR (Maaz 2026-09-13, screenshot 5's NAV). The masthead
   is off, so this slim sticky bar is the whole top of the screen: back chevron
   + title on the left, the one Save on the right, a hairline under it. It rides
   the page's own background so a sticky bar never shows the form scrolling
   through it. */
body.studio .pf-topbar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 14px 0; margin-bottom: 8px;
  background: var(--bg); border-bottom: 1px solid var(--border);
}
body.studio .pf-topbar__lead { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
body.studio .pf-topbar__back {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 999px; color: var(--muted); flex: none;
}
body.studio .pf-topbar__back:hover { color: var(--text); background: var(--surface-2); }
body.studio .pf-topbar__title {
  font-size: 1.05rem; font-weight: 600; letter-spacing: -0.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.studio .pf-topbar__save { flex: none; font-weight: 700; padding: 10px 22px; font-size: 0.9rem; }
/* The one tap lives in the header's own action slot, so the heading and the
   button share a row by construction and the control above them is the way
   back. Only the weight is this page's: it is the studio's gold primary. */
body.studio .pf-shell .page-head__act .btn { font-weight: 700; padding: 12px 24px; font-size: 0.9rem; }
/* THE WAY BACK, IN THE CRUMB'S OWN SLOT (Maaz 2026-09-11). A railless studio
   screen has neither rail nor crumb, so `studio/_page_header`'s `back:` local
   draws this in the crumb's place — the crumb is a <p> carrying the UA's own
   paragraph margins, and this is that box stated explicitly, so the 44px of
   air above and the H1's own 8px below are both unchanged.

   NO `display` OVERRIDE. `.cr-back` is inline-flex, so the block H1 after it
   already starts a new line for free; promoting it to a block-level flex box
   would stretch the link — and its hit area — across the whole column. */
body.studio .page-head__back { margin: 10px 0; }

body.studio .pf-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) 328px;
  gap: 30px; margin-top: 26px; align-items: start;
}
body.studio .pf-note { margin-top: 10px; }
/* SECTION HEADINGS ARE WORDS, NOT AN EYEBROW (Maaz's QA mocks, 23:20): Price,
   Access, Live rhythm, Selling — set in the page's own type, with a deck under
   the three that need one. The gold all-caps eyebrow stays on The offer, which
   is the block the first create round drew that way. */
body.studio .pf-h { margin: 0 0 6px; font-size: 1.18rem; font-weight: 600;
  letter-spacing: -0.01em; }
body.studio .pf-deck { margin: 0 0 18px; color: var(--muted); font-size: 0.92rem;
  --lh: 1.5; line-height: var(--lh-snap); max-width: 62ch; }
body.studio .pf-deck--tight { margin: 0 0 10px; }
/* THE PRICE AND ACCESS BLURBS RUN THE FULL INNER WIDTH (Maaz 2026-09-13): the
   62ch clamp orphaned "that"/"them" onto a second line while the column still
   had room. To the section's own padding, so no word is stranded that fits. */
body.studio .pf-deck--wide { max-width: none; }
/* ONE LINE TALL, RESIZE HANDLE INTACT (Maaz 2026-09-13). rows: 1 sets the
   height; this drops the 80px floor the shared .textarea rule carries so the
   box opens one line tall, and `resize: vertical` is inherited from it. */
body.studio .pf-blurb { min-height: 48px; }
body.studio .pf-sub { margin: 22px 0 8px; font-size: 0.98rem; font-weight: 600; }
body.studio section > .pf-sub:first-of-type { margin-top: 0; }
/* A LINE THAT IS ALREADY TRUE — a gold tick and a sentence, never a control:
   this is what the Pass does whether or not anything below it is picked. */
body.studio .pf-said { display: flex; align-items: flex-start; gap: 9px; margin: 0 0 12px;
  font-size: 0.92rem; --lh: 1.5; line-height: var(--lh-snap); }
body.studio .pf-said .lucide { flex: none; color: var(--gold); margin-top: 2px; }

/* ── THE WAYS TO PAY (Maaz 2026-09-11) ───────────────────────────────────────
   One row per payment method, added and removed from a menu. Two things are
   worth reading twice.

   THE `[hidden]` PAIR IS THE WHOLE NO-JAVASCRIPT STORY. `.pf-way` gives itself
   a display, which OUT-RANKS the UA's own `[hidden] { display: none }` (r14
   item 5) — so at rest the rows the server marks `hidden` would all be on
   screen. The rule under it takes them away again, and the form's own
   `<noscript>` block un-does that one. With no JavaScript a creator gets five
   rows and five real tick boxes; with it, one row per way plus the menu.

   AND THE ROW IS `position: relative` BECAUSE THE FLAG IS ABSOLUTE. An
   absolutely positioned input in a static row lands at its static position
   inside the nearest POSITIONED ancestor — the Team sheet's 12,627px of
   phantom overflow, which is .sr-only-in-a-table-cell wearing another name. */
body.studio .pf-ways { display: grid; gap: 10px; margin: 4px 0 14px; }
body.studio .pf-way {
  position: relative;
  display: grid; align-items: center; gap: 12px;
  /* FIXED TRAILING TRACKS, so five rows read as a column rather than five
     independently-sized rows: the money box, the period and the badge land at
     the same x whatever the description above them wraps to. The X is PLACED
     rather than auto-flowed, because a hidden badge generates no grid item and
     the X would slide up into its cell. */
  grid-template-columns: auto minmax(0, 1fr) 128px 78px minmax(94px, auto) 34px;
  padding: 13px 16px; border: 1px solid var(--border);
  border-radius: 12px; background: var(--surface-2);
}
body.studio .pf-way__x, body.studio .pf-way__held { grid-column: 6; }
/* A grid item stretches to its track by default, and these two are pills:
   on a phone, where the badge cell takes the whole row, an unpinned chip
   becomes a 340px lozenge. */
body.studio .pf-way .pass-best, body.studio .pf-way .pass-saves { justify-self: start; }
body.studio .pf-way[hidden] { display: none; }
body.studio .pf-way__flag { position: absolute; left: 12px; opacity: 0; pointer-events: none; }
/* NO JAVASCRIPT IS A WORKING FORM (Maaz r12 #4). With no JS every method is a
   row with a real tick box and the add-menu / remove-X are gone. This lives in
   the stylesheet behind `html.no-js` — NOT in a per-form `<noscript><style>`,
   which Turbo re-activated inside a frame (DOMParser parses noscript children as
   live elements), reappearing the checkboxes on a Holders → Details swap. */
html.no-js body.studio .pf-way[hidden] { display: grid; }
html.no-js body.studio .pf-way__flag { position: static; left: auto; opacity: 1; pointer-events: auto; }
html.no-js body.studio .pf-way__x, html.no-js body.studio .pf-add { display: none; }
body.studio .pf-way__mark {
  display: grid; place-items: center; width: 34px; height: 34px;
  border: 1px solid var(--hair-gold); border-radius: 999px; color: var(--gold);
}
body.studio .pf-way__id { min-width: 0; }
body.studio .pf-way__id b { display: block; font-size: 0.98rem; font-weight: 500; }
body.studio .pf-way__id span { display: block; margin-top: 2px; color: var(--muted);
  font-size: 0.86rem; --lh: 1.4; line-height: var(--lh-snap); }
body.studio .pf-way__money { width: auto; }
body.studio .pf-way__every { color: var(--muted); font-size: 0.9rem; white-space: nowrap; }
/* The way off a row, in the house's own 34px box — and the glyph never
   resolves the pointer (the one global .lucide rule), so the whole box is the
   button rather than the 16px cross inside it. */
body.studio .pf-way__x, body.studio .pf-way__held {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border: 0; border-radius: 999px;
  background: none; color: var(--muted); cursor: pointer;
}
body.studio .pf-way__x:hover { color: var(--text); background: var(--surface-3); }
/* A ROW THAT CANNOT COME OFF IS DRAWN WITHOUT AN X AND SAYS WHY — and its
   reason is a SENTENCE on a 34px control, which is the horizontal-scroll class
   this house keeps re-learning. `[data-tip]::after` is `left: 50%` and nowrap
   against its own element, so the containing block has to move: the ROW is the
   positioned ancestor, the glyph is static, and the tip wraps and anchors
   right (it is the last thing in the row). A width clamp cannot fix where a
   box STARTS. */
body.studio .pf-way__held { position: static; cursor: default; color: var(--dim); }
body.studio .pf-way__held[data-tip]::after {
  left: auto; right: 0; transform: none; white-space: normal;
  max-width: 100%; box-sizing: border-box;
}
body.studio .pf-way__held[data-tip]:hover::after,
body.studio .pf-way__held[data-tip]:focus-visible::after { transform: none; }

/* BEST VALUE AND A SAVING. One is a fact about a one-time seat and is true
   whenever its row is on screen; the other is measured, so its number is the
   only thing JavaScript writes into either. Both are unfenced, because the
   fan's own card wears them too (studio_scope_law's law 2). */
.pass-best, .pass-saves {
  display: inline-block; white-space: nowrap; font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.04em; text-transform: uppercase; padding: 4px 9px;
  border-radius: 999px; line-height: 1.2;
}
.pass-best { color: var(--gold-ink); background: var(--gold); }
.pass-saves { color: var(--gold); border: 1px solid var(--hair-gold); }
.pass-best[hidden], .pass-saves[hidden] { display: none; }
.pass-best-line { margin: 8px 0 0; text-align: center; }

/* ADD A WAY TO PAY. The house disclosure wearing the ghost button's own box —
   `.menu > summary` is (0,1,1) and ships later in this file, so the override
   has to out-specify it rather than merely follow it (the avatar's lesson). */
body.studio .pf-add { display: inline-block; }
body.studio .pf-add[hidden] { display: none; }
body.studio .pf-add__box > summary.pf-add__go {
  display: inline-flex; align-items: center; gap: 8px; width: auto; height: auto;
  padding: 10px 18px; border: 1px solid var(--border-strong); border-radius: 999px;
  color: var(--text); font-size: 0.9rem; list-style: none;
}
body.studio .pf-add__box > summary.pf-add__go::-webkit-details-marker { display: none; }
body.studio .pf-add__box > summary.pf-add__go:hover { border-color: var(--hair-gold); color: var(--gold); }
body.studio .pf-add .menu__panel { min-width: 190px; }
body.studio .pf-add .menu__item[hidden] { display: none; }

/* THE ONE REFUSAL THAT IS ON SCREEN BEFORE THE SAVE. Live-red, because it is
   the state the form is in rather than a hint about how to use it. */
body.studio .pf-zero { margin: 0 0 14px; color: var(--live); font-size: 0.92rem; }
body.studio .pf-zero[hidden] { display: none; }

@media (max-width: 760px) {
  /* The money takes a line of its own under the words, rather than the row
     pushing the document sideways. */
  body.studio .pf-way { grid-template-columns: auto minmax(0, 1fr) auto auto; gap: 10px; }
  body.studio .pf-way__x, body.studio .pf-way__held { grid-column: auto; }
  body.studio .pf-way__money { grid-column: 1 / -1; }
}

/* A FIELD WITH A SMALL-CAPS LABEL OVER IT (the mock's REPEATS). */
/* A full-width picker. The RENEWS dropdown that first asked for this is
   retired; the rhythm's own still wears it. */
body.studio .pf-select { width: 100%; }
body.studio .pf-field { display: block; max-width: 310px; }
body.studio .pf-field__lab { display: block; margin-bottom: 8px; }

/* THE ACCESS PICKER, CLOSED: a control that looks like the select it stands in
   for. The panel is IN FLOW under it — it pushes the form down rather than
   floating, so nothing can clip it and no z-index is involved. */
body.studio .pf-drop { border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--input-bg); }
/* THE BOX IS A SELECT'S BOX (r8, Maaz 2026-09-11): this control stands in for
   a <select>, and on the filter sheet it stands BESIDE four of them — so the
   padding and the type size are the ones the house gives a real one (11px/16px,
   forms block above), not 12px/0.95rem. His screenshot 1 is a Tags trigger a
   measurable amount off the Sign-in select on its own row.

   `line-height: normal` IS THE THIRD PART, and it is what measuring found
   rather than reasoning: a real form control gets it from the house's own
   `input, select, textarea` rule at the top of this file, and a control made
   of spans does not — so padding and font-size alone left the face 47px
   against the select's 41 (measured).

   IT HAS TO BE ON THE WORDS AND NOT JUST ON THE FACE. `*, *::before, *::after
   { line-height: var(--lh-snap) }` (line 48) applies DIRECTLY to the inner
   span, and a universal rule on the child out-ranks inheritance from the
   parent however specific the parent's own selector is — which is why a
   <select> needs one declaration for this and a <summary> needs two. That is
   also why a 16px face was 47px tall: 16 × 1.55 + 22 of padding. */
body.studio .pf-drop__face { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 11px 14px; cursor: pointer; list-style: none;
  color: var(--muted); font-size: 16px; line-height: normal; }
body.studio .pf-drop__said, body.studio .pf-drop__said * { line-height: normal; }
body.studio .pf-drop__face::-webkit-details-marker { display: none; }
body.studio .pf-drop[open] > .pf-drop__face { border-bottom: 1px solid var(--border); }
body.studio .pf-drop__face:hover { color: var(--text); }
/* A FACE THAT CARRIES AN ANSWER READS AS ONE (r8). "All tags" and "All passes"
   are what the filter is DOING right now — the same class of word as the "Any
   access" a real select beside them shows — so they are the body text colour
   at rest, and hover is not the only way they go white (his own line). The
   Pass form's own pickers deliberately keep the muted face: their resting
   words are PROMPTS ("Choose upcoming lives"), and a prompt painted as an
   answer is the same lie facing the other way. */
body.studio .pf-drop--value > .pf-drop__face { color: var(--text); }
body.studio .pf-drop:has(> summary:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .pf-drop__chev { flex: none; transition: transform 150ms var(--ease); }
body.studio .pf-drop[open] .pf-drop__chev { transform: rotate(180deg); }
body.studio .pf-drop__said [hidden] { display: none; }
body.studio .pf-drop:has(input:checked) .pf-drop__face { color: var(--text); }
body.studio .pf-drop__panel { padding: 6px; max-height: 320px; overflow-y: auto; }
body.studio .pf-drop__zero { margin: 8px 10px; }

/* A body that a switch reveals. `[hidden]` has to WIN, because a rule that
   gives an element a display out-ranks the UA's own [hidden] (r14 item 5).
   The revealed control hugs its switch (Maaz 2026-09-13, screenshot 4): the
   margin above it is small, and the toggle rows drop most of their own bottom
   padding for the same reason — the seat count, the close date, the chips and
   the tag row all used to sit ~28px under their toggle. */
body.studio .pf-body { margin: 4px 0 8px; }
body.studio .pf-body[hidden] { display: none; }
/* A money field: the sign and the period are the house's words, the number is
   the creator's. */
/* THE MONEY BOX IS ONE FIELD with the sign inside it (the mock): a `$` set
   outside the stroke reads as a caption, and the creator is typing a price. */
body.studio .pf-money { display: flex; align-items: center; gap: 6px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--input-bg); padding: 0 14px; min-width: 0; }
body.studio .pf-money:focus-within { border-color: var(--accent); }
body.studio .pf-money input { flex: 1 1 auto; width: 100%; min-width: 0;
  border: 0; background: none; padding: 11px 0; }
body.studio .pf-money input:focus { border-color: transparent; }
body.studio .pf-money input[hidden] { display: none; }
body.studio .pf-money__sign, body.studio .pf-money__per { flex: none; color: var(--muted); font-size: 0.92rem; }
/* CHIPS. An .sr-only input is `position: absolute` with no offsets, so it
   lands at its STATIC position inside the nearest POSITIONED ancestor — the
   team sheet's 12,627px of phantom overflow. Every label here that hides its
   own input carries `position: relative`. */
body.studio .pf-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
body.studio .pf-chip {
  position: relative; display: inline-flex; align-items: center; padding: 7px 14px;
  border: var(--hair-w) solid var(--border); border-radius: 999px; cursor: pointer;
  color: var(--muted); font-size: 0.85rem;
  transition: border-color 150ms ease, color 150ms ease, background-color 150ms ease;
}
body.studio .pf-chip:hover { border-color: var(--border-strong); color: var(--text); }
body.studio .pf-chip:has(input:checked) { border-color: var(--hair-gold); color: var(--gold); background: var(--surface-3); }
body.studio .pf-chip:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Monthly is the base, not a choice — it is drawn lit and resolves no pointer. */
body.studio .pf-chip--fixed { border-color: var(--hair-gold); color: var(--gold); cursor: default; }
/* THE PICKERS. Only pass-gated lives and encores land here, so a row is always
   a door this Pass can be another key to. */
body.studio .pf-picks {
  display: flex; flex-direction: column; gap: 2px; margin-top: 4px;
  max-height: 260px; overflow-y: auto;
}
body.studio .pf-pick {
  position: relative; display: flex; align-items: flex-start; gap: 12px;
  padding: 10px 12px; border-radius: var(--radius); cursor: pointer;
}
body.studio .pf-pick:hover { background: var(--surface-2); }
body.studio .pf-pick__mark {
  flex: none; width: 18px; height: 18px; margin-top: 2px; border-radius: 5px;
  border: var(--hair-w) solid var(--border-strong); color: transparent;
  display: inline-flex; align-items: center; justify-content: center;
}
body.studio .pf-pick:has(input:checked) .pf-pick__mark { background: var(--gold); border-color: var(--gold); color: var(--gold-ink); }
body.studio .pf-pick:has(input:focus-visible) .pf-pick__mark { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .pf-pick__id { min-width: 0; }
body.studio .pf-pick__id b { display: block; font-weight: 500; font-size: 0.95rem; }
body.studio .pf-pick__id span { display: block; margin-top: 2px; }
/* A PICK WITH A VERB BESIDE IT (r8 — the Tags list's delete). The row is the
   flex box and the label keeps its own hit area inside it, because a button
   inside a <label> is a click that toggles the label. The row's hover fill
   moves onto the row so the whole thing lights as one thing. */
body.studio .pf-pick-row { display: flex; align-items: center; gap: 2px;
  border-radius: var(--radius); }
body.studio .pf-pick-row:hover { background: var(--surface-2); }
body.studio .pf-pick-row .pf-pick { flex: 1 1 auto; min-width: 0; }
body.studio .pf-pick-row .pf-pick:hover { background: none; }
body.studio .pf-pick__del { flex: none; display: inline-flex; align-items: center;
  justify-content: center; width: 32px; height: 32px; margin-right: 6px;
  border: 0; border-radius: var(--radius-sm); background: none; color: var(--muted);
  cursor: pointer; }
body.studio .pf-pick__del:hover { color: var(--live); background: var(--surface-3); }
body.studio .pf-pick__del:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The delete's own form is a holder for `form=`-joined submitters and nothing
   else: it has no fields, so it must take no room (the Account card's idiom). */
body.studio .fol-tag-forget { display: none; }
/* A day and a time on one row, each labelled. */
/* LIVE RHYTHM: THREE DROPDOWNS ON ONE ROW (Maaz 2026-09-13, screenshot 2).
   Each part carries its own small-caps label over a full-width select; the
   zone rides beside Time. `.pf-when` already lays the parts out in a row — the
   rhythm just holds them from wrapping too eagerly. */
body.studio .pf-rhythm .pf-when__part { flex: 1 1 160px; }
body.studio .pf-when__clock { display: flex; align-items: center; gap: 8px; }
body.studio .pf-when__clock select { min-width: 0; }
body.studio .pf-when__zone { color: var(--muted); font-size: 0.9rem; white-space: nowrap; }
/* CLOSE SALES: DATE + TIME + IST ON ONE ROW, NO LABELS (screenshot 4). */
body.studio .pf-when--inline .pf-when__part--time { display: flex; align-items: center; gap: 8px; }
body.studio .pf-when--inline .pf-when__part--time select { flex: 1 1 auto; min-width: 0; }
/* LIMITED TO [N] PEOPLE, INLINE (screenshot 4). */
body.studio .pf-inline { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0; color: var(--muted); font-size: 0.82rem; }
body.studio .pf-inline__num { width: 6em; flex: none; }
/* PICK A TAG + NEW TAG + ADD on one row, the two fields EQUAL WIDTH (Maaz
   2026-09-13): New tag no longer dwarfs Pick a tag, so neither reads as the
   more important one. `flex: 1 1 0` on both shares the row evenly; Add keeps
   its own content width. */
body.studio .pf-newtag__pick { flex: 1 1 0; width: auto; min-width: 0; }
body.studio .pf-when { display: flex; gap: 12px; flex-wrap: wrap; }
body.studio .pf-when__part { flex: 1 1 150px; min-width: 0; }
body.studio .pf-when__part .field-label { margin: 0 0 6px; }
body.studio .pf-when__part select, body.studio .pf-when__part input { width: 100%; }
/* THE NATIVE DATE CONTROL, dressed. This form does not mount the composer's
   `date-picker` face — one date a year is not worth a second calendar — so the
   browser's own control is the whole control, and `color-scheme: dark` is what
   keeps its text, its spinners and its picker from painting a white box in a
   near-black card (the .dpick__native rule's own reasoning). */
body.studio .pf-when__part input[type="date"] {
  padding: 12px 14px; font-size: 1rem; border-radius: var(--radius);
  border: var(--hair-w) solid var(--border-strong); background: var(--input-bg);
  color: var(--text); font-family: var(--sans); color-scheme: dark;
}
/* THE SELLING STACK (the mock): four switches with room to breathe, indented
   from the heading the way the mock draws them — and flush again on a phone,
   where 18px is width the row cannot spare. */
body.studio .pf-sell { padding-left: 18px; }
body.studio .pf-sell > .toggle-row { padding: 14px 0 6px; }
body.studio .pf-sell > .toggle-row:first-child { padding-top: 4px; }
/* THE SECOND LINE ON A SWITCH MATCHES THE THANK-YOU DECK (Maaz 2026-09-13):
   the badge's and the tag's subtitles read at the same size as "After paying,
   buyers land on this link…" (.small, 0.82rem), scoped so the board's own
   toggle rows keep their size. */
body.studio .pf-sell .toggle-row span:not(.switch) { font-size: 0.82rem; }
@media (max-width: 620px) { body.studio .pf-sell { padding-left: 0; } }
body.studio .pf-newtag { display: flex; gap: 8px; margin-top: 10px; }
body.studio .pf-newtag input { flex: 1 1 0; min-width: 0; }

/* ADVANCED SETTINGS, one disclosure closed by default (Maaz r12 #1). The
   summary carries the heading + subhead and a chevron that turns on open;
   `list-style: none` drops the UA marker triangle. */
body.studio .pf-adv > .pf-sell { padding-top: 6px; }
body.studio .pf-adv__summary { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; cursor: pointer; list-style: none; }
body.studio .pf-adv__summary::-webkit-details-marker { display: none; }
body.studio .pf-adv__head .pf-h { display: block; margin: 0 0 6px; }
body.studio .pf-adv__head .pf-deck { display: block; margin: 0; }
body.studio .pf-adv__chev { color: var(--muted); flex: none; transition: transform var(--dur) var(--ease); margin-top: 2px; }
body.studio .pf-adv[open] > .pf-adv__summary .pf-adv__chev { transform: rotate(180deg); }
body.studio .pf-h--sub { font-size: 1.02rem; }
body.studio .pf-adv__sub { margin-top: 6px; }

/* Custom thank-you page: now a switch like the four above it (Maaz
   2026-09-13). The revealed deck + URL ride the shared `.pf-body` spacing, so
   the block adds no padding of its own; the deck hugs the switch. */
body.studio .pf-thanks { padding: 0; }
body.studio .pf-thanks__deck { margin-top: 0; margin-bottom: 8px; }
body.studio .pf-thanks input { width: 100%; }
/* HELPER SUBTEXT, ONE TOKEN (Maaz 2026-09-13). The thank-you line's own
   colour and size — var(--dim) at 0.82rem — for every hint under a control on
   this form: the badge and tag lines (which were the brighter var(--muted)),
   the Access "you can also choose…" lines (var(--muted) at the larger 0.92rem)
   and the thank-you deck that is the token itself. The second selector is the
   specificity the toggle-row's own muted-span rule (`.toggle-row
   span:not(.switch)`, 0,3,2) forces — `.pf-form .pf-hint` alone (0,3,1) would
   lose the colour there; on .pf-deck (0,2,1) the form-scoped rule already
   wins, so the Access lines keep pf-deck's line-height and clamp and take only
   the colour and size from here. */
body.studio .pf-form .pf-hint,
body.studio .pf-sell .toggle-row .pf-hint { color: var(--dim); font-size: 0.82rem; }

/* Promo links: minted in place. "Create promo links" is its own disclosure. */
body.studio .pf-promos { padding-top: 8px; }
body.studio .pf-promos__list { margin-top: 12px; }
body.studio .pf-promos__empty { margin-top: 10px; }
body.studio .pf-promo-new { margin-top: 14px; }
body.studio .pf-promo-new__head { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; cursor: pointer; list-style: none; }
body.studio .pf-promo-new__head::-webkit-details-marker { display: none; }
body.studio .pf-promo-new__head .block { margin-top: 2px; }
body.studio .pf-promo-new[open] > .pf-promo-new__head .pf-adv__chev { transform: rotate(180deg); }
body.studio .pf-promo-new__fields { align-items: center; }

/* THE PREVIEW COLUMN — the composer's own .summary, with the Pass's card in
   the frame the night's cover uses. */
body.studio .pf-preview .cover { cursor: pointer; }
body.studio .pf-cover { aspect-ratio: 1.91 / 1; }
body.studio .pf-metal {
  position: relative; z-index: 1; width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center; text-align: center; padding: 16px;
}
body.studio .pf-metal[hidden] { display: none; }
body.studio .pf-metal__title {
  font-family: var(--display, var(--sans)); font-weight: 700; font-size: 1.35rem;
  letter-spacing: 0.02em; text-transform: uppercase; text-wrap: balance;
}
/* An unpainted metal (nothing tapped yet) is the frame's own surface, so the
   empty state reads through it. */
body.studio .pf-metal:not([class*="pf-metal--"]) { background: none; }
body.studio .pf-metals { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
body.studio .pf-metal-pick {
  position: relative; display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 11px; border-radius: 999px; cursor: pointer;
  border: var(--hair-w) solid var(--border); color: var(--muted); font-size: 0.78rem;
  transition: border-color 150ms ease, color 150ms ease;
}
body.studio .pf-metal-pick::before {
  content: ""; width: 11px; height: 11px; border-radius: 50%; flex: none;
  border: 1px solid rgb(0 0 0 / 0.35);
}
body.studio .pf-metal-pick--bronze::before { background: linear-gradient(135deg, #a9662f, #e0a266); }
body.studio .pf-metal-pick--silver::before { background: linear-gradient(135deg, #9aa0a8, #e6e9ed); }
body.studio .pf-metal-pick--gold::before { background: linear-gradient(135deg, #c69a3c, #f4dc9a); }
body.studio .pf-metal-pick--diamond::before { background: linear-gradient(135deg, #8fc4d6, #eaf7fb); }
body.studio .pf-metal-pick--platinum::before { background: linear-gradient(135deg, #c8ccd2, #f6f8fa); }
body.studio .pf-metal-pick--custom::before { background: none; border-style: dashed; }
body.studio .pf-metal-pick:hover { border-color: var(--border-strong); color: var(--text); }
body.studio .pf-metal-pick:has(input:checked) { border-color: var(--hair-gold); color: var(--gold); }
body.studio .pf-metal-pick:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
body.studio .pf-remove {
  display: flex; align-items: center; gap: 8px; margin-top: 10px;
  color: var(--muted); font-size: 0.82rem; cursor: pointer;
}
/* ── THE PREVIEW'S PLAN PICKER (Maaz 2026-09-11) ─────────────────────────────
   One way to pay is a price and a line; two or more is "Choose a plan" over a
   segmented control. The head and the segments are drawn either way and
   hidden below two, so nothing here has to be built in JavaScript. */
body.studio .pf-plans { margin-top: 14px; }
body.studio .pf-plans__head { display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
body.studio .pf-plans__head[hidden] { display: none; }
body.studio .pf-plans__lead { color: var(--gold); font-size: 1rem; font-weight: 500; }
body.studio .pf-plans__count { color: var(--muted); font-size: 0.8rem; }
body.studio .pf-seg { display: flex; flex-wrap: wrap; gap: 2px; padding: 3px;
  border: 1px solid var(--border); border-radius: 999px; margin-bottom: 14px; }
body.studio .pf-seg[hidden] { display: none; }
body.studio .pf-seg__pick { flex: 1 1 auto; min-width: 0; padding: 7px 12px;
  border: 1px solid transparent; border-radius: 999px; background: none;
  color: var(--muted); font-size: 0.84rem; white-space: nowrap; cursor: pointer; }
body.studio .pf-seg__pick[hidden] { display: none; }
body.studio .pf-seg__pick:hover { color: var(--text); }
body.studio .pf-seg__pick[aria-pressed="true"] { border-color: var(--hair-gold); color: var(--text); }
body.studio .pf-plan[hidden] { display: none; }
body.studio .pf-plan__said { margin: 4px 0 0; color: var(--muted); font-size: 0.86rem;
  --lh: 1.45; line-height: var(--lh-snap); }
body.studio .pf-price { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px; margin: 0; }
body.studio .pf-price[hidden] { display: none; }
body.studio .pf-price b { color: var(--gold); font-weight: 700; font-size: 1.6rem; }
/* The caption beside the sticker is the PERIOD, named rather than reached for
   as "every span in here": the chips beside it are shared with the fan's own
   card, and a `.pf-price span` rule would grey them out AND fence a class a
   non-studio view wears (studio_scope_law's law 1). */
body.studio .pf-price .pf-price__per { color: var(--muted); font-size: 0.88rem; }
/* "/month" hangs off the number with no gap — it is a suffix, not a phrase. */
body.studio .pf-price .pf-price__per { margin-left: -2px; }
body.studio .summary__line[hidden] { display: none; }
body.studio .pf-blurb { color: var(--muted); }
body.studio .pf-promos { max-width: 720px; margin-top: 40px; }

@media (max-width: 899px) {
  /* One column: the card lands under the questions and stops being sticky — a
     sticky panel in a single column parks itself over the form. */
  body.studio .pf-grid { grid-template-columns: minmax(0, 1fr); }
  body.studio .pf-preview { position: static; }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE PASS BADGE (Maaz 2026-09-09) — the mark a holder wears beside their
   name whenever they talk in a creator's rooms.

   DELIBERATELY UNFENCED. The chat this renders in is a FAN surface (the live
   room, the waiting room, the encore's replaying chat), and a body.studio
   class on one renders bare with nothing said — studio_scope_law's law 2.

   OVERLAPPING, OFFSET RIGHT, NOT STACKED FLUSH (the mock's own shape): the
   negative margin is what makes three marks read as three rather than as one
   thick one, and the ring is the canvas showing through between them.
   ═══════════════════════════════════════════════════════════════════════ */
.pass-badges { display: inline-flex; align-items: center; vertical-align: middle;
  margin-right: 6px; flex: none; }
.pass-badges__dot { display: block; width: 11px; height: 11px; border-radius: 999px;
  box-shadow: 0 0 0 2px var(--bg); }
.pass-badges__dot + .pass-badges__dot { margin-left: -4px; }
.pass-badges__more { margin-left: 5px; font-size: 0.68rem; color: var(--dim); }
/* One vocabulary with the cover's metals, and the same five gradients — a
   creator who taps Gold on both gets one colour, not two. */
.pass-badges__dot--bronze { background: linear-gradient(135deg, #a9662f, #e0a266); }
.pass-badges__dot--silver { background: linear-gradient(135deg, #8f979f, #dfe5ea); }
.pass-badges__dot--gold { background: linear-gradient(135deg, #a5761b, #f3d27a); }
.pass-badges__dot--diamond { background: linear-gradient(135deg, #5d8fa3, #cfeaf3); }
.pass-badges__dot--platinum { background: linear-gradient(135deg, #8d9095, #e6e8ea); }

/* ── THE PASS SHELF (Maaz 2026-09-09) ─────────────────────────────────────
   Fenced: .pass-card / .pass-row / .pass-intro / .pass-bar / .pass-stats /
   .pass-chart / .pass-lists are this screen's own furniture. `.pass-badges`
   above is NOT — it is worn on the fan side. */
body.studio .pass-intro { display: flex; align-items: flex-start; gap: 24px;
  justify-content: space-between; margin-top: 22px; }
body.studio .pass-intro .prose { max-width: 62ch; margin: 0; }
body.studio .pass-intro .btn { flex: none; }
body.studio .pass-bar { display: flex; justify-content: flex-end; margin-top: 22px; }

/* Three across a 1280px desk — the mock's density, the Lives grid's own
   floor, and what keeps the counts row on one line. */
body.studio .grid--passes { display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); }

body.studio .pass-card { display: flex; flex-direction: column; min-width: 0;
  border: var(--hair-w) solid var(--border); border-radius: var(--radius);
  background: var(--surface); overflow: hidden;
  transition: border-color var(--dur) var(--ease); }
body.studio .pass-card:hover { border-color: var(--border-strong); }
/* THE WHOLE CARD IS THE LINK (his QA item 4) — the Lives board's own
   interaction. The title stays a real anchor; this is the keyboard's ring. */
body.studio .pass-card, body.studio .pass-row { cursor: pointer; }
body.studio .pass-card:focus-visible, body.studio .pass-row:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
body.studio .pass-card__acts, body.studio .pass-row__acts { cursor: default; }
body.studio .pass-card__frame { position: relative; aspect-ratio: 16 / 9;
  background: var(--surface-2); display: block; overflow: hidden;
  border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0; }
body.studio .pass-card__frame .pass-cover,
body.studio .pass-card__frame .pass-thumb { width: 100%; height: 100%; border-radius: 0;
  object-fit: cover; }
/* A Pass with no cover chosen draws no invented card — just the house glyph,
   centred, on the surface (the "no picture ⇒ no og:image" law's studio half). */
body.studio .pass-card__bare { position: absolute; inset: 0; display: grid;
  place-items: center; color: var(--dim); }
/* THE STATE, ON THE PICTURE (Maaz's QA mock, 2026-09-09 23:20) — a FILLED
   pill and not the shelf's outline chip: a 1px gold outline on a gold metal
   plate is a stroke nobody can read. Gold still marks the positive state only;
   everything else is the canvas with the house hairline. */
body.studio .pass-card__pill { position: absolute; top: 10px; left: 10px;
  display: inline-flex; align-items: center; border-radius: 999px;
  padding: 4px 10px; font-size: 0.58rem; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; white-space: nowrap; }
body.studio .pass-card__pill--on { background: var(--gold); color: var(--gold-ink); }
body.studio .pass-card__pill--off {
  background: color-mix(in srgb, var(--bg) 84%, transparent); color: var(--text);
  border: var(--hair-w) solid var(--border-strong);
}
/* AN OPEN KEBAB IS NOT CLIPPED BY THE CARD IT HANGS OFF (his QA item 2). The
   card carries `overflow: hidden` for its cover's top corners, and an overflow
   box clips its content whether or not it scrolls — no z-index escapes an
   ancestor's clip (the .team-scroll lesson). So the containment stands down
   while a menu is open, the FRAME keeps its own radius so nothing squares off,
   and the card is raised over the ones beside it (.enc-row's own idiom, which
   is also what beats the shell's house-fade making each row its own stacking
   context while it animates). */
body.studio .pass-card:has(details.menu[open]),
body.studio .pass-row:has(details.menu[open]) {
  overflow: visible; position: relative; z-index: 50;
}
body.studio .pass-card__body { padding: 14px; display: flex; flex-direction: column; gap: 8px; }
body.studio .pass-card__title { font-weight: 600; font-size: 1rem; display: block;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
body.studio .pass-card__price { display: flex; align-items: center; gap: 8px;
  margin: 0; color: var(--muted); font-size: 0.9rem; flex-wrap: wrap; }
body.studio .chip--badge { display: inline-flex; align-items: center; gap: 5px;
  font-size: 0.62rem; }
body.studio .pass-card__counts { display: flex; align-items: center; gap: 16px; margin: 0;
  color: var(--dim); font-size: 0.82rem; }
/* A sentence-length [data-tip] on a small control inflates its own scrollWidth
   and pushes the document sideways (the r12 / post-tray class), so the ROW is
   the containing block and the tip resolves inside the card at any width. */
body.studio .pass-card__counts { position: relative; }
body.studio .pass-card__counts > span { display: inline-flex; align-items: center; gap: 5px;
  position: static; }
body.studio .pass-card__counts > span[data-tip]::after { left: 0; transform: none;
  max-width: 100%; white-space: normal; }
body.studio .pass-card__counts > span[data-tip]:hover::after,
body.studio .pass-card__counts > span[data-tip]:focus-visible::after { transform: none; }
body.studio .pass-card__acts { display: flex; align-items: center; gap: 8px;
  margin-top: 2px; border-top: var(--hair-w) solid var(--border); padding-top: 10px; }
/* FOOTER CHROME LIKE LIVES (Maaz 2026-09-13): the Analytics button takes the
   width the two glyph buttons leave, exactly as `.live-card__go` does on the
   Lives card. `margin-top: auto` is not needed here — the counts row above is
   a fixed height — but the primary flexing wide is the same shape. */
body.studio .pass-card__acts .btn { padding: 9px 16px; }
body.studio .pass-card__go { flex: 1 1 auto; justify-content: center; }
body.studio .pass-menu { margin-left: auto; }
/* STROKED SQUARES LIKE LIVES (r17, reversing the 2026-09-13 bare-glyph mock):
   the Lives row's right cluster is Analytics + a bordered share square + a
   bordered kebab square, and the Pass list must match it. So share keeps the
   `.btn` stroke its `.btn btn--icon` already carries, and the kebab's
   `.menu > summary` keeps its own hairline — both 40x40, same height as the
   Analytics button beside them. */
body.studio .pass-card__acts .btn--icon:hover,
body.studio .pass-card__acts .menu > summary:hover { background: var(--surface-2); }
body.studio .pass-card__acts .menu > summary { width: 40px; height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 8px; color: var(--muted); }

/* THE LIST ROW (Maaz's QA mock, 2026-09-09 23:20) — A GRID, NOT A WRAPPING
   FLEX ROW, and that is the whole fix. Every part after the name had a content
   width it could not shrink past — the pill, the sticker, the badge, three
   counts and two controls — so `flex-wrap` did the only thing it could and put
   Share and the kebab on a leftover line under the card (his screenshot).
   Three columns cannot wrap:

     cover (the state pill on it) | name · price · counts | share + kebab

   The cover is the row's height by construction, so every row is one height. */
body.studio .pass-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start; gap: 18px; padding: 18px;
  border: var(--hair-w) solid var(--border); border-radius: var(--radius);
  background: var(--surface); margin-bottom: 12px;
  transition: border-color var(--dur) var(--ease);
}
body.studio .pass-row:hover { border-color: var(--border-strong); }
/* MATCH THE LIVES ROW THUMBNAIL SIZE (r17): the pass cover used to run up to
   216px, half again the Lives board's 144px column, so a shelf of Passes read
   heavier than the same creator's lives. Fixed at the Lives row's own 144px,
   the card's 3:2 shape kept (a Pass cover is a card, not a 16:9 still). */
body.studio .pass-row__thumb { flex: none; position: relative;
  width: 144px; aspect-ratio: 3 / 2;
  border-radius: 12px; overflow: hidden; background: var(--surface-2); }
body.studio .pass-row__thumb .pass-cover,
body.studio .pass-row__thumb .pass-thumb { width: 100%; height: 100%; border-radius: 0;
  object-fit: cover; border: 0; }
body.studio .pass-row__thumb .pass-cover__title { font-size: 0.72rem; }
body.studio .pass-row__id { min-width: 0; padding-top: 2px; }
body.studio .pass-row__title-line { display: flex; align-items: center; gap: 10px;
  min-width: 0; }
body.studio .pass-row__title { font-weight: 500; font-size: 1.05rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
body.studio .pass-row__price { margin: 6px 0 0; color: var(--muted); font-size: 0.95rem; }
body.studio .pass-row__id .pass-card__counts { margin-top: 10px; }
/* Top-right, VERTICALLY CENTERED ON THE COVER (r20 — the Lives row's own
   chrome), and no rule above them: the card's own stroke is the only one this
   row draws (r8's one-stroke rule). The grid is `align-items: start`, so the
   cluster sat pinned to the title row a full head above the 96px cover's
   midpoint (Maaz's mock, 2026-09-14); `align-self: center` puts the cluster's
   midpoint on the cover's — the cover is the row's tallest item by
   construction, so its centre IS the track's. The id column stays at start,
   where the title belongs. */
body.studio .pass-row__acts { margin-top: 0; border-top: 0; padding-top: 0;
  align-self: center; }
@media (max-width: 560px) {
  body.studio .pass-row { gap: 12px; padding: 12px; }
  body.studio .pass-row__thumb { width: 96px; }
  body.studio .pass-row__title { font-size: 0.95rem; }
  /* THE FOOTER DROPS TO ITS OWN LINE on a phone (the Lives row's own move):
     Analytics plus share plus the kebab will not sit beside a 96px cover and
     the counts without pushing the row sideways, so the grid falls to two
     columns and the actions span the full width under them. The counts are
     free to wrap too — a fourth figure (the earned dollars) is what tipped it. */
  body.studio .pass-row { grid-template-columns: auto minmax(0, 1fr); }
  body.studio .pass-row__acts { grid-column: 1 / -1; justify-content: flex-end;
    margin-top: 4px; }
  body.studio .pass-card__counts { flex-wrap: wrap; }
}

/* ── ONE PASS'S OWN PAGE ─────────────────────────────────────────────────── */
/* Six figures wrap on a narrow desk rather than squeezing, and each carries
   its own subtitle under the label — the mock's shape. */
body.studio .pass-stats { gap: clamp(20px, 3.4vw, 54px);
  /* THE CONTAINING BLOCK FOR THE (i)'s TIP. `.tip-i[data-tip]::after` is a
     236px box anchored on a 13px glyph, and this row carries two of them:
     measured 181px of sideways scroll at a true 390px, with NO element's
     bounding box outside the viewport (the r12 / post-tray signature — a
     width clamp cannot fix where the control landed, so the containing block
     moves instead). The `.cap-cost` precedent, on this row. */
  position: relative; }
body.studio .pass-stats .tip-i[data-tip] { position: static; }
body.studio .pass-stats .tip-i[data-tip]::after {
  /* BORDER-BOX, and it is the last 22px. The global `* { box-sizing }` does
     NOT reach a pseudo-element, so `max-width: 100%` caps the CONTENT box and
     the tooltip's own 10px padding either side is added on top — measured 22px
     of document scroll after the containing block had already been moved. */
  box-sizing: border-box;
  width: max-content; max-width: 100%; left: 0; right: auto;
}
/* The subtitle is a SENTENCE, not a label: `.stat span`'s all-small-caps is
   right for "MEMBERS" and wrong for "12 active" / "average across 4 lives",
   which the mock draws in ordinary sentence case. `font-variant-caps` is what
   does the shouting here — `text-transform` alone leaves it in place. */
body.studio .pass-stats .stat__sub { display: block; margin-top: 4px; letter-spacing: 0;
  font-size: 0.78rem; font-weight: 400; text-transform: none;
  font-variant-caps: normal; color: var(--dim); }
body.studio .pass-stats .stat__sub .num { margin-right: 8px; }

body.studio .pass-chart { margin-top: 34px; }
body.studio .pass-spark { margin: 12px 0 0; position: relative; padding-left: 26px; }
body.studio .pass-spark svg { display: block; width: 100%; height: 120px;
  border: var(--hair-w) solid var(--border); border-radius: var(--radius);
  background: var(--surface); }
body.studio .pass-spark polyline { stroke: var(--gold); stroke-width: 1.5; }
body.studio .pass-spark__axis { position: absolute; left: 0; top: 0; height: 120px;
  display: flex; flex-direction: column; justify-content: space-between;
  font-size: 0.7rem; color: var(--dim); font-variant-numeric: tabular-nums lining-nums; }
body.studio .pass-spark figcaption { display: flex; justify-content: space-between;
  margin-top: 8px; font-size: 0.76rem; color: var(--dim); }

body.studio .pass-lists { display: grid; gap: 24px; margin-top: 34px;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }
/* INNER PADDING so the mark doesn't kiss the left edge and WENT LIVE doesn't
   kiss the right (Maaz r12). The rows live in a `.card--flush` (padding: 0), so
   the breathing room is the row's own. */
body.studio .pass-list__row { padding: 14px 16px; gap: 16px; }
body.studio .pass-list__id { display: inline-flex; align-items: center; gap: 10px;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
body.studio .pass-list__id .lucide { color: var(--dim); flex: none; }
body.studio .pass-list__when { flex: none; text-align: right; }
body.studio .pass-list__when .eyebrow { display: block; margin-bottom: 2px; }
body.studio .pass-list__when .num { font-size: 0.86rem; color: var(--muted); }

/* The holders table wears the Followers roster's own furniture; only its
   secondary lines need a rule of their own. */
body.studio .pass-holders .block { display: block; margin-top: 2px; }
body.studio .pass-holders td .lucide { vertical-align: -1px; }

/* ── THE FORM'S TWO NEW CONTROLS ─────────────────────────────────────────── */
/* The one tap on the Pass's own Details tab, where the page header above it
   belongs to the page and not to the form. */
body.studio .pf-save { display: flex; justify-content: flex-end; margin: 18px 0 4px; }
body.studio .pf-chip--locked { opacity: 0.6; cursor: default; }
body.studio .pf-chip--badge { gap: 7px; }
body.studio .pf-badge { display: flex; align-items: center; gap: 8px; }
/* The promise line, with the mock's own calendar glyph beside it. Declared
   here because #506 rendered the class and the sheet never carried it — a
   class name is not a style (#415). TWO children only: the glyph and the
   SENTENCE, because a flex container blocks every child and a sentence split
   across five of them is five lines. */
body.studio .pf-rhythm { display: flex; align-items: baseline; gap: 7px; }
body.studio .pf-rhythm .lucide { flex: none; color: var(--dim); transform: translateY(2px); }
/* ============================== THE CHANNELS TAB (Maaz's mocks, 2026-09-09)
   /studio/communications — the landing. Cards for the four rails, the
   quiet-hours bar, the By-live table and the Who-hears-what matrix. All
   body.studio-fenced; the switch is the house's (button.switch, gold when
   aria-checked). The matrix is a GRID, not a table: minmax(0,1fr) on the
   label is what keeps five columns inside 390px (the no-scroll walk covers
   this address). */
body.studio .chn-h2 { margin: 34px 0 0; font-size: 1.15rem; display: flex; align-items: center; position: relative; }
/* An absolute [data-tip] box is LAID OUT at rest, and .tip-i's 236px anchored
   on a glyph near a heading's end pushed the document 36px past a true 390
   (measured — CI's fence; no rect ever leaves the viewport, only scrollWidth
   moves). The containing block moves (the .cap-cost idiom): the heading is the
   anchor, the tip clamps to it and wraps. Both hosts of the tip on this page
   get the same treatment. */
body.studio .chn-h2 .tip-i[data-tip] { position: static; }
/* box-sizing: the tip's 6/10 padding + hairline ride OUTSIDE a max-width'd
   content box by default, which is 22px of scrollable overflow per tip
   (measured) — border-box puts the clamp on the whole box. */
body.studio .chn-h2 .tip-i[data-tip]::after { box-sizing: border-box; width: max-content; max-width: 100%; left: 0; right: auto; }
body.studio .chn-card__head { position: relative; }
body.studio .chn-card__head .tip-i[data-tip] { position: static; }
body.studio .chn-card__head .tip-i[data-tip]::after { box-sizing: border-box; width: max-content; max-width: 100%; left: 0; right: auto; }
body.studio .chn-sub { margin: 4px 0 0; }
/* THE RAIL'S ONE CONTROL LINE (Maaz 2026-09-12): the three addresses left, the
   30/90/All time switch right, on one row and on all three tabs. Its own class
   rather than the Encores shelf's `.enc-row`, which is that round's decision
   about that round's row (the `.gl-side` rule) — the shape is the same shape
   the house wears in three places.

   flex-wrap is the phone answer and it is load-bearing: six pills do not fit a
   390px viewport, so the switch drops to its own line rather than pushing the
   document sideways (studio_no_horizontal_scroll_test walks all three of these
   addresses). `.tabs` already carries the pills' own wrap. */
body.studio .chn-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-top: 22px;
}
/* The pills' own margin lives on the row now, or the two rails sit on two
   different baselines when the line does not wrap. */
body.studio .chn-row__pills { margin-top: 0; }
body.studio .chn-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-top: 14px; }
@media (max-width: 720px) { body.studio .chn-grid { grid-template-columns: minmax(0, 1fr); } }
body.studio .chn-card { padding: 16px 18px; }
body.studio .chn-card__head { display: flex; align-items: center; gap: 8px; }
body.studio .chn-card__head b { font-weight: 600; }
body.studio .chn-card__icon {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 34px; height: 34px; border-radius: 50%;
  border: var(--hair-w, 1px) solid var(--hair-gold, var(--border-strong)); color: var(--gold);
}
/* The pip is the transport's own answer — gold only when the rail can send. */
body.studio .chn-pip { margin-left: auto; color: var(--dim); font-size: 0.88rem; display: inline-flex; align-items: center; gap: 6px; }
body.studio .chn-pip::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--dim); }
body.studio .chn-pip--on { color: var(--text); }
body.studio .chn-pip--on::before { background: var(--gold); }
body.studio .chn-card__reach { margin: 10px 0 0; padding-bottom: 12px; border-bottom: 1px solid var(--border); }
body.studio .chn-card__figure { padding: 14px 0 12px; border-bottom: 1px solid var(--border); }
body.studio .chn-card__figure b { display: block; font-size: 2.1rem; font-weight: 700; line-height: 1.1; }
body.studio .chn-card__label {
  display: block; margin-top: 4px; color: var(--dim); font-size: 0.72rem;
  letter-spacing: 0.08em; text-transform: uppercase;
}
body.studio .chn-card__tracked { margin: 12px 0 0; }
/* THE OPEN-RATE TILE (Maaz 2026-09-13, the Delivery log's two tiles). It is
   the landing's own card — one radius and one stroke across the tab — with the
   figure as the LAST thing in the box rather than the middle of three, so the
   hairline under it would hang beside nothing. Keyed to the VARIANT and never
   to `.chn-card__figure` itself (the `dpick--inflow` rule: the decision travels
   with the markup that asked for it, so the landing's four cards keep their
   own rules byte for byte). */
body.studio .chn-card--open .chn-card__figure { padding: 14px 0 0; border-bottom: 0; }
/* The label here is a sentence ("from 200 recipients"), not a category, so it
   drops the eyebrow's uppercase and tracking — those are for the landing's
   "OF ALL JOINS · LAST 30 DAYS", where the words ARE a label. */
body.studio .chn-card--open .chn-card__label {
  text-transform: none; letter-spacing: 0; font-size: 0.9rem; color: var(--muted);
}
body.studio .chn-quiet { display: flex; align-items: center; gap: 14px; margin-top: 14px; padding: 14px 18px; }
body.studio .chn-quiet span { color: var(--muted); font-size: 0.95rem; }
/* By live: the % is the cell's figure, the "N of M joins" its subline. The
   sublines come off under 640px — column hiding is the roster's answer to
   width, never a sideways scroll.

   The table sits in a bordered card (Maaz 2026-09-10) — `card card--flush`,
   the Delivery log's own wrapper, so the two tables on this tab wear one
   radius and one stroke. The wrapper carries the top margin, so the table
   itself has none: two margins would double the gap under the subtitle. Its
   `overflow: hidden` is what keeps a hovered row's fill inside the corner
   radius, and nothing in these cells has a popover to clip. */
body.studio .chn-bylive-card { margin-top: 12px; }
/* The Delivery log's own table and pager, and both are classes rather than the
   inline styles they were: the header grew a sortable link and an anchored
   filter menu this round, which is more than a `style=` should be carrying. */
body.studio .chn-log-card { margin-top: 20px; }
body.studio .chn-log-pager { margin-top: 10px; }
/* THE PAGER IS A CLUSTER, NOT A SPREAD (Maaz 2026-09-13): Newer, the counted
   range and Older sit together rather than at opposite ends of a 900px row.
   `.chn-bylive-pager__links` went out with the span it dressed — a class named
   after retired markup is how the next surface inherits a box by reaching for
   a familiar name (the `.gl-side` P0). */
body.studio .chn-bylive-pager { margin-top: 12px; }
body.studio .chn-bylive__live a { color: var(--text); font-weight: 600; text-decoration: none; }
body.studio .chn-bylive__live a:hover { color: var(--gold); }
body.studio .chn-bylive__live .small { display: block; margin-top: 2px; }
body.studio .chn-bylive td b { display: block; }
body.studio .chn-bylive__of { display: block; margin-top: 2px; white-space: nowrap; }
@media (max-width: 640px) { body.studio .chn-bylive__of { display: none; } }
/* THE WHO-HEARS-WHAT MATRIX IS GONE FROM THIS PAGE (Maaz 2026-09-13) and so
   are its rules: `.chn-matrix*` and `.chn-soon` were deleted WITH the markup,
   because a class named after retired markup is how the next surface inherits
   a box by reaching for a familiar name (the `.gl-side` P0). Which rails carry
   which send moment is Automations' question now — `.au-*` below. */

/* ── TRANSACTIONS (Maaz 2026-09-09) — the per-charge ledger. Studio-only
   furniture, fenced. The kebab lives in the LAST column so its panel anchors
   right (the dest5-table rule); the row raises itself while a menu is open
   (the lives-row rule) because the house-fade makes shell children stacking
   contexts. Width is answered by breakpoint COLUMN HIDING, never a scroll —
   every hidden fact lives in the row's details sheet. */
/* Transactions' figures are the house `.stat-row .stat` trio since Maaz
   2026-09-13 21:49 (number, then the nature UNDER it — the Lives shape, no
   heading over a metric); `.txn-figs` only adds the gap to the table, because
   `.stat-row`'s own margin is top-only and `.txn-table` carries none. The
   label-over-number strip below is the MONEY page's now (payouts/_board is
   its one host) — do not point new figures at it. */
body.studio .txn-figs { margin-bottom: 24px; }
body.studio .txn-stats { display: flex; gap: clamp(28px, 6vw, 96px); flex-wrap: wrap; margin: 26px 0 24px; }
body.studio .txn-stat .meta { display: block; margin: 0 0 8px; }
body.studio .txn-stat__fig {
  display: block; font-family: var(--sans); font-weight: 500; font-size: 2.1rem;
  --lh: 1.05; line-height: var(--lh-snap); letter-spacing: -0.02em; color: var(--text);
}
body.studio .txn-stat .small { display: block; margin-top: 6px; }
body.studio .txn-table { width: 100%; }
body.studio .txn-table td { vertical-align: middle; }
body.studio .txn-table tr:has(details.menu[open]) { position: relative; z-index: 40; }
body.studio .txn-table .menu__panel { right: 0; left: auto; min-width: 220px; }
body.studio .txn-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.72rem;
  color: var(--dim); display: inline-block; max-width: 128px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom;
}
body.studio .txn-id--full { max-width: none; white-space: normal; word-break: break-all; color: var(--text); }
body.studio .txn-member { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 160px; }
body.studio .txn-bought { max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
body.studio .txn-acts { text-align: right; }
body.studio .txn-sheet { width: min(520px, calc(100vw - 32px)); }
body.studio .txn-sheet__tags { margin: 16px 0 6px; }
body.studio .txn-detail { margin: 14px 0 0; display: grid; gap: 8px; }
body.studio .txn-detail > div { display: grid; grid-template-columns: 110px minmax(0, 1fr); gap: 10px; align-items: baseline; }
body.studio .txn-detail dt { font-size: 0.62rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--dim); }
body.studio .txn-detail dd { margin: 0; overflow-wrap: anywhere; }
body.studio .txn-pager { margin-top: 12px; align-items: center; }
body.studio .txn-pager__links { display: inline-flex; gap: 14px; }
body.studio .txn-zero { margin-top: 4px; padding: 22px; }
/* THE FILTERS SHEET (Maaz 2026-09-10 — his prototype). The button rides the
   header beside the window pills (one cluster, wrapping on a phone); the
   sheet itself is the Followers sheet's own furniture (fol-sheet--filters),
   and the one addition is the prototype's full-width gold primary. */
body.studio .txn-head { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: flex-end; }
body.studio .txn-filters__show { width: 100%; margin-top: 18px; }
/* A dispute is its own status — live-red text on the house hairline, never a
   filled live chip (that is the ON AIR badge's) and never folded into warn. */
body.studio .chip--disputed { color: var(--live); border-color: var(--hair-live); }
@media (max-width: 960px) {
  body.studio .txn-col-cycle { display: none; }
}
@media (max-width: 820px) {
  body.studio .txn-col-contact { display: none; }
}
@media (max-width: 640px) {
  body.studio .txn-stats { gap: 22px 34px; }
  body.studio .txn-col-id { display: none; }
  body.studio .txn-bought { max-width: 120px; }
  body.studio .txn-member { max-width: 100px; }
}

/* ── MONEY (Maaz's 2026-09-12 screenshots) ──────────────────────────────────
   /studio/payouts rebuilt to the mocks: window pills, the three-figure strip
   (the .txn-stat shape — label over number, window-obedient, which is why
   this page carries no header stat row), the Earnings|Withdrawal pills, the
   gold weekly bars, the three earnings cards, the withdrawal card and the
   statement. Fenced body.studio; the strip reuses .txn-stats on purpose (it
   is this page's ALONE since Transactions went Live-shaped, 2026-09-13). */
/* The window pills ride the page header's ACTION slot now (Maaz 2026-09-13 —
   the Transactions layout, next to the title, never under the lede), inside
   their own turbo-frame (#money_windows); .page-head__act's own padding is
   the alignment, so the nav carries no class and no margin of its own. */
/* .menu > summary's own 30px icon box loses everything else to body.studio
   .tab (higher specificity, later in the file) — width is the one property
   the pill never declares, so it is stood down here. */
body.studio .money-range > .menu > summary,
body.studio .money-export > .menu > summary { width: auto; transition: none; }
body.studio .money-range__panel { min-width: 260px; padding: 14px; }
body.studio .money-range__form { display: grid; gap: 12px; justify-items: start; }
/* Stacked, not side by side: the panel stays narrow enough to clamp whole
   inside a phone viewport (menu_controller's anchored fixed positioning). */
body.studio .money-range__fields { display: grid; gap: 10px; width: 100%; }
body.studio .money-range__field { display: grid; gap: 6px; }
/* THE (i) BESIDE YOUR EARNINGS (Maaz 2026-09-13 21:43). `.tip-i[data-tip]`'s
   ::after is a 236px box hung off a 12px glyph — the .pass-stats precedent
   verbatim: the STRIP is the containing block (a width clamp cannot fix where
   the box starts), the glyph goes static, and the pseudo takes its own
   border-box because the global box-sizing reset does not reach a pseudo. */
body.studio .money-figs { margin: 26px 0 0; position: relative; }
body.studio .money-figs .tip-i[data-tip] { position: static; }
body.studio .money-figs .tip-i[data-tip]::after {
  box-sizing: border-box; width: max-content; max-width: 100%; left: 0; right: auto;
}
body.studio .money-figs .txn-stat { max-width: 300px; }
body.studio .money-stat--gold .txn-stat__fig { color: var(--gold); }
body.studio .money-figs__rule { margin: 24px 0 0; }
/* .money-tabs went with the Earnings | Withdrawal rail (Maaz 2026-09-13 —
   one Money address; the chart and the cards below render on Analytics →
   Earnings now, still body.studio). */

/* The chart: net per bucket, value above the bar, the bucket's date below —
   CSS bars, no chart lib (the streak row's own rule). The tallest bar takes
   72% of the box so both labels always fit inside the fixed height. */
body.studio .money-chart {
  display: flex; align-items: flex-end; gap: 20px; height: 220px;
  margin-top: 28px; overflow-x: auto;
}
body.studio .money-chart__col {
  display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
  flex: 1 1 0; min-width: 64px; max-width: 170px; height: 100%;
}
body.studio .money-chart__val { color: var(--muted); font-size: 0.85rem; margin-bottom: 8px; }
body.studio .money-chart__bar {
  display: block; width: 100%; background: var(--gold);
  border-radius: 3px 3px 0 0; min-height: 2px;
}
body.studio .money-chart__label { color: var(--dim); font-size: 0.82rem; margin-top: 10px; }
body.studio .money-chart-zero { margin-top: 28px; }

body.studio .money-card { margin-top: 26px; padding: 22px 24px; }
body.studio .money-card__head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
body.studio .money-card__title { margin: 0; font-size: 1.3rem; }
body.studio .money-card__sub { margin: 6px 0 0; }
body.studio .money-card__total { font-size: 1.7rem; font-weight: 500; letter-spacing: -0.02em; white-space: nowrap; }
body.studio .money-card__zero { margin: 14px 0 0; }
/* Five columns since the 2026-09-13 earnings round (Name · Created · Price ·
   Earned · Transactions), so the floor moved with the column count — a
   460px table sharing itself five ways runs the money into the name. The
   overflow is the marked scroll region's; the DOCUMENT still never scrolls. */
body.studio .money-table { margin-top: 10px; min-width: 560px; }
body.studio .money-table__name { font-weight: 500; }
/* A scroll region somebody asked for by name (the data-hscroll opt-in) — the
   DOCUMENT still never scrolls sideways. */
body.studio .money-scroll[data-hscroll] {
  overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-color: var(--border-strong) transparent;
}

/* Two halves of one card (Maaz 2026-09-13: the bank status "in that card on
   the right"): the amount/partial/Request on the left, the payout account —
   or the way to one — on the right. Wrapping flex, so a phone stacks the bank
   under the form instead of pushing the document sideways. */
body.studio .money-withdraw {
  margin-top: 26px; padding: 24px;
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 24px 48px; flex-wrap: wrap;
}
body.studio .money-withdraw__left { flex: 1 1 320px; min-width: 0; }
body.studio .money-withdraw__bank { flex: 0 1 300px; }
body.studio .money-withdraw__bank .meta { display: block; margin: 0 0 8px; }
body.studio .money-withdraw__bank-said { margin: 0 0 14px; max-width: 32ch; }
body.studio .money-withdraw__bank-name { margin: 0; font-size: 1.15rem; }
body.studio .money-withdraw__bank-name .num { margin-left: 8px; color: var(--muted); }
body.studio .money-withdraw__bank-sub { margin: 6px 0 0; }
body.studio .money-withdraw__amount { display: flex; align-items: baseline; gap: 2px; margin-top: 10px; }
body.studio .money-withdraw__cur,
body.studio .money-withdraw__fig {
  font-size: 2rem; font-weight: 500; letter-spacing: -0.02em;
  --lh: 1.1; line-height: var(--lh-snap);
}
/* The figure IS an input — readonly at rest (the no-JS form still posts a
   working full withdrawal), editable only once Partial is ticked. Width kept
   off 100%: the acct-row lesson — a house `input { width: 100% }` beside
   flex siblings is how a field ends up 44px square. */
body.studio .money-withdraw__input {
  font-size: 2rem; font-weight: 500; letter-spacing: -0.02em; color: var(--text);
  --lh: 1.1; line-height: var(--lh-snap);
  background: transparent; border: 0; border-bottom: 1px solid transparent;
  border-radius: 0; padding: 0; width: min(60vw, 320px);
}
body.studio .money-withdraw__input:not([readonly]) { border-bottom-color: var(--gold); }
body.studio .money-withdraw__input:focus { outline: none; }
body.studio .money-withdraw__partial {
  display: flex; align-items: baseline; gap: 10px; margin-top: 18px; cursor: pointer;
  flex-wrap: wrap;
}
body.studio .money-withdraw__partial input { width: 15px; height: 15px; accent-color: var(--gold); flex: none; align-self: center; }
body.studio .money-withdraw__act { display: flex; align-items: center; gap: 20px; margin-top: 22px; flex-wrap: wrap; }
body.studio .money-withdraw__act form,
body.studio .money-withdraw__bank form { margin: 0; }

body.studio .money-stmt { margin-top: 36px; }
body.studio .money-stmt__head { display: flex; justify-content: space-between; align-items: center; gap: 14px; flex-wrap: wrap; }
body.studio .money-stmt__title { margin: 0; font-size: 1.3rem; }
body.studio .money-stmt__controls { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
body.studio .money-stmt__controls .tabs { margin-top: 0; }
body.studio .money-export__panel { min-width: 190px; }
body.studio .money-stmt__table { margin-top: 14px; min-width: 660px; }
body.studio .money-stmt__table td { vertical-align: middle; }
body.studio .money-stmt__date { white-space: nowrap; }
body.studio .money-stmt__what { display: block; }
body.studio .money-stmt__sub { display: flex; gap: 12px; margin-top: 4px; align-items: baseline; flex-wrap: wrap; }
/* The IN column is gold in the mock — money that came in. */
body.studio .money-stmt__in { color: var(--gold); }
body.studio .money-stmt__acts { text-align: right; }
/* ONE COPY CONTROL PER VALUE, LEFT OF THE TEXT (Maaz 2026-09-13 21:43): the
   id and the email are plain text again and each carries a small glyph button
   before it. The button's label span is EMPTY at rest — share_controller
   writes "Copied" into it for a beat and empties it again — and :empty is
   what keeps the flex gap from holding a slot for a word that isn't there
   (the .lobby__side rule at 4px scale). */
body.studio .money-stmt__val { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
body.studio .money-copy {
  background: none; border: 0; padding: 0; color: var(--dim); cursor: pointer;
  font-size: inherit; font-family: inherit;
  display: inline-flex; align-items: center; gap: 4px; flex: none;
}
body.studio .money-copy:hover, body.studio .money-copy:focus-visible { color: var(--text); }
body.studio .money-copy__said:empty { display: none; }
body.studio .money-receipt { width: min(480px, calc(100vw - 32px)); }
body.studio .money-stmt__pager { margin-top: 12px; align-items: center; }
body.studio .money-stmt__zero { margin-top: 16px; }
@media (max-width: 640px) {
  body.studio .money-figs { gap: 22px 34px; }
  body.studio .money-chart { gap: 12px; }
}

/* ── THE FOLLOWER PROFILE (Maaz 2026-09-11) ─────────────────────────────────
   /studio/audience/fans/:id, rebuilt to his mock: hero · stat row · six
   metric cards · badges, open rate and tags · the purchase ledger · recent
   activity beside the one note. All fenced body.studio — every host is a
   studio page (studio_scope_law's law 1). */
body.studio .fan-hero { display: flex; align-items: flex-start; gap: 22px;
  margin-top: 18px; flex-wrap: wrap; }
/* THE GOLD RING is this page's, not the house's: `.ring` is --border-strong
   everywhere else and the mock lights this one, because it is the page's
   subject rather than a 34px marker in a row. */
body.studio .fan-hero > .ring { border-color: var(--hair-gold); flex: none; }
body.studio .fan-hero__who { flex: 1 1 300px; min-width: 0; }
body.studio .fan-hero__name { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
body.studio .fan-hero__name h1 { margin: 0; overflow-wrap: anywhere; }
/* The access chip's text is BOUNDED (Follower, Revoked, "1p · 2t · 1d"), so
   `.chip`'s own `overflow-wrap: anywhere` has nothing to protect here and
   would break the shortest word it says onto two lines (the r4 rule, beside
   a heading rather than in a cell). */
body.studio .fan-chip { white-space: nowrap; overflow-wrap: normal; }
body.studio .fan-hero__country { margin: 8px 0 0; display: flex; align-items: center;
  gap: 8px; color: var(--text); }
/* The flag is a font glyph, so it is sized in its own terms and never
   line-height'd into the sentence beside it. */
body.studio .fan-flag { font-size: 1.15rem; line-height: 1; }
body.studio .fan-hero__meta { margin: 6px 0 0; color: var(--muted); }
body.studio .fan-hero__meta b { color: var(--text); font-weight: 600; }
body.studio .fan-dot { margin: 0 8px; color: var(--dim); }
body.studio .fan-hero__export { flex: none; }

/* A REGION LABEL, NOT THE HOUSE EYEBROW. `.eyebrow` is (0,1,0) and
   `body.studio h1, body.studio h2` is (0,1,1), so a heading carrying the
   eyebrow class inside the studio silently renders as a 20px display heading
   in mixed case (MEASURED: 20px, text-transform none) — which is why every
   other studio surface puts that class on a <span> or an <h4>. These are
   headings for real regions, and the mock draws them muted rather than gold,
   so they take their own class at the weight that beats the reset. */
body.studio .fan-label, body.studio .fan-metric__label { font-family: var(--sans);
  font-size: 0.62rem; font-weight: 600; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--dim); }
body.studio .fan-label { margin: 0 0 10px; }

/* THE STAT ROW — FIVE CELLS, ONE ROW at desk width, on one hairline-divided
   rail. auto-fit cannot do it: Contact needs the width of two, and `span 2`
   then asks for six tracks out of five, which wraps Reachable on onto a line
   of its own (measured). Explicit fractions instead, reflowing on a laptop
   and stacking on a phone at the two breakpoints below — and Reachable on is
   the widest of the four narrow cells because the ladder line under its
   icons is a SENTENCE, which wraps in every state and should not also break
   a date in half. */
body.studio .fan-facts { margin-top: 20px; padding: 0;
  display: grid; grid-template-columns: 1.6fr 0.85fr 1fr 1.1fr 1.2fr; }
body.studio .fan-fact { padding: 18px 22px; border-left: var(--hair-w) solid var(--border); min-width: 0; }
body.studio .fan-fact:first-child { border-left: 0; }
body.studio .fan-fact__line { margin: 0; display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
body.studio .fan-fact__glyph, body.studio .fan-contact__glyph { color: var(--muted); align-self: center; }
body.studio .fan-fact__sub { flex-basis: 100%; padding-left: 25px; font-size: 0.8rem; }
body.studio .fan-contact { margin: 0 0 8px; display: flex; align-items: center; gap: 10px; min-width: 0; }
body.studio .fan-contact:last-child { margin-bottom: 0; }
body.studio .fan-trunc { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
body.studio .fan-reach { margin: 0; display: inline-flex; gap: 10px; }
body.studio .fan-reach > span { display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 10px; color: var(--dim);
  border: var(--hair-w) solid var(--border); }
/* `body.studio .fan-reach > span` is (0,2,2) and a bare `.fan-reach--on`
   would be (0,2,1), so the lit state LOSES and every icon paints dim —
   measured, with the class correctly on the element. Read the browser, never
   the rule that was supposed to win. */
body.studio .fan-reach > span.fan-reach--on { color: var(--text); border-color: var(--border-strong); }
/* The ladder line sits under the icons rather than beside them, and takes no
   left indent — there is no glyph on its row to line up with. */
body.studio .fan-ladder { margin-top: 10px; padding-left: 0; }

/* THE SIX FIGURES. Gold is money's colour and only money's, so exactly one
   of them wears it. */
body.studio .fan-metrics { margin-top: 14px; display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); }
body.studio .fan-metric { padding: 18px; border: var(--hair-w) solid var(--border);
  border-radius: var(--radius); background: var(--surface); min-width: 0; }
body.studio .fan-metric__top { display: flex; align-items: center;
  justify-content: space-between; gap: 10px; }
body.studio .fan-metric__value { font-size: 1.7rem; font-weight: 650;
  letter-spacing: -0.01em; --lh: 1.1; line-height: var(--lh-snap); }
body.studio .fan-metric__label { display: block; margin-top: 8px; }
body.studio .fan-metric__mark { display: inline-flex; align-items: center; justify-content: center;
  flex: none; width: 32px; height: 32px; border-radius: 50%; color: var(--gold);
  border: var(--hair-w) solid var(--hair-gold); }

body.studio .fan-meta { margin-top: 12px; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
body.studio .fan-meta__cell { padding: 18px 22px; border-left: var(--hair-w) solid var(--border); min-width: 0; }
body.studio .fan-meta__cell:first-child { border-left: 0; }
body.studio .fan-meta__cell p { margin: 0; }
body.studio .fan-meta__figure { font-size: 1.2rem; font-weight: 600; }
body.studio .fan-badges { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
body.studio .fan-tags { gap: 6px; }

/* THE LEDGER. `card--flush` is the Delivery log's own wrapper, so the table
   wears one radius and one stroke and the head sits inside it. */
body.studio .fan-ledger { margin-top: 18px; }
body.studio .fan-ledger__head { display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; padding: 18px 22px; flex-wrap: wrap; }
body.studio .fan-card__h { margin: 0; font-size: 1rem; font-weight: 600;
  display: inline-flex; align-items: center; gap: 9px; }
body.studio .fan-card__glyph { color: var(--gold); }
body.studio .fan-ledger__table { width: 100%; }
body.studio .fan-item { display: inline-flex; align-items: center; gap: 11px; min-width: 0; }
body.studio .fan-item__mark { display: inline-flex; align-items: center; justify-content: center;
  flex: none; width: 32px; height: 32px; border-radius: 9px; color: var(--muted);
  border: var(--hair-w) solid var(--border); }
body.studio .fan-item__name { display: block; font-weight: 600; overflow-wrap: anywhere; }
body.studio .fan-item__sub { display: block; font-size: 0.78rem; }
body.studio .fan-ledger__more { padding: 4px 22px 18px; }
body.studio .fan-ledger__more button[hidden] { display: none; }
body.studio .fan-ledger__zero, body.studio .fan-ledger__gifts { margin: 0; padding: 0 22px 18px; }
body.studio .fan-ledger__table ~ .fan-ledger__gifts { padding-top: 14px; }
/* WITH NO JAVASCRIPT THE WHOLE HISTORY IS THE PAGE. The rows past four are
   drawn hidden and the "+ N more" button is drawn hidden too, so a reader
   whose controller never connects gets every row and no control — never four
   rows under a button that cannot do anything. The reveal is the ledger's own
   <noscript><style> in _purchases.html.erb (the Filters sheet's idiom),
   because a rule written only in markup can only ever be in one of the two
   states — there is deliberately nothing to declare here. */

body.studio .fan-bottom { margin-top: 12px; display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); align-items: start; }
body.studio .fan-recent__list { list-style: none; margin: 16px 0 0; padding: 0; display: grid; gap: 14px; }
body.studio .fan-recent__list li { display: flex; align-items: flex-start; gap: 12px; }
body.studio .fan-recent__mark { display: inline-flex; align-items: center; justify-content: center;
  flex: none; width: 32px; height: 32px; border-radius: 9px; color: var(--gold);
  border: var(--hair-w) solid var(--hair-gold); }
body.studio .fan-recent__sub { display: block; margin-top: 2px; font-size: 0.8rem; }
body.studio .fan-note__head { align-items: center; gap: 12px; }
body.studio .fan-note__save { flex: none; }
body.studio .fan-note__form { margin-top: 14px; }
body.studio .fan-note textarea { width: 100%; }
body.studio .fan-note__foot { margin-top: 10px; }

@media (max-width: 1120px) {
  body.studio .fan-facts { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
}
@media (max-width: 720px) {
  body.studio .fan-facts { grid-template-columns: 1fr; }
  body.studio .fan-fact { border-left: 0; border-top: var(--hair-w) solid var(--border); }
  body.studio .fan-fact:first-child { border-top: 0; }
  body.studio .fan-meta__cell { border-left: 0; border-top: var(--hair-w) solid var(--border); }
  body.studio .fan-meta__cell:first-child { border-top: 0; }
}

/* ── AUTOMATIONS — EVERY SEND MOMENT (Maaz 2026-09-13) ──────────────────────
   Fenced `body.studio`: `.au-*` is this screen's own furniture and no other
   screen wears it (the scope fence's law 2 works both ways — a fan view
   wearing a fenced class renders bare, and an unfenced one becomes house
   vocabulary nobody meant to publish). It reuses the declared vocabulary on
   purpose — `.card`, `.switch` (whose gold IS `[aria-checked="true"]` on a
   `button_to` submit, the destinations board's own idiom), `.dim`, `.small`,
   `.num` — because two near-identical blocks is how two screens start
   disagreeing about which element carries a stroke (the r8 hazard).

   ONE LIST, TWO SWITCHES (Maaz's QA of the shipped page, 2026-09-13). It was
   two cards with two grids and an `--au-cells` custom property, because the
   moments card carried a third On column; both went — the page is ten rows in
   one card, and both rails off IS off, so the grid is fixed at two switch
   columns and there is nothing left to parameterise. */
body.studio .au-card { margin-bottom: 18px; padding: 6px 20px; }
body.studio .au-rows { display: block; }
body.studio .au-row {
  display: grid; grid-template-columns: minmax(0, 1fr) repeat(2, 60px);
  align-items: center; gap: 10px; padding: 14px 0;
  border-top: var(--hair-w) solid var(--border);
  /* THE ROW IS THE CONTAINING BLOCK FOR A LOCKED SWITCH'S TIP, not the cell.
     `[data-tip]::after` is `position: absolute; left: 50%; white-space: nowrap`
     against its OWN element — a ~300px sentence on a 50px control at the RIGHT
     EDGE of the card, which inflates the cell's scrollWidth, then the row's,
     then the card's, then the document's (the post-tray class of bug: measured
     horizontal scroll with no element's bounding box outside the viewport, and
     a rect-only probe cannot see it). Clamping a width cannot fix WHERE the
     control landed, so the block moves: the row goes relative, the cell goes
     static, and the tip resolves against the row — which is inside the card by
     construction at any width — anchored RIGHT and wrapping. */
  position: relative;
}
/* The header row is the one that carries no rule: the card's own head is
   directly above it. */
body.studio .au-row--head {
  border-top: 0; padding: 4px 0 2px; color: var(--dim);
  font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase;
}
body.studio .au-row--head span { text-align: center; }
body.studio .au-row--head span:first-child { text-align: left; }
body.studio .au-row__label { display: block; min-width: 0; }
body.studio .au-row__label b { display: block; font-weight: 500; }
body.studio .au-row__label > span { display: block; margin-top: 3px; }
body.studio .au-row__cell { display: flex; justify-content: center; }
body.studio .au-row__cell form { display: inline-flex; }
/* THE HEAD SAYS THE LOCK ONCE (Maaz 2026-09-14, his mock): a lock and his own
   sentence in the cell that used to be an empty gutter beside EMAIL / PUSH. It
   gives that row's treatment back — a tracked, uppercased sentence is a
   heading, and this is a note — and it is `--muted` rather than the row's own
   `--dim`, whose 45% alpha carries a documented contrast ceiling that a column
   word can live with and a sentence cannot. `inline-flex`, because a
   baseline-aligned SVG overhangs its line box by the font's descender (r8). */
body.studio .au-head__note {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--muted); font-size: 0.8rem;
  text-transform: none; letter-spacing: 0;
}
/* A LOCKED MOMENT IS DRAWN ON AND CANNOT MOVE (Maaz 2026-09-13; his mock of
   2026-09-14): the gold the switch already wears, with a lock centered on it.
   The pill itself is `.switch--locked`, declared with the switch's own
   vocabulary above — what lives here is the TIP.

   AND THE TIP HANGS ON THE CELL, NOT ON THE PILL, WHICH IS A FIX AND NOT A
   TIDY. `[data-tip]::after` and `.switch::after` are THE SAME PSEUDO, and the
   switch claims it as the thumb 478 lines later at equal specificity — so a
   `data-tip` on a `.switch` loses `content` to `content: ""` and paints an
   empty box wearing the thumb's fill (the dark oval in his screenshot), which
   is why this sentence had never once appeared. A control with its own
   `::after` cannot host this tip; the wrapper does (`.cta-gate` / `.tip-i`,
   the same idiom). `position: static` keeps the ROW as the containing block:
   a sentence-length box on a 50px control at the card's right edge is the
   post-tray horizontal-overflow class, and clamping a width cannot fix where
   the control landed. */
body.studio .au-row__cell[data-tip] { position: static; }
body.studio .au-row__cell[data-tip]::after {
  left: auto; right: 0; transform: translateY(2px); width: max-content; max-width: 100%;
  white-space: normal; text-transform: none; letter-spacing: 0; }
body.studio .au-row__cell[data-tip]:hover::after,
body.studio .au-row__cell[data-tip]:focus-visible::after { transform: translateY(0); }
@media (max-width: 560px) {
  /* The switch narrows and its knob's travel narrows with it (the ::after
     transform is the 50px control's own 22px), so two columns plus a readable
     label fit a 390px shell. */
  body.studio .au-row { grid-template-columns: minmax(0, 1fr) repeat(2, 46px); gap: 6px; }
  body.studio .au-row .switch { width: 46px; }
  body.studio .au-row .switch[aria-checked="true"]::after { transform: translateX(18px); }
  body.studio .au-row--head { font-size: 0.62rem; letter-spacing: 0; text-transform: none; }
}

/* THE WEBHOOK EVENT CHIPS BORROW THE RAIL'S TWO STATES, NOT THE CHIP'S FILL
   (Maaz 2026-09-14: "every moment chip is yellow and it overwhelms the page").
   Every event on that form is CHECKED by default, so `.chip--input`'s lit
   state painted thirteen tungsten blocks and the card became the loudest thing
   on the screen. The pill rail one section up already has the two states this
   control needs — a quiet stroke at rest, a GOLD STROKE over the same dark
   fill when it is on — so the checked chip takes `.tab--on`'s border and ink
   and nothing else. No third pill was invented: the resting state is the base
   `.chip`'s own stroke, which is already the rail's.

   THE BASE `.chip--input` FILL IS LEFT EXACTLY AS IT IS. It has seven other
   hosts — the fan's own notification dials among them, where "checked
   preference chips are LIT, never gold" is the law (A16.4) — so restyling it
   here would restyle all of them, which is the `.tab` hazard facing the other
   way. Scoped to this card's own class for the same reason. */
body.studio .wh-event:has(input:checked) {
  color: var(--text); background: transparent; border-color: var(--gold);
}

/* ---------- Plan (Maaz's three mocks, 2026-09-13) ------------------------
   Free / Starter $97 / Pro $297. Three cards, the middle one ringed and
   badged, a three-cell status strip above them, invoices below. Every colour
   is a house token: the mocks are drawn on a COOL near-black and this studio
   is warm, so the values below are the mock's SHAPE with the mood's own ink —
   repainting the mood for one page is how a studio ends up with two greys. */
body.studio .plan-page { max-width: 1080px; }

/* THE DUNNING BANNER (mock 3). Full width above the rail, drawn by the layout
   from `content_for(:studio_banner)` — the acting bar's own row and its own
   geometry, in live-red because a declined card is the one thing on this page
   that is going wrong. Left-aligned, unlike the acting bar: the mock starts
   its sentence on the page's own gutter. */
body.studio .studio-fail {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 11px 20px; font-size: 0.9rem;
  color: var(--live);
  background: color-mix(in srgb, var(--live) 12%, var(--bg));
  border-bottom: var(--hair-w) solid var(--hair-live);
}
body.studio .studio-fail__link {
  color: var(--live); font-weight: 600;
  text-decoration: underline; text-underline-offset: 3px;
}
body.studio .studio-fail__link:hover { color: var(--text); }

/* Status · Renews · Payment method. An OUTLINE card with no fill — measured
   off both mocks, where the strip reads as the page with a hairline round it
   and the cards below it are the filled things. */
body.studio .plan-status {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px; margin-top: 22px; padding: 18px 20px;
  border: var(--hair-w) solid var(--border); border-radius: 14px;
}
/* Each cell is a grid item, so `min-width: 0` is what stops a long value (a
   renewal date, a payment-method sentence) widening its own track and
   squeezing the two beside it. */
body.studio .plan-status__cell { min-width: 0; }
body.studio .plan-status__label {
  display: block; color: var(--muted); font-size: 0.82rem; margin-bottom: 10px;
}
body.studio .plan-status__value { color: var(--text); }
/* An integer height on a fully-round stroked pill (r8/r9): an even 28 in an
   odd row is a half pixel, and an arc cannot be snapped. */
body.studio .plan-status__pill {
  display: inline-flex; align-items: center; height: 32px; padding: 0 14px;
  border: var(--hair-w) solid var(--hair-gold); border-radius: 999px;
  color: var(--gold); font-size: 0.88rem; font-weight: 600;
}
body.studio .plan-status__pill--fail {
  border-color: var(--hair-live); color: var(--live);
}

body.studio .plan-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 18px; margin-top: 22px;
}
/* The cards stretch to one height (the grid's default `stretch`, which mock 2
   shows: three cards, one bottom edge) and each CTA still sits directly under
   its OWN list — never pushed to the card's foot with `margin-top: auto`,
   which is what the mocks do NOT draw (Free's "Current plan" is high, Pro's
   "Choose plan" is low, because Pro has four more lines). */
body.studio .plan-card {
  position: relative; display: flex; flex-direction: column;
  padding: 24px 22px 26px; border: var(--hair-w) solid var(--border);
  border-radius: 16px; background: var(--surface);
}
body.studio .plan-card--featured { border-color: var(--hair-gold); }
/* The badge STRADDLES the ringed card's top border and is filled with the
   PAGE, not the card — which is what makes the gold ring appear to break for
   it (measured off both mocks: an outline pill, gold ink, page-coloured
   fill — never a filled gold block). */
body.studio .plan-card__badge {
  position: absolute; top: -13px; left: 50%; transform: translateX(-50%);
  display: inline-flex; align-items: center; height: 26px; padding: 0 12px;
  border: var(--hair-w) solid var(--hair-gold); border-radius: 999px;
  background: var(--bg); color: var(--gold);
  font-size: 0.78rem; white-space: nowrap;
}
body.studio .plan-card__top {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
}
body.studio .plan-card__name { margin: 0; font-size: 1.4rem; font-weight: 650; }
body.studio .plan-card__price { margin: 0; font-size: 1.4rem; font-weight: 650; color: var(--text); }
body.studio .plan-card__per { font-size: 0.78rem; font-weight: 500; color: var(--muted); }
body.studio .plan-card__list {
  list-style: none; margin: 20px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 12px;
}
/* The tick is its own flex item, so a two-line row hangs under its own first
   line instead of under the glyph. `.lucide` already takes no pointer events
   house-wide. */
body.studio .plan-card__list li {
  display: flex; align-items: flex-start; gap: 10px;
  color: var(--muted); font-size: 0.94rem; line-height: 1.4;
}
body.studio .plan-card__tick { flex: none; color: var(--gold); margin-top: 0.18em; }
/* The six marks sit on their own line under the row's words (the mocks' own
   shape), and `inline-flex` keeps a baseline-aligned SVG from overhanging its
   line box by the font's descender (r8). */
body.studio .plan-card__socials {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin-top: 8px; color: var(--text);
}
body.studio .plan-card__socials .brand { display: inline-flex; }
body.studio .plan-card__cta {
  margin-top: 22px; display: flex; flex-direction: column;
  align-items: flex-start; gap: 10px;
}
body.studio .plan-card__form { display: contents; }
/* THE MOCK'S BUTTON VOICE, ON THE HOUSE'S GOLD. `.btn--gold` keeps the one
   gold recipe (fill, hover, focus, disabled) and only the typography is this
   page's: the mocks draw sentence case at reading size where the house button
   is small-caps and letterspaced. Content width, left-aligned — never 100%,
   which the mocks do not draw. */
body.studio .plan-card__btn {
  padding: 0 22px; height: 44px; border-radius: 12px;
  font-size: 1rem; font-weight: 700; letter-spacing: 0;
  font-variant-caps: normal; text-transform: none;
}
body.studio .plan-card__current {
  display: inline-flex; align-items: center; justify-content: center;
  height: 40px; padding: 0 18px; border-radius: 999px;
  border: var(--hair-w) solid var(--border-strong); color: var(--text);
  font-size: 0.95rem;
}

body.studio .plan-invoices { margin-top: 44px; }
body.studio .plan-invoices h2 { margin: 0 0 10px; font-size: 1.25rem; }
body.studio .plan-invoices p { margin: 0 0 8px; }
body.studio .plan-invoices__rows { padding: 0 20px; }
body.studio .plan-invoices__what { flex: 1; min-width: 0; }
body.studio .plan-invoices__what .dim { margin-left: 8px; }

/* The support card and the grandfathered rung's own card. The Enterprise
   card's and the archive dial's own classes left this sheet WITH their markup
   on 2026-09-14 (the `.gl-side` P0: a class named after a retired block is
   how a new surface inherits a box by reaching for a familiar name) — the
   support card is plan-help, which is what it IS rather than what the box it
   replaced used to sell. */
body.studio .plan-help, body.studio .plan-legacy { margin-top: 16px; }
body.studio .plan-help h3 { margin: 0; }
body.studio .plan-help p { margin: 6px 0 0; }
body.studio .plan-legacy {
  display: flex; justify-content: space-between; align-items: center; gap: 16px;
  padding: 18px 20px; border: var(--hair-w) solid var(--border); border-radius: 16px;
}
body.studio .plan-legacy h3 { margin: 0; }
body.studio .plan-legacy__act { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }

@media (max-width: 900px) {
  body.studio .plan-grid, body.studio .plan-status { grid-template-columns: 1fr; }
  body.studio .plan-card__badge { left: 22px; transform: none; }
}

/* ───────────────────────── THE FAN SHELL (FOLLOWER-DISCOVER 2026-09-14) ────
   Fan chrome, deliberately UNFENCED (studio_scope_law's law 2: a fan surface
   wearing a fenced class renders bare). New names on purpose — `.fan-card`
   and `.fan-hero` are TAKEN, fenced studio furniture on the follower profile,
   so the proto's card names were not copied. The rail is the left column on
   every signed-in fan page; under 1100px it becomes the bottom bar + sheets
   (never a missing list). */
.fan-frame { display: grid; grid-template-columns: 248px minmax(0, 1fr); align-items: start; }
.fan-main { min-width: 0; }
/* grid-row is pinned on the RAIL and on the narrow toggle and nothing else
   (the studio's own r8 lesson): the toggle shares the rail's cell so it can sit
   on the rail's own hairline, and a definitely-placed item is laid out first —
   so without a row on the rail, auto-placement finds column 1 row 1 taken and
   drops the rail under a 28px button. `.fan-main` stays UNPINNED: it
   auto-places into column 2 row 1 on a desk, and below 1100px the frame is a
   block and the bottom bar is fixed. */
/* THE DIVIDER RUNS TO THE BOTTOM OF THE VIEWPORT (Maaz 2026-09-15, item 10:
   "the rule stops after the last name"). It is a `border-right` on a column
   whose height was its CONTENT's, so a fan following two people got a 120px
   stub of a line and then nothing — the rail read as a floating card rather
   than as one side of the page. `min-height` against the same 58px the sticky
   offset already uses (ONE number, so the top and the bottom can never
   disagree), which pairs with the `max-height` to hold the box at exactly the
   viewport's height: a short list leaves it full and unscrolled, a long one
   still scrolls inside itself. */
.fan-rail {
  grid-column: 1; grid-row: 1;
  position: sticky; top: 58px;
  min-height: calc(100vh - 58px); max-height: calc(100vh - 58px); overflow-y: auto;
  display: flex; flex-direction: column; gap: 26px;
  padding: 22px 14px 32px; border-right: 1px solid var(--border);
}
/* THE NARROW TOGGLE IS THE STUDIO'S (QA 2026-09-14, item 1) — same `‹‹`, same
   28px box, same place: half over the rail's divider, sticky at the rail's own
   offset so the two never drift apart on scroll. It is NOT fenced, because
   this one is drawn on a fan surface (scope law 2). */
.fan-rail__narrow {
  grid-column: 1; grid-row: 1; justify-self: end; align-self: start; z-index: 1;
  position: sticky; top: 78px; margin: 20px -14px 0 0;
  width: 28px; height: 28px; display: inline-flex;
  align-items: center; justify-content: center; cursor: pointer;
  background: var(--bg); color: var(--dim); font: inherit;
  line-height: var(--lh-snap); font-size: 0.8rem;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.fan-rail__narrow:hover { color: var(--text); border-color: var(--text); }
.fan-rail__narrow:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Each group is the column its rows stand in — the rail's own 26px gap is what
   separates one from the next. It shipped as a class with no rule at all (the
   #415 shape, caught by `fan_lobby_css_declared_test` on the way in); this is
   what it was doing implicitly, plus the `min-width: 0` a flex child needs for
   the ellipsis on `.fan-rail__who` to ever resolve. */
.fan-rail__group { display: flex; flex-direction: column; min-width: 0; }
.fan-rail__label {
  margin: 0 8px 6px; font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--dim);
}
/* HOME'S MARK SITS WHERE AN AVATAR WOULD (items 6/8): the same 44px box a
   `.ring` + `.avatar--sm` occupies, so Home and the names under it share one
   left edge — and so the collapsed rail centres the glyph on exactly the
   column its own 72px was measured for. */
.fan-rail__glyph {
  flex: none; width: 44px; height: 44px; display: grid; place-items: center;
  color: var(--muted);
}
.fan-rail__home:hover .fan-rail__glyph { color: var(--text); }
/* THE SELECTED ROW IS TUNGSTEN, NEVER GOLD (his own line: "subtle selected,
   tungsten fill like the identity switch — not gold money"). The fan side is
   the DOOR mood, where `--accent` IS the brand gold (#e0b669), so reading the
   token here would paint the house's money colour behind a navigation row. The
   identity menu already answers this by PINNING the token on its own panel
   (`body.door .menu__panel`); this is the same pin on one row, so the fill and
   the ink stay one declaration of one colour and the shape stays the menu's
   (8% fill, ink, no trailing check — #655). */
.fan-rail__home[aria-current] {
  --accent: #ffd9a3;
  color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.fan-rail__home[aria-current] .fan-rail__glyph,
.fan-rail__home[aria-current]:hover .fan-rail__glyph { color: var(--accent); }
.fan-rail__home[aria-current]:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); }
/* A RULE UNDER HOME, not a gap: the rail's own 26px already separates the
   groups, and his note asks for the line. It is the rail's own divider weight
   — 1px opaque, never `--hair-w`, which is spent on round strokes (r9) — and
   it bleeds to the rail's padding edges so it reads as a rule across the
   column rather than as a box's top border. */
.fan-rail__rule {
  height: 1px; flex: none; margin: -14px -14px -12px;
  background: var(--border);
}
.fan-rail__row {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 8px; border-radius: 8px; color: var(--text);
}
.fan-rail__row:hover { background: var(--surface); }
.fan-rail__row .ring { flex: none; }
.fan-rail__who {
  flex: 1; min-width: 0; font-size: 0.9rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fan-rail__next { display: block; font-size: 0.76rem; color: var(--dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fan-rail__live {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.04em; color: var(--live);
}
.fan-rail__see { display: block; padding: 6px 8px; font-size: 0.84rem; color: var(--muted); }
.fan-rail__see:hover { color: var(--text); }
.fan-rail__none { margin: 0 8px 4px; }
.fan-rail__cover {
  flex: none; width: 38px; height: 26px; border-radius: 5px; overflow: hidden;
  display: grid; place-items: center;
  background: var(--surface-2); border: 1px solid var(--border);
}
.fan-rail__cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fan-rail__preset { width: 100%; height: 100%; }
.fan-rail__preset .pass-cover__title { display: none; }
.fan-rail__mark { font-size: 0.8rem; font-weight: 700; color: var(--accent); }

/* Collapsed: the rail narrows to its marks; the words and the ways-out hide.
   Each hidden piece gets its own rule — no [hidden] involved, a class is the
   state here. */
/* 72px, MEASURED: an `.avatar--sm` inside `.ring` is 34 + 6 of padding + 4 of
   border = 44px, so a 68px column with 14px of rail padding and 8px of row
   padding either side asked for 88 and the mark ran into the divider (his
   screenshot 2). The row centres and drops its own inline padding — the
   studio's collapsed idiom — so 72 − 16 leaves the 44px mark 6px clear inside
   the rail's own 8px, i.e. 14px off the rule. */
.fan-frame--collapsed { grid-template-columns: 72px minmax(0, 1fr); }
.fan-frame--collapsed .fan-rail { padding-inline: 8px; }
/* The rule follows the narrower padding (it bleeds to the rail's edges). */
.fan-frame--collapsed .fan-rail__rule { margin-inline: -8px; }
.fan-frame--collapsed .fan-rail__row { justify-content: center; padding-inline: 0; }
.fan-frame--collapsed .fan-rail__who,
.fan-frame--collapsed .fan-rail__next,
.fan-frame--collapsed .fan-rail__label,
.fan-frame--collapsed .fan-rail__see,
.fan-frame--collapsed .fan-rail__live,
.fan-frame--collapsed .fan-rail__none { display: none; }
.fan-frame--collapsed .fan-rail__narrow span[aria-hidden] { transform: rotate(180deg); }

/* The phone shape: bar + sheets. `[hidden]` must keep winning over the panel's
   own display (the r14 trap — a rule that hands an element a display outranks
   the UA's [hidden]). */
.fan-sheet { display: none; }
.fan-sheet__panel[hidden] { display: none; }
@media (max-width: 1100px) {
  .fan-frame, .fan-frame--collapsed { display: block; }
  .fan-rail { display: none; }
  /* A narrowed rail is a desk decision, not a phone one (the studio's own
     rule): below this breakpoint the rail is a sheet, it always carries its
     words, and there is no divider for the toggle to sit on. */
  .fan-rail__narrow { display: none; }
  .fan-main { padding-bottom: 64px; }
  .fan-sheet { display: block; position: fixed; left: 0; right: 0; bottom: 0; z-index: 39; }
  .fan-sheet__bar {
    display: flex; border-top: 1px solid var(--border); background: var(--bg);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .fan-sheet__tab {
    flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    padding: 13px 10px; border: 0; background: none; color: var(--muted);
    font: inherit; font-size: 0.86rem; cursor: pointer;
  }
  .fan-sheet__tab[aria-expanded="true"] { color: var(--text); }
  .fan-sheet__panel {
    position: fixed; left: 0; right: 0; bottom: calc(48px + env(safe-area-inset-bottom));
    max-height: 60vh; overflow-y: auto; background: var(--bg);
    border-top: 1px solid var(--border); padding: 16px 16px 10px;
  }
}

/* Discover's section furniture + the mixed cards' follow mark. */
.fan-sec__empty { margin: 6px 0 0; color: var(--muted); }
.foll { font-size: 0.8rem; color: var(--muted); white-space: nowrap; }

/* THE FOLLOW PILL, THE HEIGHT OF THE NAME IT SITS BESIDE (Maaz 2026-09-15,
   item 1: "Current FOLLOW chip is bigger than the creator name. Wrong.").

   It was `btn btn--ghost btn--compact`, and the loudest part of that was not
   the padding: `.btn` carries `font-variant-caps: all-small-caps` with 0.1em
   of tracking, so the one word on this control rendered as FOLLOW — a second
   primary button beside a 0.82rem name. This is its own control, not a `.btn`
   variant, so no rule further down the sheet can hand it a `.btn`'s box again.

   `font: inherit` resets font-variant to normal on its own; `font-variant-caps`
   and `text-transform` are restated anyway, because the whole point of this
   class is that the word keeps the case it was written in and a shorthand
   somebody edits later must not quietly take it away.

   `var(--hair-w)` for the stroke: this is a fully-round pill, and a one-device-
   pixel stroke on an arc loses half its weight where it turns (r9). ✓ Following
   stays as it is — plain text, `.foll` above — which is his own line. */
.foll-btn, .up-card__act {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 3px 12px; border: var(--hair-w) solid var(--border-strong);
  border-radius: 999px; background: transparent; cursor: pointer;
  font: inherit; font-size: 0.78rem; font-weight: 600;
  font-variant-caps: normal; text-transform: none; letter-spacing: 0;
  --lh: 1.35; line-height: var(--lh-snap);
  color: var(--muted); white-space: nowrap;
  transition: color 150ms ease, border-color 150ms ease;
}
.foll-btn:hover { color: var(--text); border-color: var(--text); }
.foll-btn:focus-visible, .up-card__act:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* THE UPCOMING CARD'S ONE ACT — the same pill, one size up, in gold (Maaz
   2026-09-15 r3, item 3: "compact gold pill beside the name"). It shares
   `.foll-btn`'s box above because it stands in the slot `.foll-btn` used to
   hold, and two recipes for one shape is how the next edit moves one of them.

   THE WASH IS MEASURED OFF HIS OWN MOCK: the pill's fill reads (31,28,23) on
   a (10,10,11) canvas, which is #e0b669 at 0.10 — the same tint
   `.follow-menu > summary:hover` already carries, and the OUTLINE gold rather
   than `.btn--gold`'s solid block (that one is the studio's, fenced behind
   `body.studio`, and would render bare on a fan card).

   NO GLYPH ON EITHER ACT. The mock draws a calendar on Count me in and a lock
   on Unlock; his copy block strikes both by name. */
.up-card__act {
  padding: 7px 16px; font-size: 0.82rem;
  color: var(--gold); border-color: var(--hair-gold);
  background: color-mix(in srgb, var(--gold) 10%, transparent);
}
.up-card__act:hover { color: var(--gold-bright); border-color: var(--gold); }
/* A HELD SEAT IS A FACT, SO IT IS NOT A PILL (screenshot 4): the check and
   the words in the same muted ink as the creator's name beside it. Nothing
   here is tappable, so nothing here looks it — `.lobby__in`'s own ruling,
   which dropped `.btn` from "You're in" for exactly this reason. */
.up-card__said { font-size: 0.8rem; color: var(--muted); white-space: nowrap; }
/* WHO THIS IS, WITHOUT LEAVING THE WALL (Maaz 2026-09-15, item 4): hovering a
   card's creator name opens the profile header in miniature.

   IT IS `position: fixed`, AND THAT IS FORCED. `.up-rail` is `overflow-x:
   auto`, which makes it a scroll container on BOTH axes, and an overflow box
   clips its content whether or not it scrolls — no z-index escapes an
   ancestor's clip (the Followers roster's r6 lesson). A fixed element's
   containing block is the VIEWPORT, so nothing on the way up can cut it; what
   it gives up is CSS's own anchoring, which is what the two custom properties
   put back (`creator_hover_controller` writes them off the trigger's rect).

   `[hidden]` IS LOAD-BEARING (the r14 trap, five times over now): the rule
   below hands this element a `display`, which out-ranks the UA's own
   `[hidden]` — without it every popover on the wall paints at once, over the
   page, with the markup perfectly correct.

   THE WRAPPER TAKES OVER THE NAME'S TRUNCATION, and that is a regression it
   would otherwise cause. The name used to be the by-row's own flex ITEM, and
   a flex item is BLOCKIFIED whatever its `display` says — which is the only
   reason `overflow: hidden` + `text-overflow: ellipsis` ever applied to it
   (neither property applies to a non-replaced inline box). Wrapping it moved
   the flex item one level out, so the shrink and the clip move with it and the
   name is a block inside. Without this a long display name runs straight out
   of a 300px card. */
.chover { position: relative; display: block; min-width: 0; }
.chover__card {
  position: fixed; left: var(--chover-x, 0); top: var(--chover-y, 0); z-index: 60;
  display: block; width: 264px; max-width: calc(100vw - 16px);
  padding: 14px; border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); box-shadow: 0 18px 40px rgb(0 0 0 / 0.45);
  text-align: left; white-space: normal;
}
.chover__card[hidden] { display: none; }
/* THE GAP IS BRIDGED, OR THE POPOVER DIES ON THE WAY TO ITS OWN FOLLOW (Maaz
   2026-09-15 r2, item 2). `creator_hover_controller` places the card GAP px
   clear of the name, and that strip belongs to neither of them: the pointer
   leaves the trigger, `leave()` reads `:hover` off the DOM one frame later,
   and at any ordinary mouse speed the pointer is still in the gap — so the
   card closed under the hand reaching for the Follow it exists to offer.
   This pseudo-element is a child of the CARD, and hover propagates up the DOM
   tree rather than through geometry, so covering the strip keeps `.chover`
   hovered for the whole journey. It is exactly `--chover-gap` tall and no
   taller: one more pixel and it would sit over the name and take its click.
   ONE DEFINITION OF THE DISTANCE — the controller writes `--chover-gap` when
   it places the card, because two numbers holding one gap is how the next edit
   moves one and not the other. The direction follows `data-chover-placed`, so
   a card that flipped UP bridges downward. */
.chover__card::before {
  content: ""; position: absolute; left: 0; right: 0; height: var(--chover-gap, 8px);
}
.chover__card[data-chover-placed="down"]::before { top: calc(-1 * var(--chover-gap, 8px)); }
.chover__card[data-chover-placed="up"]::before { bottom: calc(-1 * var(--chover-gap, 8px)); }
.chover__id { display: flex; align-items: center; gap: 10px; }
.chover__id .ring { flex: none; }
.chover__who { min-width: 0; }
.chover__name {
  display: block; font-weight: 700; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chover__cat { margin: 1px 0 0; color: var(--muted); }
.chover__cat a { color: var(--muted); }
.chover__cat a:hover { color: var(--text); }
/* THREE WELLS, NOT A LINE (Maaz 2026-09-15 r3, screenshots 1–2). It was the
   altar's own sentence one size down — "22,470 followers · 4 lives · 3
   encores" — and his card draws the mark over the figure in a raised box.
   `.chover__meta` went out of the stylesheet with that markup (the .gl-side
   P0: a class named after retired markup is how the next surface inherits a
   box by reaching for a familiar name).

   ONE STEP LIGHTER THAN THE CARD, WHICH IS MEASURED OFF HIS OWN MOCK: the
   popover reads (20,20,22) and a well (28,28,31), i.e. raised rather than
   recessed. `--surface-3` is the house's own next step up from `--surface`
   and carries no stroke, exactly as the mock draws it.

   `1 1 0` SO N WELLS SHARE THE ROW: the floor means a creator's first week
   draws ONE of these, and a fixed basis would leave it stranded beside two
   gaps. */
.chover__stats { display: flex; gap: 6px; margin-top: 12px; }
.chover__stat {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 8px 6px; border-radius: var(--radius-sm);
  background: var(--surface-3); color: var(--muted);
}
.chover__stat b { color: var(--text); font-size: 0.86rem; font-weight: 600; }
.chover__cta { margin-top: 12px; }
.chover__cta form { display: block; margin: 0; }
/* THE OFFER IS GOLD AND THE STATE IS THE MUTED PILL IT BECOMES, and the
   element says which: an <a>/<button> in here is the act, a <span> is the
   fact. One box (`.foll-btn`), two faces, no third class — and the 10% gold
   wash is measured off his mock (a #e0b669 fill at 0.10 over the card), which
   is the same tint `.follow-menu > summary:hover` already carries. This is
   the OUTLINE gold, never `.btn--gold`'s solid block: that one is money and
   is fenced behind `body.studio` besides. */
.chover__cta .foll-btn { width: 100%; }
.chover__cta a.foll-btn, .chover__cta button.foll-btn {
  color: var(--gold); border-color: var(--hair-gold);
  background: color-mix(in srgb, var(--gold) 10%, transparent);
}
.chover__cta a.foll-btn:hover, .chover__cta button.foll-btn:hover {
  color: var(--gold-bright); border-color: var(--gold);
}
.chover__cta span.foll-btn { cursor: default; }
.chover__cta span.foll-btn:hover { color: var(--muted); border-color: var(--border-strong); }

/* The fan's own place (Settings since the QA round, item 4): the tabs are
   addresses, the rows are people, the chips are the two rails a follow can
   ring.

   ONE SHELL FOR ALL FOUR (item 5). Profile and Preferences were
   `max-width: 640px` ON THE SHELL, and `.shell` carries `margin: 0 auto` — so
   those two sat centred and narrow against People and Passes beside them, i.e.
   two widths and two alignments for one page. `.fan-place` is the shell's own
   measure on every tab and `.fan-form` is the reading measure for the two that
   want one, as a block with NO auto margins: same shell, left-aligned. */
.fan-place { max-width: 1100px; }
.fan-place__head { margin: 44px 0 0; }
.fan-form { max-width: 640px; }
.fan-tabs { display: flex; gap: 20px; margin-top: 16px; border-bottom: 1px solid var(--border); }
.fan-tabs a { padding: 9px 2px; color: var(--muted); font-size: 0.92rem; }
.fan-tabs a:hover { color: var(--text); }
.fan-tabs a[aria-current="page"] {
  color: var(--text); font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--accent);
}
.fan-filter { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 18px 0 4px; }
.fan-filter__note { font-size: 0.82rem; color: var(--dim); }
.fan-person { display: flex; align-items: center; gap: 14px; padding: 16px 0; border-bottom: 1px solid var(--border); }
.fan-person__who { flex: 1; min-width: 0; }
.fan-person__who b { display: block; }
.fan-person__next { display: block; margin-top: 2px; font-size: 0.84rem; color: var(--muted); }
.fan-person__live { color: var(--live); font-weight: 600; }
.fan-person__chips { display: flex; gap: 8px; flex: none; }
.fan-ch {
  font: inherit; font-size: 0.8rem; padding: 5px 12px; border-radius: 999px;
  border: var(--hair-w, 1px) solid var(--border); background: none; color: var(--dim); cursor: pointer;
}
.fan-ch[aria-pressed="true"] { color: var(--text); border-color: var(--accent); }
.fan-pass-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); gap: 14px; margin-top: 18px; }

/* ─────────────────── THE NAV SEARCH (Maaz 2026-09-15, item 3) ──────────────
   Fan chrome, UNFENCED (scope law 2). A rounded field in the middle of the
   masthead with its answer hanging under it — his own mock — and nothing in
   here is a `.btn`, a `.chip` or a `.menu`: a dropdown of results is its own
   thing, and borrowing one of those three would have given it a box somebody
   later restyles for another reason.

   THE FIELD TAKES THE SLACK AND THE MASTHEAD'S OWN ROW IS UNTOUCHED. `.topbar`
   is a wrapping flex row whose `.topbar__nav` carries `margin-left: auto`; a
   growing item between the wordmark and that nav is exactly what the auto
   margin is for, so nothing needed a new layout — and below 760px the field
   drops to its own line rather than squeezing the bell and the avatar (the
   FOLLOWERS r4 rule: the field gives, the acts do not). */
/* `margin-inline: auto` beside `.topbar__nav`'s own `margin-left: auto` puts
   the field near the middle of the bar, which is where his mock draws it: the
   three auto margins split the row's free space evenly. Below the phone
   breakpoint it takes its own line, where an auto margin does nothing. */
.fsearch { position: relative; flex: 1 1 320px; max-width: 560px; min-width: 0; margin-inline: auto; }
.fsearch__box {
  display: flex; align-items: center; gap: 8px;
  padding: 0 14px; min-height: 38px;
  border: var(--hair-w) solid var(--border); border-radius: 999px;
  background: var(--surface); color: var(--dim);
  transition: border-color 150ms ease;
}
.fsearch__box:focus-within { border-color: var(--border-strong); }
.fsearch__box input {
  flex: 1; min-width: 0; border: 0; background: none; padding: 8px 0;
  font: inherit; font-size: 0.88rem; color: var(--text);
}
.fsearch__box input:focus { outline: none; }
/* Safari draws its own clear affordance inside a type=search; ours is the
   Escape key and the field's own selection, and the native one lands on the
   panel's own edge. */
.fsearch__box input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
/* [hidden] must keep winning over the panel's own display (the r14 trap: a
   rule that hands an element a display out-ranks the UA's [hidden]) — and
   this is the panel that is hidden on every page render. */
.fsearch__panel[hidden] { display: none; }
.fsearch__panel {
  position: absolute; left: 0; right: 0; top: calc(100% + 8px); z-index: 50;
  max-height: min(70vh, 520px); overflow-y: auto;
  padding: 12px; border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); box-shadow: 0 18px 40px rgb(0 0 0 / 0.45);
}
.fsearch__cats { display: flex; flex-wrap: wrap; gap: 8px; }
.fsearch__cat {
  padding: 7px 14px; border: var(--hair-w) solid var(--border-strong);
  border-radius: 999px; font-size: 0.84rem; color: var(--muted);
  transition: color 150ms ease, border-color 150ms ease;
}
.fsearch__cat:hover, .fsearch__cat:focus-visible { color: var(--text); border-color: var(--text); }
.fsearch__group {
  margin: 12px 4px 6px; font-size: 0.7rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--dim);
}
.fsearch__group:first-child { margin-top: 2px; }
.fsearch__row {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 8px; border-radius: 8px; color: var(--text);
}
.fsearch__row:hover, .fsearch__row:focus-visible { background: var(--surface-2); outline: none; }
.fsearch__row .ring { flex: none; }
.fsearch__what { min-width: 0; }
.fsearch__name {
  display: block; font-size: 0.92rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fsearch__sub {
  display: flex; align-items: center; gap: 5px;
  margin-top: 1px; font-size: 0.78rem; color: var(--muted);
}
/* `.live-dot` is `background: currentColor`, so inside a muted line it paints
   grey — and a room that is ON AIR is the one fact in this panel that has a
   colour in the house. The words stay muted; only the mark is live-red. */
.fsearch__sub .live-dot { color: var(--live); }
.fsearch__see { display: block; padding: 7px 8px; font-size: 0.86rem; color: var(--muted); }
.fsearch__see:hover { color: var(--text); }
.fsearch__none { margin: 4px 8px; color: var(--muted); }
/* The no-JavaScript page at /search reads the same partial, so its rows are
   the panel's rows standing in a column instead of a popover. */
.fsearch-page { margin-top: 18px; max-width: 640px; }
.fsearch-page .fsearch__cats { margin-top: 16px; }

@media (max-width: 760px) {
  /* Its own line rather than a squeezed one — the acts to its right keep
     their size, which is the whole point of the field being the thing that
     gives (FOLLOWERS r4). */
  .fsearch { order: 3; flex-basis: 100%; max-width: none; }
}

/* ───────────────── THE CREATORS TAB (Maaz 2026-09-15, item 4) ──────────────
   His own card: a cover, the by-row, `category · followers`, and the one
   control. `crx-` because `.creator-*` is not a namespace this sheet has and
   `.fan-card` is TAKEN (fenced studio furniture on the follower profile) —
   the same trap that named `.fan-frame` a year of rounds ago. */
.fan-tabs--scroll { overflow-x: auto; gap: 18px; scrollbar-width: none; }
.fan-tabs--scroll::-webkit-scrollbar { display: none; }
.fan-tabs--scroll a { flex: none; white-space: nowrap; }
.crx-said { margin: 14px 0 0; color: var(--muted); }
.crx-grid {
  display: grid; gap: 16px; margin-top: 20px;
  grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
}
.crx-card {
  display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden;
  background: var(--surface); transition: border-color 150ms ease;
}
.crx-card:hover { border-color: var(--border-strong); }
.crx-card__frame { display: block; aspect-ratio: 16 / 9; background: var(--surface-2); }
.crx-card__frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
.crx-card__body { padding: 14px 16px 16px; }
.crx-card__by { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.crx-card__by .ring { flex: none; }
.crx-card__by .avatar { width: 26px; height: 26px; font-size: 0.66rem; }
.crx-card__name {
  min-width: 0; font-weight: 600; font-size: 0.92rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.crx-card__meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0 5px;
  margin-top: 6px; color: var(--muted);
}
.crx-card__meta a:hover { color: var(--text); text-decoration: underline; text-underline-offset: 2px; }
.crx-card__cta { margin-top: 12px; }
/* The card's control is the SAME pill as the Lobby's, one size up — his mock
   draws it wider under the words than beside them, and it is still the word
   in its own case, never a `.btn` (item 1). */
.foll-btn--cta { padding: 7px 18px; font-size: 0.82rem; }
