/* ── TV Leaderboard — designed for 1080p landscape display ── */

:root {
  --tv-green:      #0a5a46;
  --tv-green-stripe: #0d6250;
  --tv-green-mid:  #0f6e56;
  --tv-green-acc:  #5dcaa5;
  --tv-gold:       #d4a029;
  --tv-silver:     #94a3b8;
  --tv-bronze:     #b45309;
  --tv-blue:       #3b82f6;
  --tv-red:        #ef4444;
  --tv-white:      #ffffff;
  --tv-off-white:  #f1f5f9;
  --tv-slate:      #cbd5e1;
  --tv-hairline-rgb: 255,255,255;
  /* Fixed dark teal for the page header/footer bands — matches
     leaderboard.html's own header exactly (--green-700, #0f6e56) and,
     unlike --tv-green-mid, is NOT redefined by the light theme below: the
     header stays dark regardless of theme, same as leaderboard.html never
     has a light/dark toggle for its own header. */
  --tv-header-bg:  #0f6e56;

  /* Display settings — overridden per-event from tv.js (applyTvSettings),
     these are just the fallback defaults matching the original fixed design. */
  --tv-font-scale:   1;
  --tv-margin-top:   0vh;
  --tv-margin-bottom: 0vh;
  --tv-margin-side:  0vw;
  --tv-header-h:     11vh;
  --tv-footer-h:     8vh;
  --tv-banner-h:     6.5vh;
}

/* ── FULLSCREEN TOGGLE — small corner button, lives outside #tv-root so it
   survives every page re-render. Dimmed by default so it doesn't distract
   on the TV; brightens on hover/focus for whoever's setting the display up. */
#tv-fullscreen-btn {
  position: fixed;
  top: 0.6vh;
  right: 0.6vw;
  z-index: 1000;
  width: 3.2vh;
  height: 3.2vh;
  min-width: 28px;
  min-height: 28px;
  border: none;
  border-radius: 6px;
  background: rgba(0,0,0,0.35);
  color: #ffffff;
  font-size: 1.8vh;
  line-height: 1;
  cursor: pointer;
  opacity: 0.35;
  transition: opacity 0.2s ease;
}
#tv-fullscreen-btn:hover,
#tv-fullscreen-btn:focus {
  opacity: 1;
}
#tv-fullscreen-btn.is-fullscreen {
  opacity: 0;
  pointer-events: none;
}

/* ── LIGHT THEME — cloned from leaderboard.html's own palette (its
   --green-50 body, --green-700 header — see the header rule above, which
   uses the always-dark --tv-header-bg instead of this block, exactly
   matching how leaderboard.html's own header never changes with a
   light/dark toggle). Applied via data-tv-theme="light" on <html> (see
   tv.js applyTvSettings). Page background is a flat pale mint instead of
   the dark theme's diagonal stripe (--tv-green/--tv-green-stripe set to
   the same value here, so the repeating-linear-gradient below paints one
   solid color) — a busy stripe pattern reads as "TV design", not the
   clean flat page Johan asked to match. A few elements that must stay a
   fixed color regardless of theme (the red LIVE badge, the "under/even/
   over" score colors) are set explicitly further down rather than through
   these variables. */
:root[data-tv-theme="light"] {
  --tv-green:        #eaf6f2;
  --tv-green-stripe: #eaf6f2;
  --tv-green-acc:    #0f6e56;
  --tv-white:        #2c2c2a;
  --tv-slate:        #64748b;
  --tv-hairline-rgb: 0,0,0;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  width: 100%; height: 100%;
  background: repeating-linear-gradient(
    108deg,
    var(--tv-green) 0px, var(--tv-green) 90px,
    var(--tv-green-stripe) 90px, var(--tv-green-stripe) 180px
  );
  color: var(--tv-white);
  font-family: "Open Sans", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

#tv-root {
  width: 100vw; height: 100vh;
  padding: var(--tv-margin-top) var(--tv-margin-side) var(--tv-margin-bottom);
  display: flex; flex-direction: column;
}

/* ── TRANSITION ── */
.tv-fade { transition: opacity 0.35s ease; }
.tv-fade.out { opacity: 0; }

