/* Streambar ticker overlay — portrait / Whatnot variant, 980 × 190 (MVP).
 *
 * Every rule is scoped to .sb-* so this same file can be loaded by the control
 * panel to render the live preview through the SAME module viewers get. The
 * page-level rules (transparent body, 980×190, overflow) live in overlay.html —
 * they must never leak into the panel.
 *
 * TWO HEIGHTS, one gate. `[data-ticker="on"]` is the full two-row bar the
 * reference documents (banner 90 + scores/ticker 110 = 200, stage 300); the MVP
 * mounts with `showTicker: false`, which stamps `[data-ticker="off"]` and
 * collapses the stage to logo overhang 100 + banner 90 = 190. NOTHING below is
 * deleted — the ticker rules wait here for live scores.
 *
 * Colours arrive as CSS variables only. The module sets --primary/--secondary/
 * --accent/--text (validated #RRGGBB, server-side) plus the --*-rgb triplets it
 * derives from them, so every translucent shade re-skins with the theme — the
 * one pattern worth copying from the reference overlay.
 *
 * THREE MOTIONS, one row. `[data-motion]` on .sb picks what moves in the banner:
 * `fade` cross-fades the two slots, `vertical` rolls them up, `horizontal` hides
 * them and scrolls .sb-mq-track across the message area instead. Only `transform`
 * and `opacity` are ever transitioned — no filter, no box-shadow, no geometry —
 * and nothing on this row uses a CSS animation (the marquee is rAF-driven).
 *
 * THREE LAYOUTS, one row. `[data-layout]` on .sb picks the CHROME: `broadcast`
 * (the default, and the only one with rules + texture + the 190px overhanging
 * logo), `minimal` (no rule, no texture, 30px/400 type, 150px circular logo,
 * quieter type) and `scorebug` (a rounded secondary cell with a 2px accent inset
 * line, a 150px accent logo block, 32px type). No layout changes the 980 × 190
 * stage, the 90px bar or a single timing — and none adds a DOM node: the cell is
 * a pseudo-element and the gutters are one CSS variable.
 *
 * Each message may lead with an emoji (.sb-e). In Scroll motion that emoji is
 * the spacer: .sb-mq-sep is a 34px gap before an emoji-led item and a 55% dot
 * before one without. No ◆ anywhere any more.
 *
 * Dimensions/timings: see docs/overlay-dimensions.md.
 */

/* Self-hosted brand font (deliberate deviation from the reference's Bebas Neue
   + Google Fonts). Relative URLs resolve against /o/static/, so no version
   placeholder is needed here.

   ONE face, not two: Sora ships from Google Fonts as a *variable* woff2 whose
   wght axis runs 400-800, so a single 25 KB file serves every weight the bar
   renders and there is no second request. It replaces Sora-400/700.woff2 —
   those were the `latin-ext` subsets (143 code points, no a-z, no digits), so
   every string on this bar was silently falling back to a system face. */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url('fonts/Sora-latin.woff2') format('woff2');
}

:root {
  --primary: #7B2FBE;
  --secondary: #1a0a2e;
  --accent: #E5B800;
  --text: #FFFFFF;
  --accent-rgb: 229, 184, 0;
  --text-rgb: 255, 255, 255;
  --fade: 700ms;
}

/* ---- shell ---- */
.sb, .sb *, .sb *::before, .sb *::after { margin: 0; padding: 0; box-sizing: border-box; }
.sb {
  /* Same defaults as :root, re-declared so a mount inside a themed page (the
     panel) is never tinted by its host. The module overwrites them inline. */
  --primary: #7B2FBE;
  --secondary: #1a0a2e;
  --accent: #E5B800;
  --text: #FFFFFF;
  --accent-rgb: 229, 184, 0;
  --text-rgb: 255, 255, 255;
  --fade: 700ms;
  position: relative;
  width: 980px;
  height: 300px;                 /* two-row default; the off-gate below trims it */
  font-family: 'Sora', system-ui, -apple-system, 'Segoe UI', sans-serif;
  color: var(--text);
  text-transform: uppercase;
  -webkit-font-smoothing: antialiased;
}

/* Logo overhangs 100px above the bar (reference: body padding-top 100px). */
.sb-logo {
  position: absolute;
  top: 0; left: 16px;
  width: 190px; height: 190px;
  object-fit: contain;
  z-index: 50;
  pointer-events: none;
  filter: drop-shadow(0 4px 14px rgba(0, 0, 0, .85));
}
/* No logo, or one that has not decoded yet: opacity only, so the swap never
   touches layout. overlay.js preloads the new file in a detached Image() and
   only then points this element at it — there is no empty frame in between. */
