:root {
  --bg: #0b0e14;
  --bg-elev: #151923;
  --bg-elev-2: #1d2230;
  --border: #2a3142;
  --text: #e6edf3;
  --text-dim: #8b95a7;
  --text-faint: #5a6478;
  --accent: #6ee7ff;
  --accent-2: #a78bfa;
  --success: #4ade80;
  --warning: #fbbf24;
  --danger: #f87171;
  --info: #60a5fa;
  --bg-glow-1: rgba(110, 231, 255, 0.08);
  --bg-glow-2: rgba(167, 139, 250, 0.06);
  --gap: 8px;
  --radius: 12px;
  --shadow: 0 14px 40px rgba(0,0,0,0.35);
  /* Global text-size knob. Every font-size is `calc(clamp(...) * var(--type-scale))`,
     so bumping this one number scales all text uniformly (1 = design size,
     1.15 = 15% bigger everywhere — handy for 10-foot TV readability). */
  --type-scale: 1.2;
}

/* Synthwave / 80s neon — hot pink + cyan on deep indigo */
[data-theme="synthwave"] {
  --bg: #16082c;
  --bg-elev: #1f0b3a;
  --bg-elev-2: #2a1147;
  --border: #4a1f7a;
  --text: #f5e9ff;
  --text-dim: #c4a7e7;
  --text-faint: #8867b5;
  --accent: #ff2e9a;
  --accent-2: #00f0ff;
  --success: #00ff9d;
  --warning: #ffb800;
  --danger: #ff3b6f;
  --info: #00d1ff;
  --bg-glow-1: rgba(255, 46, 154, 0.14);
  --bg-glow-2: rgba(0, 240, 255, 0.10);
}

/* Amber CRT — warm amber-on-near-black, VT100 vibe */
[data-theme="amber"] {
  --bg: #0a0500;
  --bg-elev: #150b02;
  --bg-elev-2: #1f1106;
  --border: #4d2a08;
  --text: #ffd27a;
  --text-dim: #c98e3b;
  --text-faint: #7a5418;
  --accent: #ffb000;
  --accent-2: #ff8c00;
  --success: #c2ff66;
  --warning: #ffd633;
  --danger: #ff6644;
  --info: #ffaa44;
  --bg-glow-1: rgba(255, 176, 0, 0.12);
  --bg-glow-2: rgba(255, 140, 0, 0.08);
}

/* Green phosphor CRT — single-hue retro terminal */
[data-theme="phosphor"] {
  --bg: #001408;
  --bg-elev: #002010;
  --bg-elev-2: #003018;
  --border: #1f6b3a;
  --text: #b6ffb0;
  --text-dim: #5fcc66;
  --text-faint: #2a6638;
  --accent: #39ff14;
  --accent-2: #00cc66;
  --success: #66ff66;
  --warning: #c8ff39;
  --danger: #ffd14d;
  --info: #4dffd1;
  --bg-glow-1: rgba(57, 255, 20, 0.10);
  --bg-glow-2: rgba(0, 204, 102, 0.06);
}

/* Pixel art — 16-bit JRPG menu palette: chunky cyan borders, gold/magenta
   accents on royal purple, hard offset shadows, near-square corners, mono
   font. Reads like a SNES status screen on the TV. */
[data-theme="pixel"] {
  --bg: #1a0b3d;
  --bg-elev: #2d1670;
  --bg-elev-2: #3d22a3;
  --border: #74d4ff;
  --text: #ffffff;
  --text-dim: #d4ebff;
  --text-faint: #c8b9f0;
  --accent: #ffe14a;
  --accent-2: #ff8bff;
  --success: #88ff88;
  --warning: #ffc94a;
  --danger: #ff7a96;
  --info: #88d6ff;
  --bg-glow-1: rgba(255, 225, 74, 0.08);
  --bg-glow-2: rgba(255, 107, 255, 0.07);
  --radius: 2px;
  --shadow: 4px 4px 0 rgba(0, 0, 0, 0.55);
}
[data-theme="pixel"] body {
  font-family: ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace;
}

/* Nord — Arctic polar-night palette (https://www.nordtheme.com) */
[data-theme="nord"] {
  --bg: #2e3440;
  --bg-elev: #3b4252;
  --bg-elev-2: #434c5e;
  --border: #4c566a;
  --text: #eceff4;
  --text-dim: #d8dee9;
  --text-faint: #7e8a9c;
  --accent: #88c0d0;
  --accent-2: #81a1c1;
  --success: #a3be8c;
  --warning: #ebcb8b;
  --danger: #bf616a;
  --info: #5e81ac;
  --bg-glow-1: rgba(136, 192, 208, 0.08);
  --bg-glow-2: rgba(129, 161, 193, 0.06);
}

/* Dracula — popular high-contrast dev palette */
[data-theme="dracula"] {
  --bg: #282a36;
  --bg-elev: #343746;
  --bg-elev-2: #44475a;
  --border: #6272a4;
  --text: #f8f8f2;
  --text-dim: #c9d1e3;
  --text-faint: #6272a4;
  --accent: #ff79c6;
  --accent-2: #8be9fd;
  --success: #50fa7b;
  --warning: #f1fa8c;
  --danger: #ff5555;
  --info: #bd93f9;
  --bg-glow-1: rgba(255, 121, 198, 0.10);
  --bg-glow-2: rgba(139, 233, 253, 0.08);
}

/* Tokyo Night — moody indigo with pastel accents */
[data-theme="tokyo-night"] {
  --bg: #1a1b26;
  --bg-elev: #24283b;
  --bg-elev-2: #2f334d;
  --border: #414868;
  --text: #c0caf5;
  --text-dim: #a9b1d6;
  --text-faint: #565f89;
  --accent: #bb9af7;
  --accent-2: #7dcfff;
  --success: #9ece6a;
  --warning: #e0af68;
  --danger: #f7768e;
  --info: #7aa2f7;
  --bg-glow-1: rgba(187, 154, 247, 0.09);
  --bg-glow-2: rgba(125, 207, 255, 0.07);
}

/* Gruvbox Dark — warm earth tones, mustard + coral */
[data-theme="gruvbox"] {
  --bg: #282828;
  --bg-elev: #32302f;
  --bg-elev-2: #3c3836;
  --border: #665c54;
  --text: #fbf1c7;
  --text-dim: #ebdbb2;
  --text-faint: #928374;
  --accent: #fabd2f;
  --accent-2: #fb4934;
  --success: #b8bb26;
  --warning: #fe8019;
  --danger: #cc241d;
  --info: #83a598;
  --bg-glow-1: rgba(250, 189, 47, 0.09);
  --bg-glow-2: rgba(251, 73, 52, 0.06);
}

/* Newsroom — cream-paper light theme; first non-dark option */
[data-theme="newsroom"] {
  --bg: #f7f3ea;
  --bg-elev: #fffdf6;
  --bg-elev-2: #efe9d8;
  --border: #cfc7b3;
  --text: #1a1611;
  --text-dim: #5a4f3f;
  --text-faint: #8a7e6a;
  --accent: #c0271e;
  --accent-2: #1f3a68;
  --success: #2f7a3e;
  --warning: #b87217;
  --danger: #a31621;
  --info: #1f3a68;
  --bg-glow-1: rgba(192, 39, 30, 0.05);
  --bg-glow-2: rgba(31, 58, 104, 0.04);
}

/* Brutalist — pure mono with one red accent, no glow, hard edges */
[data-theme="brutalist"] {
  --bg: #000000;
  --bg-elev: #0a0a0a;
  --bg-elev-2: #161616;
  --border: #ffffff;
  --text: #ffffff;
  --text-dim: #bbbbbb;
  --text-faint: #777777;
  --accent: #ff0033;
  --accent-2: #ffffff;
  --success: #ffffff;
  --warning: #ffffff;
  --danger: #ff0033;
  --info: #ffffff;
  --bg-glow-1: transparent;
  --bg-glow-2: transparent;
  --radius: 0px;
  --shadow: none;
}

/* Greyscale — single neutral ramp, all hue removed */
[data-theme="greyscale"] {
  --bg: #0d0d0d;
  --bg-elev: #1a1a1a;
  --bg-elev-2: #262626;
  --border: #3a3a3a;
  --text: #f0f0f0;
  --text-dim: #9c9c9c;
  --text-faint: #5e5e5e;
  --accent: #d4d4d4;
  --accent-2: #a0a0a0;
  --success: #c0c0c0;
  --warning: #a8a8a8;
  --danger: #888888;
  --info: #b0b0b0;
  --bg-glow-1: rgba(255, 255, 255, 0.04);
  --bg-glow-2: rgba(255, 255, 255, 0.02);
}

/* Windows 3.1 — softer teal desktop, cream Workgroups panels, chunkier
   raised bevels than Win95. Pre-Chicago, very early-90s LAN-party energy. */
[data-theme="windows-31"] {
  --bg: #3a8e8e;
  --bg-elev: #d4d0c8;
  --bg-elev-2: #efeae0;
  --border: #000080;
  --text: #000000;
  --text-dim: #303030;
  --text-faint: #707070;
  --accent: #000080;
  --accent-2: #5050a0;
  --success: #007a00;
  --warning: #806000;
  --danger: #800000;
  --info: #006080;
  --bg-glow-1: transparent;
  --bg-glow-2: transparent;
  --radius: 0px;
  --shadow:
    inset 2px 2px 0 #ffffff,
    inset -2px -2px 0 #707070,
    3px 3px 0 #000000;
}
[data-theme="windows-31"] body {
  font-family: "MS Sans Serif", "System", Tahoma, Geneva, sans-serif;
}

/* Windows 95 — teal desktop, grey dialogs, navy chrome, 3D bevel */
[data-theme="win95"] {
  --bg: #008080;
  --bg-elev: #c0c0c0;
  --bg-elev-2: #dfdfdf;
  --border: #000080;
  --text: #000000;
  --text-dim: #404040;
  --text-faint: #707070;
  --accent: #000080;
  --accent-2: #0000c0;
  --success: #008000;
  --warning: #808000;
  --danger: #800000;
  --info: #008080;
  --bg-glow-1: transparent;
  --bg-glow-2: transparent;
  --radius: 0px;
  --shadow: inset 1px 1px 0 #ffffff, inset -1px -1px 0 #808080, 2px 2px 0 #000;
}
[data-theme="win95"] body {
  font-family: "MS Sans Serif", "Microsoft Sans Serif", Tahoma, Geneva, sans-serif;
}

/* Windows XP — Luna desktop blue, ECE9D8 cream window chrome, orange accent */
[data-theme="winxp"] {
  --bg: #245edb;
  --bg-elev: #ece9d8;
  --bg-elev-2: #fbfaf2;
  --border: #0054e3;
  --text: #000000;
  --text-dim: #404040;
  --text-faint: #7a7a7a;
  --accent: #ff9c00;
  --accent-2: #0054e3;
  --success: #2da132;
  --warning: #ec9928;
  --danger: #c80000;
  --info: #0054e3;
  --bg-glow-1: rgba(255, 156, 0, 0.07);
  --bg-glow-2: rgba(0, 84, 227, 0.06);
  --radius: 8px;
  --shadow: 0 4px 14px rgba(0, 0, 0, 0.28);
}
[data-theme="winxp"] body {
  font-family: Tahoma, "Microsoft Sans Serif", Verdana, sans-serif;
}

/* Mac OS Classic (Platinum) — soft greys with muted blue accent */
[data-theme="macos-classic"] {
  --bg: #dddddd;
  --bg-elev: #ececec;
  --bg-elev-2: #f5f5f5;
  --border: #999999;
  --text: #000000;
  --text-dim: #444444;
  --text-faint: #888888;
  --accent: #4d4d99;
  --accent-2: #7a7aa4;
  --success: #4d7a4d;
  --warning: #a0791f;
  --danger: #a04040;
  --info: #4d4d99;
  --bg-glow-1: rgba(77, 77, 153, 0.06);
  --bg-glow-2: rgba(122, 122, 164, 0.04);
  --radius: 6px;
  --shadow: 1px 2px 4px rgba(0, 0, 0, 0.18);
}
[data-theme="macos-classic"] body {
  font-family: "Chicago", "Charcoal", "Geneva", -apple-system, sans-serif;
}

/* Pac-Man — black maze, yellow Pacman, blue walls, ghost-coloured accents */
[data-theme="pacman"] {
  --bg: #000000;
  --bg-elev: #0a0a3a;
  --bg-elev-2: #14149a;
  --border: #2121ff;
  --text: #ffff00;
  --text-dim: #ffffff;
  --text-faint: #aaaa00;
  --accent: #ffff00;
  --accent-2: #ffb8de;
  --success: #00ffff;
  --warning: #ffb851;
  --danger: #ff0000;
  --info: #00ffff;
  --bg-glow-1: rgba(255, 255, 0, 0.08);
  --bg-glow-2: rgba(33, 33, 255, 0.10);
  --radius: 4px;
  --shadow: 0 0 18px rgba(33, 33, 255, 0.45);
}
[data-theme="pacman"] body {
  font-family: ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace;
}

/* Game Boy DMG-01 — authentic LCD: dark pixels on light yellow-green screen.
   Inverting the original 4-shade palette (light bg + dark text) gives the
   real GB look AND high readability, which the dark-on-darker version
   didn't have. */
[data-theme="game-boy"] {
  --bg: #6e8b08;
  --bg-elev: #9bbc0f;
  --bg-elev-2: #c0d846;
  --border: #0f380f;
  --text: #0f380f;
  --text-dim: #1a4a14;
  --text-faint: #2d5d24;
  --accent: #0f380f;
  --accent-2: #306230;
  --success: #306230;
  --warning: #1a4a14;
  --danger: #0f380f;
  --info: #306230;
  --bg-glow-1: transparent;
  --bg-glow-2: transparent;
  --radius: 0px;
  --shadow: none;
}
[data-theme="game-boy"] body {
  font-family: ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace;
}

/* LCARS — Star Trek TNG computer UI: peach + lavender on pure black */
[data-theme="lcars"] {
  --bg: #000000;
  --bg-elev: #0a0a0a;
  --bg-elev-2: #1a1a1a;
  --border: #ff9900;
  --text: #ffffff;
  --text-dim: #ffcc99;
  --text-faint: #9999cc;
  --accent: #ff9900;
  --accent-2: #cc99ff;
  --success: #99ccff;
  --warning: #ffcc66;
  --danger: #cc6666;
  --info: #cc99ff;
  --bg-glow-1: rgba(255, 153, 0, 0.08);
  --bg-glow-2: rgba(204, 153, 255, 0.06);
  --radius: 24px;
  --shadow: none;
}
[data-theme="lcars"] body {
  font-family: "Helvetica Neue", "Antonio", "Oswald", Helvetica, sans-serif;
}

/* Mattressman — light-body theme matching the brand site: deep navy chrome,
   brand purple accent, sale-red highlight, with a purple→red gradient hero
   reference on .panel-title for the signature Flash Sale feel. */
[data-theme="mattressman"] {
  --bg: #f4f3f8;
  --bg-elev: #ffffff;
  --bg-elev-2: #efedf6;
  --border: #1d226c;
  --text: #1d226c;
  --text-dim: #4a4d8a;
  --text-faint: #8a8eb0;
  --accent: #5b21c4;
  --accent-2: #e23e44;
  --success: #16a34a;
  --warning: #d97706;
  --danger: #e23e44;
  --info: #5b21c4;
  --bg-glow-1: rgba(91, 33, 196, 0.06);
  --bg-glow-2: rgba(226, 62, 68, 0.05);
  --radius: 12px;
  --shadow: 0 4px 14px rgba(29, 34, 108, 0.10);
}

/* Tron — pure void + electric cyan lines, orange for danger/villain */
[data-theme="tron"] {
  --bg: #000000;
  --bg-elev: #001022;
  --bg-elev-2: #001a33;
  --border: #0ff0fc;
  --text: #ffffff;
  --text-dim: #88e9ff;
  --text-faint: #4a8aa3;
  --accent: #0ff0fc;
  --accent-2: #ff6b35;
  --success: #0ff0fc;
  --warning: #ffaa00;
  --danger: #ff6b35;
  --info: #88e9ff;
  --bg-glow-1: rgba(15, 240, 252, 0.10);
  --bg-glow-2: rgba(255, 107, 53, 0.06);
  --radius: 0px;
  --shadow: 0 0 16px rgba(15, 240, 252, 0.25);
}

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

html, body {
  height: 100%;
  background:
    radial-gradient(circle at 15% 0%, var(--bg-glow-1), transparent 40%),
    radial-gradient(circle at 90% 100%, var(--bg-glow-2), transparent 40%),
    var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif;
  font-feature-settings: "tnum";
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

/* ---------- Top-level grid ---------- */
.dashboard {
  display: grid;
  width: 100vw;
  height: 100vh;
  padding: var(--gap) var(--gap) 0 var(--gap);
  gap: var(--gap);
  grid-template-columns: 1.05fr 1.15fr 1.75fr 0.85fr;
  grid-template-rows: minmax(0, 0.55fr) 1fr auto auto;
  grid-template-areas:
    "clock    weather  spotify  calendar"
    "carousel carousel carousel carousel"
    "ticker   ticker   ticker   ticker"
    "dots     dots     dots     dots";
}
.news-ticker    { grid-area: ticker; }

.clock-panel    { grid-area: clock; }
.weather-panel  { grid-area: weather; }
.spotify-panel  { grid-area: spotify; }
.calendar-panel { grid-area: calendar; }
.carousel       { grid-area: carousel; }
.dots           { grid-area: dots; }

.panel {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: clamp(10px, 0.8vw, 32px) clamp(14px, 1vw, 38px);
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  box-shadow: var(--shadow);
}

.panel-title {
  font-size: calc(clamp(0.8rem, 0.8vw, 3rem) * var(--type-scale));
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-dim);
  font-weight: 600;
  margin-bottom: clamp(8px, 0.6vw, 20px);
  display: flex;
  align-items: center;
  gap: 12px;
}