/* ── LEADERBOARD LAYOUT ── */
.tv-lb-wrap {
  display: flex; flex-direction: column; height: 100vh;
}

/* Header — cloned from leaderboard.html's own header (small logo, big bold
   competition title, LIVE badge, then a meta line: event name · course ·
   a format pill) instead of the old 3-band masthead/title-banner/header
   stack. Always the fixed dark teal --tv-header-bg regardless of the
   light/dark theme toggle, exactly like leaderboard.html's header never
   changes look either way. */
.tv-page-header {
  background: var(--tv-header-bg);
  color: #ffffff;
  padding: 1vh 3vw 1.3vh;
  flex-shrink: 0;
}
.tv-page-header-top {
  display: flex; align-items: center; gap: 1.2vw;
}
.tv-page-logo {
  height: 5vh; width: 5vh; min-height: 32px; min-width: 32px;
  border-radius: 8px; object-fit: contain;
  background: rgba(255,255,255,.15); padding: .4vh; flex-shrink: 0;
}
.tv-page-title {
  flex: 1; min-width: 0;
  font-family: "Montserrat", Arial, sans-serif; font-weight: 800;
  font-size: calc(min(2.6vw, 4.6vh) * var(--tv-font-scale)); line-height: 1.15;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tv-page-meta {
  font-size: calc(min(1.3vw, 2.3vh) * var(--tv-font-scale)); opacity: .85;
  margin-top: .7vh; display: flex; align-items: center; justify-content: space-between; gap: .6vw; flex-wrap: wrap;
}
.tv-page-meta-left {
  display: flex; align-items: center; gap: .6vw; flex-wrap: wrap;
}
.tv-page-format-pill {
  background: rgba(255,255,255,.2); border-radius: 10px;
  padding: .2vh .8vw; font-size: calc(min(1.1vw, 1.96vh) * var(--tv-font-scale)); font-weight: 600;
}
/* Event banner, now shown inline in the header row (same height as the
   logo) instead of its own full-width strip above it — see pageHeaderHtml. */
.tv-page-banner-inline {
  height: var(--tv-banner-h); max-height: 5vh; width: auto; max-width: 16vw;
  object-fit: contain; border-radius: 6px; flex-shrink: 0;
  background: #ffffff; padding: .2vh .4vw;
}
/* Presenting sponsor, now part of the header's meta line (right-aligned)
   instead of a separate, easy-to-miss block in the footer. */
.tv-header-sponsor {
  display: flex; align-items: center; gap: .5vw; flex-shrink: 0;
}
.tv-header-sponsor-label {
  font-size: calc(min(1vw, 1.778vh) * var(--tv-font-scale)); text-transform: uppercase; letter-spacing: .06em; opacity: .75;
}
.tv-header-sponsor-img {
  height: 12vh; min-height: 88px; max-width: 36vw; object-fit: contain;
}
.tv-header-sponsor-name {
  font-size: calc(min(3vw, 5.3vh) * var(--tv-font-scale)); font-weight: 800;
}

/* ── Admin-configured 3-block header (events.header_layout) — replaces
   .tv-page-header entirely when set. See headerBlocks.js (shared with
   leaderboard.html) for the HTML this styles: 35% Block 1 (logo(s) + one
   optional line), 30% Block 2 (admin-authored lines, each with its own
   size/color), 35% Block 3 (one optional line + logo(s), right-aligned). */
/* fr units, not %. A flex row with flex-basis:35%/30%/35% plus a gap adds
   the gap ON TOP of that 100%, overflowing the container — switching to
   Grid with PERCENTAGE columns has the exact same bug: percentage tracks
   are still sized against the full content box, then the gap is added on
   top of that, overflowing by the gap's width every time (confirmed with a
   real headless-browser render: block 3's right edge landed ~19px past the
   header's own edge, matching 2 x 2vw gap almost exactly). fr units are
   the one column unit that's sized from the space actually LEFT OVER after
   gaps are subtracted, which is what avoids this. */
.hb-header {
  display: grid; grid-template-columns: 35fr 30fr 35fr; align-items: stretch;
  padding: 1vh 3vw; gap: 2vw; flex-shrink: 0; overflow: hidden;
}
.hb-block { display: flex; flex-direction: column; justify-content: center; min-width: 0; }
.hb-block1 { align-items: flex-start; gap: .6vh; }
.hb-block2 { align-items: center; text-align: center; gap: .2vh; }
.hb-block3 { align-items: flex-end; text-align: right; gap: .6vh; }
.hb-logo {
  /* max-width:100% alone wasn't enough — .hb-block3's align-items:flex-end
     makes its children shrink-to-fit rather than stretch to the column's
     width, and a wide/landscape logo's own intrinsic size could still push
     past the column and get clipped by .hb-header's overflow:hidden. A
     hard vw cap (comfortably inside the ~35vw column) guarantees it never
     can, regardless of the logo's own aspect ratio. */
  max-height: 8vh; max-width: 28vw; object-fit: contain;
}
.hb-block1-text, .hb-block3-text {
  font-size: calc(min(1.4vw, 2.5vh) * var(--tv-font-scale)); font-weight: 600;
}
.hb-block2-line {
  font-family: "Montserrat", Arial, sans-serif; font-weight: 800; line-height: 1.2;
}
.hb-block2-line.hb-size-sm { font-size: calc(min(1.3vw, 2.3vh) * var(--tv-font-scale)); font-weight: 600; }
.hb-block2-line.hb-size-md { font-size: calc(min(1.8vw, 3.2vh) * var(--tv-font-scale)); }
.hb-block2-line.hb-size-lg { font-size: calc(min(2.6vw, 4.6vh) * var(--tv-font-scale)); }
.hb-block2-line.hb-size-xl { font-size: calc(min(3.4vw, 6vh) * var(--tv-font-scale)); }
.hb-block3-logos { display: flex; flex-direction: column; align-items: flex-end; gap: .6vh; }
.hb-live-badge {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--tv-red); color: #ffffff; border-radius: 4px;
  padding: 3px 12px; font-size: calc(min(1.3vw, 2.311vh) * var(--tv-font-scale)); font-weight: 800;
  letter-spacing: .1em; width: fit-content;
}
.hb-live-dot {
  width: 10px; height: 10px; border-radius: 50%; background: white;
  animation: pulse 1.2s ease infinite; display: inline-block;
}

