/* ── Brand fonts, SELF-HOSTED ───────────────────────────────────────────────
 * A broadcast source must not depend on a font CDN being reachable at
 * showtime, and a missing webfont fails SILENTLY to a fallback (Anton ->
 * Impact), which looks "fine" on the machine but off-brand on air. Shipped
 * as TTF rather than woff2 because no brotli encoder is available in this
 * toolchain; they load once when the scene starts. Not subsetted on purpose —
 * the production title is config-driven, so an unexpected glyph must never
 * render as tofu mid-show. */
@font-face {
  font-family: 'Anton';
  src: url('./fonts/Anton-Regular.ttf') format('truetype');
  font-weight: 400; font-style: normal; font-display: block;
}
@font-face {
  font-family: 'InterBrand';
  src: url('./fonts/Inter-Regular.ttf') format('truetype');
  font-weight: 400; font-style: normal; font-display: block;
}
@font-face {
  font-family: 'InterBrand';
  src: url('./fonts/Inter-SemiBold.ttf') format('truetype');
  font-weight: 600; font-style: normal; font-display: block;
}

/* Feedback broadcast overlays — TRANSPARENT background for OBS browser sources.
   Big, legible, motion-on-update. No app chrome. */

:root {
  --blue: #5b8cff;
  --gold: #ffca3a;
  --good: #36d399;
  --bad: #f8576b;
  --ink: #ffffff;
  --ink-dim: rgba(255, 255, 255, 0.72);
  --panel: rgba(12, 15, 22, 0.82);
  --panel-2: rgba(255, 255, 255, 0.06);
  --shadow: 0 10px 40px rgba(0, 0, 0, 0.55);
}

* { box-sizing: border-box; }

/* Transparent — OBS composites this over the video. */
html, body {
  margin: 0;
  padding: 0;
  background: transparent;
  color: var(--ink);
  font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

.overlay-root {
  position: fixed;
  left: 48px;
  bottom: 48px;
  max-width: 640px;
}

/* Configurable anchor (?pos=, SMS-308) — safe-area margin 48px. */
.overlay-root.pos-bottom-left { left: 48px; right: auto; top: auto; bottom: 48px; transform: none; }
.overlay-root.pos-bottom-right { right: 48px; left: auto; top: auto; bottom: 48px; transform: none; }
.overlay-root.pos-top-left { left: 48px; right: auto; top: 48px; bottom: auto; transform: none; }
.overlay-root.pos-top-right { right: 48px; left: auto; top: 48px; bottom: auto; transform: none; }
.overlay-root.pos-bottom-center { left: 50%; right: auto; top: auto; bottom: 48px; transform: translateX(-50%); }

/* Connection dot — tiny, top-right; hide with ?status=0 */
.status-dot {
  position: fixed; top: 12px; right: 12px; width: 10px; height: 10px;
  border-radius: 50%; z-index: 100; opacity: 0.85;
}
.status-live { background: var(--good); box-shadow: 0 0 8px var(--good); }
.status-connecting, .status-reconnecting { background: var(--gold); animation: blink 1s infinite; }
.status-error { background: var(--bad); }
@keyframes blink { 50% { opacity: 0.25; } }

/* ── Lower third (leaderboard / favorite / tally) ── */
.lower-third {
  background: var(--panel);
  border-radius: 16px;
  padding: 18px 22px;
  box-shadow: var(--shadow);
  backdrop-filter: blur(8px);
  border-left: 5px solid var(--blue);
  animation: slidein 0.45s cubic-bezier(0.2, 0.9, 0.2, 1);
  min-width: 380px;
}
.accent-blue { border-left-color: var(--blue); }
.accent-gold { border-left-color: var(--gold); }
.accent-muted { border-left-color: rgba(255, 255, 255, 0.25); }
/* At Risk — the only board that is bad news, so it gets the one warning colour
   already in the palette rather than a new one. */
.accent-bad { border-left-color: var(--bad); }

.lt-title {
  font-size: 15px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-dim); margin-bottom: 12px;
}
.accent-gold .lt-title { color: var(--gold); }
.accent-bad .lt-title { color: var(--bad); }
.lt-list { display: flex; flex-direction: column; gap: 8px; }
.lt-empty { color: var(--ink-dim); font-size: 16px; padding: 6px 0; }

.lt-row {
  display: grid; grid-template-columns: 40px auto 1fr auto; gap: 14px; align-items: center;
  padding: 8px 12px; border-radius: 10px; background: var(--panel-2);
}
.lt-rank { font-size: 22px; font-weight: 900; color: var(--blue); text-align: center; }
.accent-gold .lt-rank { color: var(--gold); }
.accent-bad .lt-rank { color: var(--bad); }