.panel-title-text { letter-spacing: 0.12em; }

/* Small Spotify mark in the Now Playing panel header. */
.spotify-brand {
  width: clamp(18px, 1.3vw, 48px);
  height: auto;
  flex-shrink: 0;
  display: block;
}

.panel-count {
  background: var(--bg-elev-2);
  color: var(--accent);
  font-size: calc(clamp(0.65rem, 0.6vw, 2.2rem) * var(--type-scale));
  padding: 2px 10px;
  border-radius: 999px;
  letter-spacing: 0;
  text-transform: none;
}

/* ---------- Unified window-tag (24h / live 15m) ----------
   Used in panel titles AND in elmah subheaders. Just dot + colored text,
   no pill background, so the design is consistent regardless of context. */
.window-tag {
  display: inline-flex;
  align-items: center;
  gap: clamp(6px, 0.4vw, 14px);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: none;
}
.window-tag::before {
  content: "";
  display: inline-block;
  width: clamp(8px, 0.5vw, 16px);
  height: clamp(8px, 0.5vw, 16px);
  border-radius: 50%;
  flex-shrink: 0;
}
.window-tag--24h         { color: #93c5fd; }
.window-tag--24h::before { background: #3b82f6; }
.window-tag--live        { color: #fca5a5; }
.window-tag--live::before {
  background: #ef4444;
  animation: window-tag-pulse 1.6s ease-in-out infinite;
}
@keyframes window-tag-pulse {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.6); }
  50%      { opacity: 0.65; box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
}

/* When used inside a panel-count slot (panel header right-side), strip
   the default pill background so we don't double-up. */
.panel-count.window-tag {
  background: none;
  padding: 0;
  border-radius: 0;
}

.icon-btn {
  margin-left: auto;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  color: var(--text-dim);
  width: clamp(32px, 1.9vw, 66px);
  height: clamp(32px, 1.9vw, 66px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  cursor: pointer;
  font-family: inherit;
  padding: 0;
  transition: color .2s ease, border-color .2s ease, background .25s ease, transform .25s ease;
}
.icon-btn:hover  { color: var(--text); border-color: var(--text-dim); transform: scale(1.05); }
.icon-btn.active {
  color: var(--accent);
  border-color: var(--accent);
  background: rgba(110, 231, 255, 0.12);
  box-shadow: 0 0 0 6px rgba(110, 231, 255, 0.05);
}
.icon-btn[aria-busy="true"] { opacity: 0.6; cursor: wait; }
.icon-btn svg {
  width: 55%; height: 55%;
  display: block;
  fill: currentColor;
}

/* sub-cycle indicator (tiny dots inside a tile) */
.sub-dots {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 6px;
}
.sub-dots .sub-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  transition: background .25s ease, transform .25s ease, border-color .25s ease;
}
.sub-dots .sub-dot.active {
  background: var(--accent);
  border-color: var(--accent);
  transform: scale(1.4);
}

/* ---------- Clock ---------- */
.clock-panel {
  /* Time/date centred in the upper portion of the panel; fact-block pinned
     to the bottom via margin-top: auto. */
  align-items: center;
  text-align: center;
  padding: clamp(20px, 1.4vw, 56px) clamp(24px, 1.6vw, 64px);
}
.clock-time {
  margin: auto 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.time {
  font-size: calc(clamp(3rem, 4.5vw, 24rem) * var(--type-scale));
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  white-space: nowrap;
}
.time-seconds {
  font-size: 0.32em;
  font-weight: 600;
  margin-left: 0.15em;
  vertical-align: baseline;
  color: var(--text-dim);
  background: none;
  -webkit-background-clip: initial;
  background-clip: initial;
  letter-spacing: 0;
}
.date {
  font-size: calc(clamp(1rem, 1.2vw, 5rem) * var(--type-scale));
  color: var(--text-dim);
  margin-top: 10px;
  font-weight: 500;
}
/* "Random Fact" block — pinned to the bottom of the clock panel. Small
   label header above the fact text, separated from the time block above
   by a hairline. The fact text wraps and clamps to a few lines. */
.fact-block {
  margin-top: auto;
  padding-top: clamp(8px, 0.6vw, 18px);
  border-top: 1px solid var(--border);
  width: 100%;
  text-align: center;
}
.fact-label {
  font-size: calc(clamp(0.6rem, 0.55vw, 1.8rem) * var(--type-scale));
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-weight: 700;
  color: var(--text-faint);
  margin-bottom: clamp(4px, 0.35vw, 12px);
}
/* Rotator stage — fixed-height box the joke pane and sneez-o-meter pane share.
   Both panes are absolutely stacked and cross-fade; only .active is visible.
   The joke pane stays laid out even when faded so its scroll length can be
   measured at any time. */
.rotator-stage {
  position: relative;
  height: clamp(78px, 8.5vh, 180px);
}
.rotator-pane {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.6s ease;
  pointer-events: none;
}
.rotator-pane.active { opacity: 1; }
.fact-viewport {
  /* Clipping box that caps how much of the joke is visible at once. The
     inner .fact translates upward via JS-driven animation when its content
     overflows this viewport — ping-pong scroll for long jokes. Height is
     ~2 lines of the bumped font so any joke longer than that scrolls. */
  position: relative;
  overflow: hidden;
  max-height: 100%;
  mask-image: linear-gradient(to bottom, transparent 0, black 10%, black 90%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 10%, black 90%, transparent 100%);
}
.fact {
  font-size: calc(clamp(0.95rem, 0.95vw, 3rem) * var(--type-scale));
  color: var(--text);
  font-style: italic;
  line-height: 1.4;
  will-change: transform;
}
@keyframes factScroll {
  0%, 18%   { transform: translateY(0); }
  50%, 68%  { transform: translateY(var(--fact-scroll-end, 0)); }
  100%      { transform: translateY(0); }
}
.fact:empty::before { content: "—"; opacity: 0.4; font-style: normal; }

/* Sneez-o-meter — three round dial gauges (Grass / Trees / Weeds), mirroring
   the Met Office / Kleenex forecast layout. The arc sweep + colour both ride
   severity (the per-column --sneeze-color var); the day-peak count sits in the
   centre, the level word below. */
.sneeze {
  width: 100%;
  display: flex;
  align-items: flex-start;
  justify-content: space-around;
  gap: clamp(4px, 0.6vw, 18px);
}
.sneeze-cat {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(1px, 0.3vw, 7px);
}
.sneeze-cat[data-level="0"],
.sneeze-cat[data-level="1"] { --sneeze-color: var(--success); }
.sneeze-cat[data-level="2"] { --sneeze-color: var(--warning); }
.sneeze-cat[data-level="3"] { --sneeze-color: #fb923c; }
.sneeze-cat[data-level="4"] { --sneeze-color: var(--danger); }
.sneeze-ring {
  position: relative;
  width: clamp(34px, 2.6vw, 64px);
  height: clamp(34px, 2.6vw, 64px);
}
.sneeze-ring svg { display: block; width: 100%; height: 100%; }
.ring-track {
  fill: none;
  stroke: var(--border);
  stroke-width: 3.2;
}
.ring-fill {
  fill: none;
  stroke: var(--sneeze-color, var(--accent));
  stroke-width: 3.2;
  stroke-linecap: round;
  transition: stroke-dasharray 0.6s ease;
}
.sneeze-ring-val {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(clamp(0.72rem, 0.72vw, 2rem) * var(--type-scale));
  font-weight: 700;
  color: var(--text);
}
.sneeze-cat-name {
  font-size: calc(clamp(0.55rem, 0.5vw, 1.5rem) * var(--type-scale));
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 700;
  color: var(--text-dim);
  white-space: nowrap;
}
.sneeze-cat-level {
  font-size: calc(clamp(0.8rem, 0.8vw, 2.2rem) * var(--type-scale));
  font-weight: 700;
  color: var(--sneeze-color, var(--text));
  line-height: 1.05;
}

/* ---------- Weather ---------- */
/* Ambient tint + animated weather effects layer behind the content. */
.weather-panel {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.weather-panel::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--weather-tint, transparent);
  opacity: 0.18;
  filter: blur(8px) saturate(1.1);
  z-index: -1;
  pointer-events: none;
  transition: opacity 1.5s ease;
}
.weather-panel > .panel-title,
.weather-panel > .weather-body { position: relative; z-index: 1; }

.weather-fx {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 0;
}

/* Rain — diagonal streaks. */
.rain-drop {
  position: absolute;
  top: -20px;
  width: 1.5px;
  height: clamp(10px, 1vw, 27px);
  background: linear-gradient(180deg, transparent, rgba(180,210,255,0.55));
  border-radius: 1px;
  animation: rainFall linear infinite;
}
@keyframes rainFall {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(20px, 600px, 0); }
}

/* Snow — drifting circles with horizontal sway. */
.snow-flake {
  position: absolute;
  top: -10px;
  background: rgba(255,255,255,0.85);
  border-radius: 50%;
  box-shadow: 0 0 4px rgba(255,255,255,0.4);
  animation: snowFall linear infinite;
}
@keyframes snowFall {
  0%   { transform: translate3d(0, 0, 0); opacity: 0; }
  10%  { opacity: 0.95; }
  90%  { opacity: 0.7; }
  100% { transform: translate3d(var(--drift, 30px), 600px, 0); opacity: 0; }
}

/* Sun — soft glow + slow rotating ray pattern. */
.sun-glow {
  position: absolute;
  top: -30%;
  right: -20%;
  width: 70%;
  aspect-ratio: 1 / 1;
  background: radial-gradient(circle, rgba(255,200,100,0.4) 0%, transparent 65%);
  filter: blur(24px);
  animation: sunPulse 6s ease-in-out infinite;
}
@keyframes sunPulse {
  0%, 100% { transform: scale(1);   opacity: 0.45; }
  50%      { transform: scale(1.1); opacity: 0.65; }
}
.sun-rays {
  position: absolute;
  top: -50%;
  right: -50%;
  width: 200%;
  height: 200%;
  background: conic-gradient(
    from 0deg,
    transparent       0deg,  rgba(255,212,122,0.05) 6deg,  transparent 12deg,
    transparent      45deg,  rgba(255,212,122,0.05) 51deg, transparent 57deg,
    transparent      90deg,  rgba(255,212,122,0.05) 96deg, transparent 102deg,
    transparent     135deg,  rgba(255,212,122,0.05) 141deg,transparent 147deg,
    transparent     180deg,  rgba(255,212,122,0.05) 186deg,transparent 192deg,
    transparent     225deg,  rgba(255,212,122,0.05) 231deg,transparent 237deg,
    transparent     270deg,  rgba(255,212,122,0.05) 276deg,transparent 282deg,
    transparent     315deg,  rgba(255,212,122,0.05) 321deg,transparent 327deg
  );
  animation: sunRotate 90s linear infinite;
}
@keyframes sunRotate {
  to { transform: rotate(360deg); }
}

/* Clouds — drifting soft-edge ellipses. */
.cloud-drift {
  position: absolute;
  width: clamp(140px, 14vw, 420px);
  height: clamp(50px, 5vw, 150px);
  background: radial-gradient(ellipse at center, rgba(255,255,255,0.08) 0%, transparent 65%);
  filter: blur(12px);
  left: -200px;
  animation: cloudDrift linear infinite;
}
@keyframes cloudDrift {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(100vw + 200px)); }
}

/* Fog — heavier drifting mist. */
.fog-drift {
  position: absolute;
  inset: 30% -50% 30% -50%;
  background: radial-gradient(ellipse at center, rgba(220,225,235,0.18) 0%, transparent 60%);
  filter: blur(20px);
  animation: cloudDrift 40s linear infinite;
}

/* Thunderstorm — occasional flash overlay. */
.storm-flash {
  position: absolute;
  inset: 0;
  background: rgba(220,230,255,0.6);
  opacity: 0;
  animation: stormFlash 7s ease-in-out infinite;
}
@keyframes stormFlash {
  0%, 88%, 93%, 100% { opacity: 0; }
  89%, 92%           { opacity: 0.18; }
  91%                { opacity: 0; }
}

.weather-body {
  display: flex;
  flex-direction: column;
  gap: clamp(6px, 0.45vw, 16px);
  flex: 1;
  min-height: 0;
}
.weather-now {
  display: flex;
  align-items: center;
  gap: clamp(14px, 1vw, 39px);
}
.weather-temp {
  font-size: calc(clamp(1.6rem, 2.2vw, 6.8rem) * var(--type-scale));
  font-weight: 700;
  line-height: 1;
  transition: opacity .3s ease;
}
.weather-meta { flex: 1; min-width: 0; }
.weather-desc { font-size: calc(clamp(0.85rem, 0.85vw, 3.6rem) * var(--type-scale)); font-weight: 600; }
.weather-loc  { color: var(--text-dim); margin-top: 4px; font-size: calc(clamp(0.75rem, 0.7vw, 2.6rem) * var(--type-scale)); }

.weather-feels {
  text-align: right;
  flex-shrink: 0;
  border-left: 1px solid var(--border);
  padding-left: clamp(12px, 1vw, 33px);
}
.weather-feels .feels-label {
  font-size: calc(clamp(0.6rem, 0.55vw, 1.9rem) * var(--type-scale));
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-faint);
}
.weather-feels .feels-temp {
  font-size: calc(clamp(1rem, 1.1vw, 4.2rem) * var(--type-scale));
  font-weight: 700;
  line-height: 1.1;
  margin-top: 4px;
}