.tv-live-badge {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--tv-red); color: #ffffff; border-radius: 4px;
  padding: 3px 12px; font-size: calc(min(1.3vw, 2.311vh) * var(--tv-font-scale)); font-weight: 800;
  letter-spacing: .1em; width: fit-content; flex-shrink: 0;
}
.tv-live-dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: white;
  animation: pulse 1.2s ease infinite;
}
@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%       { opacity: .4; transform: scale(.85); }
}

/* Column headers */
.tv-col-headers {
  display: grid;
  grid-template-columns: 7vw 1fr 10vw 14vw;
  padding: 1vh 3vw;
  background: rgba(var(--tv-hairline-rgb),.05);
  border-bottom: 1px solid rgba(var(--tv-hairline-rgb),.1);
  font-size: calc(min(1.1vw, 1.956vh) * var(--tv-font-scale)); font-weight: 700; color: var(--tv-slate);
  text-transform: uppercase; letter-spacing: .08em;
  flex-shrink: 0;
}

/* Player rows — .tv-rows is the fixed-height viewport (overflow hidden);
   .tv-rows-scroll-inner is what runScrollAndAdvance() in tv.js actually
   transforms. Rows get a genuinely fixed height (container ÷ rows-per-
   page, same visual density the old fixed pages used) instead of sharing
   the container via flex — a field bigger than one screen now overflows
   the inner wrapper (and scrolls) instead of every row just shrinking to
   fit; a smaller field simply leaves blank space below, same as a
   half-full page did before. */
.tv-rows { position: relative; flex: 1; overflow: hidden; display: flex; flex-direction: column; }
/* height:100% gives this a definite height (chained from .tv-rows' own
   flex:1-derived height) for .tv-row's own percentage height below to
   resolve against — deliberately NOT overflow:hidden here, only on
   .tv-rows above, so extra rows flow past this box's bottom edge instead
   of being clipped, and get scrolled into view instead. */