/* Actor headshot (SMS-305) — small round face next to the name. The <img> and
   the initials fallback share the same box so the grid column stays aligned. */
.lt-shot {
  width: 40px; height: 40px; border-radius: 50%; object-fit: cover;
  background: var(--panel-2); border: 2px solid rgba(255, 255, 255, 0.16);
  flex: none;
}
.lt-shot-initials {
  display: flex; align-items: center; justify-content: center;
  font-size: 18px; font-weight: 800; color: var(--ink-dim);
  text-transform: uppercase; line-height: 1;
}

.lt-name { font-size: 22px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lt-score { font-size: 22px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ink); }

.pulse { animation: pulse 0.6s ease; }
@keyframes pulse {
  0% { background: rgba(91, 140, 255, 0.45); transform: scale(1.015); }
  100% { background: var(--panel-2); transform: scale(1); }
}

/* ── Leaderboard motion (SMS-211): count-up, climber flash, row enter ──
   FLIP transforms are applied inline by overlays.js; these carry the
   number pop, the green climb flash, and a new entrant's fade-up. */
.lt-row { will-change: transform; }
.lt-score { display: inline-block; will-change: transform; }
.lt-score.tick { animation: scoretick 0.5s ease; }
@keyframes scoretick {
  0% { transform: scale(1.22); color: var(--good); }
  55% { color: var(--good); }
  100% { transform: scale(1); }
}
.lt-row.climb { animation: climbflash 0.75s ease; }
@keyframes climbflash {
  0% { background: rgba(54, 211, 153, 0.42); }
  100% { background: var(--panel-2); }
}
.lt-row.row-enter { animation: rowenter 0.45s cubic-bezier(0.2, 0.9, 0.2, 1); }
@keyframes rowenter {
  0% { opacity: 0; transform: translateY(12px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* ── Tally bars ── */
.bar-row { padding: 6px 0; }
.bar-head { display: flex; justify-content: space-between; font-size: 19px; font-weight: 700; margin-bottom: 5px; }
.bar-val { font-variant-numeric: tabular-nums; color: var(--ink-dim); }
.bar-track { height: 14px; border-radius: 8px; background: var(--panel-2); overflow: hidden; }
.bar { height: 100%; background: linear-gradient(90deg, var(--blue), #8ab0ff); border-radius: 8px; transition: width 0.4s cubic-bezier(0.2, 0.9, 0.2, 1); }

/* ── Reveal ── */
.reveal-card {
  background: var(--panel); border-radius: 16px; padding: 22px 26px; box-shadow: var(--shadow);
  backdrop-filter: blur(8px); border-left: 5px solid var(--blue); min-width: 380px;
}
.reveal-card.revealed { animation: reveal 0.6s cubic-bezier(0.2, 0.9, 0.2, 1); }
.reveal-sealed {
  font-size: 40px; font-weight: 900; letter-spacing: 0.3em; color: var(--ink-dim);
  text-align: center; padding: 18px 0;
}
@keyframes reveal { 0% { transform: scale(0.9); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }
.reveal-card .lt-row { margin-top: 8px; }

/* ── Compare ── */
.compare-card {
  background: var(--panel); border-radius: 16px; padding: 22px 26px; box-shadow: var(--shadow);
  backdrop-filter: blur(8px); border-left: 5px solid var(--blue);
  animation: slidein 0.45s cubic-bezier(0.2, 0.9, 0.2, 1);
}
.compare-row { display: grid; grid-template-columns: 1fr auto 1fr; gap: 18px; align-items: center; }
.compare-side { text-align: center; }
.compare-name { font-size: 22px; font-weight: 800; margin-bottom: 6px; }
.compare-score { font-size: 44px; font-weight: 900; color: var(--blue); font-variant-numeric: tabular-nums; }
.compare-rank { font-size: 16px; color: var(--ink-dim); }
.compare-vs { font-size: 22px; font-weight: 900; color: var(--ink-dim); }

/* ── Elimination ── */
.elim-card {
  background: var(--panel); border-radius: 16px; padding: 22px 28px; box-shadow: var(--shadow);
  backdrop-filter: blur(8px); border-left: 5px solid var(--bad); text-align: center; min-width: 360px;
}
.elim-card.confirmed { animation: stamp 0.5s cubic-bezier(0.2, 0.9, 0.2, 1); }
.elim-name { font-size: 38px; font-weight: 900; margin-top: 6px; }
.elim-name.at-risk { color: var(--gold); font-size: 30px; }
.elim-atrisk-label { font-size: 14px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-dim); }
@keyframes stamp { 0% { transform: scale(1.3) rotate(-3deg); opacity: 0; } 100% { transform: scale(1) rotate(0); opacity: 1; } }

@keyframes slidein { 0% { transform: translateY(18px); opacity: 0; } 100% { transform: translateY(0); opacity: 1; } }

/* ── Director mode (SMS-211): one full-canvas overlay, graphics self-positioned ── */
/* max-width/left/bottom from the base .overlay-root would cap this at 640px and
   pin it left, so slot-bottom-right anchors mid-canvas. Force the full viewport. */
.overlay-root.is-director {
  position: fixed; inset: 0; padding: 0;
  left: 0; right: 0; top: 0; bottom: 0;
  width: 100vw; height: 100vh; max-width: none;
}
.director { position: absolute; inset: 0; }
.dir-slot { position: absolute; }
.dir-slot.slot-bottom-left  { left: 48px;  bottom: 48px; }
.dir-slot.slot-bottom-right { right: 48px; bottom: 48px; }
.dir-slot.slot-top-center   { top: 48px;   left: 50%; transform: translateX(-50%); }
.dir-slot.slot-center       { top: 50%;    left: 50%; transform: translate(-50%, -50%); }
/* Now Auditioning sits top-left — the only graphic in that corner, so it can
   stay up under any board without colliding. This position had no rule before:
   the slot existed in the URL param list but no director graphic used it. */
.dir-slot.slot-top-left     { left: 48px;  top: 48px; }
/* With the furniture rail on, top-left must clear the header band: the rail is
   7.4% of the canvas (~80 design px at 1080), so the Now Auditioning card
   starts just below it instead of underneath it (Kris, 2026-08-19: name +
   @handle in the upper-left, BELOW the overlay header). Design units so it
   scales with the source like the rest of the furniture chrome. */
.dir-shell .dir-slot.slot-top-left { left: calc(48 * var(--u)); top: calc(100 * var(--u)); }
/* bottom-center had NO rule at all, ever — so the director-mode Elimination
   card (and now At Risk, which shares its slot) rendered at the container's
   default position: the TOP-LEFT corner of the canvas, on top of whatever
   lived there. Found by rendering dir-classic in the fixture harness. */
.dir-slot.slot-bottom-center { left: 50%; bottom: 48px; transform: translateX(-50%); }

/* ── Panel vs Audience (SMS-211): one actor, overall + all rated categories.
   Match the other lower-thirds exactly: solid --panel bg + blur + 16px radius +
   shadow + gold left rail. (Earlier this referenced app-side tokens --card /
   --line / --text that don't exist in this stylesheet, so it rendered
   transparent and unlike the rest.) ── */
.versus-card {
  background: var(--panel);
  border-radius: 16px;
  border-left: 5px solid var(--gold);
  padding: 20px 24px;
  box-shadow: var(--shadow);
  backdrop-filter: blur(8px);
  min-width: 460px;
  /* no slidein: versus() rebuilds the card each render, so an entrance
     animation would replay on every rating while the graphic is on air. */
}
.versus-head { display: flex; align-items: center; gap: 16px; margin-bottom: 6px; }
.versus-face img, .versus-face .pool-shot-empty,
.versus-face .lt-shot, .versus-face > * { width: 64px; height: 64px; border-radius: 50%; }
.versus-name { font-weight: 800; font-size: 26px; color: var(--ink); }
.versus-line { display: grid; grid-template-columns: 1fr 92px 92px; align-items: center; padding: 6px 0; border-top: 1px solid rgba(255, 255, 255, 0.08); }
.versus-line.versus-colhead { border-top: none; padding-bottom: 2px; }
.versus-line.overall { border-top: 1px solid rgba(255, 255, 255, 0.16); margin-top: 2px; }
.versus-cat { font-size: 15px; color: var(--ink-dim); }
.versus-line.overall .versus-cat { font-size: 14px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink); }
.versus-cell { text-align: center; font-weight: 600; font-size: 20px; color: var(--ink); font-variant-numeric: tabular-nums; }
.versus-line.overall .versus-cell { font-size: 30px; font-weight: 900; }
.versus-colhead .versus-cell { font-size: 12px; letter-spacing: 0.12em; font-weight: 700; text-transform: uppercase; color: var(--ink-dim); }
.versus-cell.panel { color: var(--gold); }
.versus-cell.audience { color: var(--blue); }
.versus-line:not(.overall):not(.versus-colhead) .versus-cell { color: var(--ink); }

/* Per-judge reveal (SMS-580): the three named judges' individual overalls. */
.versus-line.versus-judgehead { border-top: 1px solid rgba(255, 255, 255, 0.16); margin-top: 6px; padding-top: 8px; }
.versus-line.versus-judgehead .versus-cat { font-family: var(--font-caps, inherit); font-size: 13px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink); }
.versus-line.versus-judgehead .versus-cell.panel { font-size: 11px; letter-spacing: 0.12em; color: var(--gold); }
.versus-line.versus-judge .versus-cat { display: flex; flex-direction: column; gap: 1px; }
.versus-line.versus-judge .versus-seat { font-size: 10px; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; color: var(--gold); }
.versus-line.versus-judge .versus-judgename { font-size: 15px; color: var(--ink); }
.versus-line.versus-judge .versus-cell.panel { color: var(--gold); font-weight: 700; }

/* ── Show furniture (?overlay=furniture) ────────────────────────────────────
 * Replaces the baked casting-overlay-static.png. Positioned in percentages of
 * a 1920x1080 design space (see config.js CASTING_FURNITURE), so one source is
 * correct at any 16:9 output size. Seat interiors stay TRANSPARENT — the
 * Restream cam shows through; we only draw the frame around it. */
/* One design pixel: EXACT division, not a truncated decimal. 0.0520833vw is
 * slightly under 100/1920, and Chrome floors border-width in 0.5px steps — so
 * the 2px seat border computed as 1.5px, visibly thinner than designed.
 * Geometry is authored in a 1920x1080 design space; every
 * dimension below is expressed in these units so the ENTIRE layer scales with
 * the browser source. The seat rects were already percentage-based, so without
 * this the frames scaled while the chrome stayed at fixed px — the rail text
 * and padding would be visibly oversized at 1280x720. */
.furniture, .dir-shell { --u: calc(100vw / 1920); }

.furniture { position: fixed; inset: 0; pointer-events: none; }

/* Top rail */
.fx-rail {
  position: absolute; left: 0; right: 0; top: 0; height: 7.4%;
  display: flex; align-items: center; gap: calc(18 * var(--u)); padding: 0 calc(26 * var(--u));
  background: linear-gradient(180deg, rgba(6,6,8,0.92) 0%, rgba(6,6,8,0.55) 62%, rgba(6,6,8,0) 100%);
}
.fx-brand { display: flex; align-items: center; gap: calc(12 * var(--u)); }
.fx-mark { width: calc(38 * var(--u)); height: calc(38 * var(--u)); display: block; flex: none; }
.fx-mark img { width: 100%; height: 100%; display: block; object-fit: contain; }
.fx-wordmark {
  font-family: Anton, Impact, sans-serif; font-size: calc(30 * var(--u)); letter-spacing: 0.04em;
  color: var(--gold, #c9a962);
}
.fx-series {
  font-family: Anton, Impact, sans-serif; font-size: calc(13 * var(--u)); letter-spacing: 0.22em;
  color: var(--gold, #c9a962); opacity: 0.85; margin-left: calc(10 * var(--u));
}
/* Rail zones: left and right size to content, centre takes the slack, so the
 * production title is optically centred WITHOUT leaving the flow (an absolute
 * centre overlapped the CTAs below the design width). */
.fx-zone { display: flex; align-items: center; gap: calc(14 * var(--u)); min-width: 0; }
.fx-zone-left { flex: 0 0 auto; }
.fx-zone-centre { flex: 1 1 auto; justify-content: center; overflow: hidden; }
.fx-zone-right { flex: 0 0 auto; }
.fx-title {
  font-family: Anton, Impact, sans-serif; font-size: calc(34 * var(--u)); letter-spacing: 0.02em;
  color: #f00203; text-shadow: 0 calc(2 * var(--u)) calc(10 * var(--u)) rgba(0,0,0,0.55);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fx-cta {
  font-family: InterBrand, Inter, system-ui, sans-serif; font-size: calc(15 * var(--u));
  font-weight: 600; padding: calc(9 * var(--u)) calc(18 * var(--u)); border-radius: 999px; white-space: nowrap;
}
.fx-cta-secondary { background: rgba(255,255,255,0.14); color: #fff; }
.fx-cta-primary { background: var(--gold, #c9a962); color: #14100a; }
.fx-onair {
  font-family: Anton, Impact, sans-serif; font-size: calc(13 * var(--u)); letter-spacing: 0.16em;
  color: #ff5a4f; border: calc(1 * var(--u)) solid rgba(255,90,79,0.5); border-radius: 999px;
  padding: calc(6 * var(--u)) calc(14 * var(--u)); margin-left: calc(4 * var(--u));
}

/* Seat frames — border only, transparent interior */
.fx-seat {
  position: absolute; border: calc(2 * var(--u)) solid rgba(201,169,98,0.55); border-radius: calc(10 * var(--u));
  box-shadow: 0 calc(10 * var(--u)) calc(34 * var(--u)) rgba(0,0,0,0.45);
}
.fx-seat-no, .fx-seat-title {
  position: absolute; left: calc(14 * var(--u));
  font-family: Anton, Impact, sans-serif; letter-spacing: 0.14em; white-space: nowrap;
  text-shadow: 0 calc(2 * var(--u)) calc(6 * var(--u)) rgba(0,0,0,0.8);
}
.fx-seat-no { font-size: calc(11 * var(--u)); color: rgba(242,236,224,0.62); }
.fx-seat-title { font-size: calc(14 * var(--u)); color: var(--gold, #c9a962); }
/* Restream stamps its own guest name tag bottom-left; ours rides the top so
 * the two never overlap (they did on the 2026-07-27 capture). */
.fx-seat.label-top .fx-seat-no { top: calc(10 * var(--u)); }
.fx-seat.label-top .fx-seat-title { top: calc(26 * var(--u)); }
.fx-seat.label-bottom .fx-seat-no { bottom: calc(32 * var(--u)); }
.fx-seat.label-bottom .fx-seat-title { bottom: calc(12 * var(--u)); }
.fx-rec {
  position: absolute; top: calc(12 * var(--u)); right: calc(12 * var(--u)); width: calc(9 * var(--u)); height: calc(9 * var(--u));
  border-radius: 50%; background: #ff2a1f; box-shadow: 0 0 calc(10 * var(--u)) rgba(255,42,31,0.8);
}

/* Audience card — ours, so it gets a filled panel rather than a cam cutout */
.fx-audience {
  position: absolute; border: calc(1 * var(--u)) solid rgba(120,170,255,0.45); border-radius: calc(10 * var(--u));
  background: rgba(8,12,24,0.55); box-shadow: 0 calc(10 * var(--u)) calc(34 * var(--u)) rgba(0,0,0,0.45);
}
.fx-aud-title {
  position: absolute; top: calc(12 * var(--u)); left: calc(16 * var(--u));
  font-family: Anton, Impact, sans-serif; font-size: calc(13 * var(--u)); letter-spacing: 0.14em;
  color: #7fb0ff;
}
.fx-cta-ico { margin-right: calc(8 * var(--u)); font-size: calc(13 * var(--u)); opacity: 0.9; }
/* Far-right slate label, matching the reference rail. */
.fx-slate {
  font-family: Anton, Impact, sans-serif; font-size: calc(12 * var(--u)); letter-spacing: 0.22em;
  color: rgba(242,236,224,0.42); margin-left: calc(10 * var(--u));
}
/* Director shell: furniture underneath, dynamic graphics on top. */
.dir-shell { position: fixed; inset: 0; pointer-events: none; --u: calc(100vw / 1920); }


/* ── Now Auditioning (nowplaying) ──────────────────────────────────────────
   The lower-third naming who is on stage. Same credit order as every other
   actor surface: headshot, real name big, @handle and character beneath. */
.now-card {
  background: var(--panel);
  border-radius: 16px;
  padding: 16px 22px;
  box-shadow: var(--shadow);
  backdrop-filter: blur(8px);
  border-left: 5px solid var(--blue);
  animation: slidein 0.45s cubic-bezier(0.2, 0.9, 0.2, 1);
  min-width: 340px;
}
.now-label {
  font-size: 13px; font-weight: 800; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--blue); margin-bottom: 10px;
}
.now-body { display: flex; align-items: center; gap: 14px; }
/* The headshot is the shared .lt-shot, so it matches the leaderboard rows. */
.now-body .lt-shot { width: 56px; height: 56px; }
.now-name { font-size: 30px; font-weight: 900; line-height: 1.1; }
.now-sub { font-size: 15px; font-weight: 600; color: var(--ink-dim); margin-top: 3px; }
.now-role { text-transform: uppercase; letter-spacing: 0.08em; }
