/* Sumo Basho Countdown — core styles
   Design language: Japanese minimalism + broadcast-timing-screen precision. */

:root {
  --ink: #14161b;
  --ink-soft: #4b4f58;
  --paper: #faf7f3;
  --paper-raised: #ffffff;
  --line: rgba(20, 22, 27, 0.08);
  --accent: #c8102e;       /* rising-sun red */
  --accent-soft: #ffd9e6;  /* sign pink */
  --accent-deep: #8f0c22;
  --gold: #c99a3e;
  --shadow-sm: 0 1px 2px rgba(20, 22, 27, 0.06), 0 1px 1px rgba(20, 22, 27, 0.04);
  --shadow-md: 0 8px 24px rgba(20, 22, 27, 0.10);
  --shadow-lg: 0 24px 60px rgba(20, 22, 27, 0.16);
  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 24px;
  --font-display: "Zen Kaku Gothic New", "Hiragino Sans", "Yu Gothic", "Helvetica Neue", Arial, sans-serif;
  --font-mono: "JetBrains Mono", "SF Mono", ui-monospace, Menlo, Consolas, monospace;
  --font-pixel: "Press Start 2P", "VT323", var(--font-mono);
  --font-led: "VT323", var(--font-mono);
  --led-glow: #ff5f80;
  --header-h: 64px;
  --transition-fast: 160ms cubic-bezier(.2,.8,.2,1);
  --transition-med: 320ms cubic-bezier(.2,.8,.2,1);
  --pixel: 4px; /* base "pixel" unit used for stepped borders */
  color-scheme: light;
}

/* Pixel-cut corners, used on cards, buttons, badges for a game-UI feel.
   Cheaper and more robust across breakpoints than image-based 9-slices.
   NOTE: deliberately does NOT include .flip-card. --pixel is a fixed 4px
   notch on every corner — fine on a panel/button that's 100+ px across,
   but a mobile digit box can render as small as ~16-20px, where that same
   4px notch eats a large fraction of each corner. clip-path clips
   everything in the box, not just the background, so it was cutting into
   the digit glyphs themselves — small enough boxes read as a cross/plus
   rather than a rectangle with clipped corners. .flip-card gets plain
   square corners instead (see below) so nothing ever eats into a digit. */
.pixel-corners, .hero-card, .panel, .t-card, .venue-card, .ghost-button,
.settings-panel, .icon-button, .t-status, .live-badge {
  clip-path: polygon(
    0 var(--pixel), var(--pixel) var(--pixel), var(--pixel) 0,
    calc(100% - var(--pixel)) 0, calc(100% - var(--pixel)) var(--pixel), 100% var(--pixel),
    100% calc(100% - var(--pixel)), calc(100% - var(--pixel)) calc(100% - var(--pixel)), calc(100% - var(--pixel)) 100%,
    var(--pixel) 100%, var(--pixel) calc(100% - var(--pixel)), 0 calc(100% - var(--pixel))
  );
}

[data-theme="dark"] {
  --ink: #f4f1ee;
  --ink-soft: #b9bcc4;
  --paper: #101216;
  --paper-raised: #181b21;
  --line: rgba(255, 255, 255, 0.09);
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.4);
  --shadow-md: 0 8px 24px rgba(0,0,0,0.45);
  --shadow-lg: 0 24px 60px rgba(0,0,0,0.55);
  color-scheme: dark;
}