.tv-rows-scroll-inner { height: 100%; display: flex; flex-direction: column; }

.tv-row {
  display: grid;
  grid-template-columns: 7vw 1fr 10vw 14vw;
  padding: 0 3vw;
  align-items: center;
  border-bottom: 1px solid rgba(var(--tv-hairline-rgb),.06);
  height: calc(100% / var(--tv-rows-per-page, 12));
  flex: none;
}
/* Admin-configured "Identity Column" (Team Name/Club/Country) adds a 5th
   column between PLAYER and HOLES — only present in the DOM at all when
   the event's setting isn't "blank" (see tv.js renderLeaderboard). */
.tv-col-headers.has-identity,
.tv-row.has-identity {
  grid-template-columns: 7vw 1fr 12vw 10vw 14vw;
}
.tv-identity {
  font-size: calc(min(2.6vw, 4.622vh) * var(--tv-font-scale)); color: var(--tv-white); text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1;
}
.tv-row:nth-child(even) { background: rgba(var(--tv-hairline-rgb),.03); }
.tv-row.rank-1, .tv-team-row.rank-1 { background: rgba(251,191,36,.08); }
.tv-row.rank-2, .tv-team-row.rank-2 { background: rgba(148,163,184,.05); }
.tv-row.rank-3, .tv-team-row.rank-3 { background: rgba(180,83,9,.06); }

/* ── Team match play — live per-player points scoreboard ──────────────────
   One row per drawn group, one column per team (grid-template-columns set
   inline per page since team count varies) — sized naturally by content
   like .tv-match-row, not .tv-row's rows-per-page division, since it's a
   different shape (a name + a number stacked, not a single line). */
