/* brand.css — the shared brand layer: every @font-face, and the wordmark.
 *
 * THE ONE RULE: if it has to look identical on every surface, it lives here.
 * The wordmark used to be defined twice (static/style.css and
 * static/css/overlay.css) and had already drifted — the app rendered it at
 * font-weight 700, the overlays at 800. Same logo, two weights, and nothing
 * caught it, because you never see both at once.
 * tests/test_brand_integrity.py now fails the build if a second definition or
 * a hand-built copy appears.
 *
 * WHY A SEPARATE SHEET. The app and the broadcast surfaces do not share a
 * stylesheet. templates/overlay.html and templates/tv.html are STANDALONE
 * pages (no `extends base.html`) because they are OBS browser sources, so
 * they never load style.css. Declaring the faces inside style.css meant the
 * overlays could not use them. This sheet is linked by all four contexts:
 *   base.html            → the app (and anything extending it)
 *   overlay.html         → the OBS overlay sources
 *   tv.html              → the TV / broadcast view
 *
 * WHY IT MATTERS MORE FOR BROADCAST. A stream rendered on a cloud encoder is
 * rendered on Linux, where Impact, Georgia, Arial Black, Courier New and
 * Comic Sans MS do not exist. Anything relying on a system font degrades
 * silently to DejaVu — on air, and only in production. Every face the
 * broadcast look depends on has to be self-hosted and shipped with the page.
 *
 * Load this BEFORE the sheet that consumes it so the faces are already
 * declared when the first rule asks for them.
 */

/* ── Instrument Sans — the LiveSlates face ────────────────────────────────
   ONE variable file covers 400-700 (30 KB). The declared range says 400 900
   while the wght axis actually stops at 700, on purpose: the app asks for 800
   in a lot of places, and declaring only 400 700 would leave the browser to
   fake it with synthetic bold, which smears at 9-10px. 400 900 keeps 800
   inside the face's own range so it clamps to the real 700 master.
   Consequence: 700 IS the heaviest weight — 800 will not look heavier. */
@font-face {
  font-family: "Instrument Sans";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("/static/fonts/instrument-sans/InstrumentSans-var.woff2?v=1") format("woff2");
}

/* ── Bebas Neue — the scoreboard / display face ───────────────────────────
   Tall narrow capitals, sports-poster aesthetic. Used by the SCOREBOARD title
   on the dashboard frame, .bb-abbr team codes, and the MLB scoreboard widget.
   Was loading from fonts.googleapis.com on every dashboard render — a
   third-party request in the critical path, and unavailable to an offline or
   locked-down encoder. Self-hosted now; the CDN link and its two preconnects
   are gone from partials/_dashboard_styles.html.
   Single weight by design — the face only ships a 400 and it already reads
   heavy, so 400 IS the display weight. */
@font-face {
  font-family: "Bebas Neue";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/bebas-neue/BebasNeue-400.woff2?v=1") format("woff2");
}

/* ── Chivo — the WORDMARK face ────────────────────────────────────────────
   The logo's own typeface, and deliberately NOT the UI face. A mark that
   restyles itself when --font changes is not a mark: before this the wordmark
   had no font-family of its own, inherited var(--font), and silently redrew
   when the app moved to Instrument Sans. Signage lineage, which is why it
   holds up on a broadcast ground and at watermark size.
   One variable file, 400-900. */
@font-face {
  font-family: "Chivo";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("/static/fonts/chivo/Chivo-var.woff2?v=1") format("woff2");
}

/* ── Scene faces — the vidiprinter headline variants ──────────────────────
   Declared here but NOT downloaded on a normal page load. A browser fetches a
   webfont only when a rendered element actually needs it, so these four cost
   one CSS rule each until a polaroid / jumbotron / editorial / comic scene is
   on screen. That is why they can live in the shared sheet instead of a
   broadcast-only one: the scenes render inside the app (dashboard vidi widget,
   settings preview) as well as on a stream, so scoping by page would have
   missed half the call sites while still paying for the other half.

   Each replaces a SYSTEM font that does not exist on a Linux encoder —
   Georgia, Impact, Courier New, Comic Sans MS. See the header note above. */