/* Inline sparkline of next 12h temperatures, with header + axis labels. */
.weather-spark {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.spark-header {
  display: flex;
  align-items: center;
  gap: clamp(6px, 0.55vw, 16px);
  font-size: calc(clamp(0.6rem, 0.58vw, 2.2rem) * var(--type-scale));
  color: var(--text-faint);
  letter-spacing: 0.08em;
}
.spark-header .spark-label {
  text-transform: uppercase;
}
.spark-header .spark-trend {
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.spark-header .spark-trend .arrow { color: var(--text-faint); margin: 0 2px; }
.spark-header .spark-range {
  margin-left: auto;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.spark-svg {
  width: 100%;
  height: clamp(24px, 1.6vw, 70px);
  display: block;
}
.spark-axis {
  display: flex;
  justify-content: space-between;
  font-size: calc(clamp(0.55rem, 0.52vw, 2rem) * var(--type-scale));
  color: var(--text-faint);
  letter-spacing: 0.08em;
}

/* Sub-view layer — daily/hourly cross-fade */
.weather-views {
  position: relative;
  flex: 1;
  min-height: 0;
}
.weather-view {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transform: translateY(6px);
  transition: opacity .5s ease, transform .5s ease;
}
.weather-view.active {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}

/* Vertical scrolling list of forecast rows.
   The inner .forecast-scroll-v contains DOUBLE the rows (duplicated) so the
   translateY(-50%) loop point lines up with the start visually. */
.forecast-list {
  position: relative;
  height: 100%;
  overflow: hidden;
  mask-image: linear-gradient(to bottom, transparent 0, black 5%, black 95%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 5%, black 95%, transparent 100%);
}
.forecast-scroll-v {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(3px, 0.3vw, 10px);
  padding-bottom: clamp(3px, 0.3vw, 10px);
  animation: scroll-y 130s linear infinite;
  will-change: transform;
}
@keyframes scroll-y {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, -50%, 0); }
}

.forecast-row {
  display: flex;
  align-items: center;
  gap: clamp(8px, 0.55vw, 18px);
  padding: clamp(5px, 0.4vw, 14px) clamp(8px, 0.6vw, 20px);
  background: var(--bg-elev-2);
  border-radius: 10px;
  flex-shrink: 0;
}
.forecast-row.fr-now {
  border-left: 3px solid var(--accent);
}
.forecast-row .fr-when {
  font-size: calc(clamp(0.78rem, 0.75vw, 3rem) * var(--type-scale));
  font-weight: 700;
  color: var(--accent);
  flex-shrink: 0;
  /* No fixed px width — em-based min so it scales with font size and never
     gets narrower than the content (e.g. "Tomorrow"). */
  min-width: 4em;
  font-variant-numeric: tabular-nums;
  padding-right: 0.5em;
}
.forecast-row .fr-icon {
  font-size: calc(clamp(1.1rem, 1.3vw, 6rem) * var(--type-scale));
  line-height: 1;
  flex-shrink: 0;
  /* Width follows font-size in em so the emoji glyph always fits its slot. */
  width: 1.4em;
  text-align: center;
}
.forecast-row .fr-desc {
  flex: 1;
  min-width: 0;
  font-size: calc(clamp(0.72rem, 0.7vw, 2.8rem) * var(--type-scale));
  color: var(--text);
  line-height: 1.35;
  padding-bottom: 0.08em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.forecast-row .fr-temp {
  font-weight: 700;
  font-size: calc(clamp(0.85rem, 0.85vw, 3.6rem) * var(--type-scale));
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
  padding-left: 0.4em;
}
.forecast-row .fr-temp-lo {
  color: var(--text-faint);
  margin-left: 8px;
  font-weight: 500;
}

/* ---------- Spotify ---------- */
/* Blurred album-art ambient background fills the whole panel.
   JS sets --spotify-art-bg to url("<art>") when a track plays. */
.spotify-panel {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  /* Single source of truth for the album-art size — the playing-state grid
     column and the artwork box both read it. */
  --art-size: clamp(140px, 22vh, 460px);
}

/* Playing state: header + album art stack in a left column; song info,
   progress and Up Next fill a taller right column. `display: contents` lets
   the JS-rendered media block and meta become cells of this grid. The class is
   toggled by js/spotify.js (renderTrack adds it; auth/idle remove it), so the
   QR-pairing and "no music" states keep the normal stacked panel. */
.spotify-panel--playing {
  display: grid;
  grid-template-columns: var(--art-size) minmax(0, 1fr);
  grid-template-rows: auto 1fr;
  grid-template-areas:
    "title meta"
    "media meta";
  align-items: start;
  column-gap: clamp(28px, 2.2vw, 72px);
  row-gap: clamp(6px, 0.5vw, 16px);
}
.spotify-panel--playing > .panel-title    { grid-area: title; margin-bottom: 0; }
.spotify-panel--playing > .spotify-body   { display: contents; }
.spotify-panel--playing .spotify-media-block { grid-area: media; }
.spotify-panel--playing .spotify-meta {
  grid-area: meta;
  align-self: stretch;   /* fill both rows so Up Next bottom-aligns with the art */
  height: auto;
  max-height: none;
  margin-top: 0;
}
/* Rate-limit pill — only visible while the server is in 429 back-off. */
.spotify-rl-pill {
  position: absolute;
  top: 10px;
  right: 12px;
  z-index: 3;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(248, 113, 113, 0.18);
  color: #fecaca;
  border: 1px solid rgba(248, 113, 113, 0.45);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
  pointer-events: none;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.spotify-rl-pill[hidden] { display: none; }
/* Tighter title space + pull the meta column up so the track title sits
   noticeably higher than the album art's top edge. */
.spotify-panel > .panel-title {
  margin-bottom: 0;
  position: relative;
  z-index: 2;
}
.spotify-meta {
  margin-top: clamp(-48px, -1.2vw, -18px);
}
.spotify-panel::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--spotify-art-bg, none);
  background-size: cover;
  background-position: center;
  /* Darker brightness so text stays legible even on pale-album-art tracks. */
  filter: blur(60px) brightness(0.32) saturate(1.2);
  opacity: var(--spotify-art-opacity, 0);
  transition: opacity 1.2s ease;
  transform: scale(1.15);
  z-index: -1;
  pointer-events: none;
}

.spotify-body {
  /* Non-playing (auth/idle) layout: a simple row. The playing state is a grid
     on .spotify-panel--playing above; --art-size is defined on the panel so
     both layouts share one artwork size. Art is always strictly square (1:1). */
  flex: 1;
  display: flex;
  align-items: flex-start;
  gap: clamp(28px, 2.2vw, 72px);
  min-height: 0;
  position: relative;
}
.spotify-body--idle {
  align-items: center;
  justify-content: center;
}

/* Media column: artwork fills the full panel height as a square; caption
   slot pinned underneath. Both columns top-align to the same line so the
   track title sits flush with the top of the artwork, and the meta column
   pushes Up Next to the bottom so it aligns with the artwork's bottom edge. */
.spotify-media-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(6px, 0.5vw, 18px);
  flex-shrink: 0;
}
.spotify-media {
  /* Both dimensions tied to the same var → guaranteed 1:1, no surprises. */
  position: relative;
  width: var(--art-size);
  height: var(--art-size);
  flex-shrink: 0;
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 14px 36px rgba(0,0,0,0.6);
  margin-top: 0.5rem;
}
.spotify-media-caption {
  font-size: calc(clamp(0.78rem, 0.78vw, 3.15rem) * var(--type-scale));
  color: var(--text-faint);
  letter-spacing: 0.04em;
  text-align: center;
  opacity: 0;
  height: 0;
  transition: opacity .4s ease;
}
.spotify-media-caption.visible,
.spotify-auth .spotify-media-caption {
  opacity: 1;
  height: auto;
}
/* Playing state: the caption overlays the bottom of the artwork box (it's a
   child of .spotify-media) instead of sitting below it in flow. The box is a
   fixed square, so toggling the caption during the art⇄QR cycle no longer
   changes the column height — Up Next stays put instead of jumping. */
.spotify-media > .spotify-media-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: auto;
  /* Much smaller than body text — it's a tiny hint, and a thinner bar covers
     less of the QR. Still scales with --type-scale, just from a small base. */
  font-size: calc(clamp(0.4rem, 0.4vw, 1.4rem) * var(--type-scale));
  padding: clamp(2px, 0.2vw, 6px) clamp(4px, 0.35vw, 10px);
  background: none;
  color: #000;
  z-index: 3;
  pointer-events: none;
}
.spotify-media .media-layer {
  position: absolute;
  inset: 0;
  opacity: 0;
  transform: scale(0.96);
  transition: opacity .8s ease, transform .8s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-elev-2);
}
.spotify-media .media-layer.active {
  opacity: 1;
  transform: scale(1);
}
.spotify-media .media-layer img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.spotify-media .media-layer--qr {
  background: #fff;
  padding: 8%;
}
.spotify-media .media-layer--qr img {
  object-fit: contain;
}

/* ===========================================================================
   Spotiamp — Winamp 2 main-window skin for the Now Playing media area.
   Toggled by the global `spotiamp` setting; mounted as the second rotating
   media layer (art ⇄ Winamp) in place of the QR. Colours are deliberately
   hard-coded to the classic Winamp palette — it should look like Winamp on
   every dashboard theme. All sizing is in cqw so the widget scales with the
   square media box (made a query container below).
   ========================================================================= */
/* Spotiamp takes over the entire Now Playing panel: the panel title stays on
   top, then a full-size stage rotates the two Winamp windows (main ⇄ playlist)
   on the media timer. No album art, no QR, no meta column. cqw units in the
   widget below resolve against this stage's width. */
/* Winamp owns the entire panel — drop the "Now Playing" title and trim the
   panel padding so the player frame reaches the edges (a small inset keeps the
   square frame off the panel's rounded corners). */
.spotify-panel--winamp { display: flex; flex-direction: column; padding: clamp(6px, 0.5vw, 16px); }
.spotify-panel--winamp > .panel-title { display: none; }
.spotify-panel--winamp > .spotify-body {
  display: block;
  flex: 1;
  min-height: 0;
  position: relative;
}
.spotify-winamp-stage {
  position: relative;
  width: 100%;
  height: 100%;
  container-type: inline-size;
}

.spotify-meta {
  /* Height matches the artwork exactly — so the wrap's auto-margin pushes
     Up Next down to bottom-align with the bottom of the album art. */
  flex: 1;
  min-width: 0;
  height: var(--art-size);
  max-height: var(--art-size);
  display: flex;
  flex-direction: column;
  gap: clamp(4px, 0.4vw, 12px);
  padding: 0;
  min-height: 0;
  /* Soft dark glow behind every glyph so bright album-art ambient doesn't
     wash text out. Two stacked shadows = a smooth halo. */
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.85),
    0 0 12px rgba(0, 0, 0, 0.6);
}
/* The wrap is the DIRECT child of .spotify-meta — that's where the auto
   margin has to live so flex distributes the remaining space above it. */
#spotifyUpNextWrap {
  margin-top: auto;
  min-width: 0;
}

.spotify-track-row {
  display: flex;
  align-items: center;
  gap: clamp(8px, 0.6vw, 21px);
  min-width: 0;
}
.spotify-track {
  font-size: calc(clamp(1.1rem, 1.15vw, 4.6rem) * var(--type-scale));
  font-weight: 700;
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1 1 auto;
  min-width: 0;
  padding-bottom: 0.05em;
}
.spotify-track-row {
  flex-shrink: 0;
}
.spotify-artist {
  flex-shrink: 0;
  line-height: 1.35;
  padding-bottom: 0.08em;
  font-size: calc(clamp(0.98rem, 0.98vw, 3.9rem) * var(--type-scale));
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.spotify-album {
  flex-shrink: 0;
  line-height: 1.35;
  padding-bottom: 0.08em;
  font-size: calc(clamp(0.82rem, 0.8vw, 3rem) * var(--type-scale));
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Marquee scroll for any overflowing Spotify text (title/artist/album).
   JS adds .marquee-active and inserts two duplicated text spans separated by
   a fixed gap so the scroll loops seamlessly. */
.marquee-active {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: clip;
}
.marquee-active .marquee-inner {
  display: inline-block;
  animation: marquee var(--marquee-duration, 18s) linear infinite;
  will-change: transform;
}
.marquee-active .marquee-gap {
  display: inline-block;
  width: 3em;
  flex-shrink: 0;
}
@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-1 * var(--marquee-distance, 50%))); }
}

/* (EQ bars retired — the progress bar's animated shimmer is now the
   "music is playing" indicator. See .spotify-progress-bar::after below.) */

.spotify-times {
  display: flex;
  justify-content: space-between;
  font-size: calc(clamp(0.78rem, 0.78vw, 3.15rem) * var(--type-scale));
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
  margin-top: clamp(6px, 0.5vw, 18px);
  flex-shrink: 0;
}
/* Animated waveform progress bar.
   Two flex rows of 50 vertical bars sit in the track:
     .wf-row--bg     full width, dim grey (the "unplayed" track)
     .wf-row--played width = current playback %, bright green (the "played"
                     portion overlaying the same bars in the same positions)
   Each bar has its own animation-delay (set inline via --d) so the heights
   bounce out-of-phase, simulating real-time audio amplitude. Both rows use
   the same delays so the green bars stay in sync with their grey neighbours. */
.spotify-progress {
  flex-shrink: 0;
  position: relative;
  height: clamp(20px, 1.5vw, 48px);
  margin-top: clamp(8px, 0.6vw, 18px);
  overflow: hidden;
  background: transparent;
}
.wf-row {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 2px;
  gap: 1px;
  overflow: hidden;
}
.wf-row--played {
  /* Width stays 100% so the played bars sit at the same x-positions as the
     grey ones. The "fill amount" is driven by clip-path on the right edge,
     set via inline style by spotify.js as the playhead advances. */
  width: 100%;
  clip-path: inset(0 100% 0 0);
  transition: clip-path 1s linear;
}
.wf-bar {
  flex: 1 1 0;
  min-width: 1px;
  /* Grey bars are a flat uniform line; only the green ones bounce. */
  height: 35%;
  background: rgba(160, 170, 190, 0.55);
  border-radius: 1.5px;
  transform-origin: center;
}
.wf-row--played .wf-bar {
  background: #4ade80;
  /* Full-height box; the bounce is a transform: scaleY, not an animated
     height. height-animating all 50 bars relayout the flex row every frame,
     which stuttered the (compositor-driven) scrollers on the TV. scaleY skips
     layout entirely. transform-origin: center keeps the same symmetric bounce. */
  height: 100%;
  transform: scaleY(0.3);
  animation: waveformBarPlayed 2.4s ease-in-out infinite;
  animation-delay: var(--d, 0s);
}
@keyframes waveformBarPlayed {
  0%, 100% { transform: scaleY(0.30); }
  25%      { transform: scaleY(0.80); }
  50%      { transform: scaleY(0.45); }
  75%      { transform: scaleY(0.95); }
}
.spotify-body.paused .wf-row--played .wf-bar {
  animation-play-state: paused;
  background: var(--text-faint);
}
/* Vertical playhead line right at the played/unplayed boundary. */
.wf-row--played::after {
  content: "";
  position: absolute;
  top: -2px;
  bottom: -2px;
  right: 0;
  width: 2px;
  background: rgba(255, 255, 255, 0.9);
  border-radius: 2px;
  box-shadow: 0 0 6px rgba(74, 222, 128, 0.9);
}
.spotify-body.paused .wf-row--played::after {
  opacity: 0.45;
  box-shadow: none;
}
/* Up Next — sits inside #spotifyUpNextWrap which is auto-pushed to the bottom
   of the meta column. Strong visual gap above via padding + hairline. */
.spotify-up-next {
  padding-top: clamp(8px, 0.7vw, 22px);
  min-width: 0;
}
.spotify-up-next .un-label {
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: calc(clamp(0.6rem, 0.55vw, 2rem) * var(--type-scale));
  margin-bottom: clamp(2px, 0.2vw, 8px);
}
.un-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(1px, 0.15vw, 6px);
}
.un-item {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: calc(clamp(0.8rem, 0.75vw, 2.75rem) * var(--type-scale));
  color: var(--text);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.un-item .un-title {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  flex-shrink: 1;
  min-width: 0;
}
.un-item .un-sep { color: var(--text-faint); flex-shrink: 0; }
.un-item .un-artist {
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  flex-shrink: 1;
  min-width: 0;
}
.spotify-idle {
  color: var(--text-dim);
  font-size: calc(clamp(1rem, 1vw, 3.9rem) * var(--type-scale));
  text-align: center;
}

/* Nothing-playing "card" — Spotify logo + helpful hint. */
.spotify-idle-card {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(10px, 0.9vw, 27px);
  text-align: center;
}
.spotify-logo {
  width: clamp(80px, 7vw, 210px);
  height: auto;
  display: block;
  animation: spotifyPulse 3.2s ease-in-out infinite;
  filter: drop-shadow(0 8px 24px rgba(30,215,96,0.35));
}
@keyframes spotifyPulse {
  0%, 100% { transform: scale(1);    opacity: 0.9; }
  50%      { transform: scale(1.06); opacity: 1; }
}
.spotify-idle-title {
  font-size: calc(clamp(1.2rem, 1.4vw, 6.6rem) * var(--type-scale));
  font-weight: 700;
  letter-spacing: -0.01em;
}
.spotify-idle-sub {
  font-size: calc(clamp(0.85rem, 0.85vw, 3.45rem) * var(--type-scale));
  color: var(--text-dim);
  max-width: 36ch;
  line-height: 1.4;
}

.connect-btn {
  background: #1db954;
  color: white;
  border: none;
  padding: clamp(16px, 1.4vw, 48px) clamp(28px, 2.4vw, 80px);
  border-radius: 999px;
  font-size: calc(clamp(1.3rem, 1.6vw, 5rem) * var(--type-scale));
  font-weight: 800;
  letter-spacing: 0.02em;
  cursor: pointer;
  font-family: inherit;
  box-shadow: 0 10px 30px rgba(29, 185, 84, 0.4);
}
.connect-btn:hover { filter: brightness(1.1); }

/* Auth QR — shown when the server has no Spotify tokens yet. */
.spotify-auth {
  flex: 1;
  display: flex;
  align-items: stretch;
  gap: clamp(16px, 1.3vw, 48px);
  min-height: 0;
}
.spotify-auth-qr {
  width: clamp(140px, 13vw, 480px);
  aspect-ratio: 1 / 1;
  height: auto;
  max-height: 100%;
  flex-shrink: 0;
  background: #fff;
  border-radius: 14px;
  padding: clamp(6px, 0.5vw, 15px);
  box-shadow: 0 14px 36px rgba(0,0,0,0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  animation: pulseGlow 3s ease-in-out infinite;
}
.spotify-auth-qr img { width: 100%; height: 100%; }
@keyframes pulseGlow {
  0%, 100% { box-shadow: 0 12px 32px rgba(0,0,0,0.55), 0 0 0 0 rgba(29,185,84,0.0); }
  50%      { box-shadow: 0 12px 32px rgba(0,0,0,0.55), 0 0 0 8px rgba(29,185,84,0.15); }
}
.spotify-auth-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(6px, 0.5vw, 18px);
}
.spotify-auth-title {
  font-size: calc(clamp(1rem, 1.2vw, 5rem) * var(--type-scale));
  font-weight: 700;
  color: #1db954;
}
.spotify-auth-sub {
  font-size: calc(clamp(0.8rem, 0.85vw, 3rem) * var(--type-scale));
  color: var(--text);
  line-height: 1.3;
}
.spotify-auth-tiny {
  font-size: calc(clamp(0.65rem, 0.6vw, 2rem) * var(--type-scale));
  color: var(--text-faint);
  margin-top: 6px;
  line-height: 1.4;
}

/* ---------- Calendar ---------- */
.calendar-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 0.6vw, 21px);
  overflow: hidden;
  min-height: 0;
}
.cal-day-label {
  font-size: calc(clamp(0.62rem, 0.58vw, 2.1rem) * var(--type-scale));
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-faint);
  margin-top: clamp(6px, 0.4vw, 15px);
}
.cal-day-label:first-child { margin-top: 0; }

.cal-event {
  display: flex;
  align-items: center;
  gap: clamp(8px, 0.55vw, 18px);
  padding: clamp(6px, 0.45vw, 16px) clamp(8px, 0.55vw, 16px);
  background: var(--bg-elev-2);
  border-radius: 8px;
  border-left: 3px solid var(--cal-accent, var(--accent-2));
  animation: rowIn .45s ease both;
}
/* Left-edge colour per source calendar (set via cal-src-N from js/calendar.js).
   These only set the variable, so the .live rule below still wins for NOW events. */