* { box-sizing: border-box; }
html {
  scroll-behavior: smooth;
  /* Mobile browsers (Android Chrome especially) silently "boost" small
     text for readability, ignoring our clamp() floors on the countdown
     digits — invisible in desktop/headless testing since that boosting
     is a real-device-only behavior. Without this, the digital clock's
     deliberately tiny compact-mode floors (see responsive.css) can get
     inflated back past the width that made "seconds" fit on one line in
     the first place. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: var(--font-display);
  background: var(--paper);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  transition: background var(--transition-med), color var(--transition-med);
}

img { max-width: 100%; display: block; }
a { color: inherit; }
button, select, input { font-family: inherit; }

.pixel-art, .hero-sprite, .venue-scene, .hero-image, .loading-sprite, .t-card .t-venue-art,
.venue-card img, .champion-strip img, .particle {
  image-rendering: pixelated;
  image-rendering: -moz-crisp-edges;
  image-rendering: crisp-edges;
}

.brand-title, .panel h2, .basho-name, .live-badge, .t-status, .ghost-button,
.eyebrow, .settings-panel h2 {
  font-family: var(--font-pixel);
  letter-spacing: 0.02em;
}
.brand-title { font-size: 11px; line-height: 1.6; }
.panel h2 { font-size: 12px; line-height: 1.7; }
.basho-name { font-size: clamp(9px, 1.5vw, 13px); line-height: 1.8; }
.eyebrow { font-family: var(--font-pixel); font-size: clamp(7px, 1.1vw, 10px); }
.t-status { font-family: var(--font-pixel); font-size: 7px; }
.ghost-button, .settings-panel h2 { font-size: 10px; }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

/* ---------- App shell ---------- */
.app-shell {
  position: relative;
  min-height: 100vh;
  overflow-x: clip;
}

/* ---------- Topbar ---------- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 20px;
  min-height: var(--header-h);
  background: color-mix(in srgb, var(--paper) 85%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}

.brand { display: flex; align-items: center; gap: 10px; }
.brand-mark {
  width: 34px; height: 34px;
  background-image: url('../assets/icons/icon-192.png');
  background-size: cover;
  image-rendering: pixelated;
  box-shadow: var(--shadow-sm);
}
.brand-mark::before { content: ""; }
.brand-title { font-weight: 700; font-size: 15px; letter-spacing: 0.01em; }
.brand-subtitle { font-size: 11px; color: var(--ink-soft); }

.controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.icon-button {
  width: 38px; height: 38px;
  display: grid; place-items: center;
  border: 1px solid var(--line);
  background: var(--paper-raised);
  cursor: pointer;
  font-size: 16px;
  transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}
.icon-button:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.icon-button:focus-visible, a:focus-visible, button:focus-visible, select:focus-visible, input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.ghost-button {
  border: 1px solid var(--line);
  background: var(--paper-raised);
  color: var(--ink);
  padding: 8px 14px;
  border-radius: 999px;
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}
.ghost-button:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.ghost-button:disabled { cursor: default; opacity: 0.6; }
.ghost-button:disabled:hover { transform: none; box-shadow: none; }
.hidden { display: none !important; }

/* ---------- Hero ---------- */
.content { max-width: 1200px; margin: 0 auto; padding: 24px 20px 80px; }

.hero-card {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-lg);
  background: var(--paper-raised);
}

/* Wider "stage" aspect than before on purpose: the rikishi no longer fills
   the whole frame (see .hero-rig below), so there's a real venue scene
   to see either side of and above him. */
.hero-figure { position: relative; width: 100%; aspect-ratio: 16 / 10; max-height: 70vh; overflow: hidden; background: #10131c; }

/* Animated pixel-art venue backdrop, swapped by JS (venue.js / countdown.js)
   to match the upcoming or live tournament's host arena, day/night aware. */
.venue-scene {
  position: absolute; inset: 0;
  background-size: cover;
  background-position: center;
  transition: background-image 900ms ease, opacity 900ms ease;
}
.venue-scene::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(16,19,28,0) 40%, rgba(16,19,28,0.55) 100%);
}

/* Cheap CSS-only crowd band along the bottom of the venue — no extra image
   assets, just two rows of "heads" (radial-gradient dots) with a slow
   opacity flicker so the arena reads as occupied/alive behind the rikishi. */
.venue-crowd {
  position: absolute;
  left: 0; right: 0; bottom: 14%;
  height: 6%;
  min-height: 10px;
  background-image:
    radial-gradient(circle at 6px 60%, rgba(20,16,14,0.55) 0 3px, transparent 4px),
    radial-gradient(circle at 22px 40%, rgba(20,16,14,0.45) 0 3px, transparent 4px);
  background-size: 28px 100%;
  background-repeat: repeat-x;
  opacity: 0.7;
  animation: crowdFlicker 3.6s ease-in-out infinite;
  pointer-events: none;
}
@keyframes crowdFlicker {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 0.8; }
}
@media (prefers-reduced-motion: reduce) { .venue-crowd { animation: none; opacity: 0.65; } }

/* ---------- Hero rig: the rikishi + his sign, as one movable unit ---------- */
/* Anchored to the bottom of the stage and deliberately sized well under
   100% of the frame, so venue is visible above and to both sides.
   Hovering / focusing / tapping it (see js/hero.js) adds .approach, which
   scales it up and lifts it toward the viewer — "he steps forward to
   show you the sign". */
.hero-rig {
  position: absolute;
  left: 50%;
  bottom: 0;
  width: clamp(210px, 40%, 420px);
  aspect-ratio: 3 / 4;
  transform: translate(-50%, 7%) scale(0.8);
  transform-origin: 50% 100%;
  transition: transform 700ms cubic-bezier(.2,.8,.2,1);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  z-index: 2;
  /* Countdown text inside (see .hero-overlay) is sized in cqw, relative to
     THIS box, not the viewport — so it always fits the sign regardless of
     how big the rig itself is at a given breakpoint or approach-scale. */
  container-type: inline-size;
  container-name: hero-rig;
}
/* Older browsers without container-query support (no meaningful market
   share by 2026, but this keeps the clock legible rather than tiny/blank
   if it ever gets embedded somewhere odd): fall back to the previous
   viewport-relative sizing. */
@supports not (container-type: inline-size) {
  .flip-card { font-size: clamp(16px, 3.2vw, 40px) !important; }
  .clock-sep { font-size: clamp(12px, 2.2vw, 26px) !important; }
  .eyebrow { font-size: clamp(8px, 0.9vw, 12px) !important; }
  .live-badge { font-size: clamp(8px, 1vw, 12px) !important; }
}
.hero-rig:focus-visible { outline: 2px solid var(--gold); outline-offset: 6px; }
.hero-rig.approach {
  transform: translate(-50%, -8%) scale(1.06);
}
@media (prefers-reduced-motion: reduce) {
  .hero-rig, .hero-rig.approach { transition: none; }
}