/* Editorial scene — the newspaper. Companion to Instrument Sans by design. */
@font-face {
  font-family: "Instrument Serif";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/instrument-serif/InstrumentSerif-400.woff2?v=1") format("woff2");
}

/* Sky Sports + comic scenes — the heavy display. The open answer to Impact,
   and already named in the old fallback stack where it never once loaded. */
@font-face {
  font-family: "Anton";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/anton/Anton-400.woff2?v=1") format("woff2");
}

/* Jumbotron scene — the stadium board. One variable file (100-800).
   Declared to 900 on purpose: .vh-headline asks for font-weight 900, and a
   truthful 100 800 would leave the browser to fake it with synthetic bold.
   Declaring 900 keeps the request inside the face's range so it clamps to the
   real 800 master instead. */
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/jetbrains-mono/JetBrainsMono-var.woff2?v=1") format("woff2");
}

/* Polaroid scene — the handwritten caption. Two real weights, because the
   caption's event line and FPts are 700. This is the one that was rendering
   as COMIC SANS on Windows and as an unpredictable cursive elsewhere. */
@font-face {
  font-family: "Kalam";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/kalam/Kalam-400.woff2?v=1") format("woff2");
}
@font-face {
  font-family: "Kalam";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/kalam/Kalam-700.woff2?v=1") format("woff2");
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE WORDMARK — one definition, every surface.
   Markup comes from templates/partials/_wordmark.html; never hand-build it.

   GEOMETRY lives here because it must be identical everywhere. COLOUR does
   not: the nav sits on emerald, the footer on paper, the overlays on a
   transparent broadcast ground, and those genuinely differ — so each
   stylesheet sets .lv / .sl colours and nothing else.

   Two things this protects, both of which broke when the UI face changed:

   1. ONE TEXT RUN. .lv and .sl sit inside .txt. As direct children of the
      inline-flex root they were separate FLEX ITEMS, and a browser cannot kern
      across a box boundary — so the e|S seam read as a space. Segoe UI kerned
      that pair to nothing and hid it; Chivo and Instrument Sans do not.

   2. THE DOT SITS ON THE x-HEIGHT. Baseline alignment puts the dot's bottom on
      the baseline, so its centre lands .22em above it; the target is .5ex,
      half the font's real x-height. `bottom` lifts the difference. Derived
      from the face's own metrics, so it re-solves for any font at any size —
      unlike a pixel nudge, which is right at exactly one size.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Pinned face. !important because static/style.css carries a blanket
   span{font-family:var(--font)!important} that would otherwise win. */
.ls-wm, .ls-wm .txt, .ls-wm .lv, .ls-wm .sl {
  font-family: "Chivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif !important;
}
.ls-wm {
  display: inline-flex;
  align-items: baseline;      /* NOT center — see note 2 above */
  font-weight: 800;           /* one weight everywhere; the app used to say 700 */
  letter-spacing: -.01em;
  line-height: 1;
  white-space: nowrap;
}
.ls-wm .txt { display: inline-block; white-space: nowrap; }
.ls-wm .mark {
  position: relative;
  display: inline-block;
  flex: none;
  margin-left: .17em;
  /* Xav 2026-08-23: the dot was reading larger than the real mark. Note the
     `bottom` is DERIVED from this size — half of it, subtracted from half the
     x-height — which is what keeps the dot centred on the last `e` rather than
     on the baseline. Change the size and the offset has to follow, or the
     centring Xav asked for silently breaks. */
  width: .34em;
  height: .34em;
  bottom: calc(0.5ex - 0.17em);
}
.ls-wm .mark i { position: absolute; inset: 0; border-radius: 9999px; background: #6ee7b7; }
.ls-wm .mark i.ping { animation: ls-wm-ping 2.4s cubic-bezier(0,0,.2,1) infinite; opacity: .75; }
@keyframes ls-wm-ping { 0% { transform: scale(1); opacity: .75 } 70%, 100% { transform: scale(2.2); opacity: 0 } }
@media (prefers-reduced-motion: reduce) { .ls-wm .mark i.ping { animation: none; opacity: 0 } }