.cal-event.cal-src-0 { --cal-accent: #8b7cff; }  /* violet */
.cal-event.cal-src-1 { --cal-accent: #2dd4bf; }  /* teal   */
.cal-event.cal-src-2 { --cal-accent: #f5a623; }  /* amber  */
.cal-event.cal-src-3 { --cal-accent: #ff6b9d; }  /* pink   */
.cal-event.cal-src-4 { --cal-accent: #4aa3ff; }  /* blue   */
.cal-event.live { border-left-color: var(--success); }
.cal-event.live .cal-time { color: var(--success); }

.cal-time {
  font-size: calc(clamp(0.72rem, 0.68vw, 2.6rem) * var(--type-scale));
  font-weight: 700;
  color: var(--accent);
  flex-shrink: 0;
  width: clamp(56px, 4.2vw, 130px);
  font-variant-numeric: tabular-nums;
}
.cal-title {
  font-size: calc(clamp(0.78rem, 0.72vw, 2.8rem) * var(--type-scale));
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  min-width: 0;
}
.cal-where {
  font-size: calc(clamp(0.62rem, 0.58vw, 2.1rem) * var(--type-scale));
  color: var(--text-dim);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- Carousel ---------- */
.carousel {
  position: relative;
  min-height: 0;
}
.page {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transform: translateY(10px);
  transition: opacity .55s ease, transform .55s ease;
}
.page.active {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}
.page > .panel,
.page > .split-2 {
  width: 100%;
  height: 100%;
}

.split-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gap);
}
.split-2 > .panel { height: 100%; min-height: 0; }

@media (max-width: 1300px) {
  .split-2 { grid-template-columns: 1fr; grid-auto-rows: 1fr; }
}

/* Bitbucket page: Open PRs (top-left) + Recently Merged (bottom-left)
   + Pipelines (full-height right column). */
.bb-grid {
  display: grid;
  grid-template-columns: 1fr 1.3fr;
  grid-template-rows: 1fr 1fr;
  grid-template-areas:
    "prs    pipes"
    "merged pipes";
  gap: var(--gap);
  width: 100%;
  height: 100%;
  min-height: 0;
}
.bb-prs    { grid-area: prs; }
.bb-merged { grid-area: merged; }
.bb-pipes  { grid-area: pipes; }
.bb-grid > .panel { min-height: 0; }

/* Pipeline table — Bitbucket-style row with columns. */
.pipe-table {
  display: flex;
  flex-direction: column;
  gap: clamp(6px, 0.5vw, 14px);
  overflow-y: auto;
  flex: 1;
  min-height: 0;
  position: relative;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
.pipe-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(90px, 7vw, 220px) clamp(90px, 6.5vw, 200px) clamp(70px, 5vw, 150px);
  align-items: center;
  gap: clamp(8px, 0.6vw, 18px);
  padding: clamp(7px, 0.5vw, 14px) clamp(10px, 0.7vw, 18px);
  background: var(--bg-elev-2);
  border-radius: 10px;
  border-left: 3px solid var(--border);
  animation: rowIn .5s ease both;
}
.pipe-row.pipe-head {
  background: transparent;
  border-left-color: transparent;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding-bottom: clamp(6px, 0.5vw, 12px);
  padding-top: 0;
  font-size: calc(clamp(0.7rem, 0.7vw, 1.6rem) * var(--type-scale));
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-faint);
  font-weight: 700;
  animation: none;
}
.pipe-row.status-success { border-left-color: var(--success); }
.pipe-row.status-failed  { border-left-color: var(--danger); }
.pipe-row.status-running { border-left-color: var(--info); }
.pipe-row.status-pending { border-left-color: var(--warning); }

.pipe-info {
  display: flex;
  align-items: center;
  gap: clamp(8px, 0.6vw, 18px);
  min-width: 0;
}
.pipe-info-text { min-width: 0; flex: 1; }
.pipe-title {
  font-size: calc(clamp(0.82rem, 0.8vw, 2.7rem) * var(--type-scale));
  font-weight: 600;
  line-height: 1.25;
  padding-bottom: 0.05em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pipe-sub {
  font-size: calc(clamp(0.7rem, 0.65vw, 2rem) * var(--type-scale));
  color: var(--text-dim);
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-top: 3px;
}
.pipe-status {
  font-size: 0;
  overflow: hidden;
  min-width: 0;
}
/* Badge in pipeline rows — readable on TV but bounded so it stays inside the
   status column on wide viewports. */
.pipe-row .badge {
  font-size: calc(clamp(0.7rem, 0.65vw, 1.6rem) * var(--type-scale));
  padding: clamp(3px, 0.22vw, 8px) clamp(8px, 0.55vw, 16px);
  white-space: nowrap;
}
.pipe-started, .pipe-duration {
  font-size: calc(clamp(0.82rem, 0.8vw, 2.4rem) * var(--type-scale));
  font-weight: 600;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Slightly wider status column to accommodate the larger badge. */
.pipe-row {
  grid-template-columns: minmax(0, 1fr) clamp(110px, 8.5vw, 280px) clamp(95px, 7vw, 220px) clamp(75px, 5.5vw, 170px);
}

/* ---------- News ticker ---------- */
.news-ticker {
  overflow: hidden;
  position: relative;
  /* Tall enough for a multi-line title plus a multi-line description without
     clipping mid-word. */
  height: clamp(60px, 4vw, 140px);
  padding: clamp(2px, 0.2vw, 6px);
  background: transparent;
  /* Note: mask-image was used here for the edge fade-out but caused jerky
     scrolling on lower-powered TV browsers (the masked region gets
     recomposited every frame as the track translates underneath). Replaced
     with cheap static overlay gradients via ::before / ::after below. */
}
.news-ticker::before,
.news-ticker::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: clamp(24px, 2vw, 60px);
  pointer-events: none;
  z-index: 2;
}
.news-ticker::before {
  left: 0;
  background: linear-gradient(to right, var(--bg), transparent);
}
.news-ticker::after {
  right: 0;
  background: linear-gradient(to left, var(--bg), transparent);
}
.news-track {
  display: inline-flex;
  align-items: stretch;
  gap: clamp(10px, 0.7vw, 20px);
  height: 100%;
  animation: news-scroll var(--news-duration, 120s) linear infinite;
  will-change: transform;
  /* Force the track onto its own GPU compositing layer so the long
     translate is offloaded to the compositor thread — significantly
     smoother on weak TV browsers. */
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
  perspective: 1000px;
}
@keyframes news-scroll {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
.news-source-card {
  flex: 0 0 auto;
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
  border-radius: 12px;
  display: flex;
  align-items: center;
  padding: 0 clamp(14px, 1vw, 24px);
}
.news-source-label {
  font-size: calc(clamp(0.7rem, 0.6vw, 1.6rem) * var(--type-scale));
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: #0b0e14;
  white-space: nowrap;
}
.news-card {
  flex: 0 0 auto;
  width: clamp(500px, 55vw, 1400px);
  /* Paint-only containment so each card is composited independently — keeps
     scroll smooth on TV browsers by not invalidating neighbours. */
  contain: layout paint;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-left: 4px solid var(--accent);
  border-radius: 12px;
  padding: clamp(8px, 0.55vw, 16px) clamp(12px, 0.8vw, 22px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(6px, 0.4vw, 12px);
  white-space: normal;
  overflow: hidden;
}
.news-card-head {
  display: flex;
  align-items: center;
  gap: clamp(6px, 0.5vw, 14px);
  min-width: 0;
}
.news-card-source {
  flex-shrink: 0;
  font-size: calc(clamp(0.5rem, 0.45vw, 1.2rem) * var(--type-scale));
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: #0b0e14;
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
  padding: 2px 8px;
  border-radius: 999px;
  line-height: 1.4;
}
.news-card-head .news-card-title {
  flex: 1 1 auto;
  min-width: 0;
}
.news-card-title {
  font-size: calc(clamp(0.82rem, 0.8vw, 2.4rem) * var(--type-scale));
  font-weight: 700;
  color: var(--text);
  line-height: 1.25;
  /* No line-clamp — title wraps to whatever it needs. */
}
.news-card-desc {
  font-size: calc(clamp(0.7rem, 0.65vw, 2rem) * var(--type-scale));
  color: var(--text-dim);
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---------- Fantasy Premier League ---------- */
.fpl-panel {
  min-height: 0;
}
.fpl-grid {
  display: grid;
  grid-template-columns: 1.3fr 1fr;
  grid-template-rows: 1fr 1fr;
  grid-template-areas:
    "standings cup"
    "standings chart";
  gap: clamp(14px, 1vw, 28px);
  flex: 1;
  min-height: 0;
}
.fpl-col--standings { grid-area: standings; }
.fpl-col--cup       { grid-area: cup; }
.fpl-col--chart     { grid-area: chart; }

.fpl-col {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: clamp(10px, 0.8vw, 18px);
}
.fpl-subheader {
  font-size: calc(clamp(0.78rem, 0.75vw, 2.4rem) * var(--type-scale));
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text-faint);
  margin-bottom: clamp(8px, 0.7vw, 14px);
  padding-bottom: clamp(6px, 0.5vw, 10px);
  border-bottom: 1px solid var(--border);
}
/* League subheader carries the gameweek + deadline pill on the right, since
   the panel-level title row has been removed to free up vertical space. */
.fpl-subheader--with-pill {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(8px, 0.7vw, 18px);
}

/* Cup matches */
.fpl-cup-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 0.6vw, 14px);
  min-height: 0;
  overflow: hidden;
}
.fpl-cup-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 4px;
}
.fpl-cup-round {
  font-size: calc(clamp(1rem, 1vw, 3.15rem) * var(--type-scale));
  font-weight: 800;
  color: var(--accent);
}
.fpl-cup-gw {
  font-size: calc(clamp(0.78rem, 0.75vw, 2.4rem) * var(--type-scale));
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.fpl-cup-matches {
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 0.6vw, 14px);
  overflow: hidden;
}
.fpl-cup-match {
  display: flex;
  flex-direction: column;
  gap: clamp(2px, 0.2vw, 6px);
  padding: clamp(10px, 0.8vw, 22px) clamp(12px, 1vw, 28px);
  background: var(--bg-elev);
  border-radius: 12px;
  border-left: 3px solid var(--border);
  position: relative;
}
.fpl-cup-match.is-live { border-left-color: var(--accent); }

/* Row 1: team-left  SCORE-left  vs  SCORE-right  team-right */
.fpl-cup-teams {
  display: grid;
  grid-template-columns: 1fr auto auto auto 1fr;
  align-items: baseline;
  gap: 0 clamp(8px, 0.6vw, 18px);
}
/* Row 2: username-left . . . . . . . . . . username-right */
.fpl-cup-mgrs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(10px, 0.7vw, 22px);
}

.fpl-cup-row .t-left  { text-align: right; }
.fpl-cup-row .t-right { text-align: left; }

.fpl-cup-team {
  font-size: calc(clamp(1.1rem, 1.2vw, 5.4rem) * var(--type-scale));
  font-weight: 700;
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.fpl-cup-team.is-winner { color: var(--success); }
.fpl-cup-team.is-loser  { color: var(--text-dim); }

.fpl-cup-pts {
  font-size: calc(clamp(1.4rem, 1.5vw, 7rem) * var(--type-scale));
  font-weight: 800;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.fpl-cup-pts.w { color: var(--success); }

.fpl-cup-vs {
  font-size: calc(clamp(0.8rem, 0.85vw, 3.2rem) * var(--type-scale));
  font-weight: 600;
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  line-height: 1.1;
}

.fpl-cup-mgr {
  font-size: calc(clamp(0.85rem, 0.9vw, 3.6rem) * var(--type-scale));
  font-weight: 500;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  line-height: 1.25;
}

.fpl-cup-live {
  position: absolute;
  top: clamp(6px, 0.5vw, 14px);
  right: clamp(8px, 0.6vw, 16px);
  background: rgba(110, 231, 255, 0.15);
  color: var(--accent);
  font-size: calc(clamp(0.65rem, 0.65vw, 2.4rem) * var(--type-scale));
  font-weight: 800;
  padding: clamp(2px, 0.2vw, 6px) clamp(6px, 0.5vw, 14px);
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
}

/* Position chart */
.fpl-chart {
  flex: 1;
  display: flex;
  min-height: 0;
}
.fpl-chart-wrap {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 0.6vw, 14px);
  min-height: 0;
}
.fpl-chart-svg {
  flex: 1;
  width: 100%;
  min-height: 0;
}
.fpl-chart-svg .ax-grid {
  stroke: var(--border);
  stroke-dasharray: 2 4;
  stroke-width: 0.5;
}
.fpl-chart-svg .ax-lbl {
  fill: var(--text-faint);
  font-size: 11px;
  font-family: inherit;
}
.fpl-chart-svg .ax-title {
  fill: var(--text-dim);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-family: inherit;
}
/* HTML overlay labels at the end of each line. Positioned in % so they line
   up with the SVG endpoints regardless of how the SVG stretches. */
.fpl-chart-wrap { position: relative; }
.fpl-chart-labels {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.fpl-end-label {
  position: absolute;
  transform: translateY(-50%);
  font-size: calc(clamp(0.7rem, 0.7vw, 2rem) * var(--type-scale));
  font-weight: 700;
  white-space: nowrap;
  /* Cap label width to the right-padding region of the SVG (~28% of the
     wrap) so long team names ellipsis cleanly instead of bleeding past the
     panel edge. JS also truncates to 14 chars but CSS guards against the
     leftover edge cases. */
  max-width: 28%;
  overflow: hidden;
  text-overflow: ellipsis;
  text-shadow: 0 0 6px rgba(0,0,0,0.6);
  line-height: 1;
}
.fpl-chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(8px, 0.6vw, 16px) clamp(12px, 1vw, 24px);
}
.fpl-chart-legend-item {
  display: inline-flex;
  align-items: center;
  gap: clamp(6px, 0.4vw, 10px);
  font-size: calc(clamp(0.78rem, 0.75vw, 2.25rem) * var(--type-scale));
  color: var(--text-dim);
}
.fpl-chart-swatch {
  width: clamp(12px, 0.8vw, 18px);
  height: clamp(3px, 0.25vw, 5px);
  border-radius: 999px;
}

/* Standings table inside the column needs a tighter layout. */
.fpl-table {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: clamp(4px, 0.4vw, 15px);
  min-height: 0;
  overflow: hidden;
  position: relative;
}
.fpl-row {
  display: grid;
  grid-template-columns: clamp(50px, 4vw, 165px) clamp(50px, 3.5vw, 135px) 1fr clamp(80px, 5vw, 195px) clamp(90px, 5.5vw, 210px);
  align-items: center;
  gap: clamp(10px, 0.8vw, 30px);
  padding: clamp(6px, 0.45vw, 18px) clamp(14px, 1vw, 33px);
  background: var(--bg-elev-2);
  border-radius: 12px;
  border-left: 3px solid var(--border);
}
.fpl-row.fpl-head {
  background: transparent;
  border-left-color: transparent;
  padding-top: 0;
  padding-bottom: clamp(4px, 0.4vw, 15px);
  font-size: calc(clamp(0.72rem, 0.7vw, 4.2rem) * var(--type-scale));
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-faint);
}
.fpl-row.fpl-top { border-left-color: var(--accent); background: rgba(110,231,255,0.08); }

.fpl-rank {
  font-size: calc(clamp(1.1rem, 1.2vw, 7.8rem) * var(--type-scale));
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.fpl-move {
  font-size: calc(clamp(0.85rem, 0.85vw, 5.1rem) * var(--type-scale));
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}
.fpl-move.rm-up   { color: var(--success); }
.fpl-move.rm-down { color: var(--danger); }
.fpl-move.rm-flat { color: var(--text-faint); }
.fpl-move.rm-new  { color: var(--accent); }

.fpl-team {
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: 0;
  overflow: hidden;
  white-space: nowrap;
}
.fpl-team-name {
  font-size: calc(clamp(1rem, 1vw, 6.6rem) * var(--type-scale));
  font-weight: 700;
  flex-shrink: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 55%;
}
.fpl-mgr-sep {
  color: var(--text-faint);
  font-size: calc(clamp(0.8rem, 0.8vw, 4rem) * var(--type-scale));
  padding: 0 clamp(4px, 0.35vw, 10px);
  flex-shrink: 0;
}
.fpl-mgr {
  font-size: calc(clamp(0.85rem, 0.8vw, 4.8rem) * var(--type-scale));
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.fpl-gw, .fpl-total {
  font-size: calc(clamp(1rem, 1vw, 6.6rem) * var(--type-scale));
  font-weight: 700;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.fpl-gw  { color: var(--accent); }

/* ---------- Sprint analytics banner ---------- */
.sprint-stats {
  /* 4 count stats + flexible progress bar + sprint-name chip + end-date stat.
     Progress sits in the middle and grows with available width. */
  display: grid;
  grid-template-columns: repeat(4, auto) minmax(160px, 1fr) auto auto;
  align-items: center;
  gap: clamp(10px, 0.85vw, 30px);
  margin-bottom: clamp(8px, 0.7vw, 24px);
  padding: clamp(12px, 1vw, 34px) clamp(10px, 0.75vw, 24px);
  background: var(--bg-elev-2);
  border-radius: 10px;
  border: 1px solid var(--border);
}
.sprint-name-chip {
  background: var(--bg-elev);
  color: var(--accent);
  font-size: calc(clamp(0.7rem, 0.65vw, 2.2rem) * var(--type-scale));
  font-weight: 700;
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  white-space: nowrap;
  letter-spacing: 0.02em;
}
.sprint-stat { text-align: center; }
.sprint-stat .ss-num {
  font-size: calc(clamp(1.1rem, 1.2vw, 7rem) * var(--type-scale));
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.sprint-stat .ss-lab {
  font-size: calc(clamp(0.6rem, 0.55vw, 2.6rem) * var(--type-scale));
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-faint);
  margin-top: 4px;
}
.sprint-stat.ss-bad .ss-num { color: var(--danger); }

.sprint-progress {
  /* Single bar centred vertically in its grid cell; the % is rendered inside
     a white circle overlaid on the bar at the fill position. */
  display: flex;
  align-items: center;
  min-width: 0;
  height: 100%;
}
.sp-track {
  position: relative;
  width: 100%;
  /* Chunky enough to read on a 4K TV at viewing distance. */
  height: clamp(14px, 1vw, 56px);
  /* Strong contrast against the lighter sprint-stats panel bg. */
  background: rgba(0, 0, 0, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 999px;
  /* overflow visible so the % circle can hang over the top + bottom. */
  overflow: visible;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
.sp-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--accent), var(--success));
  transition: width .8s ease;
  border-radius: 999px;
}

/* White circular % badge centred vertically on the bar at the fill position. */
.sp-percent {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width:  clamp(28px, 2vw, 70px);
  height: clamp(28px, 2vw, 70px);
  border-radius: 50%;
  background: #ffffff;
  color: #0b0e14;
  border: 2px solid #000;
  font-weight: 800;
  font-size: calc(clamp(0.65rem, 0.65vw, 1.6rem) * var(--type-scale));
  display: flex;
  align-items: center;
  justify-content: center;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.55);
  transition: left .8s ease;
  pointer-events: none;
  z-index: 2;
  letter-spacing: -0.02em;
}
/* Vertical marker showing where "today" sits along the sprint timeline. */
.sp-elapsed {
  position: absolute;
  top: -3px;
  bottom: -3px;
  width: 2px;
  background: var(--text);
  box-shadow: 0 0 0 2px var(--bg-elev-2);
  transform: translateX(-50%);
  border-radius: 1px;
}
.sp-meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  font-variant-numeric: tabular-nums;
}
.sp-label {
  font-size: calc(clamp(0.78rem, 0.75vw, 2.25rem) * var(--type-scale));
  color: var(--text-dim);
  font-weight: 600;
}
.sp-end {
  font-size: calc(clamp(0.78rem, 0.75vw, 2.25rem) * var(--type-scale));
  color: var(--text);
  font-weight: 700;
}


/* ---------- Dots ---------- */
.dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 0;
  margin-top: -4px;
}
.dot {
  position: relative;
  width: clamp(18px, 1.4vw, 36px);
  height: 2px;
  border-radius: 1px;
  background: var(--bg-elev-2);
  border: 0;
  cursor: pointer;
  padding: 0;
  transition: background .25s ease, height .3s ease;
}
/* Invisible expanded hit area so a TV cursor can actually land on the dot.
   Visual bar stays 2px tall; click target is ~22px tall and wider. */
.dot::before {
  content: "";
  position: absolute;
  inset: -10px -3px;
}
.dot:hover { background: var(--text-faint); }
.dot.active {
  background: var(--accent);
  height: 3px;
  animation: dotPulse 2.4s ease-in-out infinite;
}
@keyframes dotPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(110,231,255,0.0); }
  50%      { box-shadow: 0 0 6px 0 rgba(110,231,255,0.55); }
}