/* The base <img> is kept for SEO/alt-text/no-JS/print; visually replaced by
   the animated sprite sheet layer once CSS animations are available. */
.hero-image { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; object-position: bottom center; opacity: 0; }

/* The idle sheet is a horizontal strip of 8 frames. The sprite itself is
   the viewport; the pseudo-element is the full 8-frame strip and moves by
   exactly one frame per step. This prevents the sheet from visibly scrolling
   and keeps every frame aligned to the viewport.
   IMPORTANT: the step count must be (frameCount - 1), not frameCount. With N
   equal-width frames there are only N-1 "jumps" needed to walk from frame 1
   to frame N (steps(N) instead divides the -87.5% travel distance into N
   equal jumps of total/N, which lands on fractional, non-frame-aligned
   offsets — that's what produced the smooth "scrolling sheet" look instead
   of a clean frame-swap). hero-meta.json's frameCount is 8, so this is
   steps(7, end). If frameCount ever changes, update both this and the
   -87.5% end value below (both derive from frameCount).
   Resting state is frame 0 of hero-idle-sheet.png itself (::before, below)
   — there is no separate hero-static.png in this layer anymore. Previously
   the resting pose was a distinct image, crossfaded in/out via opacity
   whenever the idle-sheet layer turned on/off; even though the two images
   were confirmed to align, that fade meant switching between two
   separately-authored files, and briefly ran a transition on every blink
   and every idle-sheet play. Now the strip is always visible and simply
   sits at translateX(0) at rest, so starting/ending a blink or the full
   idle loop is one instant frame-swap on a single image, not a fade
   between two. js/hero.js still adds/removes '.playing-idle' on
   #heroSprite at random intervals (see IDLE_TRIGGER_* in hero.js) to walk
   through all 8 frames once, then lets it snap back to frame 0 — rather
   than looping forever. hero-static.png itself still exists on disk and
   is still used by the plain <img class="hero-image"> above (SEO/no-JS/
   print fallback, see that rule) — it's just no longer part of this
   animated layer. */
.hero-sprite {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  overflow: hidden;
  background-repeat: no-repeat;
  background-position: center bottom;
  background-size: 100% 100%;
  background-color: transparent;
  filter: drop-shadow(0 10px 14px rgba(0,0,0,0.35));
  image-rendering: pixelated;
}
.hero-sprite::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 800%;
  height: 100%;
  background-image: url('../assets/pixel/hero-idle-sheet.png');
  background-repeat: no-repeat;
  background-size: 100% 100%;
  background-position: left bottom;
  image-rendering: pixelated;
  transform: translateX(0);
  animation: none;
  will-change: transform;
}
/* Full 8-frame walk, played once through at random intervals — see
   scheduleIdleAnim/playIdleAnim in hero.js. */
.hero-sprite.playing-idle::before {
  animation: heroIdle 1.2s steps(7, end) 1;
}
/* Blink: jumps the same layer straight to frame 1 (closed eyes, authored
   directly into hero-idle-sheet.png) and back — one frame of travel
   (-12.5% of the 800%-wide strip), instead of animating through all 8.
   hero.js runs a real open -> closed -> open sequence by toggling
   '.eyes-closed' on/off — see blinkAllowed()/idleAnimAllowed() in hero.js
   for why this and the full idle animation above are strictly mutually
   exclusive: they're the same element, so only one may drive its
   transform at a time. */
.hero-sprite.blinking.eyes-closed::before { transform: translateX(-12.5%); }

.hero-rig.approach .hero-sprite { filter: drop-shadow(0 16px 20px rgba(0,0,0,0.45)); }
/* Live basho: replace the idle strip with the dedicated celebration art. */
.hero-sprite.celebrate {
  background-image: url('../assets/pixel/hero-celebrate.png');
  background-size: 100% 100%;
}
.hero-sprite.celebrate::before { display: none; }

@keyframes heroIdle {
  from { transform: translateX(0); }
  to   { transform: translateX(-87.5%); }
}
@media (prefers-reduced-motion: reduce) {
  /* Belt-and-braces: hero.js already refuses to schedule/trigger the idle
     animation and blink under reduced motion, but this guarantees no
     motion even if those classes somehow end up on the element. */
  .hero-sprite::before,
  .hero-sprite.playing-idle::before,
  .hero-sprite.blinking.eyes-closed::before { animation: none; transform: translateX(0); }
}