.tv-points-row {
  display: grid;
  gap: .6vw;
  padding: calc(1vh * var(--tv-font-scale)) 3vw;
  align-items: center;
  border-bottom: 1px solid rgba(var(--tv-hairline-rgb),.06);
}
.tv-points-header { padding-bottom: calc(.5vh * var(--tv-font-scale)); }
.tv-points-col-header {
  font-size: calc(min(1.3vw, 2.311vh) * var(--tv-font-scale)); font-weight: 800;
  letter-spacing: .05em; text-transform: uppercase; text-align: center;
}
.tv-points-rowlabel {
  font-size: calc(min(1.1vw, 1.956vh) * var(--tv-font-scale)); font-weight: 700; color: var(--tv-slate);
}
.tv-points-cell { display: flex; flex-direction: column; align-items: center; text-align: center; }
.tv-points-cell-empty { color: var(--tv-slate); text-align: center; }
.tv-points-player {
  font-size: calc(min(1.5vw, 2.667vh) * var(--tv-font-scale)); font-weight: 600; color: var(--tv-slate);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.tv-points-value {
  font-size: calc(min(2.2vw, 3.911vh) * var(--tv-font-scale)); font-weight: 900; color: var(--tv-slate);
  line-height: 1.1;
}
.tv-points-value.final { color: var(--tv-white); }
.tv-points-totals { background: rgba(var(--tv-hairline-rgb),.05); }
.tv-points-total-cell {
  font-size: calc(min(2.6vw, 4.622vh) * var(--tv-font-scale)); font-weight: 900; text-align: center;
}

.tv-rank {
  font-size: calc(min(2.8vw, 4.978vh) * var(--tv-font-scale)); font-weight: 900; text-align: center;
  line-height: 1;
}
.tv-rank.r1 { color: var(--tv-white); }
.tv-rank.r2 { color: var(--tv-white); }
.tv-rank.r3 { color: var(--tv-white); }
.tv-rank.rn { color: var(--tv-white); }
.tv-move { font-size: calc(min(1.1vw, 1.956vh) * var(--tv-font-scale)); font-weight: 700; line-height: 1; margin-top: 2px; }
.tv-move.up   { color: #4ade80; }
.tv-move.down { color: #f87171; }
.tv-move.same { color: var(--tv-slate); }

.tv-player { overflow: hidden; }
.tv-player-name {
  font-size: calc(min(2.2vw, 3.911vh) * var(--tv-font-scale)); font-weight: 700; line-height: 1.1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tv-player-div {
  font-size: calc(min(1.1vw, 1.956vh) * var(--tv-font-scale)); color: var(--tv-green-acc); font-weight: 600;
  margin-top: 2px; text-transform: uppercase; letter-spacing: .05em;
}
/* Team/pair rows show who's actually playing right on the same line as the
   team/country name (not a second line — a second line made every row
   taller than its allocated slot and rows overlapped each other) instead
   of the division label above (division label stays as-is for individual
   rows). Smaller and dimmer than the team name so the two don't compete;
   the parent .tv-player-name's own nowrap/ellipsis truncates the whole
   line together if it runs long. */
.tv-player-names-inline {
  font-size: 0.6em; font-weight: 600; color: var(--tv-slate); margin-left: 4px;
}
.tv-score-badges {
  font-size: calc(min(1.1vw, 1.956vh) * var(--tv-font-scale)); color: var(--tv-slate); font-weight: 600;
  margin-top: 2px;
}
/* One column per round (R1/R2/...), matching leaderboard.html's own
   .lb-round-cell — see tv.js's roundColHeadersHtml/roundCellsHtml comment
   for why this replaced the earlier inline "R1 57 · R2 —" text. Sized like
   .tv-holes just below (a plain per-round number, not the main score). */
.tv-round-cell {
  font-size: calc(min(1.6vw, 2.844vh) * var(--tv-font-scale)); color: var(--tv-slate);
  text-align: center; line-height: 1;
}

.tv-holes  { font-size: calc(min(1.6vw, 2.844vh) * var(--tv-font-scale)); color: var(--tv-slate); text-align: center; line-height: 1; }
.tv-holes-note { font-size: calc(min(1.2vw, 2.1vh) * var(--tv-font-scale)); color: var(--tv-slate); opacity: .75; margin-left: .15vw; }
.tv-footer-note { font-size: calc(min(1vw, 1.778vh) * var(--tv-font-scale)); color: var(--tv-slate); opacity: .8; }

.tv-score  { font-size: calc(min(3vw, 5.334vh) * var(--tv-font-scale)); font-weight: 900; text-align: right; line-height: 1; }
.tv-score.stab    { color: var(--tv-white); }
.tv-score.under   { color: var(--tv-blue); }
.tv-score.even    { color: var(--tv-white); }
.tv-score.over    { color: var(--tv-red); }
.tv-score.dns     { font-size: calc(min(1.6vw, 2.844vh) * var(--tv-font-scale)); color: var(--tv-slate); }

/* ── Team match play — live match-by-match page ──────────────────────────── */
.tv-match-rows { position: relative; flex: 1; overflow: hidden; padding: 0 3vw; }
/* Match rows/groups are already naturally sized (not flex-stretched like
   .tv-row), so unlike .tv-rows-scroll-inner this wrapper needs no special
   height rule — it just holds however many groups there are, overflowing
   .tv-match-rows' fixed viewport when there are more than fit. */
.tv-match-rows-scroll-inner { position: relative; }
.tv-match-group { margin-top: calc(1.4vh * var(--tv-font-scale)); }
.tv-match-group:first-child { margin-top: calc(1vh * var(--tv-font-scale)); }
.tv-match-label {
  font-size: calc(min(1.3vw, 2.311vh) * var(--tv-font-scale)); font-weight: 800; letter-spacing: .06em;
  color: var(--tv-green-acc); margin-bottom: calc(.5vh * var(--tv-font-scale));
}
.tv-match-row {
  display: grid; grid-template-columns: 1fr 12vw 4vw 1fr 10vw;
  align-items: center; gap: 1vw;
  padding: calc(.7vh * var(--tv-font-scale)) 0;
  border-bottom: 1px solid rgba(var(--tv-hairline-rgb),.08);
}
.tv-match-player {
  font-size: calc(min(1.7vw, 3.022vh) * var(--tv-font-scale)); font-weight: 700; line-height: 1.1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tv-match-player-b { text-align: right; }
.tv-match-player.leading { color: var(--tv-green-acc); }
/* Team-color rows (renderTeamMatchesPage) set color inline per team, which
   beats the rule above — a marker that inherits currentColor still shows
   the leader unambiguously regardless of which side it's on. */
.tv-match-player.leading::before { content: "▲ "; font-size: 0.7em; }
.tv-match-status {
  font-size: calc(min(1.3vw, 2.311vh) * var(--tv-font-scale)); font-weight: 700; color: var(--tv-white);
  text-align: center; white-space: nowrap;
}
.tv-match-status.final { color: var(--tv-green-acc); }
.tv-match-vs {
  font-size: calc(min(.9vw, 1.6vh) * var(--tv-font-scale)); color: var(--tv-slate); text-align: center;
}
.tv-match-thru {
  font-size: calc(min(1.1vw, 1.956vh) * var(--tv-font-scale)); color: var(--tv-slate); text-align: right;
  white-space: nowrap;
}

/* Footer — sponsor/clock on the left, page indicator on the right. The
   competition name and LIVE badge that used to live here moved up into
   the header (pageHeaderHtml) once that became the single source of
   truth for "what am I looking at", so this is just the leaderboard.
   html's own quiet refresh-bar equivalent now, not a second title band. */
.tv-footer {
  background: var(--tv-header-bg);
  border-top: 2px solid rgba(var(--tv-hairline-rgb),.1);
  padding: 0 3vw;
  display: flex; align-items: center; justify-content: space-between; gap: 2vw;
  min-height: var(--tv-footer-h); flex-shrink: 0;
}
/* Light theme's footer matches leaderboard.html's own footer strip — a
   plain white bar, not the dark teal every other theme/element in this
   band uses. */
:root[data-tv-theme="light"] .tv-footer {
  background: #ffffff;
  color: var(--tv-white);
}
.tv-footer-left {
  display: flex; align-items: center; gap: 16px;
  min-width: 0;
}
.tv-clock { font-weight: 600; }

/* ── DIVISION SEPARATOR ROW ── */
.tv-div-header {
  display: flex; align-items: center;
  padding: 0.6vh 3vw;
  background: rgba(22,163,74,.15);
  border-bottom: 1px solid rgba(22,163,74,.3);
  font-size: calc(min(1.2vw, 2.133vh) * var(--tv-font-scale)); font-weight: 800; color: var(--tv-green-acc);
  text-transform: uppercase; letter-spacing: .1em;
  flex-shrink: 0;
}

/* ── WATERMARK — faint background image behind leaderboard/ticker/tee-sheet
   content, never over the masthead/header/footer bands. Negative z-index
   guarantees it paints behind the in-flow row content regardless of DOM
   order (a positioned element with z-index:auto/0 would otherwise paint
   ON TOP of normal-flow siblings in the same stacking context). ── */
.tv-watermark {
  position: absolute; z-index: -1;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  max-width: 55%; max-height: 55%;
  opacity: 0.08;
  object-fit: contain;
  pointer-events: none;
}

/* ── TEE SHEET PAGE ── */
.tv-tee-row {
  display: grid;
  grid-template-columns: 10vw 1fr 10vw;
  padding: 0.8vh 3vw;
  align-items: center;
  border-bottom: 1px solid rgba(var(--tv-hairline-rgb),.06);
  height: calc(100% / var(--tv-rows-per-page, 12));
  flex: none;
}
.tv-tee-row:nth-child(even) { background: rgba(var(--tv-hairline-rgb),.03); }
.tv-tee-time, .tv-tee-players {
  font-size: calc(min(1.8vw, 3.2vh) * var(--tv-font-scale));
}
.tv-tee-group {
  font-size: calc(min(1.8vw, 3.2vh) * var(--tv-font-scale));
  text-align: right; color: var(--tv-slate);
}

/* ── TICKER (alternative to the paginated table) ── */
.tv-ticker-wrap {
  position: relative;
  flex: 1; overflow: hidden; display: flex; align-items: center;
  padding: 0 3vw;
}
.tv-ticker-track {
  display: flex; align-items: baseline; white-space: nowrap;
}
.tv-ticker-track.scroll {
  animation-name: tv-ticker-scroll;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.tv-ticker-track.align-left   { width: 100%; justify-content: flex-start; }
.tv-ticker-track.align-center { width: 100%; justify-content: center; }
.tv-ticker-track.align-right  { width: 100%; justify-content: flex-end; }
@keyframes tv-ticker-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
.tv-ticker-item {
  display: inline-flex; align-items: baseline; gap: 0.8vw;
  font-size: calc(min(2.4vw, 4.267vh) * var(--tv-font-scale));
  font-weight: 700; color: var(--tv-white);
  padding: 4px 10px; border-radius: 6px;
}
.tv-ticker-item.leader { background: rgba(212,160,41,.18); }
.tv-ticker-trophy { font-size: calc(min(2vw, 3.556vh) * var(--tv-font-scale)); }
.tv-ticker-pos { color: var(--tv-slate); }
.tv-ticker-score { font-weight: 900; }
.tv-ticker-sep { color: var(--tv-slate); margin: 0 1.5vw; font-size: calc(min(2vw, 3.556vh) * var(--tv-font-scale)); }

/* ── NO EVENT SCREEN ── */
.tv-no-event {
  height: 100vh;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 2vh;
}
.tv-no-event-icon { font-size: 8vw; }
.tv-no-event-title { font-size: 4vw; font-weight: 900; font-family: "Montserrat", Arial, sans-serif; }
.tv-no-event-sub   { font-size: 2vw; color: var(--tv-slate); }

/* ── SPONSOR SLIDE ── */
.tv-slide {
  height: 100vh;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center; padding: 6vh 8vw;
  position: relative;
  gap: 2.5vh;
}
.tv-slide-event {
  position: absolute; bottom: 3vh; right: 3vw;
  font-size: 1.2vw; opacity: .45; text-align: right;
}
.tv-slide-progress {
  position: absolute; bottom: 0; left: 0; right: 0;
  height: 5px; background: rgba(255,255,255,.2);
}
.tv-slide-progress-bar {
  height: 100%; background: rgba(255,255,255,.6);
  transition: width linear;
}
/* Retained for renderTextPage/renderImagePage, which don't use the sponsor
   card system (no logo, just admin-authored title/body text). */
.tv-slide-name {
  font-size: 5vw; font-weight: 900; line-height: 1.1; margin-bottom: 2vh;
  font-family: "Montserrat", Arial, sans-serif;
}
.tv-slide-tagline {
  font-size: 2.2vw; opacity: .8; font-weight: 500;
}
.tv-slide-logo {
  max-height: 25vh; max-width: 50vw;
  object-fit: contain; margin-bottom: 4vh;
}

/* ── SPONSOR CARD SYSTEM ── shared "white card + gold label" look used by
   every sponsor-facing page (Slide, Wall, Headline+3, 6-Grid), matching the
   card-and-shadow style used elsewhere in the product (e.g. the homepage
   sponsor spotlight) instead of bare logos floating on the flat TV green.
   Card text is a fixed dark ink rather than the page's own text_color,
   since the card itself is always white regardless of the TV's dark/light
   theme toggle — text_color still governs the gold label's surrounding
   page text (e.g. the corner event-name). */
.tv-sponsor-label {
  font-size: 1.15vw; font-weight: 800; letter-spacing: .18em;
  text-transform: uppercase; color: var(--tv-gold);
  text-align: center; font-family: "Montserrat", Arial, sans-serif;
}
.tv-sponsor-card {
  background: #ffffff;
  border-radius: 1.2vh;
  box-shadow: 0 1vh 3vh rgba(0,0,0,.28);
  color: #0f2a20;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center;
}
.tv-sponsor-card .tv-sponsor-name {
  font-weight: 800; font-family: "Montserrat", Arial, sans-serif; color: #0f2a20;
}
.tv-sponsor-card .tv-sponsor-tagline { color: #52685e; font-weight: 500; }

/* Shared by the plain Sponsor Slide and Headline+3's top slot — both are
   logo-only (no name/tagline). The card fills the available width and
   remaining flex height of its container (78vw wide, whatever height the
   surrounding page/section gives it); the logo uses width/height:100%
   (NOT max-width/max-height, which only ever shrinks an oversized image —
   it never enlarges a small one) so object-fit:contain both scales it up
   or down to fill that box as fully as possible without distorting it. */
.tv-sponsor-card--headline {
  width: 78vw;
  flex: 1;
  min-height: 0;
  padding: 3vh 4vw;
}
.tv-sponsor-card--headline .tv-sponsor-logo {
  width: 100%; height: 100%; object-fit: contain;
}

/* Headline+3's three lower blocks — same logo-only, fill-the-card treatment
   as the headline slot above, just at the row's smaller allotted size
   (each block is capped to 26vw by .tv-headline-bottom-row .tv-wall-item). */
.tv-sponsor-card--headline-block { padding: 1.5vh 1vw; height: 100%; width: 100%; }
.tv-sponsor-card--headline-block .tv-sponsor-logo { width: 100%; height: 100%; object-fit: contain; }

/* Used by the 6-Grid — unlike --headline-block, this card still shows a
   name/tagline below the logo, so the logo can't just take 100% of the
   card like the logo-only variants. flex:1 (+ min-height:0, so it can
   actually shrink to make room) lets it grow to fill whatever space is
   left over after the name/tagline's own natural height, upscaling via
   object-fit:contain the same way the other card sizes now do. */
.tv-sponsor-card--block { padding: 2vh 1.8vw; gap: .8vh; height: 100%; width: 100%; }
.tv-sponsor-card--block .tv-sponsor-logo { flex: 1; min-height: 0; width: 100%; object-fit: contain; }
.tv-sponsor-card--block .tv-sponsor-name { font-size: 1.3vw; }
.tv-sponsor-card--block .tv-sponsor-tagline { font-size: 1vw; }

.tv-sponsor-card--tile { padding: 1.1vh .9vw; gap: .5vh; height: 100%; width: 100%; }
.tv-sponsor-card--tile .tv-sponsor-logo { max-height: 6.5vh; max-width: 85%; object-fit: contain; }
.tv-sponsor-card--tile .tv-sponsor-name { font-size: .85vw; }
.tv-sponsor-card--tile .tv-sponsor-tagline { font-size: .68vw; }

/* ── SPONSOR WALL (fills the screen, repeating the sponsor list as many
   times as needed — like a printed sponsor board tiles its logos rather
   than leaving empty space for a small sponsor list) ── */
.tv-wall-page {
  height: 100vh;
  position: relative;
}
.tv-wall-grid {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  grid-template-rows: repeat(4, 1fr);
  height: 100%;
  width: 100%;
  padding: 1.5vh 1.5vw;
  gap: 1.2vh 1vw;
}
.tv-wall-item { min-height: 0; min-width: 0; }

/* ── HEADLINE + 3 SPONSORS ── */
.tv-headline-page {
  height: 100vh;
  display: flex; flex-direction: column;
  position: relative;
}
.tv-headline-top {
  flex: 1.15;
  min-height: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 1.5vh;
  padding: 2.5vh 5vw 1.5vh;
}
.tv-headline-bottom {
  flex: 1;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 1.8vh;
  padding: 2vh 4vw 3vh;
}
.tv-headline-bottom-row {
  display: flex;
  align-items: stretch; justify-content: center;
  gap: 2.5vw;
  width: 100%; height: 100%;
}
.tv-headline-bottom-row .tv-wall-item { flex: 1; max-width: 26vw; }

/* ── 6 SPONSORS GRID ── */
.tv-six-page {
  height: 100vh;
  display: flex; flex-direction: column;
  position: relative;
  padding-top: 2.5vh;
  gap: 1.5vh;
}
.tv-six-grid {
  flex: 1;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(2, 1fr);
  padding: 0 3vw 3vh;
  gap: 2vh 2vw;
}
.tv-six-block {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
}
/* .tv-six-block is stretched to its full, identical grid-cell size by
   Grid's own default stretch behaviour -- but its single child was only
   ever centered at its own content size, not told to fill that cell. Every
   card ended up as tall as its own logo happened to render, varying card
   to card by aspect ratio, instead of all 6 being a uniform size the way
   the Slide/Headline+3 cards already are. Same fix already used for
   Headline+3's row (.tv-headline-bottom-row .tv-wall-item), applied here. */
.tv-six-block .tv-wall-item { flex: 1; min-height: 0; width: 100%; }