/* ---------- Elmah error log ---------- */

/* App label on each error row (Mattressman / Casebook / Middleware / Services) */
.badge.app-badge {
  background: rgba(110, 231, 255, 0.12);
  color: #6ee7ff;
  border: 1px solid rgba(110, 231, 255, 0.35);
}

/* Count chip shown on deduped error rows (e.g. "× 47"). */
.elmah-count {
  display: inline-block;
  padding: clamp(2px, 0.2vw, 8px) clamp(8px, 0.6vw, 18px);
  background: rgba(248, 113, 113, 0.18);
  color: #fca5a5;
  border: 1px solid rgba(248, 113, 113, 0.4);
  border-radius: 6px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  font-size: calc(clamp(0.9rem, 0.85vw, 2.3rem) * var(--type-scale));
}

/* === Combined Errors page: 50/50 split, 2x2 cards each side === */
.elmah-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(16px, 1.2vw, 40px);
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.elmah-split-col {
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 0.7vw, 22px);
  min-width: 0;
  min-height: 0;
}
.elmah-split-head {
  font-size: calc(clamp(0.95rem, 0.9vw, 2.5rem) * var(--type-scale));
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-weight: 700;
  display: flex;
  align-items: center;
  gap: clamp(8px, 0.5vw, 16px);
}
/* Errors page header: panel title "Errors" sits on the left, while a
   transparent overlay grid mirrors the elmah-split below (1fr 1fr with the
   same gap) so each window-tag aligns over the right edge of its column. */
.elmah-header {
  position: relative;
  margin-bottom: clamp(8px, 0.6vw, 20px);
}
.elmah-header .panel-title { margin-bottom: 0; }
.elmah-tag-row {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(16px, 1.2vw, 40px);
  align-items: center;
  pointer-events: none;
}
.elmah-tag-row .window-tag { justify-self: end; }
.elmah-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: clamp(12px, 0.9vw, 28px);
  flex: 1;
  min-height: 0;
  min-width: 0;
}
/* Card: dark panel with colored accent dot + colored sparkline, matching
   the Cloudflare-style gradient-fill-with-line aesthetic used elsewhere. */