/* The overlay is positioned to match the blank sign panel baked into the
   hero art (see assets/pixel/hero-meta.json — sign box measured in the
   latest hero-static/hero-celebrate artwork, which agree closely: left
   25.92%, right 73.25%, top 53.2%, bottom 77.45%). Percentages of the
   .hero-rig (not the whole stage), so it never overflows the sign at any
   breakpoint or rig scale.
   There's now a single idle-sheet image behind every pose (see hero-sprite
   above) — frame 0 doubles as the resting pose, hero-idle-sheet.png's
   other 7 frames are the idle walk/blink, and hero-celebrate.png is worn
   for an entire live tournament rather than a quick flash — so it matters
   that all of these agree on where the sign sits, and they do.
   The sign in this artwork is smaller than the original design's, so the
   text below is also scaled down (see the cqw values on .live-badge/
   .eyebrow/.countdown-shell/.digital-clock/.clock-sep/.flip-card/
   .clock-label/.basho-name/.basho-meta/.progress-text, all reduced by
   the same ~0.52 ratio) — the px floors are left unchanged so nothing
   becomes unreadably small; on the rare viewport/zoom combination where
   that floor is wider than the sign, the existing flex-wrap safety net
   kicks in rather than clipping. */
.hero-overlay {
  position: absolute;
  left: 25.92%;
  top: 53.2%;
  width: 47.33%;
  height: 24.25%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(1px, 0.35cqw, 5px);
  padding: 1.5%;
  box-sizing: border-box;
  overflow: hidden;
  text-align: center;
  color: #f6ead8;
  /* BUGFIX: every cqw value inside here (flip-card, clock-sep, eyebrow,
     etc.) was resolving against .hero-rig's inline size, not this box's —
     the only other container in the tree is .hero-rig, which is over 2x
     wider than the sign area (this box is 47.33% of it). That made the
     clock size itself as if it had ~2x the room it actually has, so on
     phones the 4th (seconds) column reliably didn't fit, wrapped, and got
     clipped by this element's fixed height + overflow:hidden. Establishing
     the query container here instead makes every descendant cqw actually
     relative to the space they need to fit in. */
  container-type: inline-size;
  container-name: hero-overlay;
}
.hero-overlay .basho-meta { color: #cdbfae; }
.hero-overlay .eyebrow { color: #ffb37a; }

.hero-hint {
  position: absolute;
  left: 50%;
  bottom: 4%;
  transform: translateX(-50%);
  z-index: 3;
  font-family: var(--font-pixel);
  font-size: 8px;
  letter-spacing: 0.06em;
  color: #f6ead8;
  background: rgba(16,19,28,0.55);
  border: 1px solid rgba(255,255,255,0.18);
  border-radius: 999px;
  padding: 5px 12px;
  pointer-events: none;
  opacity: 0.85;
  animation: hintPulse 2.4s ease-in-out infinite;
  transition: opacity 400ms ease;
}
.hero-hint.hidden { opacity: 0; }
@keyframes hintPulse {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 0.9; }
}
@media (hover: hover) and (pointer: fine) { .hero-hint { display: none; } }
@media (prefers-reduced-motion: reduce) { .hero-hint { animation: none; } }

.live-badge {
  position: absolute;
  top: -10px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--accent);
  color: #fff;
  font-weight: 700;
  font-size: clamp(8px, 2.3cqw, 13px);
  padding: 3px 10px;
  border-radius: 999px;
  display: flex;
  align-items: center;
  gap: 6px;
  box-shadow: 0 4px 12px rgba(200,16,46,0.45);
  white-space: nowrap;
}
.live-badge .dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: #fff;
  animation: liveBlink 1.2s ease-in-out infinite;
}

.eyebrow {
  font-size: clamp(8px, 2.1cqw, 13px);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--accent-deep);
  font-weight: 700;
}

.countdown-shell { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: clamp(1px, 0.55cqw, 5px); width: 100%; min-width: 0; min-height: 0; overflow: hidden; }

/* flex-wrap is a deliberate safety net: even if the cqw sizing below is
   ever slightly too generous for some odd viewport/zoom combination, the
   clock wraps onto a second line instead of ever clipping or overflowing
   the sign — it must never overflow, per the brief. */
.digital-clock {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: center;
  gap: clamp(2px, 1.3cqw, 10px);
  width: 100%;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}
.clock-part { display: flex; flex-direction: column; align-items: center; gap: 1px; min-width: 0; flex: 0 0 auto; }
.clock-sep {
  font-family: var(--font-led);
  font-weight: 400;
  font-size: clamp(14px, 5.2cqw, 34px);
  color: var(--led-glow);
  padding-bottom: clamp(6px, 2.1cqw, 12px);
  opacity: 0.75;
  flex: 0 0 auto;
}

.pixel-range {
  -webkit-appearance: none; appearance: none;
  width: 120px; height: 10px;
  background: var(--line);
  outline: none;
  image-rendering: pixelated;
}
.pixel-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px; height: 18px;
  background: var(--accent);
  border: 2px solid var(--ink);
  cursor: pointer;
}
.pixel-range::-moz-range-thumb {
  width: 14px; height: 18px;
  background: var(--accent);
  border: 2px solid var(--ink);
  cursor: pointer;
  border-radius: 0;
}