.sb-logo { transition: opacity 180ms ease; }
.sb-logo.is-off { opacity: 0; }
.sb-logo[hidden] { display: none; }

.sb-bar {
  position: absolute;
  left: 0; top: 100px;
  width: 980px; height: 200px;
  border-radius: 4px;
  overflow: hidden;
  box-shadow: 0 4px 32px rgba(0, 0, 0, .8);
}

/* ---- MVP gate: scores/ticker row OFF ----
   The row is not in the DOM at all (overlay.js never appends it), so this is
   belt-and-braces plus the two heights that matter. The bar shrinks to the
   banner exactly, which hands the banner row the bar's bottom edge — the 4px
   radius and the drop shadow — with no leftover strip, border or gap below it.
   `display:none` (not height:0) so a stray row can never reserve space. */
/* ---- message-area gutters, one source of truth ----
   --gut-l clears the logo treatment, --gut-r is the right edge — a plain 28px in
   every layout now that the corner badge is gone (free plans carry a locked
   MESSAGE instead, which rides the rotation). The slots pad by them and the
   Scroll window insets by them, so a layout never has to restate either.
   Order matters: .no-logo comes last so it wins the tie. */
.sb { --gut-l: 216px; --gut-r: 28px; }                                  /* 16 + 190 + 10 */
.sb[data-layout="minimal"], .sb[data-layout="scorebug"] { --gut-l: 182px; }  /* 16 + 150 + 16 */
.sb.no-logo { --gut-l: 28px; }

.sb[data-ticker="off"] { height: 190px; }        /* 100 overhang + 90 banner */
.sb[data-ticker="off"] .sb-bar { height: 90px; } /* banner only, no dead space */
.sb[data-ticker="off"] .sb-ticker { display: none; }

/* ---- row 1: banner (90px) ---- */
.sb-banner {
  position: relative;
  height: 90px;
  background: var(--primary);
  overflow: hidden;
}
/* Static hatching — costs one paint, never animates (OBS repaint cost). */
.sb-banner::before {
  content: '';
  position: absolute; inset: 0;
  background: repeating-linear-gradient(90deg, transparent 0 80px, rgba(var(--accent-rgb), .04) 80px 81px);
  pointer-events: none;
}
.sb-rule-top {
  position: absolute; top: 0; left: 0; right: 0; height: 2px; z-index: 3;
  background: linear-gradient(90deg, transparent, var(--accent) 20%, var(--accent) 80%, transparent);
}
.sb-rule-bot {
  position: absolute; bottom: 0; left: 0; right: 0; height: 3px; z-index: 3;
  background: var(--accent);
  box-shadow: 0 0 12px rgba(var(--accent-rgb), .6);
}

/* Two stacked slots show one message at a time, for BOTH the fade and the
   vertical motions — same hold (dwell), same transition (fade), different
   transform. transform + opacity only: the reference's blur(6px) is deliberately
   dropped (an animated filter is the single most expensive thing an OBS browser
   source can be asked to repaint).
     fade     enter opacity 0→1 + scale 1.06→1 · exit 0 / scale .94
     vertical enter opacity 0→1 + translateY 45%→0 · exit 0 / translateY -45% */
.sb-slot {
  position: absolute; inset: 0; z-index: 1;
  display: flex; align-items: center;
  padding: 3px var(--gut-r) 3px var(--gut-l);
  opacity: 0;
  transform: scale(1.06);
  transition: opacity var(--fade) ease, transform var(--fade) ease;
}
.sb-slot.is-in { opacity: 1; transform: none; }
.sb-slot.is-out { opacity: 0; transform: scale(.94); }
/* The roll gets a decelerating curve — it reads as broadcast motion rather than
   a linear slide. Same duration, same properties: no runtime cost. */
.sb[data-motion="vertical"] .sb-slot { transform: translateY(45%); transition-timing-function: cubic-bezier(.22, 1, .36, 1); }
.sb[data-motion="vertical"] .sb-slot.is-in { transform: none; }
.sb[data-motion="vertical"] .sb-slot.is-out { transform: translateY(-45%); }
/* The message's optional leading emoji — one class for every row and layout.
   `em` resolves against whichever type size the layout gave the parent, so the
   emoji is always 0.9 of the text it leads. It must not change the line box:
   no line-height of its own, vertical-align middle, a fixed 0.35em run-in. */