.elmah-card {
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: clamp(12px, 0.9vw, 28px);
  color: var(--text);
  display: flex;
  flex-direction: column;
  gap: clamp(6px, 0.5vw, 16px);
  min-height: 0;
  min-width: 0;
  position: relative;
  overflow: hidden;
}
.elmah-card-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: clamp(6px, 0.5vw, 18px);
  min-width: 0;
}
.elmah-card-name {
  display: flex;
  align-items: center;
  gap: clamp(6px, 0.45vw, 14px);
  font-size: calc(clamp(1rem, 0.95vw, 2.8rem) * var(--type-scale));
  font-weight: 800;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.elmah-dot {
  display: inline-block;
  width: clamp(10px, 0.7vw, 20px);
  height: clamp(10px, 0.7vw, 20px);
  border-radius: 50%;
  flex-shrink: 0;
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.04);
}
.elmah-card-total {
  font-size: calc(clamp(1.7rem, 1.7vw, 5.4rem) * var(--type-scale));
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.elmah-card-sub {
  font-size: calc(clamp(0.72rem, 0.68vw, 1.9rem) * var(--type-scale));
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.elmah-spark {
  width: 100%;
  height: clamp(55px, 5vw, 150px);
  flex: 1;
  min-height: 45px;
}
.elmah-sev-row {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(4px, 0.3vw, 10px);
  font-size: calc(clamp(0.78rem, 0.72vw, 2rem) * var(--type-scale));
}
.elmah-sev {
  padding: clamp(2px, 0.15vw, 5px) clamp(6px, 0.45vw, 14px);
  background: rgba(255, 255, 255, 0.06);
  border-radius: 5px;
  font-weight: 600;
}
.elmah-sev b { font-weight: 800; }
.elmah-sev-fatal       { background: rgba(220, 38, 38, 0.20); color: #fca5a5; }
.elmah-sev-error       { background: rgba(248, 113, 113, 0.16); color: #fca5a5; }
.elmah-sev-warning     { background: rgba(245, 158, 11, 0.18); color: #fbbf24; }
.elmah-sev-information { background: rgba(96, 165, 250, 0.18); color: #93c5fd; }
.elmah-sev-quiet {
  color: var(--text-faint);
  font-style: italic;
}
.elmah-card-latest {
  font-size: calc(clamp(0.72rem, 0.68vw, 1.85rem) * var(--type-scale));
  color: var(--text-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.elmah-card-latest b { color: var(--text); font-weight: 700; }
.elmah-card--quiet { opacity: 0.5; }
.elmah-card--quiet .elmah-card-total { color: var(--text-faint) !important; }
.elmah-card--error {
  border-color: rgba(248, 113, 113, 0.35);
}
.elmah-card-err {
  font-size: calc(clamp(0.78rem, 0.72vw, 1.95rem) * var(--type-scale));
  color: var(--text-faint);
  font-style: italic;
}

/* FE / BE pages — two side-by-side dedup'd error feeds. min-width: 0 on
   every grid item so long unbreakable exception titles can't force one
   column to consume the other. */
.elmah-feed {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(14px, 1vw, 36px);
  flex: 1;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}
.elmah-feed-col {
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: clamp(14px, 1vw, 30px);
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 0.7vw, 22px);
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}
.elmah-feed-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  column-gap: clamp(8px, 0.7vw, 22px);
  row-gap: 4px;
  font-size: calc(clamp(1.05rem, 1vw, 2.8rem) * var(--type-scale));
  font-weight: 800;
  letter-spacing: -0.01em;
  min-width: 0;
}
.elmah-feed-meta {
  font-size: calc(clamp(0.78rem, 0.7vw, 1.95rem) * var(--type-scale));
  color: var(--text-faint);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-left: auto;
}
.elmah-feed-list {
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 0.6vw, 18px);
  overflow-y: auto;
  flex: 1;
  min-height: 0;
  min-width: 0;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
  position: relative;
}
.elmah-feed-list::-webkit-scrollbar { width: 8px; }
.elmah-feed-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 8px; }

/* Count chip shown on deduped error rows in the FE/BE feed lists. */
.elmah-count {
  display: inline-block;
  padding: clamp(2px, 0.2vw, 8px) clamp(8px, 0.6vw, 18px);
  background: rgba(248, 113, 113, 0.18);
  color: #fca5a5;
  border: 1px solid rgba(248, 113, 113, 0.4);
  border-radius: 6px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  font-size: calc(clamp(0.9rem, 0.85vw, 2.3rem) * var(--type-scale));
}


/* ---------- Cloudflare ---------- */
.cf-zones {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr));
  gap: clamp(14px, 1vw, 30px);
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.cf-zone {
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: clamp(14px, 1vw, 30px);
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 0.9vw, 26px);
  min-height: 0;
  overflow: hidden;
}
.cf-zone--error { border-color: rgba(248,113,113,0.35); }
.cf-zone-name {
  font-size: calc(clamp(1.1rem, 1.2vw, 3.6rem) * var(--type-scale));
  font-weight: 800;
  letter-spacing: -0.01em;
  color: #f6821f;
}

/* (cf-live-tag retired — use .window-tag.window-tag--live instead) */
.cf-stats {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(10px, 0.8vw, 22px);
}
.cf-stat {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: clamp(10px, 0.7vw, 22px) clamp(12px, 0.9vw, 28px);
}
.cf-stat--bad { border-color: rgba(248,113,113,0.4); }
.cf-stat .cf-num {
  font-size: calc(clamp(1.5rem, 1.6vw, 5rem) * var(--type-scale));
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1.05;
}
.cf-stat--bad .cf-num { color: var(--danger); }
.cf-stat .cf-lab {
  font-size: calc(clamp(0.78rem, 0.75vw, 2rem) * var(--type-scale));
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  margin-top: 4px;
}
.cf-spark-wrap {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.cf-spark-label {
  font-size: calc(clamp(0.7rem, 0.7vw, 1.8rem) * var(--type-scale));
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.12em;
}
.cf-spark {
  width: 100%;
  height: clamp(50px, 4vw, 120px);
}

/* Breakdown blocks: top countries / status mix / top paths / devices.
   Stacked single-column so all four sections fit one above the other; the
   container is auto-scrolled by cloudflare.js so the rest comes around even
   when the visible window only shows the first one or two. */
.cf-breakdowns {
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 0.8vw, 24px);
  margin-top: clamp(8px, 0.6vw, 18px);
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.cf-block {
  display: flex;
  flex-direction: column;
  gap: clamp(4px, 0.4vw, 12px);
  min-width: 0;
}
.cf-block-title {
  font-size: calc(clamp(0.95rem, 0.95vw, 2.6rem) * var(--type-scale));
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-weight: 700;
}
.cf-empty {
  font-size: calc(clamp(0.95rem, 0.9vw, 2.4rem) * var(--type-scale));
  color: var(--text-faint);
  font-style: italic;
}

/* Horizontal bar rows shared by countries / paths / devices / statuses. */
.cf-bar-row {
  display: grid;
  grid-template-columns: minmax(80px, 32%) 1fr auto;
  align-items: center;
  gap: clamp(8px, 0.7vw, 22px);
  font-size: calc(clamp(1.05rem, 1vw, 2.8rem) * var(--type-scale));
  min-width: 0;
}
.cf-bar-label {
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}
.cf-bar-label.cf-path {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: calc(clamp(0.95rem, 0.9vw, 2.5rem) * var(--type-scale));
  color: #e2e8f0;
}
.cf-bar-track {
  background: rgba(255, 255, 255, 0.05);
  border-radius: 6px;
  height: clamp(9px, 0.75vw, 22px);
  overflow: hidden;
  position: relative;
}
.cf-bar-fill {
  height: 100%;
  background: linear-gradient(90deg, #f6821f, #ffb05e);
  border-radius: 4px;
  transition: width 0.4s ease;
}
.cf-bar-val {
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  white-space: nowrap;
}
.cf-bar-extra {
  color: var(--text-faint);
  font-weight: 400;
  margin-left: 4px;
  opacity: 0.75;
}

/* Status bucket colour coding — applied to both the chip and the bar fill. */
.cf-status-pill {
  display: inline-block;
  padding: clamp(2px, 0.2vw, 8px) clamp(8px, 0.7vw, 20px);
  border-radius: 6px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-size: calc(clamp(1rem, 0.95vw, 2.6rem) * var(--type-scale));
}
.cf-bk-ok    { background: rgba(34, 197, 94, 0.18); color: #4ade80; }
.cf-bk-ok.cf-bar-fill    { background: linear-gradient(90deg, #22c55e, #4ade80); }
.cf-bk-redir { background: rgba(96, 165, 250, 0.18); color: #93c5fd; }
.cf-bk-redir.cf-bar-fill { background: linear-gradient(90deg, #3b82f6, #60a5fa); }
.cf-bk-warn  { background: rgba(245, 158, 11, 0.20); color: #fbbf24; }
.cf-bk-warn.cf-bar-fill  { background: linear-gradient(90deg, #f59e0b, #fbbf24); }
.cf-bk-bad   { background: rgba(248, 113, 113, 0.20); color: #fca5a5; }
.cf-bk-bad.cf-bar-fill   { background: linear-gradient(90deg, #ef4444, #f87171); }

/* Inline chips listing the top individual 4xx/5xx codes. */
.cf-status-codes {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(4px, 0.4vw, 10px);
  margin-top: clamp(4px, 0.4vw, 10px);
}
.cf-code-chip {
  display: inline-flex;
  align-items: center;
  gap: clamp(6px, 0.5vw, 14px);
  padding: clamp(3px, 0.3vw, 9px) clamp(10px, 0.8vw, 22px);
  background: rgba(248, 113, 113, 0.12);
  color: #fca5a5;
  border: 1px solid rgba(248, 113, 113, 0.3);
  border-radius: 6px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-size: calc(clamp(1rem, 0.95vw, 2.5rem) * var(--type-scale));
}
.cf-code-chip .cf-code-n {
  font-weight: 400;
  color: var(--text-faint);
}

/* ---------- Lists ---------- */
.list {
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 0.6vw, 21px);
  overflow-y: auto;
  flex: 1;
  min-height: 0;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
  position: relative;
}
.list::-webkit-scrollbar { width: 8px; }
.list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 8px; }

/* When auto-scrolling: hide scrollbar, mask edges, swap to vertical ticker.
   Applies to any container (list, calendar-body, etc.) that gets the class. */
.list-autoscrolling {
  overflow: hidden;
  scrollbar-width: none;
  mask-image: linear-gradient(to bottom, transparent 0, black 5%, black 95%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 5%, black 95%, transparent 100%);
}
.list-autoscrolling::-webkit-scrollbar { display: none; }

.list-scroll-v {
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 0.6vw, 21px);
  animation: scroll-list-y var(--scroll-duration, 60s) linear infinite;
  will-change: transform;
}

/* Sprint kanban view — one column per status, each independently scrolling.
   minmax(0, 1fr) lets every configured column squeeze to fit on one row even
   when there are 8 of them on a 1080p TV. */
.sprint-columns {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: clamp(6px, 0.5vw, 18px);
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.sprint-column {
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: clamp(8px, 0.65vw, 20px);
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}
.sprint-col-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  margin-bottom: clamp(6px, 0.45vw, 16px);
  padding-bottom: clamp(4px, 0.35vw, 12px);
  border-bottom: 2px solid var(--col-accent, var(--border));
}
.sprint-col-name {
  font-size: calc(clamp(0.7rem, 0.68vw, 2.6rem) * var(--type-scale));
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--col-accent, var(--text-dim));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sprint-col-count {
  background: var(--bg-elev);
  color: var(--col-accent, var(--text-dim));
  font-size: calc(clamp(0.65rem, 0.6vw, 2.2rem) * var(--type-scale));
  font-weight: 700;
  padding: 2px 10px;
  border-radius: 999px;
  flex-shrink: 0;
}
.sprint-col-list {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: clamp(6px, 0.45vw, 16px);
  min-height: 0;
  overflow: hidden;
  position: relative;
}
.sprint-col-empty {
  color: var(--text-faint);
  text-align: center;
  font-size: calc(clamp(0.85rem, 0.85vw, 3.3rem) * var(--type-scale));
  padding: 8px 0;
}

/* When there are many columns, dial back row padding/font so cards still
   look decent at narrow widths. */
.sprint-columns .row {
  padding: clamp(6px, 0.45vw, 16px) clamp(8px, 0.55vw, 18px);
}
.sprint-columns .row .row-title {
  font-size: calc(clamp(0.72rem, 0.7vw, 2.6rem) * var(--type-scale));
  white-space: normal;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.25;
}
.sprint-columns .row .row-sub {
  font-size: calc(clamp(0.62rem, 0.58vw, 2.1rem) * var(--type-scale));
}
.sprint-columns .row .avatar {
  width: clamp(20px, 1.25vw, 42px);
  height: clamp(20px, 1.25vw, 42px);
}

/* Paged-list mode — pages cross-fade in place on a timer. */
.list-paged {
  position: relative;
  overflow: hidden;
}
.list-page {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 0.6vw, 21px);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity .55s ease, transform .55s ease;
  pointer-events: none;
}
.list-page.active {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
@keyframes scroll-list-y {
  0%   { transform: translate3d(0, 0, 0); }
  /* Pause briefly at the start so freshest rows are readable */
  8%   { transform: translate3d(0, 0, 0); }
  100% { transform: translate3d(0, -50%, 0); }
}

.row {
  display: flex;
  align-items: center;
  gap: clamp(8px, 0.55vw, 18px);
  padding: clamp(7px, 0.5vw, 18px) clamp(10px, 0.75vw, 24px);
  background: var(--bg-elev-2);
  border-radius: 12px;
  border-left: 3px solid var(--border);
  animation: rowIn .5s ease both;
}

/* Stale PR highlights: warm border + subtle bg tint so older PRs stand
   out without screaming. Tier 1 (>=7 days) warning, tier 2 (>=14d) danger. */
.row.pr-aging {
  border-left-color: var(--warning, #f59e0b);
  background: linear-gradient(90deg, rgba(245, 158, 11, 0.08), var(--bg-elev-2) 30%);
}
.row.pr-stale {
  border-left-color: var(--danger, #ef4444);
  background: linear-gradient(90deg, rgba(239, 68, 68, 0.10), var(--bg-elev-2) 30%);
}
.row .row-main { flex: 1; min-width: 0; }
.row .row-title {
  font-size: calc(clamp(0.85rem, 0.8vw, 3.3rem) * var(--type-scale));
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.row .row-sub {
  font-size: calc(clamp(0.72rem, 0.65vw, 2.6rem) * var(--type-scale));
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-top: 2px;
}
.row .row-meta {
  font-size: calc(clamp(0.7rem, 0.6vw, 2.4rem) * var(--type-scale));
  color: var(--text-dim);
  text-align: right;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: flex-end;
}
/* Inline variant: badge + timestamp on the same line, larger timestamp. */
.row .row-meta--inline {
  flex-direction: row;
  align-items: baseline;
  gap: clamp(10px, 0.8vw, 22px);
}
.row .row-meta--inline .row-time {
  font-size: calc(clamp(1rem, 1.05vw, 3rem) * var(--type-scale));
  color: var(--text-dim);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

@keyframes rowIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* Stagger row entrance */
.row:nth-child(1)  { animation-delay: .00s; }
.row:nth-child(2)  { animation-delay: .04s; }
.row:nth-child(3)  { animation-delay: .08s; }
.row:nth-child(4)  { animation-delay: .12s; }
.row:nth-child(5)  { animation-delay: .16s; }
.row:nth-child(6)  { animation-delay: .20s; }
.row:nth-child(7)  { animation-delay: .24s; }
.row:nth-child(8)  { animation-delay: .28s; }
.row:nth-child(9)  { animation-delay: .32s; }
.row:nth-child(10) { animation-delay: .36s; }

.badge {
  display: inline-block;
  padding: 2px 10px;
  font-size: calc(clamp(0.7rem, 0.7vw, 2.85rem) * var(--type-scale));
  font-weight: 700;
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  background: var(--bg-elev);
  color: var(--text-dim);
}
.badge.success { background: rgba(74,222,128,0.15); color: var(--success); }
.badge.danger  { background: rgba(248,113,113,0.15); color: var(--danger); }
.badge.warning { background: rgba(251,191,36,0.15); color: var(--warning); }
.badge.info    { background: rgba(96,165,250,0.15); color: var(--info); }

.row.status-success { border-left-color: var(--success); }
.row.status-failed  { border-left-color: var(--danger); }
.row.status-running { border-left-color: var(--info); }
.row.status-pending { border-left-color: var(--warning); }

.avatar {
  width: clamp(30px, 2vw, 66px);
  height: clamp(30px, 2vw, 66px);
  border-radius: 50%;
  background: var(--bg-elev);
  flex-shrink: 0;
  object-fit: cover;
}

.loading, .empty, .error {
  color: var(--text-dim);
  padding: 20px;
  text-align: center;
  font-size: calc(clamp(0.95rem, 0.9vw, 3.6rem) * var(--type-scale));
}
.error { color: var(--danger); }

/* Small/legacy displays fall back to a stacked layout */
@media (max-width: 1100px) {
  .dashboard {
    grid-template-columns: 1fr 1fr;
    grid-template-areas:
      "clock    weather"
      "spotify  spotify"
      "carousel carousel"
      "dots     dots";
  }
  .forecast-grid--hourly { grid-template-columns: repeat(4, 1fr); }
}

/* ---------- Norwich Local page ----------
   2-column layout: Travel (compact, top-left) + Radar (big, bottom-left)
   on the left; News spanning the full-height right column. Travel takes
   its natural compact height; Radar gets all the remaining space. */
.norwich-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* Cap travel row so a busy day of alerts can't crush the radar.
     Bumped from 36% → 42% to fit both road + train strips at natural
     pill height without cropping. */
  grid-template-rows: minmax(160px, 42%) 1fr;
  grid-template-areas:
    "travel news"
    "radar  news";
  gap: clamp(10px, 0.8vw, 24px);
  flex: 1;
  min-height: 0;
}
.norwich-tile--travel { grid-area: travel; }
.norwich-tile--radar  { grid-area: radar; }
.norwich-tile--news   { grid-area: news; }
.norwich-tile {
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: clamp(10px, 0.8vw, 22px);
  display: flex;
  flex-direction: column;
  gap: clamp(6px, 0.5vw, 14px);
  min-height: 0;
  overflow: hidden;
}
.norwich-tile-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: clamp(6px, 0.5vw, 14px);
  padding-bottom: clamp(4px, 0.4vw, 10px);
  border-bottom: 1px solid var(--border);
}
.norwich-tile-title {
  font-size: calc(clamp(0.85rem, 0.85vw, 2.6rem) * var(--type-scale));
  font-weight: 800;
  letter-spacing: -0.01em;
}
.norwich-tile-sub {
  font-size: calc(clamp(0.65rem, 0.6vw, 2rem) * var(--type-scale));
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.norwich-tile-body {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: clamp(6px, 0.5vw, 14px);
}
/* Travel tile specifically: tighter gap between road + train strips. */
.norwich-tile--travel .norwich-tile-body { gap: clamp(4px, 0.3vw, 8px); }

/* ---- Rain radar tile ---- */
/* The radar body holds the Leaflet map + a small overlay bar showing the
   currently-displayed frame's timestamp + past/forecast label. */
.norwich-tile--radar .norwich-tile-body { position: relative; }
.norwich-radar-map {
  flex: 1;
  min-height: 0;
  background: var(--bg);
  border-radius: 8px;
  overflow: hidden;
}
.norwich-radar-bar {
  position: absolute;
  left: clamp(8px, 0.6vw, 16px);
  bottom: clamp(8px, 0.6vw, 16px);
  display: flex;
  align-items: center;
  gap: clamp(6px, 0.5vw, 12px);
  background: rgba(0, 0, 0, 0.65);
  border: 1px solid var(--border);
  padding: clamp(3px, 0.25vw, 8px) clamp(8px, 0.6vw, 14px);
  border-radius: 999px;
  z-index: 400;
  pointer-events: none;
}
.norwich-radar-time {
  color: var(--text);
  font-size: calc(clamp(0.7rem, 0.65vw, 1.9rem) * var(--type-scale));
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.norwich-radar-tag {
  font-size: calc(clamp(0.5rem, 0.45vw, 1.3rem) * var(--type-scale));
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: #0b0e14;
  background: var(--text-faint);
  padding: 1px 8px;
  border-radius: 999px;
}
.norwich-radar-tag--forecast { background: var(--accent); }
.leaflet-container {
  cursor: default !important;
  background: var(--bg);
}

/* ---- Travel tile (Traffic + Trains combined) ---- */
/* No section labels — the route badges + green time chips read as roads
   vs trains on their own. The tile-body's flex gap separates the strips. */
.travel-empty {
  font-size: calc(clamp(0.72rem, 0.7vw, 2.2rem) * var(--type-scale));
  color: var(--success);
  padding: clamp(4px, 0.4vw, 10px) 0;
}
.travel-empty--muted { color: var(--text-faint); }
.travel-route {
  flex-shrink: 0;
  min-width: clamp(34px, 2.6vw, 60px);
  height: clamp(22px, 1.8vw, 40px);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: calc(clamp(0.7rem, 0.7vw, 2rem) * var(--type-scale));
  border-radius: 6px;
  color: #0b0e14;
  padding: 0 clamp(4px, 0.3vw, 8px);
}
/* Norfolk-internal routes (A47, A140, A146, A148, A149) — warm/orange. */
.travel-route--norfolk { background: linear-gradient(135deg, #f59e0b, #fbbf24); }
/* London-bound arteries (A11, A12, A14) — cool/blue. */
.travel-route--london  { background: linear-gradient(135deg, #3b82f6, #60a5fa); color: white; }
/* Train departure — Greater Anglia green; delayed services tint amber. */
.travel-route--train   { background: linear-gradient(135deg, #1d6f42, #2da961); color: white; font-variant-numeric: tabular-nums; }
.travel-route--delayed { background: linear-gradient(135deg, #b45309, #f59e0b); color: #0b0e14; }

/* Horizontal scroll strip used by both road alerts and train departures.
   Section heads stay pinned above; the pills underneath scroll left-to-right
   with no visible scrollbar (matches the rest of the dashboard). */
.travel-strip {
  display: flex;
  gap: clamp(6px, 0.5vw, 12px);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -ms-overflow-style: none;
  align-items: flex-start;
  flex: 0 0 auto;
}
.travel-strip::-webkit-scrollbar { display: none; }
/* Two adjacent strips (road + train): the first (road) gets to flex into
   whatever space remains; the second (train) sits at its natural height
   so its pills are never cropped from below. */
.travel-strip:first-child {
  flex: 1 1 0;
  min-height: 0;
}
.train-pill {
  flex: 0 0 clamp(110px, 9vw, 200px);
  min-width: 0;
  height: clamp(60px, 3.8vw, 80px);
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-left: 3px solid #2da961;
  border-radius: 8px;
  padding: clamp(5px, 0.4vw, 10px) clamp(7px, 0.55vw, 12px);
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.road-pill {
  flex: 0 0 clamp(280px, 22vw, 460px);
  min-width: 0;
  height: clamp(60px, 3.8vw, 80px);
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: clamp(6px, 0.5vw, 12px) clamp(8px, 0.6vw, 14px);
  display: flex;
  gap: clamp(6px, 0.5vw, 12px);
  align-items: flex-start;
}
.road-pill-info { min-width: 0; flex: 1; }
.road-pill-location {
  font-size: calc(clamp(0.65rem, 0.62vw, 1.9rem) * var(--type-scale));
  font-weight: 600;
  color: var(--text);
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.road-pill-reason {
  font-size: calc(clamp(0.55rem, 0.52vw, 1.6rem) * var(--type-scale));
  color: var(--text-dim);
  margin-top: 2px;
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.train-pill--delayed { border-left-color: #f59e0b; }
.train-pill-time {
  font-size: calc(clamp(0.78rem, 0.75vw, 2.2rem) * var(--type-scale));
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  line-height: 1.1;
}
.train-pill--delayed .train-pill-time { color: #fbbf24; }
.train-pill-dest {
  font-size: calc(clamp(0.6rem, 0.55vw, 1.7rem) * var(--type-scale));
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}
.train-pill-meta {
  font-size: calc(clamp(0.7rem, 0.65vw, 1.95rem) * var(--type-scale));
  font-weight: 600;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-top: 2px;
}
/* ---- Norwich news tile (BBC Norfolk) ---- */
.nn-row {
  display: grid;
  grid-template-columns: clamp(54px, 4.5vw, 100px) 1fr;
  gap: clamp(8px, 0.6vw, 16px);
  padding: clamp(5px, 0.4vw, 12px) 0;
  border-top: 1px solid var(--border);
}
.nn-row:first-child { border-top: 0; padding-top: 0; }
.nn-time {
  font-size: calc(clamp(0.6rem, 0.55vw, 1.7rem) * var(--type-scale));
  color: var(--text-faint);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding-top: 2px;
  font-variant-numeric: tabular-nums;
}
.nn-text { min-width: 0; }
.nn-title {
  font-size: calc(clamp(0.75rem, 0.7vw, 2.2rem) * var(--type-scale));
  font-weight: 700;
  color: var(--text);
  line-height: 1.3;
}
.nn-source {
  display: inline-block;
  font-size: calc(clamp(0.5rem, 0.45vw, 1.3rem) * var(--type-scale));
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: #0b0e14;
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
  padding: 1px 6px;
  border-radius: 999px;
  vertical-align: middle;
  margin-right: 6px;
}
.nn-desc {
  font-size: calc(clamp(0.6rem, 0.58vw, 1.8rem) * var(--type-scale));
  color: var(--text-dim);
  margin-top: 2px;
  line-height: 1.3;
}

/* ---- Norwich City FC page ---- */
.ncfc-panel { min-height: 0; }
.ncfc-page-grid {
  display: grid;
  grid-template-columns: 1.5fr 1fr;
  gap: clamp(12px, 1vw, 28px);
  flex: 1;
  min-height: 0;
}
.ncfc-page-col {
  display: flex;
  flex-direction: column;
  gap: clamp(6px, 0.5vw, 14px);
  min-height: 0;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: clamp(10px, 0.8vw, 22px);
  overflow: hidden;
}
.ncfc-page-subheader {
  font-size: calc(clamp(0.65rem, 0.6vw, 1.9rem) * var(--type-scale));
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text-faint);
  padding-bottom: clamp(4px, 0.35vw, 10px);
  border-bottom: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: clamp(8px, 0.6vw, 14px);
}
.ncfc-page-subheader + .ncfc-page-subheader { margin-top: clamp(6px, 0.5vw, 14px); }

/* Championship / Premier League table.
   .ncfc-tbl-head is a fixed header that lives OUTSIDE the auto-scrolling
   rows container so the column labels stay put while rows ticker upward.
   .ncfc-table holds the row list and is the autoScrollList target. */
.ncfc-table {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.ncfc-tbl-head, .ncfc-tbl-row {
  display: grid;
  grid-template-columns: clamp(22px, 1.8vw, 40px) minmax(0, 1fr) repeat(6, clamp(20px, 1.6vw, 36px)) clamp(70px, 6vw, 130px);
  align-items: center;
  gap: clamp(4px, 0.4vw, 10px);
  padding: clamp(3px, 0.3vw, 8px) clamp(6px, 0.5vw, 12px);
  font-size: calc(clamp(0.62rem, 0.6vw, 1.8rem) * var(--type-scale));
  border-bottom: 1px solid var(--border);
}
.ncfc-tbl-head {
  font-size: calc(clamp(0.55rem, 0.5vw, 1.5rem) * var(--type-scale));
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-faint);
  flex-shrink: 0;
}
.ncfc-tbl-row { color: var(--text); }
.ncfc-tbl-row:nth-child(odd) { background: rgba(255,255,255,0.02); }
.ncfc-tbl-row--us {
  background: linear-gradient(90deg, rgba(255, 200, 0, 0.18), rgba(255, 200, 0, 0.05)) !important;
  border-left: 3px solid #ffc800;
  padding-left: clamp(3px, 0.25vw, 9px);
  font-weight: 700;
}
.ncfc-tbl-pos {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--text-dim);
}
.ncfc-tbl-row--us .ncfc-tbl-pos { color: var(--accent); }
.ncfc-tbl-team {
  display: flex;
  align-items: center;
  gap: clamp(4px, 0.4vw, 10px);
  min-width: 0;
}
.ncfc-tbl-num {
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
}
.ncfc-tbl-pts { color: var(--text); font-weight: 700; }
.ncfc-tbl-form {
  display: flex;
  gap: 2px;
  justify-content: flex-end;
}

/* Shared crest / team-name styles (also used in fixture rows). */
.ncfc-team-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ncfc-crest {
  width: clamp(16px, 1.1vw, 28px);
  height: clamp(16px, 1.1vw, 28px);
  flex-shrink: 0;
  object-fit: contain;
}

/* Form chips reused from the old tile design. */
.ncfc-form-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: clamp(12px, 0.9vw, 22px);
  height: clamp(12px, 0.9vw, 22px);
  border-radius: 3px;
  font-size: calc(clamp(0.5rem, 0.48vw, 1.4rem) * var(--type-scale));
  font-weight: 800;
  color: #0b0e14;
}
.ncfc-form-w { background: var(--success); }
.ncfc-form-d { background: var(--text-faint); }
.ncfc-form-l { background: var(--danger); }

/* Fixture rows (recent + upcoming). */
.ncfc-fixtures {
  display: flex;
  flex-direction: column;
  gap: clamp(3px, 0.3vw, 8px);
}
.ncfc-fx-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: clamp(6px, 0.5vw, 14px);
  background: var(--bg-elev);
  border-radius: 8px;
  padding: clamp(5px, 0.4vw, 10px) clamp(8px, 0.6vw, 14px);
}
.ncfc-fx-side {
  display: flex;
  align-items: center;
  gap: clamp(4px, 0.4vw, 10px);
  min-width: 0;
  font-size: calc(clamp(0.7rem, 0.68vw, 2.1rem) * var(--type-scale));
}
.ncfc-fx-side--away { justify-content: flex-end; }
.ncfc-fx-score {
  font-size: calc(clamp(0.95rem, 0.95vw, 2.6rem) * var(--type-scale));
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  padding: 0 clamp(6px, 0.5vw, 14px);
}
.ncfc-fx-when {
  font-size: calc(clamp(0.6rem, 0.58vw, 1.8rem) * var(--type-scale));
  color: var(--text-dim);
  text-align: center;
  white-space: nowrap;
  padding: 0 clamp(6px, 0.5vw, 14px);
}

/* ---------- Formula 1 page ---------- */
.f1-panel {
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 0.7vw, 18px);
}
.f1-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(10px, 0.8vw, 22px);
  padding-bottom: clamp(6px, 0.5vw, 14px);
  border-bottom: 1px solid var(--border);
}
.f1-title { display: flex; flex-direction: column; gap: 2px; }
.f1-title-main {
  font-size: calc(clamp(0.95rem, 0.95vw, 2.6rem) * var(--type-scale));
  font-weight: 800;
  letter-spacing: -0.01em;
  color: #e10600;  /* Formula 1 red */
}
.f1-title-sub {
  font-size: calc(clamp(0.6rem, 0.55vw, 1.8rem) * var(--type-scale));
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.12em;
}
.f1-next {
  display: flex;
  align-items: baseline;
  gap: clamp(8px, 0.6vw, 14px);
  font-variant-numeric: tabular-nums;
}
.f1-next-label {
  font-size: calc(clamp(0.55rem, 0.5vw, 1.5rem) * var(--type-scale));
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text-faint);
}
.f1-next-name {
  font-size: calc(clamp(0.85rem, 0.85vw, 2.3rem) * var(--type-scale));
  font-weight: 800;
  color: var(--text);
}
.f1-next-when {
  font-size: calc(clamp(0.7rem, 0.65vw, 1.9rem) * var(--type-scale));
  color: var(--accent);
}

/* 4-column grid: drivers | constructors | last race result | next race */
.f1-grid {
  display: grid;
  grid-template-columns: 1.1fr 0.95fr 1fr 1.05fr;
  gap: clamp(8px, 0.6vw, 16px);
  flex: 1;
  min-height: 0;
}
.f1-col {
  display: flex;
  flex-direction: column;
  gap: clamp(5px, 0.4vw, 10px);
  min-height: 0;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: clamp(7px, 0.55vw, 14px);
  overflow: hidden;
}
.f1-col-head {
  font-size: calc(clamp(0.6rem, 0.55vw, 1.8rem) * var(--type-scale));
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text-faint);
  padding-bottom: clamp(4px, 0.35vw, 10px);
  border-bottom: 1px solid var(--border);
}
.f1-list {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: clamp(3px, 0.3vw, 8px);
}

.f1-row {
  display: grid;
  grid-template-columns: clamp(16px, 1.2vw, 26px) 2px 1fr auto;
  align-items: center;
  gap: clamp(4px, 0.35vw, 9px);
  padding: clamp(3px, 0.25vw, 7px) clamp(5px, 0.4vw, 9px);
  background: var(--bg-elev);
  border-radius: 5px;
}
.f1-pos {
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  color: var(--text-dim);
  font-size: calc(clamp(0.68rem, 0.62vw, 1.8rem) * var(--type-scale));
  text-align: center;
}
.f1-team-bar {
  height: clamp(16px, 1.2vw, 28px);
  width: 2px;
  border-radius: 1px;
}
.f1-driver { min-width: 0; }
.f1-driver-name {
  font-size: calc(clamp(0.65rem, 0.62vw, 1.85rem) * var(--type-scale));
  font-weight: 700;
  display: flex;
  align-items: center;
  gap: clamp(3px, 0.25vw, 6px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.f1-flag { font-size: 1em; line-height: 1; }
.f1-driver-team {
  font-size: calc(clamp(0.52rem, 0.5vw, 1.5rem) * var(--type-scale));
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.f1-points {
  font-size: calc(clamp(0.75rem, 0.7vw, 2rem) * var(--type-scale));
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  display: flex;
  align-items: baseline;
  gap: 2px;
}
.f1-pts-lab {
  font-size: calc(clamp(0.45rem, 0.4vw, 1.2rem) * var(--type-scale));
  color: var(--text-faint);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.f1-row--result .f1-points {
  color: var(--text);
  font-size: calc(clamp(0.62rem, 0.58vw, 1.7rem) * var(--type-scale));
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Next-race tile (4th column) — round + name on top, locality/date in the
   middle, Leaflet circuit map fills the rest. */
.f1-next-tile {
  display: flex;
  flex-direction: column;
  gap: clamp(4px, 0.35vw, 10px);
  flex: 1;
  min-height: 0;
}
.f1-nr-head { display: flex; flex-direction: column; gap: 2px; }
.f1-nr-round {
  font-size: calc(clamp(0.55rem, 0.5vw, 1.5rem) * var(--type-scale));
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-faint);
  display: flex;
  align-items: center;
  gap: 4px;
}
.f1-nr-name {
  font-size: calc(clamp(0.82rem, 0.78vw, 2.2rem) * var(--type-scale));
  font-weight: 800;
  color: var(--text);
  line-height: 1.2;
}
.f1-nr-meta {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding-bottom: clamp(3px, 0.3vw, 8px);
  border-bottom: 1px solid var(--border);
}
.f1-nr-circuit {
  font-size: calc(clamp(0.62rem, 0.58vw, 1.7rem) * var(--type-scale));
  color: var(--text-dim);
  line-height: 1.3;
}
.f1-nr-locality {
  font-size: calc(clamp(0.55rem, 0.5vw, 1.5rem) * var(--type-scale));
  color: var(--text-faint);
}
.f1-nr-when {
  font-size: calc(clamp(0.62rem, 0.58vw, 1.7rem) * var(--type-scale));
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  margin-top: 2px;
}

/* Weekend schedule list — FP/Q/Sprint/Race rows above the circuit map. */
.f1-nr-schedule {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.f1-sess-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: clamp(6px, 0.5vw, 12px);
  font-size: calc(clamp(0.6rem, 0.58vw, 1.7rem) * var(--type-scale));
  padding: clamp(2px, 0.18vw, 5px) clamp(4px, 0.3vw, 8px);
  border-radius: 4px;
}
.f1-sess-row:nth-child(odd) { background: rgba(255, 255, 255, 0.025); }
.f1-sess-row:last-child {
  background: rgba(225, 6, 0, 0.18);
  border-left: 2px solid #e10600;
  font-weight: 700;
}
.f1-sess-lbl {
  font-size: calc(clamp(0.55rem, 0.5vw, 1.5rem) * var(--type-scale));
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-faint);
}
.f1-sess-row:last-child .f1-sess-lbl { color: var(--text); }
.f1-sess-when {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.f1-map {
  flex: 1;
  min-height: clamp(70px, 6vw, 160px);
  border-radius: 6px;
  overflow: hidden;
  background: var(--bg);
}

/* ---------- Standalone /settings page ---------- */
.settings-page {
  width: 100vw;
  min-height: 100vh;
  padding: clamp(16px, 1.2vw, 36px);
  display: flex;
  /* Allow the page to scroll if an open section overflows — a safety net for
     TVs whose remote can scroll. The collapsible sections below are the
     primary mechanism for keeping everything reachable without scrolling. */
  overflow-x: hidden;
  overflow-y: auto;
}
.settings-page .settings-panel {
  flex: 1;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: clamp(14px, 1.1vw, 40px) clamp(18px, 1.3vw, 48px);
  box-shadow: var(--shadow);
}
.settings-actions {
  display: flex;
  align-items: center;
  gap: clamp(8px, 0.6vw, 16px);
}
.settings-back {
  color: var(--text-dim);
  text-decoration: none;
  font-size: calc(clamp(0.7rem, 0.65vw, 1.9rem) * var(--type-scale));
  padding: clamp(8px, 0.6vw, 16px) clamp(12px, 0.9vw, 22px);
  border-radius: 8px;
  transition: color .15s ease, background .15s ease;
}
.settings-back:hover {
  color: var(--text);
  background: var(--bg-elev-2);
}

.settings-panel {
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 0.8vw, 22px);
}
.settings-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(10px, 0.8vw, 22px);
  padding-bottom: clamp(8px, 0.6vw, 16px);
  border-bottom: 1px solid var(--border);
}
.settings-title { display: flex; flex-direction: column; gap: 2px; }
.settings-title-main {
  font-size: calc(clamp(0.95rem, 0.95vw, 2.6rem) * var(--type-scale));
  font-weight: 800;
  letter-spacing: -0.01em;
}
.settings-title-sub {
  font-size: calc(clamp(0.6rem, 0.55vw, 1.8rem) * var(--type-scale));
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.12em;
}
.settings-apply, .settings-reset {
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  color: var(--text);
  padding: clamp(8px, 0.6vw, 16px) clamp(14px, 1vw, 26px);
  border-radius: 8px;
  font-size: calc(clamp(0.7rem, 0.65vw, 1.9rem) * var(--type-scale));
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.settings-apply:hover, .settings-reset:hover {
  background: #232a3b;
  border-color: var(--accent);
}
.settings-apply--dirty {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  border-color: transparent;
  color: #0b0e14;
}
.settings-apply:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Page list — responsive 2-3 column grid of cards */
.settings-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(clamp(220px, 18vw, 360px), 1fr));
  gap: clamp(8px, 0.6vw, 18px);
  align-content: start;
}
.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(8px, 0.6vw, 16px);
  padding: clamp(8px, 0.7vw, 18px) clamp(12px, 0.9vw, 22px);
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.settings-row:hover { border-color: var(--accent); }
.settings-row.is-on { border-left: 3px solid var(--success); }
.settings-row.is-off {
  border-left: 3px solid var(--text-faint);
  opacity: 0.7;
}
.settings-row-label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.settings-row-name {
  font-size: calc(clamp(0.8rem, 0.75vw, 2.2rem) * var(--type-scale));
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.settings-row-id {
  font-size: calc(clamp(0.55rem, 0.5vw, 1.6rem) * var(--type-scale));
  color: var(--text-faint);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  text-transform: lowercase;
}

/* Toggle switch */
.settings-switch {
  position: relative;
  width: clamp(38px, 2.8vw, 60px);
  height: clamp(22px, 1.6vw, 34px);
  flex-shrink: 0;
}
.settings-switch-track {
  position: absolute;
  inset: 0;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 999px;
  transition: background .2s ease, border-color .2s ease;
}
.settings-switch-thumb {
  position: absolute;
  top: 50%;
  left: 3px;
  transform: translateY(-50%);
  width: clamp(14px, 1.1vw, 24px);
  height: clamp(14px, 1.1vw, 24px);
  background: var(--text-faint);
  border-radius: 50%;
  transition: left .2s ease, background .2s ease;
}
.settings-row.is-on .settings-switch-track {
  background: rgba(74, 222, 128, 0.25);
  border-color: var(--success);
}
.settings-row.is-on .settings-switch-thumb {
  left: calc(100% - clamp(14px, 1.1vw, 24px) - 3px);
  background: var(--success);
}

/* ---------- Collapsible sections (Theme / Pages / Status) --------------- */
.settings-section {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elev-2);
  overflow: hidden;
}
.settings-section-summary {
  display: flex;
  align-items: center;
  gap: clamp(10px, 0.7vw, 18px);
  padding: clamp(12px, 0.9vw, 26px) clamp(14px, 1vw, 28px);
  cursor: pointer;
  user-select: none;
  list-style: none;
  font-size: calc(clamp(0.8rem, 0.8vw, 2.2rem) * var(--type-scale));
  font-weight: 800;
  letter-spacing: 0.02em;
}
/* Hide the native disclosure triangle in favour of our own chevron. */
.settings-section-summary::-webkit-details-marker { display: none; }
.settings-section-summary::marker { content: ""; }
.settings-section-summary:hover { background: var(--bg-elev); }
.settings-section-summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
/* CSS-triangle chevron: points right when closed, rotates down when open. */
.settings-section-chevron {
  flex-shrink: 0;
  width: 0;
  height: 0;
  border-left: clamp(7px, 0.5vw, 13px) solid var(--text-dim);
  border-top: clamp(5px, 0.36vw, 9px) solid transparent;
  border-bottom: clamp(5px, 0.36vw, 9px) solid transparent;
  transition: transform .15s ease;
}
.settings-section[open] .settings-section-chevron { transform: rotate(90deg); }
.settings-section-name { flex: 1; }
.settings-section-body {
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 0.6vw, 16px);
  padding: 0 clamp(14px, 1vw, 28px) clamp(14px, 1vw, 28px);
}
.settings-section-actions {
  display: flex;
  justify-content: flex-end;
}

/* ---------- System status block (integration health + display info) ----- */
.settings-status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(clamp(180px, 14vw, 280px), 1fr));
  gap: clamp(6px, 0.5vw, 12px);
}
.status-row {
  display: flex;
  align-items: flex-start;
  gap: clamp(6px, 0.4vw, 12px);
  padding: clamp(8px, 0.6vw, 14px) clamp(10px, 0.7vw, 18px);
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.status-dot {
  flex-shrink: 0;
  width: clamp(8px, 0.6vw, 16px);
  height: clamp(8px, 0.6vw, 16px);
  border-radius: 50%;
  margin-top: clamp(4px, 0.3vw, 8px);
  background: var(--text-faint);
}
.status-row.is-ok            .status-dot { background: var(--success); box-shadow: 0 0 6px var(--success); }
.status-row.is-error         .status-dot { background: var(--danger);  box-shadow: 0 0 6px var(--danger); }
.status-row.is-unconfigured  .status-dot { background: var(--text-faint); }
.status-body { min-width: 0; flex: 1; }
.status-name {
  font-weight: 700;
  font-size: calc(clamp(0.75rem, 0.7vw, 2rem) * var(--type-scale));
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.status-meta {
  font-size: calc(clamp(0.6rem, 0.55vw, 1.6rem) * var(--type-scale));
  color: var(--text-dim);
  margin-top: 2px;
}
.status-detail {
  font-size: calc(clamp(0.55rem, 0.5vw, 1.5rem) * var(--type-scale));
  color: var(--text-faint);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  margin-top: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  word-break: break-all;
}

.settings-display-info {
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: clamp(8px, 0.6vw, 14px) clamp(10px, 0.7vw, 18px);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(clamp(160px, 13vw, 260px), 1fr));
  gap: clamp(4px, 0.3vw, 10px) clamp(10px, 0.8vw, 18px);
  font-size: calc(clamp(0.55rem, 0.55vw, 1.5rem) * var(--type-scale));
}
.display-info-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: clamp(6px, 0.4vw, 12px);
  min-width: 0;
}
.display-info-row span { color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.05em; }
.display-info-row b    { color: var(--text); font-weight: 600; overflow: hidden; text-overflow: ellipsis; }
.display-info-ua { grid-column: 1 / -1; }
.display-info-ua b { white-space: normal; word-break: break-all; }

.settings-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(10px, 0.8vw, 22px);
  padding-top: clamp(6px, 0.5vw, 14px);
  border-top: 1px solid var(--border);
}
.settings-hint {
  font-size: calc(clamp(0.6rem, 0.55vw, 1.7rem) * var(--type-scale));
  color: var(--text-faint);
}

/* Theme picker (settings page) */
.settings-themes-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(clamp(140px, 11vw, 240px), 1fr));
  gap: clamp(8px, 0.6vw, 16px);
}
.theme-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 0.5vw, 14px);
  padding: clamp(10px, 0.7vw, 18px);
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  cursor: pointer;
  transition: border-color .15s ease;
}
.theme-card:hover { border-color: var(--accent); }
.theme-card.is-selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent) inset;
}
.theme-card-swatch {
  height: clamp(36px, 3vw, 64px);
  border-radius: 8px;
  border: 1px solid rgba(255,255,255,0.06);
  position: relative;
  overflow: hidden;
  background: var(--tc-bg, #000);
}
.theme-card-swatch::before,
.theme-card-swatch::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 22%;
  height: 60%;
  border-radius: 6px;
  transform: translateY(-50%);
}
.theme-card-swatch::before {
  left: 12%;
  background: var(--tc-accent, #fff);
}
.theme-card-swatch::after {
  left: 42%;
  background: var(--tc-accent-2, #fff);
}
.theme-card-name {
  font-size: calc(clamp(0.75rem, 0.7vw, 2rem) * var(--type-scale));
  font-weight: 700;
  color: var(--text);
}
.theme-card-desc {
  font-size: calc(clamp(0.55rem, 0.5vw, 1.5rem) * var(--type-scale));
  color: var(--text-faint);
  line-height: 1.3;
}

/* ===========================================================================
   Theme-specific finishing touches
   ---------------------------------------------------------------------------
   Beyond the per-theme CSS variables, each theme can opt-in to its own
   visual flourishes (glow, bevel, serif, etc.) so the dashboard actually
   *feels* like the era/style — not just recoloured. Grouped by theme to
   make tuning each one easy.
   ========================================================================= */

/* --- Synthwave: neon glow on headlines, badges, sparkline ---------------- */
[data-theme="synthwave"] .panel-title,
[data-theme="synthwave"] .time {
  text-shadow: 0 0 6px var(--accent), 0 0 18px var(--accent);
}
[data-theme="synthwave"] .panel-count,
[data-theme="synthwave"] .badge {
  text-shadow: 0 0 6px currentColor;
  box-shadow: 0 0 10px rgba(255, 46, 154, 0.35);
}
[data-theme="synthwave"] .panel {
  box-shadow: var(--shadow), 0 0 24px rgba(255, 46, 154, 0.10);
}
[data-theme="synthwave"] .spark-svg {
  filter: drop-shadow(0 0 6px var(--accent));
}

/* --- Green Phosphor / Amber CRT: subtle phosphor glow on headline text -- */
[data-theme="phosphor"] .panel-title,
[data-theme="phosphor"] .time,
[data-theme="amber"] .panel-title,
[data-theme="amber"] .time {
  text-shadow: 0 0 4px currentColor, 0 0 12px currentColor;
}
[data-theme="phosphor"] .spark-svg,
[data-theme="amber"] .spark-svg {
  filter: drop-shadow(0 0 4px var(--accent));
}

/* --- Newsroom: serif headlines + underline accent on panel titles ------- */
[data-theme="newsroom"] .panel-title,
[data-theme="newsroom"] .time,
[data-theme="newsroom"] .date,
[data-theme="newsroom"] .settings-title-main {
  font-family: Georgia, "Times New Roman", "Iowan Old Style", serif;
  letter-spacing: 0;
  font-weight: 700;
}
[data-theme="newsroom"] .panel-title {
  text-transform: none;
  border-bottom: 2px solid var(--text);
  padding-bottom: clamp(4px, 0.4vw, 10px);
}

/* --- Pixel Art: chunky borders + hard 1px drop-shadow on badges --------- */
[data-theme="pixel"] .panel-count,
[data-theme="pixel"] .badge {
  border: 2px solid var(--border);
  background: var(--bg);
  text-shadow: 1px 1px 0 #000;
  box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.5);
  border-radius: 2px;
}
[data-theme="pixel"] .panel {
  border-width: 2px;
}

/* --- Windows 95: hard 3D bevel on badges + count chips ------------------ */
[data-theme="win95"] .panel-count,
[data-theme="win95"] .badge {
  box-shadow: inset 1px 1px 0 #ffffff, inset -1px -1px 0 #808080;
  border-radius: 0;
  border: 1px solid #404040;
  background: var(--bg-elev-2);
  color: var(--text);
}

/* --- Windows XP: glassy soft bevel + slightly rounded badges ------------ */
[data-theme="winxp"] .panel-count,
[data-theme="winxp"] .badge {
  background: linear-gradient(to bottom, #ffffff, var(--bg-elev-2));
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.6), 0 1px 2px rgba(0,0,0,0.15);
}
[data-theme="winxp"] .panel-title {
  color: var(--accent-2);
  border-bottom: 1px solid var(--accent-2);
  padding-bottom: clamp(4px, 0.4vw, 10px);
}

/* --- Brutalist: kill every soft effect, stark borders ------------------- */
[data-theme="brutalist"] * {
  text-shadow: none !important;
}
[data-theme="brutalist"] .panel,
[data-theme="brutalist"] .panel-count,
[data-theme="brutalist"] .badge {
  border-width: 2px;
  box-shadow: none;
}
[data-theme="brutalist"] .panel-title {
  border-bottom: 2px solid var(--text);
  padding-bottom: clamp(4px, 0.4vw, 10px);
}

/* --- Mattressman: brand gradient on panel titles, navy underline ------- */
[data-theme="mattressman"] .panel-title {
  background: linear-gradient(90deg, #5b21c4 0%, #e23e44 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-weight: 800;
  letter-spacing: 0.08em;
}
[data-theme="mattressman"] .panel {
  border: 1px solid rgba(29, 34, 108, 0.12);
}

/* --- Pac-Man: yellow arcade glow on headlines, dotted maze panel border - */
[data-theme="pacman"] .panel-title,
[data-theme="pacman"] .time {
  text-shadow: 0 0 6px var(--accent), 0 0 14px var(--accent);
}
[data-theme="pacman"] .panel {
  border: 2px solid var(--border);
  box-shadow: 0 0 14px rgba(33, 33, 255, 0.4), inset 0 0 0 2px var(--bg);
}
[data-theme="pacman"] .panel-count,
[data-theme="pacman"] .badge {
  background: var(--bg);
  border: 2px solid var(--text);
  color: var(--text);
  text-shadow: 0 0 4px currentColor;
}
[data-theme="pacman"] .spark-svg {
  filter: drop-shadow(0 0 4px var(--accent));
}

/* --- Game Boy: LCD-grid feel, no shadows, mono everything --------------- */
[data-theme="game-boy"] .panel {
  border-width: 2px;
}
[data-theme="game-boy"] .panel-title {
  border-bottom: 2px dashed var(--text-dim);
  padding-bottom: clamp(4px, 0.4vw, 10px);
}
[data-theme="game-boy"] .panel-count,
[data-theme="game-boy"] .badge {
  border: 2px solid var(--text);
  background: var(--bg);
  color: var(--text);
  border-radius: 0;
}

/* --- LCARS: pill-shaped badges, peach side-bar feel on panel titles ----- */
[data-theme="lcars"] .panel-count,
[data-theme="lcars"] .badge {
  background: var(--accent);
  color: #000;
  border-radius: 999px;
  font-weight: 800;
  padding: 4px 14px;
  border: none;
}
[data-theme="lcars"] .panel-title {
  border-left: 8px solid var(--accent);
  padding-left: clamp(8px, 0.6vw, 16px);
  text-transform: uppercase;
}

/* --- Tron: sharp neon outlines, cyan grid feel -------------------------- */
[data-theme="tron"] .panel-title,
[data-theme="tron"] .time {
  text-shadow: 0 0 6px var(--accent), 0 0 16px var(--accent);
}
[data-theme="tron"] .panel {
  border: 1px solid var(--accent);
  box-shadow: var(--shadow), inset 0 0 0 1px rgba(15, 240, 252, 0.2);
}
[data-theme="tron"] .panel-count,
[data-theme="tron"] .badge {
  border: 1px solid var(--accent);
  background: transparent;
  text-shadow: 0 0 4px currentColor;
}
[data-theme="tron"] .spark-svg {
  filter: drop-shadow(0 0 6px var(--accent));
}

/* ===========================================================================
   Emoji icon recolouring per theme
   ---------------------------------------------------------------------------
   The dashboard uses native emoji glyphs for weather (☀️ 🌧 ❄️ etc.) and a
   few other places. Emoji ignore CSS `color`, so we use the classic
   grayscale + sepia + hue-rotate trick to retint them to match the theme.
   Only applied to themes where leaving full-colour emoji would clash badly
   (monochrome / heavily-tinted themes).
   ========================================================================= */

[data-theme="greyscale"] .fr-icon,
[data-theme="greyscale"] .weather-desc {
  filter: grayscale(1) brightness(1.05);
}
[data-theme="phosphor"] .fr-icon {
  filter: grayscale(1) brightness(1.1) sepia(1) hue-rotate(60deg) saturate(8);
}
[data-theme="amber"] .fr-icon {
  filter: grayscale(1) brightness(1.05) sepia(1) saturate(6) hue-rotate(-15deg);
}
[data-theme="brutalist"] .fr-icon {
  filter: grayscale(1) contrast(2);
}
[data-theme="game-boy"] .fr-icon {
  filter: grayscale(1) sepia(1) hue-rotate(60deg) saturate(8) brightness(0.4);
}
[data-theme="tron"] .fr-icon {
  filter: grayscale(1) brightness(1.1) sepia(1) hue-rotate(155deg) saturate(8);
}
[data-theme="pacman"] .fr-icon {
  filter: grayscale(1) sepia(1) hue-rotate(0deg) saturate(8) brightness(1.1);
}

/* ===========================================================================
   Light-theme Spotify readability fix
   ---------------------------------------------------------------------------
   The Spotify panel paints a dark blurred album-art ambient background via
   .spotify-panel::before — always dark regardless of theme (brightness 0.32
   in styles.css). On light themes the theme's dark text disappears against
   that overlay. We re-declare the text-colour vars *scoped to the panel*
   so every child element automatically picks up readable light tones,
   without changing anything else about the theme.
   ========================================================================= */

:is([data-theme="mattressman"],
    [data-theme="newsroom"],
    [data-theme="macos-classic"],
    [data-theme="game-boy"]) .spotify-panel {
  --text: #ffffff;
  --text-dim: rgba(255, 255, 255, 0.88);
  --text-faint: rgba(255, 255, 255, 0.62);
  color: var(--text);
}
/* Accent/info colours used for Spotify chrome (track title / Up Next
   hyperlinks etc.) also need to lift off the dark album-art tint. */
:is([data-theme="mattressman"],
    [data-theme="newsroom"],
    [data-theme="macos-classic"],
    [data-theme="game-boy"]) .spotify-panel .spotify-track {
  color: #ffffff;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.4);
}
:is([data-theme="mattressman"],
    [data-theme="newsroom"],
    [data-theme="macos-classic"],
    [data-theme="game-boy"]) .spotify-panel .un-title {
  color: #c9e8ff;
}
/* Mattressman's gradient panel-title is too dim on the dark Spotify
   tint — brighten the gradient stops in this one location. */
[data-theme="mattressman"] .spotify-panel .panel-title {
  background: linear-gradient(90deg, #a78bfa 0%, #fca5a5 100%);
  -webkit-background-clip: text;
  background-clip: text;
}

/* Idle "No music playing" state has NO album-art overlay — revert the
   white-text vars back to each theme's normal dark text. Scoped to the
   whole .spotify-panel via :has(.spotify-body--idle) so this also catches
   .panel-title (which lives outside the idle card). */
[data-theme="mattressman"]   .spotify-panel:has(.spotify-body--idle) { --text: #1d226c; --text-dim: #4a4d8a; --text-faint: #8a8eb0; }
[data-theme="newsroom"]      .spotify-panel:has(.spotify-body--idle) { --text: #1a1611; --text-dim: #5a4f3f; --text-faint: #8a7e6a; }
[data-theme="macos-classic"] .spotify-panel:has(.spotify-body--idle) { --text: #000000; --text-dim: #444444; --text-faint: #888888; }
[data-theme="game-boy"]      .spotify-panel:has(.spotify-body--idle) { --text: #0f380f; --text-dim: #1a4a14; --text-faint: #2d5d24; }
/* And revert Mattressman's brightened-for-art panel-title gradient back
   to the dark brand stops when idle. */
[data-theme="mattressman"] .spotify-panel:has(.spotify-body--idle) .panel-title {
  background: linear-gradient(90deg, #5b21c4 0%, #e23e44 100%);
  -webkit-background-clip: text;
  background-clip: text;
}
:is([data-theme="mattressman"],
    [data-theme="newsroom"],
    [data-theme="macos-classic"],
    [data-theme="game-boy"]) .spotify-idle-title {
  color: var(--text);
  text-shadow: none;
}
:is([data-theme="mattressman"],
    [data-theme="newsroom"],
    [data-theme="macos-classic"],
    [data-theme="game-boy"]) .spotify-idle-sub {
  color: var(--text-dim);
}

/* ===========================================================================
   Winamp skin for the Spotify tile (old Windows themes only)
   ---------------------------------------------------------------------------
   On Windows 3.1 / 95 / XP, the Spotify tile takes on a Winamp 2.x look —
   dark navy gradient body, neon-green LED track text, yellow time readout,
   the waveform bars become green LEDs. Self-contained so it can be tuned
   without touching the base Spotify styles.
   ========================================================================= */

:is([data-theme="windows-31"], [data-theme="win95"], [data-theme="winxp"]) .spotify-panel {
  background:
    linear-gradient(180deg, #2a2a5a 0%, #14143a 35%, #0a0a1f 100%) !important;
  border: 2px solid #5a5a8a !important;
  color: #1aff66;
}
:is([data-theme="windows-31"], [data-theme="win95"], [data-theme="winxp"]) .spotify-panel .panel-title,
:is([data-theme="windows-31"], [data-theme="win95"], [data-theme="winxp"]) .spotify-panel .spotify-track,
:is([data-theme="windows-31"], [data-theme="win95"], [data-theme="winxp"]) .spotify-panel .spotify-artist,
:is([data-theme="windows-31"], [data-theme="win95"], [data-theme="winxp"]) .spotify-panel .un-title,
:is([data-theme="windows-31"], [data-theme="win95"], [data-theme="winxp"]) .spotify-panel .un-artist,
:is([data-theme="windows-31"], [data-theme="win95"], [data-theme="winxp"]) .spotify-panel .un-label {
  color: #1aff66 !important;
  font-family: ui-monospace, "Cascadia Mono", "Lucida Console", "Courier New", monospace !important;
  text-shadow: 0 0 6px rgba(26, 255, 102, 0.45);
  letter-spacing: 0.02em;
}
:is([data-theme="windows-31"], [data-theme="win95"], [data-theme="winxp"]) .spotify-panel .spotify-album,
:is([data-theme="windows-31"], [data-theme="win95"], [data-theme="winxp"]) .spotify-panel .un-sep {
  color: #66c0c0 !important;
  font-family: ui-monospace, "Lucida Console", "Courier New", monospace !important;
}
:is([data-theme="windows-31"], [data-theme="win95"], [data-theme="winxp"]) .spotify-panel .spotify-times {
  color: #ffff00 !important;
  font-family: ui-monospace, "Lucida Console", "Courier New", monospace !important;
  text-shadow: 0 0 5px rgba(255, 255, 0, 0.4);
  font-weight: 700;
  letter-spacing: 0.04em;
}
:is([data-theme="windows-31"], [data-theme="win95"], [data-theme="winxp"]) .spotify-panel .spotify-progress {
  background: #001a08;
  border: 1px solid #000;
  box-shadow: inset 1px 1px 0 #000, inset -1px -1px 0 #2a2a4a;
}
:is([data-theme="windows-31"], [data-theme="win95"], [data-theme="winxp"]) .spotify-panel .wf-bar {
  background: rgba(26, 80, 40, 0.7);
  border-radius: 0;
  height: 60%;
}
:is([data-theme="windows-31"], [data-theme="win95"], [data-theme="winxp"]) .spotify-panel .wf-row--played .wf-bar {
  background: #1aff66;
  box-shadow: 0 0 4px rgba(26, 255, 102, 0.6);
}
:is([data-theme="windows-31"], [data-theme="win95"], [data-theme="winxp"]) .spotify-panel .spotify-up-next {
  border-top: 1px solid #2a2a4a !important;
}
/* Suppress per-theme title-bar styling from the host OS theme so it doesn't
   layer on top of the Winamp tile (e.g. XP's blue title gradient). */
:is([data-theme="winxp"]) .spotify-panel .panel-title {
  background: transparent !important;
  border-radius: 0 !important;
  margin: 0 0 clamp(8px, 0.6vw, 20px) !important;
  padding: 0 !important;
}

/* Pixel themes: crisp pixel-edge rendering on any raster imagery */
[data-theme="pixel"] img,
[data-theme="pixel"] .fr-icon,
[data-theme="win95"] img,
[data-theme="win95"] .fr-icon,
[data-theme="game-boy"] img,
[data-theme="game-boy"] .fr-icon,
[data-theme="pacman"] img,
[data-theme="pacman"] .fr-icon {
  image-rendering: pixelated;
}
/* …except the Spotify QR codes, which are SVG: Chromium renders an SVG <img>
   blank under `image-rendering: pixelated`, so the QR layer vanishes on the
   retro themes. They're vectors anyway, so keep them on default rendering. */
.spotify-media .media-layer--qr img,
.spotify-auth-qr img {
  image-rendering: auto;
}

/* Deploy indicator — small corner pill + spinner shown while CapRover is
   rebuilding this app (driven by /api/deploy-status). */
.deploy-indicator {
  position: fixed;
  bottom: clamp(8px, 0.8vw, 22px);
  left: clamp(8px, 0.8vw, 22px);
  z-index: 9998;
  display: flex;
  align-items: center;
  gap: clamp(6px, 0.5vw, 14px);
  padding: clamp(6px, 0.5vw, 12px) clamp(12px, 0.9vw, 22px);
  background: rgba(0, 0, 0, 0.72);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text);
  font-size: calc(clamp(0.6rem, 0.6vw, 1.9rem) * var(--type-scale));
  letter-spacing: 0.04em;
  box-shadow: var(--shadow);
}
.deploy-indicator[hidden] { display: none; }
.deploy-spinner {
  width: clamp(12px, 0.9vw, 26px);
  height: clamp(12px, 0.9vw, 26px);
  border: 2px solid var(--text-faint);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: deploy-spin 0.8s linear infinite;
  flex-shrink: 0;
}
@keyframes deploy-spin { to { transform: rotate(360deg); } }

/* Respect user preferences */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}