.volume-control { display: flex; align-items: center; gap: 8px; }
.volume-value {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-soft);
  min-width: 2.6em;
  text-align: right;
}

.setting-row.tz-preview-row { justify-content: flex-start; padding-top: 0; }
.tz-preview {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-soft);
  background: var(--line);
  border-radius: 6px;
  padding: 6px 10px;
  width: 100%;
}
.tz-preview strong { color: var(--ink); font-weight: 700; }

.flip-card {
  position: relative;
  background: #0c0e14;
  color: var(--led-glow);
  padding: clamp(2px, 0.7cqw, 6px) clamp(3px, 0.9cqw, 9px);
  font-family: var(--font-led);
  font-weight: 400;
  font-size: clamp(16px, 6.8cqw, 42px);
  line-height: 1;
  box-shadow: inset 0 0 0 2px #000, inset 0 0 0 4px rgba(255,95,128,0.25), 0 0 12px rgba(255,95,128,0.35);
  min-width: 1.35em;
  text-align: center;
  overflow: hidden;
  text-shadow: 0 0 6px var(--led-glow), 0 0 14px rgba(255,95,128,0.6);
}
/* faint LED scanline grid, purely decorative — kept subtle deliberately:
   at 0.5 opacity / rgba(0,0,0,0.35) lines this reads fine at normal zoom,
   but the flat 1px lines don't shrink with pinch-zoom, so on a zoomed-in
   phone screenshot they turn into visibly thick stripes sitting right on
   top of the digits (worst case: exactly over an LED-font digit's
   mid-height stroke, making e.g. "4" misread as "2"). Lowered well below
   the level where that happens. */
.flip-card::before {
  content: "";
  position: absolute; inset: 0;
  background: repeating-linear-gradient(0deg, rgba(0,0,0,0.18) 0 1px, transparent 1px 3px);
  pointer-events: none;
  opacity: 0.22;
}
.flip-card::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 1px;
  background: rgba(0,0,0,0.22);
}
.digit { display: inline-block; position: relative; z-index: 1; }
.digit.flip { animation: digitFlip 420ms steps(4); }

.clock-label {
  font-size: clamp(6px, 1.6cqw, 11px);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-soft);
  font-weight: 600;
}

.basho-name { font-weight: 700; font-size: clamp(8px, 2.25cqw, 15px); margin-top: 1px; max-width: 100%; overflow-wrap: anywhere; line-height: 1.05; }
.basho-meta { font-size: clamp(6px, 1.55cqw, 11px); color: var(--ink-soft); max-width: 100%; overflow-wrap: anywhere; line-height: 1.05; }

.progress-wrap { width: 92%; max-width: 100%; display: flex; align-items: center; gap: clamp(2px, 0.8cqw, 8px); margin-top: 1px; min-width: 0; }
.progress-line {
  flex: 1;
  height: 6px;
  border-radius: 999px;
  background: rgba(20,22,27,0.12);
  overflow: hidden;
}
.progress-line span {
  display: block; height: 100%;
  background: linear-gradient(90deg, var(--accent), var(--gold));
  border-radius: 999px;
  transition: width 600ms ease;
}
.progress-text { font-size: clamp(5px, 1.4cqw, 10px); color: var(--ink-soft); white-space: normal; overflow-wrap: anywhere; min-width: 0; }

/* ---------- Panels ---------- */
.info-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 16px;
  margin-top: 24px;
}
.panel {
  grid-column: span 6;
  background: var(--paper-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 20px;
  box-shadow: var(--shadow-sm);
}
.panel-wide { grid-column: span 12; }
.panel h2 { margin: 0 0 12px; font-size: 16px; }
.panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.panel-note { font-size: 12px; color: var(--ink-soft); }
.stack { display: flex; flex-direction: column; gap: 10px; }

.tournament-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 12px;
}
.t-card {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 14px;
  background: var(--paper);
  display: flex;
  flex-direction: column;
  gap: 6px;
  transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}
.t-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); }
.t-card .t-name { font-weight: 700; font-size: 14px; }
.t-card .t-venue { font-size: 12px; color: var(--ink-soft); }
.t-card .t-date { font-size: 12px; color: var(--ink-soft); }
.t-card .t-count { font-family: var(--font-mono); font-weight: 700; font-size: 15px; color: var(--accent-deep); }
.t-card .t-status {
  align-self: flex-start;
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
  padding: 2px 8px; border-radius: 999px;
  background: rgba(200,16,46,0.1); color: var(--accent-deep); font-weight: 700;
}

.row { display: flex; justify-content: space-between; gap: 12px; font-size: 13px; padding: 6px 0; border-bottom: 1px dashed var(--line); }
.row:last-child { border-bottom: none; }
.row .k { color: var(--ink-soft); }
.row .v { font-weight: 600; text-align: right; }
.row-yours .k, .row-yours .v { color: var(--accent); font-weight: 700; }
.tz-note { font-size: 11px; color: var(--ink-soft); padding-top: 6px; }