.sb-e {
  font-size: .9em;
  vertical-align: middle;
  margin-right: .35em;
  line-height: 1;
}
.sb-e[hidden] { display: none; }

.sb-slot-t {
  min-width: 0;
  font-size: 34px;
  font-weight: 700;
  letter-spacing: .02em;
  line-height: 1.1;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- banner, Scroll motion: a continuous marquee of every message ----
   The window is inset by POSITION, not padding, so `overflow:hidden` clips at
   the logo gutter and the right edge (overflow is clipped at the PADDING edge,
   so a padded window would let the text run under the logo). 980 − 216 − 28 =
   736px in broadcast with a logo. Shown only in `horizontal`, and the slots step
   aside. */
.sb-mq {
  position: absolute; z-index: 1;
  top: 0; bottom: 0; left: var(--gut-l); right: var(--gut-r);
  display: none; align-items: center;
  overflow: hidden;
}
.sb[data-motion="horizontal"] .sb-mq { display: flex; }
.sb[data-motion="horizontal"] .sb-slot { display: none; }
/* Translated by rAF (exact px/s), never by a CSS animation. */
.sb-mq-track { display: flex; align-items: center; width: max-content; will-change: transform; }
.sb-mq-item {
  font-size: 34px; font-weight: 700;
  letter-spacing: .02em; line-height: 1.1;
  color: var(--text);
  white-space: nowrap;
}
/* The gap IS the separator. 17px each side = the ~34px run-in a leading emoji
   needs; when the next item has no emoji the same span carries a subtle dot.
   Text colour at 55%, never accent — the accent belongs to the chrome. */
.sb-mq-sep { font-size: 20px; line-height: 1; color: rgba(var(--text-rgb), .55); padding: 0 17px; }
/* Over the node budget a whole block is one span (see NODE_BUDGET in
   overlay.js): the gaps are real spaces, so they must not collapse. */
.sb-run { white-space: pre; }
/* Soft right edge into the row colour, as the ticker window has. Static. */
.sb-mq::after {
  content: '';
  position: absolute; top: 0; right: 0; bottom: 0; width: 28px; z-index: 2;
  background: linear-gradient(to left, var(--primary), transparent);
  pointer-events: none;
}

/* The Streambar mark, drawn in a message's emoji slot (`icon: 'streambar'`).
   It is CSS, not an <img> and not a DOM subtree: a data: URI costs no request
   and — unlike an inline <svg>, which is 2+ nodes in every duplicated marquee
   block — no nodes at all, which is what keeps a full free overlay inside the
   50-node budget. Same box as an emoji: 0.9em, centred, 0.35em before the text.
   (Simplified from assets/icon/streambar-icon.svg: the gradient is flattened to
   --brand and the dim bar dropped — at 30px they are invisible anyway.) */
.sb-e.sb-icon {
  display: inline-block;
  width: 1em; height: 1em;                 /* 1em of .sb-e's own 0.9em */
  vertical-align: -.12em;
  /* The field carries a faint light ring: the mark's own purple is the brand
     purple, and a tenant whose banner IS that colour would otherwise see the
     field vanish and the gold pill float. Baked into the data URI, so it costs
     nothing and works on any theme. */
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 96 96'%3E%3Crect x='2' y='2' width='92' height='92' rx='23' fill='%237B2FBE' stroke='%23FFFFFF' stroke-opacity='.35' stroke-width='4'/%3E%3Crect x='18' y='37' width='60' height='22' rx='7' fill='%23E5B800'/%3E%3Cpath d='M27 42 L36 48 L27 54 Z' fill='%231A0A2E'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* ======================================================================
   LAYOUTS — chrome only. Same stage, same bar, same timings, no extra DOM.
   `broadcast` is everything above: it needs no rules of its own.
   ====================================================================== */

/* ---- minimal: no rule, no texture, quieter type, a round logo ---- */
.sb[data-layout="minimal"] .sb-banner::before,
.sb[data-layout="minimal"] .sb-rule-top,
.sb[data-layout="minimal"] .sb-rule-bot { display: none; }
.sb[data-layout="minimal"] .sb-slot-t,
.sb[data-layout="minimal"] .sb-mq-item {
  font-size: 30px;                 /* the JS fit ladder starts here too */
  font-weight: 400;                /* Sora 400; 700 is kept for emphasis only */
  letter-spacing: .05em;
}
.sb[data-layout="minimal"] .sb-logo {
  top: 24px; width: 150px; height: 150px;    /* smaller, still overhanging */
  border-radius: 50%;
  object-fit: cover;                          /* fill the circle, don't letterbox */
  filter: none;                               /* quiet: no drop shadow */
}

/* ---- scorebug: the message area in a boxed cell, the logo in a block ----
   The cell is .sb-banner::after — a pseudo-element, so the boxed look costs no
   DOM node. z-index 0 puts it over the hatching and under the slots (z-index 1).
   Its 2px accent border IS the inset line. */
.sb[data-layout="scorebug"] .sb-banner::after {
  content: '';
  position: absolute; z-index: 0;
  top: 9px; bottom: 11px;
  left: calc(var(--gut-l) - 14px);
  right: calc(var(--gut-r) - 14px);
  background: var(--secondary);
  border: 2px solid var(--accent);
  border-radius: 10px;
  pointer-events: none;
}
.sb[data-layout="scorebug"] .sb-rule-top { display: none; }
.sb[data-layout="scorebug"] .sb-rule-bot { height: 2px; box-shadow: none; }
.sb[data-layout="scorebug"] .sb-slot-t,
.sb[data-layout="scorebug"] .sb-mq-item { font-size: 32px; }
/* The marquee runs over the cell, so its right fade is the CELL's colour. */
.sb[data-layout="scorebug"] .sb-mq::after { background: linear-gradient(to left, var(--secondary), transparent); }
.sb[data-layout="scorebug"] .sb-logo {
  top: 40px; width: 150px; height: 150px;    /* bottom-flush with the stage */
  background: var(--accent);
  border-radius: 8px;
  padding: 14px;
  object-fit: contain;
  filter: none;
}

/* ---- row 2: ticker (110px) — the SCORES row, gated off for the MVP ----
   Kept whole, and unchanged, so switching `showTicker` back on restores the
   reference layout: 110px row, 64px LIVE rail, 916px marquee window. ---- */
.sb-ticker {
  position: relative;
  height: 110px;
  display: flex;
  background: var(--secondary);
}
.sb-rail {
  /* 60px of rail + a 4px gold border beside it = 64px, leaving the 916px
     marquee window the reference measures. (border-box, so 64 total.) */
  flex: 0 0 64px; width: 64px;
  background: var(--primary);
  border-right: 4px solid var(--accent);
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 6px;
}
.sb-rail-live {
  font-size: 13px; font-weight: 700;
  letter-spacing: .22em; text-indent: .22em;   /* indent cancels the trailing space */
  line-height: 1; white-space: nowrap;
  color: var(--accent);
}
.sb-pip {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 6px var(--accent);
  animation: sbPip 1.4s ease-in-out infinite;
}
.sb-rail-plat {
  font-size: 9px; font-weight: 700;
  letter-spacing: .05em; text-indent: .05em;
  line-height: 1; white-space: nowrap;
  color: rgba(var(--text-rgb), .55);
}
.sb-rail-plat[hidden] { display: none; }
/* Off air: dimmed, no pulse. */
.sb.is-off .sb-rail-live { font-size: 10px; letter-spacing: .04em; text-indent: .04em; color: rgba(var(--text-rgb), .45); }
.sb.is-off .sb-pip { animation: none; background: rgba(var(--text-rgb), .22); box-shadow: none; }

/* Marquee window = 980 − 60 rail − 4 border = 916px. */
.sb-window {
  position: relative;
  flex: 1; min-width: 0;
  overflow: hidden;
  display: flex; align-items: center;
}
.sb-window::after {
  content: '';
  position: absolute; top: 0; right: 0; bottom: 0; width: 32px; z-index: 2;
  background: linear-gradient(to left, var(--secondary), transparent);
  pointer-events: none;
}
/* Translated by rAF (exact px/s), never by a CSS animation. */
.sb-track { display: flex; align-items: center; width: max-content; will-change: transform; }
.sb-block { display: flex; align-items: center; }
.sb-item {
  font-size: 26px; font-weight: 700;
  letter-spacing: .02em; line-height: 1.1;
  color: var(--text);
  white-space: nowrap;
}
.sb-sep { font-size: 18px; line-height: 1; color: rgba(var(--text-rgb), .55); padding: 0 17px; }
/* (No static-content rule: an empty message list renders nothing at all, in
   every motion and on this row too — there is no tenant-name fallback.) */

@keyframes sbPip { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