.venue-card { display: flex; gap: 12px; align-items: center; border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 10px; }
.venue-card img { width: 96px; height: 54px; object-fit: cover; flex-shrink: 0; border: 1px solid var(--line); }
.venue-card .v-body { flex: 1; min-width: 0; }
.venue-card .v-name { font-weight: 700; font-size: 13px; }
.venue-card .v-meta { font-size: 11px; color: var(--ink-soft); }
.venue-card .v-link { font-size: 11px; color: var(--accent-deep); text-decoration: none; font-weight: 600; }

.champion-strip { display: flex; gap: 12px; align-items: center; }
.champion-strip img { width: 56px; height: 56px; object-fit: contain; background: var(--paper); border: 1px solid var(--line); }

/* ---------- News panel ---------- */
.news-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  min-height: 24px;
}
.news-item {
  display: flex;
  flex-direction: column;
  gap: 4px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  background: var(--paper);
  text-decoration: none;
  color: inherit;
  transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}
.news-item:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); }
.news-source {
  font-family: var(--font-pixel);
  font-size: 8px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-deep);
}
.news-title { font-size: 13px; font-weight: 600; line-height: 1.4; }
.news-time { font-size: 11px; color: var(--ink-soft); margin-top: auto; }

.news-sources {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px dashed var(--line);
}
.news-source-links { display: flex; flex-wrap: wrap; gap: 6px 16px; }
.news-source-links-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 10px 0 6px;
}
.news-sources a {
  font-size: 11px;
  font-weight: 600;
  color: var(--accent-deep);
  text-decoration: none;
}
.news-sources a:hover { text-decoration: underline; }

/* Live Streams tab: no longer basho-gated (see js/streams.js header
   comment for why) — every entry always renders, and each card's own
   live/offline state (is-live vs is-offline below) does the "nothing's on
   right now" messaging per-channel instead of one big panel-level gate. */
.stream-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 14px;
  min-height: 24px;
}
.stream-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 10px;
  background: var(--paper);
}
.stream-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.stream-platform {
  font-family: var(--font-pixel);
  font-size: 8px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-deep);
}
.stream-live-dot {
  /* .live-badge is normally absolutely centered over the hero sign — reset
     that positioning to sit inline in the stream card header instead. */
  position: static;
  top: auto; left: auto; transform: none;
  font-size: 10px;
}
.stream-card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
}
.stream-card-foot a { font-size: 11px; font-weight: 600; color: var(--accent-deep); text-decoration: none; flex: 0 0 auto; }
.stream-card-foot a:hover { text-decoration: underline; }
.stream-card-badges, .video-card-badges { display: flex; flex-wrap: wrap; gap: 4px; }
.panel-external-link {
  display: inline-block;
  margin-top: 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--accent-deep);
  text-decoration: none;
}
.panel-external-link:hover { text-decoration: underline; }

.banzuke-columns { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 10px; }
.banzuke-column-head {
  font-family: var(--font-pixel);
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding-bottom: 6px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 6px;
}
.banzuke-row { display: flex; justify-content: space-between; gap: 8px; padding: 4px 0; font-size: 13px; }
.banzuke-rank { color: var(--ink-soft); font-size: 11px; flex: 0 0 auto; }
.banzuke-name { font-weight: 600; text-align: right; }
@media (max-width: 640px) {
  .banzuke-columns { grid-template-columns: 1fr; gap: 4px; }
}

.torikumi-list { display: flex; flex-direction: column; gap: 6px; }
.torikumi-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-size: 13px;
}
.torikumi-row.is-decided { background: var(--paper-alt, rgba(0,0,0,0.03)); }
.torikumi-east, .torikumi-west { font-weight: 600; }
.torikumi-east.is-winner, .torikumi-west.is-winner { color: var(--accent-deep); }
.torikumi-vs { color: var(--ink-soft); font-size: 11px; }
.torikumi-result { flex: 1 0 100%; text-align: center; font-size: 11px; color: var(--ink-soft); }

.rikishi-link {
  background: none; border: none; padding: 0; margin: 0;
  font: inherit; color: inherit; cursor: pointer;
  text-decoration: underline dotted; text-underline-offset: 2px;
}
.rikishi-link:hover { color: var(--accent-deep); }

.rikishi-modal { position: fixed; inset: 0; z-index: 70; display: flex; align-items: center; justify-content: center; }
.rikishi-modal.hidden { display: none; }
.rikishi-modal-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.55); }
.rikishi-modal-body {
  position: relative;
  background: var(--paper);
  color: var(--ink);
  padding: 20px;
  max-width: 340px;
  width: calc(100% - 40px);
  max-height: 80vh;
  overflow-y: auto;
}
.rikishi-modal-body h3 { margin: 0 0 12px; font-size: 18px; }
.rikishi-modal-close {
  position: absolute; top: 8px; right: 8px;
  background: none; border: none; font-size: 22px; line-height: 1;
  color: var(--ink-soft); cursor: pointer; padding: 4px 8px;
}
.rikishi-modal-close:hover { color: var(--ink); }
.rikishi-modal-jp { font-size: 12px; font-weight: 400; color: var(--ink-soft); }
.rikishi-stats-table { width: 100%; border-collapse: collapse; margin-top: 12px; font-size: 12px; }
.rikishi-stats-table th, .rikishi-stats-table td { padding: 4px 6px; text-align: center; border-bottom: 1px solid var(--line); }
.rikishi-stats-table th { color: var(--ink-soft); font-weight: 600; }
.rikishi-stats-table td:first-child, .rikishi-stats-table th:first-child { text-align: left; }
.video-badge {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 999px;
  background: var(--paper-alt, rgba(0,0,0,0.06));
  color: var(--ink-soft);
}
.video-badge-official { background: rgba(255,95,128,0.16); color: var(--accent-deep); }
.video-card-subtitle { font-weight: 400; color: var(--ink-soft); }

/* Filter bars: shared between the Streams and Videos tabs. Category
   options are populated at render time from whatever's actually in the
   loaded JSON (see js/videos.js/js/streams.js renderFilterBar), so
   "Highlights only"/"Livestreams only"/"Analysis"/"Stable channels"/
   "Amateur sumo" from the original brief are all just category filter
   values — no separate boolean filter needed per category. "Divisions
   covered" from that same brief isn't implemented: none of the source
   data (CSV or otherwise) has real per-channel division data, and
   fabricating it would be worse than leaving it out. */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
  padding-bottom: 10px;
  border-bottom: 1px dashed var(--line);
}
.filter-select { min-width: 130px; }
.filter-checkbox { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ink-soft); cursor: pointer; }
.filter-checkbox input { accent-color: var(--accent-deep); }
.filter-empty { grid-column: 1 / -1; font-size: 13px; color: var(--ink-soft); text-align: center; padding: 18px 0; }

/* Live stream cards: click-to-embed instead of auto-loading an iframe per
   card (lighter on load, and avoids the Twitch parent-domain embed being
   the very first thing that runs). The poster shows viewer count when
   VIEWER_STATS_ENDPOINT is configured (see streams.js) — otherwise that
   line is simply absent, not a placeholder/zero. */
.stream-embed-poster {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px;
  background: #0c0e14;
}
.stream-viewers { font-size: 12px; font-weight: 600; color: var(--led-glow); }
.stream-watch-btn { font-size: 11px; padding: 8px 16px; }

.news-source-toggles {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  padding: 2px 0 10px;
}
.news-source-toggle { font-size: 12px; }

@media (max-width: 640px) {
  .stream-grid { grid-template-columns: 1fr; }
  .filter-bar { gap: 8px; }
  .filter-select { min-width: 0; flex: 1 1 auto; }
}

.stream-embed-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #0c0e14;
}
.stream-embed { position: absolute; inset: 0; width: 100%; height: 100%; }

/* The pixel-style "OFFLINE" sign — same LED font/board language as the
   countdown digits, but dim/desaturated instead of the glowing pink, so
   it visually reads as "powered down" rather than "actively counting". */
.stream-offline-sign {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #0c0e14;
  border-radius: var(--radius-sm);
  box-shadow: inset 0 0 0 2px #000, inset 0 0 0 4px rgba(150,155,165,0.18);
}
.stream-offline-sign::before {
  content: "";
  position: absolute; inset: 0;
  background: repeating-linear-gradient(0deg, rgba(0,0,0,0.35) 0 1px, transparent 1px 3px);
  pointer-events: none;
  opacity: 0.5;
  border-radius: inherit;
}
.stream-offline-text {
  position: relative;
  z-index: 1;
  font-family: var(--font-led);
  font-size: clamp(20px, 5cqw, 34px);
  letter-spacing: 0.08em;
  color: #6b7078;
  text-shadow: 0 0 6px rgba(180,185,195,0.25);
}

@media (max-width: 640px) {
  .stream-grid { grid-template-columns: 1fr; }
}

/* Watch Sumo panel: tabs switch between #streamsPanel and #videosTabPanel
   (see js/watch-tabs.js). Both tabpanels render their own content
   independently and keep it whether or not they're the visible one —
   switching tabs is purely a display:none/aria toggle, not a re-render. */
.watch-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--line);
}
.watch-tab {
  font-family: var(--font-pixel);
  font-size: 10px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  padding: 8px 4px 10px;
  cursor: pointer;
}
.watch-tab:hover { color: var(--ink); }
.watch-tab.is-active { color: var(--accent-deep); border-bottom-color: var(--accent-deep); }
.watch-tab:focus-visible { outline: 2px solid var(--accent-deep); outline-offset: 2px; }
.watch-tabpanel.hidden { display: none; }
.watch-tabpanel .panel-note { margin-bottom: 10px; }

.video-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 14px;
  min-height: 24px;
}
.video-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 10px;
  background: var(--paper);
}
.video-embed-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #0c0e14;
}
.video-embed { position: absolute; inset: 0; width: 100%; height: 100%; }
.video-card-foot { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.video-card-label { font-size: 12px; font-weight: 600; color: var(--ink); }
.video-card-link { font-size: 11px; font-weight: 600; color: var(--accent-deep); text-decoration: none; flex: 0 0 auto; }
.video-card-link:hover { text-decoration: underline; }
/* Channels without a verified channelId yet (see data/videos.json) render
   as a plain link-out card instead of a broken/empty embed. */
.video-card-linkonly {
  aspect-ratio: 16 / 9;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  background: #0c0e14;
  box-shadow: inset 0 0 0 2px #000, inset 0 0 0 4px rgba(255,95,128,0.18);
}
.video-card-linkonly-body { display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center; padding: 0 12px; }
.video-card-linkonly .video-card-label { color: var(--led-glow); font-size: 13px; }
.video-card-linkonly .video-card-link { color: var(--led-glow); opacity: 0.75; }
.video-card-linkonly:hover .video-card-link { opacity: 1; text-decoration: underline; }

@media (max-width: 640px) {
  .video-grid { grid-template-columns: 1fr; }
}


footer.site-footer {
  max-width: 1200px; margin: 0 auto; padding: 20px; text-align: center;
  color: var(--ink-soft); font-size: 12px;
}
.footer-credit { display: block; margin-top: 6px; }
.footer-credit a { color: var(--accent-deep); text-decoration: none; }
.footer-credit a:hover { text-decoration: underline; }

/* ---------- Settings panel ---------- */
.settings-dialog {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: none;
  align-items: flex-start;
  justify-content: flex-end;
}
.settings-dialog.open { display: flex; }
.settings-backdrop { position: absolute; inset: 0; background: rgba(10,10,14,0.4); }
.settings-panel {
  position: relative;
  width: min(340px, 92vw);
  height: 100%;
  background: var(--paper-raised);
  box-shadow: var(--shadow-lg);
  padding: 20px;
  overflow-y: auto;
  animation: slideIn var(--transition-med);
}
.settings-panel h2 { margin-top: 0; font-size: 16px; }
.setting-row { display: flex; align-items: center; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid var(--line); gap: 10px; }
.setting-row:last-child { border-bottom: none; }
.setting-row label:first-child { font-size: 13px; font-weight: 600; }
.setting-note { font-size: 11px; color: var(--ink-soft); padding: 0 0 10px; margin-top: -6px; }

.switch { position: relative; width: 42px; height: 24px; flex-shrink: 0; }
.switch input {
  position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer;
  z-index: 1; /* sits above .track/.thumb so clicks always reach the real checkbox */
}
.switch .track {
  position: absolute; inset: 0; background: var(--line); border-radius: 999px;
  transition: background var(--transition-fast);
  pointer-events: none; /* decorative — was silently swallowing clicks meant for input */
}
.switch .thumb {
  position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%;
  background: #fff; box-shadow: var(--shadow-sm);
  transition: transform var(--transition-fast);
  pointer-events: none; /* decorative — see .track above */
}
.switch input:checked + .track { background: var(--accent); }
.switch input:checked + .track + .thumb { transform: translateX(18px); }

select.mini { border: 1px solid var(--line); border-radius: 8px; background: var(--paper); color: var(--ink); padding: 6px 8px; font-size: 13px; }

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; }
}

/* ---------- Loading screen ---------- */
.loading-screen {
  position: fixed; inset: 0; z-index: 999;
  background: #0c0e14;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px;
  transition: opacity 500ms ease, visibility 500ms ease;
}
.loading-screen.hidden { opacity: 0; visibility: hidden; pointer-events: none; }
.loading-sprite {
  width: 48px; height: 48px;
  background-image: url('../assets/pixel/loading-sheet.png');
  background-size: 400% 100%;
  background-repeat: no-repeat;
  /* 4 frames need 3 steps to land exactly on each frame boundary — see the
     note on .hero-sprite::before above for why steps(4) here would land on
     fractional, blurred-looking offsets instead. */
  animation: loadingSpin 900ms steps(3) infinite;
}
@keyframes loadingSpin {
  0% { background-position-x: 0%; }
  100% { background-position-x: 100%; }
}
.loading-label { font-family: var(--font-pixel); font-size: 10px; color: #f6ead8; letter-spacing: 0.06em; }

/* ---------- Weather particles (pixel sprites, seasonal) ---------- */
.petal {
  position: absolute;
  top: -5%;
  will-change: transform, opacity;
  opacity: 0.9;
  width: 14px; height: 14px;
  background-repeat: no-repeat;
  background-size: contain;
  background-image: url('../assets/pixel/particle-sakura.png');
}
[data-season="summer"] .petal { background-image: url('../assets/pixel/particle-leaf-summer.png'); }
[data-season="autumn"] .petal { background-image: url('../assets/pixel/particle-leaf.png'); }
[data-season="winter"] .petal { background-image: url('../assets/pixel/particle-snow.png'); }

/* ---------- high contrast helper ---------- */
[data-contrast="high"] {
  --line: rgba(0,0,0,0.4);
  --ink-soft: var(--ink);
}
