/* styles.css — Retro WeatherStar 4000 theme in 978Live's black/brick-red/cream
   palette, 9:16 vertical (1080x1920) for TikTok. */

:root {
  --board-w: 1080px;
  --board-h: 1920px;

  --blue-1: #2c1712;   /* panel top */
  --blue-2: #0e0705;   /* panel bottom */
  --blue-3: #0a0503;   /* deep bg */
  --bevel-hi: #caa16b; /* light bevel edge */
  --bevel-lo: #140806; /* dark bevel edge */

  --yellow: #efe3c8;   /* headings */
  --yellow-2: #cbb98f;
  --cream: #e8dcc4;    /* body text */
  --hot: #b23223;      /* brick-red accents; also the alert "warning" tier */
  --tealbar: #1b2a7a;

  --alert-watch: #d9791e;    /* severe alert "watch" tier */
  --alert-advisory: #d9b23a; /* severe alert "advisory" tier */
}

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

html, body {
  width: 100%; height: 100%;
  height: 100dvh; /* dynamic viewport unit: tracks mobile address-bar show/hide */
  background: #000;
  overflow: hidden;
  font-family: "VT323", "Courier New", monospace;
  color: var(--cream);
}

/* Stage scales the fixed 1080x1920 board to fit whatever window it's previewed in.
   In OBS you point a 1080x1920 Browser Source here and it renders 1:1. */
#stage {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
}
#board {
  position: relative;
  flex-shrink: 0; /* flex items shrink below their set width on narrow (mobile) viewports otherwise, squishing the board before the JS scale transform even applies */
  width: var(--board-w);
  height: var(--board-h);
  overflow: hidden;
  /* ember-glow vignette over near-black, echoing the CRT reference shots */
  background:
    radial-gradient(120% 80% at 50% 115%, rgba(178,58,38,0.55) 0%, rgba(60,20,14,0.3) 32%, rgba(10,5,3,0) 60%),
    radial-gradient(120% 80% at 50% -15%, rgba(60,20,14,0.45) 0%, rgba(10,5,3,0) 55%),
    linear-gradient(180deg, #1c0f0b 0%, var(--blue-3) 55%, #170b08 100%);
  transform-origin: center center;
}
/* 4:3 "old TV" mode — the real WeatherStar 4000 this board homages was
   4:3 CRT, so this is closer to the historically authentic format, not
   an arbitrary resize. Overriding the custom properties (not width/height
   directly) means #board's own rule above picks up the new size for free,
   and every other landscape override below is scoped the same way. */
#board.format-4x3 {
  --board-w: 1440px;
  --board-h: 1080px;
}

/* --- Top masthead bar --- */
#topbar {
  position: absolute; top: 0; left: 0; right: 0; height: 150px;
  display: flex; align-items: center; gap: 22px;
  padding: 0 34px;
  background:
    linear-gradient(115deg, rgba(255,255,255,0.22) 0%, rgba(255,255,255,0) 26%),
    radial-gradient(70% 160% at 12% -50%, rgba(255,200,150,0.4) 0%, rgba(255,200,150,0) 55%),
    radial-gradient(60% 180% at 92% 150%, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0) 60%),
    linear-gradient(180deg, #e2603f 0%, #d1503a 20%, var(--hot) 48%, #7a2010 78%, #4a0f08 100%);
  border-bottom: 5px solid var(--bevel-hi);
  box-shadow: 0 4px 0 rgba(0,0,0,0.35);
  z-index: 30;
}
.logo {
  flex: 0 0 auto;
  width: 150px; height: 96px;
  border-radius: 14px;
  background: linear-gradient(180deg, var(--blue-1), var(--blue-2));
  border: 4px solid #fff;
  box-shadow: inset 0 0 0 3px var(--hot), 0 3px 0 rgba(0,0,0,0.4);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  line-height: 0.86; text-align: center;
  padding: 4px;
}
/* Swapped in by updateLogoAlert() (app.js) whenever there's an active
   severe alert — turns the normally-decorative logo into a shortcut
   straight to the alert segment. Pulse uses the alert's own tier color
   (set inline per-SVG-fill by app.js, not here) via a currentColor-free
   generic red glow — same attention-grabbing idea as .hp-led's glow. */
.logo.alert-active {
  cursor: pointer;
  animation: logo-alert-pulse 1.6s ease-in-out infinite;
}
.logo.alert-active:focus-visible {
  outline: 3px solid var(--yellow);
  outline-offset: 2px;
}
@keyframes logo-alert-pulse {
  0%, 100% { box-shadow: inset 0 0 0 3px var(--hot), 0 3px 0 rgba(0,0,0,0.4), 0 0 0 0 rgba(178,50,35,0.6); }
  50%      { box-shadow: inset 0 0 0 3px var(--hot), 0 3px 0 rgba(0,0,0,0.4), 0 0 16px 6px rgba(178,50,35,0.6); }
}
#topbar-badges {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
  gap: 8px;
}
#topbar-label {
  font-size: 32px; letter-spacing: 3px;
  color: var(--yellow); text-shadow: 2px 2px 0 #3a1108;
  opacity: 0.92;
}
#badge-row { display: flex; align-items: center; gap: 16px; }
#clock {
  flex: 0 0 auto; text-align: right;
  color: #fff6e6; text-shadow: 2px 2px 0 #3a1108;
  font-size: 44px; line-height: 1.0;
}
#clock .date { font-size: 40px; color: #ffe1b0; }

/* --- Segment stage (cross-fading screens) --- */
#screens {
  position: absolute; top: 226px; left: 0; right: 0; bottom: 420px;
  z-index: 10;
}
.segment {
  position: absolute; inset: 0;
  padding: 40px 46px 30px;
  display: flex; flex-direction: column;
  opacity: 0; visibility: hidden;
  transition: opacity 480ms ease;
}
.segment.active { opacity: 1; visibility: visible; }

/* Section title used on most screens */
.screen-title {
  color: var(--yellow);
  font-size: 78px; line-height: 0.95;
  text-shadow: 4px 4px 0 #1a0603, 0 0 22px rgba(178,58,38,0.35);
  letter-spacing: 2px;
  margin-bottom: 26px;
}
.screen-sub { color: var(--yellow-2); font-size: 46px; margin: -14px 0 26px; }

/* Beveled panel (the signature TWC box) */
.panel {
  background: linear-gradient(180deg, var(--blue-1), var(--blue-2));
  border: 5px solid;
  border-color: var(--bevel-hi) var(--bevel-lo) var(--bevel-lo) var(--bevel-hi);
  box-shadow: inset 0 0 0 3px rgba(0,0,0,0.35), 0 6px 0 rgba(0,0,0,0.4);
  border-radius: 8px;
}

/* ===== Regional Observations (table) ===== */
.obs-head, .obs-row {
  display: grid; grid-template-columns: 1fr 360px 150px; align-items: center;
  padding: 12px 26px;
}
.obs-head {
  color: var(--yellow); font-size: 44px;
  border-bottom: 4px solid var(--bevel-hi);
}
.obs-head .r { text-align: right; }
.obs-list { flex: 1 1 auto; display: flex; flex-direction: column; justify-content: space-between; }
.obs-row { font-size: 60px; text-shadow: 3px 3px 0 #1a0603; padding: 22px 26px; }
.obs-row .cond { display: flex; align-items: center; gap: 16px; }
.obs-row .cond .ic { width: 62px; height: 62px; flex: 0 0 auto; }
.obs-row .temp { text-align: right; color: var(--yellow-2); }
.obs-row .req { font-size: 30px; color: var(--hot); display: block; }

/* 4:3: single tall list of 7 rows doesn't fit a short canvas — 2-col grid
   instead (4+3 split), header hidden since a 2-col card grid no longer
   reads as one table with one header row. obs-row's own internal columns
   (weather/temp) need to shrink too, or there's no room left for town
   names like "Tyngsborough, MA" in the remaining name column. */
#board.format-4x3 .obs-head { display: none; }
#board.format-4x3 .obs-list {
  display: grid; grid-template-columns: repeat(2, 1fr);
  grid-auto-flow: column; grid-template-rows: repeat(4, min-content);
  column-gap: 32px; row-gap: 30px; align-content: center;
}
#board.format-4x3 .obs-head, #board.format-4x3 .obs-row {
  grid-template-columns: 1fr 200px 100px;
}
#board.format-4x3 .obs-row { font-size: 42px; padding: 28px 32px; margin: 0; }
#board.format-4x3 .obs-row .cond { gap: 10px; font-size: 34px; white-space: nowrap; }
#board.format-4x3 .obs-row .cond .ic { width: 40px; height: 40px; }

/* ===== Current Conditions ===== */
.cc-wrap { flex: 1 1 auto; display: flex; flex-direction: column; gap: 28px; }
.cc-top { display: flex; align-items: center; gap: 40px; }
.cc-icon { width: 300px; height: 300px; flex: 0 0 auto; }
.cc-tempblock { display: flex; flex-direction: column; }
.cc-temp { font-size: 260px; line-height: 0.82; color: #fff; text-shadow: 6px 6px 0 #1a0603; }
.cc-temp .deg { color: var(--yellow); font-size: 150px; vertical-align: top; }
.cc-cond { font-size: 78px; color: var(--yellow-2); }
.cc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px 34px; padding: 30px 40px; }
.cc-grid .row { display: flex; justify-content: space-between; font-size: 54px; border-bottom: 3px dashed rgba(202,161,107,0.35); padding-bottom: 8px; }
.cc-grid .row .k { color: var(--yellow); }
.cc-grid .row .v { color: #fff; }
/* Per-pollutant AQI color dot (renderAirQuality's aqiRow) — round,
   bordered so a yellow/light dot doesn't melt into the dark row
   background, vertical-align keeps it centered against the label text
   rather than sitting on its baseline. */
.aqi-dot {
  display: inline-block; width: 22px; height: 22px; border-radius: 50%;
  margin-right: 12px; vertical-align: middle;
  border: 2px solid rgba(0,0,0,0.4);
}
.cc-req { margin-top: auto; font-size: 48px; color: var(--hot); text-align: center; }
.cc-req b { color: var(--yellow-2); }

/* Air Quality's colored category indicator — a circular badge (fills
   .cc-icon's usual slot in .cc-top) with the number/label living INSIDE
   a ring colored per EPA category (set inline per-render, see
   renderAirQuality), rather than a separate flat color block sitting
   next to them. First version was a big solid-color square beside the
   number — read as oversized and disconnected from what it labeled; the
   ring keeps the color directly attached to the data instead of
   competing with it for space. */
/* .cc-top is stretched full-width by .cc-wrap's flex column (no
   align-items set there, defaults to stretch) — Current Conditions fills
   that width with icon+temp side by side, but Air Quality's .cc-top now
   has only the one badge in it, which otherwise sits flush left with a
   big empty gap beside it in 9:16. Scoped to .aqi-top (not a blanket
   .cc-top change) so Current Conditions' row is untouched. In 4:3,
   .cc-top isn't stretched (flex:0 0 auto there), so this is a no-op —
   the badge already sits fine there. */
.aqi-top { justify-content: center; }
/* .cc-wrap's base flex:1 1 auto (below) makes it grow to fill the whole
   segment — fine for Current Conditions (nothing after it), but Air
   Quality has .aqi-advisory as a sibling right after it, and that grow
   pushed a huge empty gap between the pollutant grid and the advisory
   card in 9:16 (cc-wrap's box stretched tall, its children didn't, so
   the advisory — outside cc-wrap — landed at the very bottom of that
   stretch instead of right after the grid). Scoped to .aqi-wrap only;
   4:3 is restored back to the base growing behavior below since that
   layout (hero+grid side by side, advisory right below) didn't have
   this problem. */
.aqi-wrap { flex: 0 0 auto; }
.aqi-grid-title {
  grid-column: 1 / -1; color: var(--yellow); font-size: 34px;
  letter-spacing: 2px; padding-bottom: 12px; margin-bottom: 6px;
  border-bottom: 3px solid var(--bevel-hi);
}
.aqi-badge {
  width: 320px; height: 320px; flex: 0 0 auto; border-radius: 50%;
  border: 14px solid; /* color set inline */
  background: rgba(0,0,0,0.4);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  box-shadow: inset 0 0 0 3px rgba(0,0,0,0.3), 0 6px 0 rgba(0,0,0,0.4);
}
.aqi-badge-num {
  font-size: 110px; line-height: 1; color: #fff;
  text-shadow: 4px 4px 0 #1a0603;
}
.aqi-badge-label {
  font-size: 30px; color: var(--yellow-2); margin-top: 10px;
  letter-spacing: 1px; text-align: center;
}
.aqi-advisory { margin-top: 24px; padding: 26px 40px; text-align: center; }
.aqi-advisory-primary {
  color: var(--yellow); font-size: 38px; letter-spacing: 1px;
  margin-bottom: 12px;
}
.aqi-advisory-text { color: #fff; font-size: 40px; line-height: 1.3; }

/* 4:3: hero (icon+temp) left, stat grid right, instead of stacked —
   landscape has width to spare and height to save. */
#board.format-4x3 .cc-wrap { flex-direction: row; align-items: center; gap: 70px; }
#board.format-4x3 .aqi-wrap { flex: 1 1 auto; } /* restore base grow — see .aqi-wrap comment above */
#board.format-4x3 .cc-top { flex-direction: column; gap: 14px; flex: 0 0 auto; }
#board.format-4x3 .aqi-grid-title { font-size: 26px; padding-bottom: 8px; margin-bottom: 4px; }
#board.format-4x3 .cc-icon { width: 280px; height: 280px; }
#board.format-4x3 .aqi-badge { width: 260px; height: 260px; border-width: 10px; }
#board.format-4x3 .aqi-badge-num { font-size: 92px; }
#board.format-4x3 .aqi-badge-label { font-size: 24px; margin-top: 6px; }
#board.format-4x3 .cc-temp { font-size: 220px; }
#board.format-4x3 .cc-temp .deg { font-size: 128px; }
#board.format-4x3 .cc-cond { font-size: 62px; }
#board.format-4x3 .cc-grid { flex: 1 1 auto; gap: 26px 40px; padding: 34px 40px; }
#board.format-4x3 .cc-grid .row { font-size: 46px; }
/* Air Quality's grid has 6 rows (Current Conditions' shares .cc-grid but
   stays at 3) — scoped to .aqi-grid specifically so this doesn't shrink
   Current Conditions too. .aqi-badge (260px + border, ~280px) is much
   shorter than the old swatch+temp column was, so this no longer needs
   the aggressive height-matching the swatch era did — legibility comes
   first, some vertical mismatch next to a self-contained circular badge
   reads fine (unlike a flat rectangle, a badge doesn't need to visually
   equal the grid's height to look intentional). */
#board.format-4x3 .aqi-grid { gap: 18px 30px; padding: 22px 30px; }
#board.format-4x3 .aqi-grid .row { font-size: 36px; padding-bottom: 6px; }
#board.format-4x3 .aqi-dot { width: 18px; height: 18px; margin-right: 10px; }
#board.format-4x3 .aqi-advisory { margin-top: 14px; padding: 14px 30px; }
#board.format-4x3 .aqi-advisory-primary { font-size: 26px; margin-bottom: 6px; }
#board.format-4x3 .aqi-advisory-text { font-size: 28px; }

/* ===== Feels Like ===== */
/* Reuses .cc-wrap / .cc-top / .cc-icon / .cc-tempblock / .cc-grid / .row / .k / .v
   from Current Conditions — only needs new rules for the context label and delta line. */
.fl-feels { color: var(--yellow); }
.fl-ctx-hot     { color: #ff7a5a; }   /* warm brick-red for heat index */
.fl-ctx-cold    { color: #4aa3ff; }   /* sky blue for wind chill */
.fl-ctx-neutral { color: var(--cream); }
.fl-delta {
  font-size: 32px; color: var(--yellow-2);
  margin-top: 6px; line-height: 1;
  text-shadow: 1px 1px 0 #000;
}
#board.format-4x3 .fl-delta { font-size: 24px; }

/* ===== Hour by Hour ===== */
/* ===== Hour by Hour (2×3 card grid) ===== */
.hbh-grid {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 22px;
  padding: 4px 24px 16px;
  align-content: center;
}
.hbh-card {
  display: flex; flex-direction: column; align-items: center;
  padding: 12px 12px 10px;
}
.hbh-card-time {
  font-size: 46px; color: var(--yellow);
  text-shadow: 2px 2px 0 #000;
  margin-bottom: 2px;
}
.hbh-card-icon { width: 118px; height: 118px; margin: 2px 0; }
.hbh-card-temp {
  font-size: 68px; color: var(--cream);
  text-shadow: 3px 3px 0 #000;
  margin-top: 2px; line-height: 1;
}
.hbh-card-temp .deg { font-size: 40px; vertical-align: super; }
.hbh-card-precip {
  font-size: 28px; color: #4aa3ff;
  text-shadow: 1px 1px 0 #000;
  margin-top: 6px; letter-spacing: 0.5px;
}
.hbh-precip-hi  { color: #7fd0ff; }
.hbh-precip-none { color: transparent; }
.hbh-empty {
  grid-column: 1 / -1;
  display: flex; align-items: center; justify-content: center;
  font-size: 40px; color: var(--yellow-2); opacity: 0.6;
}

/* 4:3 Hour by Hour — 3 columns × 2 rows */
#board.format-4x3 .hbh-grid {
  grid-template-columns: 1fr 1fr 1fr;
  gap: 16px; padding: 4px 16px 12px;
}
#board.format-4x3 .hbh-card { padding: 14px 10px 12px; }
#board.format-4x3 .hbh-card-time { font-size: 38px; }
#board.format-4x3 .hbh-card-icon { width: 110px; height: 110px; }
#board.format-4x3 .hbh-card-temp { font-size: 58px; }
#board.format-4x3 .hbh-card-temp .deg { font-size: 34px; }
#board.format-4x3 .hbh-card-precip { font-size: 24px; }

/* ===== Extended Forecast (3-day panels) ===== */
.ef-city { font-size: 64px; color: #fff; margin-bottom: 20px; text-shadow: 3px 3px 0 #1a0603; }
.ef-panels { flex: 1 1 auto; display: grid; grid-template-columns: repeat(3, 1fr); gap: 26px; align-content: start; align-items: start; }
.ef-day { display: flex; flex-direction: column; align-items: center; padding: 26px 12px 30px; height: fit-content; }
.ef-name { font-size: 62px; color: var(--yellow); }
.ef-ic { width: 200px; height: 200px; margin: 10px 0; }
.ef-cond { font-size: 42px; text-align: center; color: var(--cream); min-height: 96px; }
.ef-lohi { display: flex; gap: 30px; margin-top: 16px; }
.ef-lohi .col { text-align: center; }
.ef-lohi .lbl { font-size: 40px; }
.ef-lohi .lo .lbl { color: #7fd0ff; }
.ef-lohi .hi .lbl { color: var(--yellow); }
.ef-lohi .num { font-size: 78px; color: #fff; }

/* 4:3: already landscape-shaped (3-col row) — just reclaim some of the
   height the shell reflow freed up, since panel height was previously
   sized for the tall portrait content area. */
#board.format-4x3 .ef-day { padding: 18px 12px 20px; }
#board.format-4x3 .ef-ic { width: 170px; height: 170px; margin: 6px 0; }
#board.format-4x3 .ef-cond { min-height: 0; }

/* ===== Requested Cities Leaderboard ===== */
.lb-list { flex: 1 1 auto; display: flex; flex-direction: column; gap: 16px; margin-top: 10px; }
.lb-row { display: grid; grid-template-columns: 110px 1fr 220px; align-items: center; padding: 16px 28px; font-size: 62px; }
.lb-row .rank { color: var(--yellow); font-size: 70px; }
.lb-row .city { color: #fff; text-shadow: 3px 3px 0 #1a0603; }
.lb-row .votes { text-align: right; color: var(--yellow-2); }
.lb-row .votes small { font-size: 34px; color: var(--hot); }
.lb-row:first-child { border-color: var(--yellow) var(--bevel-lo) var(--bevel-lo) var(--yellow); }
.lb-cta { margin-top: 22px; text-align: center; font-size: 54px; color: var(--yellow-2); }
.lb-cta b { color: var(--hot); }

/* ===== National Roundup ===== */
.nr-grid { flex: 1 1 auto; display: grid; grid-template-columns: 1fr 1fr; gap: 26px; }
.nr-region { padding: 22px 26px; display: flex; flex-direction: column; }
.nr-region .rname { color: var(--yellow); font-size: 52px; border-bottom: 3px solid var(--bevel-hi); margin-bottom: 12px; }
.nr-region .rrow { display: flex; justify-content: space-between; font-size: 46px; padding: 4px 0; }
.nr-region .rrow .t { color: var(--yellow-2); }

/* ===== Almanac ===== */
/* --- Almanac --- */
.al-hero { display: flex; align-items: center; gap: 40px; padding: 10px 0 28px; border-bottom: 4px solid rgba(202,161,107,0.3); margin-bottom: 24px; }
.al-hero-icon { flex: 0 0 280px; height: 280px; }
.al-hero-icon svg { width: 280px; height: 280px; }
.al-hero-right { display: flex; flex-direction: column; gap: 8px; }
.al-hero-temp { font-size: 190px; line-height: 0.85; color: #fff; text-shadow: 6px 6px 0 #1a0603; }
.al-hero-temp .deg { color: var(--yellow); font-size: 110px; vertical-align: top; }
.al-hero-cond { font-size: 68px; color: var(--yellow-2); letter-spacing: 2px; }
.al-today { padding: 18px 30px; }
.al-today-lohi {
  display: flex; justify-content: space-around; align-items: center;
  gap: 60px; margin-bottom: 16px; padding-bottom: 16px;
  border-bottom: 2px dashed rgba(202,161,107,0.25);
}
.al-lh-col { display: flex; flex-direction: column; align-items: center; }
.al-lh-label { font-size: 40px; color: var(--yellow-2); }
.al-lh-num { font-size: 108px; color: #fff; line-height: 1; }
.al-stat { display: flex; justify-content: space-between; font-size: 46px; padding: 6px 0; border-bottom: 2px dashed rgba(202,161,107,0.25); }
.al-stat .k { color: var(--yellow); }
.al-stat .v { color: #fff; }

/* 4:3: shrink the hero (icon+temp) since it no longer needs to compete
   with tall portrait stat rows for height; the 8 al-stat rows go from one
   long vertical list to a 2-col x 4-row grid — 4 columns was tried first
   but "MOON PHASE" / "WAXING CRESCENT" doesn't fit that narrow (same
   lesson as Current Conditions' "PRESSURE" / "29.90 in"). */
#board.format-4x3 .al-hero { padding: 6px 0 16px; margin-bottom: 14px; gap: 26px; justify-content: center; }
#board.format-4x3 .al-hero-icon { flex-basis: 130px; height: 130px; }
#board.format-4x3 .al-hero-icon svg { width: 130px; height: 130px; }
#board.format-4x3 .al-hero-temp { font-size: 100px; }
#board.format-4x3 .al-hero-temp .deg { font-size: 58px; }
#board.format-4x3 .al-hero-cond { font-size: 42px; }
#board.format-4x3 .al-today-lohi { margin-bottom: 10px; padding-bottom: 10px; }
#board.format-4x3 .al-lh-label { font-size: 32px; }
#board.format-4x3 .al-lh-num { font-size: 80px; }
#board.format-4x3 .al-today { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 40px; }
#board.format-4x3 .al-today-lohi { grid-column: 1 / -1; }

/* ===== Regional Radar ===== */
/* The wrap is pinned to the radar bbox's own Web Mercator aspect — square,
   so the basemap background and the overlaid frames fill exactly the same
   because the bbox was picked so its projected width and height match. Both
   the basemap background and the overlaid frames therefore fill exactly the
   same box in either format, so registration comes out right by construction
   rather than by per-format tuning. Portrait fills edge to edge; 4:3 is
   height-bound and centers. */
.radar-wrap { flex: 1 1 auto; display: flex; flex-direction: column; gap: 20px; min-height: 0; }
.radar-img-wrap {
  flex: 0 1 auto; position: relative; align-self: center;
  aspect-ratio: 1 / 1; width: 100%; max-width: 100%; max-height: 100%;
  overflow: hidden; background-size: 100% 100%; background-repeat: no-repeat;
}
/* Stacked radar frames. Base opacity 0 so a frame whose animation-delay
   hasn't elapsed yet stays hidden; the inline radar-cycle keyframes light
   exactly one at a time. The static single-frame fallback opts out. */
.radar-frame { position: absolute; inset: 0; width: 100%; height: 100%; display: block; opacity: 0; }
.radar-frame-static { opacity: 1; }

/* Lowell locator */
.radar-marker { position: absolute; transform: translate(-50%, -50%); display: flex; flex-direction: column; align-items: center; pointer-events: none; }
.radar-marker-dot { width: 16px; height: 16px; border-radius: 50%; background: var(--yellow); border: 3px solid #1a0603; box-shadow: 0 0 10px rgba(0,0,0,0.8); }
.radar-marker-label { margin-top: 4px; font-size: 26px; color: var(--yellow); text-shadow: 2px 2px 0 #1a0603, 0 0 6px #1a0603; letter-spacing: 1px; }
.radar-footer { display: flex; justify-content: space-between; align-items: center; padding: 0 4px; flex-shrink: 0; }
.radar-legend { display: flex; gap: 20px; align-items: center; font-size: 32px; color: var(--cream); flex-wrap: wrap; }
.radar-legend-dot { display: inline-block; width: 22px; height: 22px; border-radius: 50%; margin-right: 8px; vertical-align: middle; border: 2px solid rgba(0,0,0,0.4); }
.radar-attr { font-size: 30px; color: var(--yellow-2); opacity: 0.7; text-align: right; }

/* 4:3 leaves radar barely 410px of height once the title, subtitle and a
   footer below the map have taken their cut, which shrinks a square map
   badly on a canvas that has width to spare. Landscape therefore reflows the
   whole segment to a row — map left at full height, legend/attribution
   stacked in the freed width to its right — and drops the subtitle. Worth
   ~150px of map. Same container-reflow strategy as .hbh-grid and .rv-hero
   above, not just a font-size pass. */
#board.format-4x3 .radar-sub { display: none; }
#board.format-4x3 .radar-title { font-size: 56px; margin-bottom: 12px; }
#board.format-4x3 .radar-wrap { flex-direction: row; align-items: center; justify-content: center; gap: 60px; }
#board.format-4x3 .radar-img-wrap { flex: 0 0 auto; height: 100%; width: auto; max-width: none; }
#board.format-4x3 .radar-footer { flex: 0 0 auto; flex-direction: column; align-items: flex-start; justify-content: center; gap: 26px; }
#board.format-4x3 .radar-legend { flex-direction: column; align-items: flex-start; gap: 20px; font-size: 34px; }
#board.format-4x3 .radar-attr { font-size: 28px; text-align: left; }
#board.format-4x3 .radar-marker-label { font-size: 22px; }
#board.format-4x3 .radar-marker-dot { width: 13px; height: 13px; border-width: 2px; }

/* ===== Merrimack River Flood Stage ===== */
.rv-wrap { flex: 1 1 auto; display: flex; flex-direction: column; gap: 22px; }
.rv-hero { display: flex; flex-direction: column; align-items: center; padding: 12px 0 4px; }
.rv-stage-num { font-size: 190px; line-height: 0.85; text-shadow: 6px 6px 0 #1a0603; }
.rv-stage-unit { font-size: 80px; vertical-align: top; color: var(--yellow-2); }
.rv-stage-label { font-size: 62px; letter-spacing: 2px; margin-top: 10px; }
.rv-stage-unavail { font-size: 80px; color: var(--yellow-2); text-align: center; line-height: 1.2; }
.rv-grid { padding: 18px 30px; }

#board.format-4x3 .rv-hero { flex-direction: row; align-items: baseline; gap: 24px; justify-content: center; }
#board.format-4x3 .rv-stage-num { font-size: 110px; }
#board.format-4x3 .rv-stage-unit { font-size: 50px; }
#board.format-4x3 .rv-stage-label { font-size: 44px; margin-top: 0; margin-left: 16px; align-self: center; }
#board.format-4x3 .rv-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 40px; }

/* --- Ticker/crawl (sits just under the topbar, clear of TikTok chat overlay) --- */
#ticker {
  position: absolute; left: 0; right: 0; top: 150px; height: 96px;
  background: linear-gradient(180deg, var(--blue-1), var(--blue-2));
  border-bottom: 5px solid var(--bevel-hi);
  display: flex; align-items: center; overflow: hidden; z-index: 30;
}
#ticker .tag {
  flex: 0 0 auto; height: 100%; display: flex; align-items: center;
  padding: 0 26px; background: var(--hot); color: #fff;
  font-size: 46px; border-right: 4px solid #2a0e08;
}
#ticker .track { flex: 1 1 auto; overflow: hidden; white-space: nowrap; position: relative; }
#ticker .crawl {
  display: inline-block; padding-left: 100%;
  font-size: 52px; color: var(--yellow-2); text-shadow: 2px 2px 0 #1a0603;
  /* 40s is only the no-JS fallback. syncCrawlSpeed() (app.js) overwrites
     animation-duration from the measured width on every updateCrawl() and
     format change, because translateX(-100%) makes the distance depend on
     content length — a fixed duration here means a variable SPEED. Don't
     re-tune this number to change the pacing; change CRAWL_EMS_PER_SEC. */
  animation: crawl 40s linear infinite;
}
#ticker .crawl span { margin: 0 60px; }
#ticker .crawl span.crawl-div { margin: 0; color: rgba(232,220,196,0.70); }
#ticker .crawl b { color: #fff; }
@keyframes crawl { from { transform: translateX(0); } to { transform: translateX(-100%); } }

/* --- Lowell mill skyline silhouette (bottom backdrop) --- */
#cityscape {
  position: absolute; left: 0; right: 0; bottom: 0; height: 420px;
  z-index: 4; /* above board gradient, below #screens (z-index:10) */
  pointer-events: none;
}
#cityscape svg { width: 100%; height: 100%; }

/* --- 4:3 shell reflow: same chrome, resized/repositioned for a short, wide
   canvas instead of a tall, narrow one. preserveAspectRatio on the cityscape
   svg is swapped at runtime (setFormat(), app.js) between "meet" (portrait,
   shrink-to-fit) and "slice" (landscape, fill width + crop excess height
   from the top) so the skyline stays bottom-anchored instead of leaving
   empty side margins. --- */
#board.format-4x3 #topbar { height: 100px; padding: 0 24px; gap: 16px; }
#board.format-4x3 .logo { width: 110px; height: 74px; }
#board.format-4x3 #topbar-badges { gap: 4px; }
#board.format-4x3 #topbar-label { font-size: 24px; letter-spacing: 2px; }
#board.format-4x3 #badge-row { gap: 10px; }
#board.format-4x3 .badge { height: 52px; padding: 0 16px; }
#board.format-4x3 #brand-bug { font-size: 30px; gap: 8px; }
#board.format-4x3 .tiktok-icon { width: 24px; height: 24px; }
#board.format-4x3 #credit-bug { padding: 3px 16px; }
#board.format-4x3 .badge-kicker { font-size: 14px; }
#board.format-4x3 .badge-main { font-size: 19px; }
#board.format-4x3 #clock { font-size: 32px; }
#board.format-4x3 #clock .date { font-size: 28px; }
#board.format-4x3 #ticker { top: 100px; height: 56px; }
#board.format-4x3 #ticker .tag { font-size: 32px; padding: 0 18px; }
#board.format-4x3 #ticker .crawl { font-size: 36px; }
/* 284px = 213 SVG units (CITYSCAPE_VIEWBOX["4x3"], app.js) * 1440/1080,
   grown again from 183px/137 units so the crop reaches the spire/finial
   tip — matching what 9:16 mode shows — without cropping the mills any
   tighter than they already were. See app.js's CITYSCAPE_VIEWBOX comment
   for the full crop-history reasoning. #screens' bottom shrinks to match
   so the two stay exactly adjacent, no overlap/gap. */
#board.format-4x3 #cityscape { height: 284px; }
#board.format-4x3 #screens { top: 136px; bottom: 284px; }
#board.format-4x3 .segment { padding: 20px 40px 16px; }

/* --- Station bugs: persistent handle/credit badges, in the topbar --- */
.badge {
  display: inline-flex; align-items: center; justify-content: center;
  height: 76px;
  background: linear-gradient(180deg, var(--blue-1), var(--blue-2));
  border: 3px solid var(--bevel-hi);
  border-radius: 12px;
  padding: 0 24px;
  letter-spacing: 1.5px; white-space: nowrap;
  color: var(--yellow);
  text-shadow: 2px 2px 0 #000;
  box-shadow: 0 3px 0 rgba(0,0,0,0.4);
  text-decoration: none; /* .badge is now also used on <a> tags (brand/credit bugs) */
}
.badge:visited { color: var(--yellow); }
#brand-bug { font-size: 42px; gap: 12px; }
.tiktok-icon { width: 34px; height: 34px; flex: 0 0 auto; color: var(--yellow); }
/* Same 76px .badge height as #brand-bug — text splits into two stacked lines
   instead of growing the badge, so the topbar row height stays untouched. */
#credit-bug { flex-direction: column; justify-content: center; gap: 1px; padding: 4px 22px; }
.badge-kicker {
  font-size: 18px; letter-spacing: 2px; text-transform: uppercase;
  opacity: 0.85; font-weight: normal;
}
.badge-main { font-size: 25px; letter-spacing: 1px; }

/* --- CRT scanline overlay for retro feel --- */
#board::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 40;
  background: repeating-linear-gradient(180deg, rgba(0,0,0,0) 0px, rgba(0,0,0,0) 2px, rgba(0,0,0,0.10) 3px, rgba(0,0,0,0.10) 4px);
  mix-blend-mode: multiply; opacity: 0.6;
}

/* --- Host control panel (hidden from capture until toggled with backtick) ---
   Styled as a physical handheld remote, not a settings form: narrow black-
   plastic body, recessed pill-shaped button clusters ("D-pad" per the
   .dpad class name, though with only 2 actions each they're really a
   transport pill, not a true 4-way pad), and the board's own brick-red/
   tan-gold accents carried over (via var(--hot)/var(--bevel-hi)/var(--yellow))
   so it still reads as *this board's* remote rather than a generic one.
   The grey/black plastic tones themselves are hardcoded here rather than
   added as :root custom properties — they're specific to this one
   element, unlike --blue-1/2 etc. which the board and panel both used
   to share before this rework. */
#hostpanel {
  position: fixed; top: 14px; left: 14px; z-index: 100;
  width: 260px; padding: 22px 16px 18px; display: none;
  background: linear-gradient(180deg, #48484c 0%, #2c2c2f 60%, #1a1a1c 100%);
  border: 4px solid;
  border-color: #5c5c60 #101012 #101012 #5c5c60;
  box-shadow: inset 0 0 0 2px rgba(0,0,0,0.5), 0 6px 0 rgba(0,0,0,0.5);
  border-radius: 26px 26px 10px 10px;
  /* var(--cream) is warm off-white, tuned for the board's warm brick-red
     panels — reads muddy/mismatched against this panel's cool grey/black
     plastic. Base text color here is a cool light grey instead, so
     anything that falls back to it (bare "Rotation:" text, the Dwell
     number input) matches the body rather than clashing with it. */
  font-family: "VT323", "Courier New", monospace; color: #d6d6da; font-size: 18px;
}
#hostpanel.open { display: block; }
/* No center seam here on purpose. #hostpanel::after used to draw a faint
   vertical "parting line" down the middle to sell a two-half molded shell;
   it was removed once the Rotation tray's third button put a cap on the
   50% mark and made the line read as damage rather than detail. The IR
   window, screws, and bevels carry the hardware look on their own. */
/* Corner screws/rivets — small beveled dots, direct children of
   #hostpanel (see .hp-screw spans in index.html) positioned against its
   corners. Top only — bottom corners sit too close to the Volume row at
   this width. */
#hostpanel .hp-screw {
  position: absolute; top: 9px; width: 7px; height: 7px; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #7a7a7e, #101012 75%);
  box-shadow: 0 1px 0 rgba(255,255,255,0.08);
}
#hostpanel .hp-screw.l { left: 10px; }
#hostpanel .hp-screw.r { right: 10px; }
/* IR-emitter window: a small dark lens inset into the top edge. */
#hostpanel .hp-ir {
  position: absolute; top: -3px; left: 50%; transform: translateX(-50%);
  width: 36px; height: 8px; border-radius: 0 0 6px 6px;
  background: linear-gradient(180deg, #050506, #1c1c1e);
  box-shadow: inset 0 -1px 2px rgba(0,0,0,0.8), 0 1px 0 rgba(255,255,255,0.05);
}
#hostpanel .hp-brand { display: flex; align-items: center; gap: 8px; }
#hostpanel .hp-led {
  width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto;
  background: var(--hot); box-shadow: 0 0 6px 2px rgba(178,50,35,0.7);
}
#hostpanel .hp-title {
  color: var(--yellow); font-size: 24px; line-height: 0.95;
  letter-spacing: 1px; text-transform: uppercase;
  text-shadow: 2px 2px 0 #000;
}
#hostpanel .hp-subtitle { color: #b8b8bc; font-size: 14px; margin: 2px 0 2px; }
#hostpanel .hp-model {
  color: #a8a8ae; font-size: 12px; letter-spacing: 1.5px;
  text-shadow: 1px 1px 0 #000;
  margin-bottom: 12px;
}
#hostpanel .hp-kicker {
  font-size: 26px; letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--yellow-2); text-shadow: 2px 2px 0 #000;
  margin: 14px 0 10px; line-height: 0.95;
}
#hostpanel .hp-divider { border-top: 2px dashed rgba(255,255,255,0.12); margin: 14px 0 4px; }
#hostpanel .fld { display: flex; align-items: center; gap: 6px; margin-bottom: 10px; }
#hostpanel input[type="text"], #hostpanel input[type="number"] {
  flex: 0 0 52px; width: 52px; background: #0e0e0f; color: #d6d6da;
  border: 1px solid #000; border-radius: 4px;
  box-shadow: inset 0 2px 3px rgba(0,0,0,0.6);
  font-family: inherit; font-size: 18px; padding: 5px 6px;
}
/* Delay's number input: the browser's native up/down spinner reads as a
   plain OS form control, not a remote — swap it for a small stacked
   pair of buttons matching the panel's own button bezel, dark-well
   framed like the input it's attached to. */
#hostpanel .stepper { display: flex; align-items: stretch; }
#hostpanel .stepper input[type="number"] {
  flex: 0 0 40px; width: 40px; border-radius: 4px 0 0 4px; border-right: none;
  -moz-appearance: textfield;
}
#hostpanel input[type="number"]::-webkit-inner-spin-button,
#hostpanel input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none; margin: 0;
}
#hostpanel .stepper-btns {
  display: flex; flex-direction: column; width: 18px; flex: 0 0 18px;
  border: 1px solid #000; border-left: none; border-radius: 0 4px 4px 0;
  background: #0e0e0f; box-shadow: inset 0 2px 3px rgba(0,0,0,0.6);
}
#hostpanel .stepper-btns button {
  flex: 1 1 50%; padding: 0; margin: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 7px; line-height: 1; border: none; border-radius: 0;
  background: linear-gradient(180deg, #4a4a4e, #2a2a2d);
  color: var(--yellow); box-shadow: none;
}
#hostpanel .stepper-btns button:first-child { border-bottom: 1px solid #000; }
#hostpanel .stepper-btns button:hover { color: #fff; background: linear-gradient(180deg, #5c5c60, #333336); }
#hostpanel .stepper-btns button:active { transform: none; background: #1c1c1e; }
#hostpanel button {
  background: linear-gradient(180deg, #55555a, #313134);
  border: 2px solid; border-color: #6a6a6e #0a0a0b #0a0a0b #6a6a6e;
  border-radius: 8px;
  color: var(--yellow); text-shadow: 1px 1px 0 #000;
  box-shadow: 0 3px 0 rgba(0,0,0,0.55);
  font-family: inherit; font-size: 15px; padding: 7px 10px; cursor: pointer;
}
#hostpanel button:hover  { border-color: var(--hot); color: #fff; }
#hostpanel button:active { transform: translateY(2px); box-shadow: 0 1px 0 rgba(0,0,0,0.5); }
#hostpanel #hp-setdwell { flex: 1 1 auto; font-size: 14px; padding: 7px 6px; }
/* Transport button pair (Pause/Next, Play/Skip): two independent round
   buttons resting in a shared recessed tray with daylight between them,
   rather than one pill split down the middle — reads as two hardware
   buttons, not a divided bar. */
#hostpanel .dpad {
  display: flex; gap: 10px; padding: 8px 10px; margin-bottom: 4px;
  background: linear-gradient(180deg, #0c0c0d, #1c1c1e);
  border-radius: 999px;
  box-shadow: inset 0 2px 5px rgba(0,0,0,0.7), inset 0 -1px 0 rgba(255,255,255,0.04);
}
/* Printed fascia captions under each transport tray — real remotes label
   the plastic below/around a button, not the button cap itself, and two
   near-identical II/▶▶ glyphs repeat between Rotation and Now Playing
   with nothing else to tell them apart at a glance. */
#hostpanel .dpad-labels {
  display: flex; gap: 10px; padding: 0 10px; margin-bottom: 12px;
}
#hostpanel .dpad-labels span {
  flex: 1; text-align: center; font-size: 14px; letter-spacing: 0.5px;
  text-transform: uppercase; color: #e8e8ec; text-shadow: 1px 1px 0 #000;
}
#hostpanel .dpad button {
  flex: 1; text-align: center; font-size: 16px; letter-spacing: 1px;
  padding: 10px 4px; border-radius: 999px;
  background: radial-gradient(circle at 50% 30%, #6a6a70, #303034 75%);
  border-color: #86868c #0a0a0b #0a0a0b #86868c;
  box-shadow: 0 3px 0 rgba(0,0,0,0.6), 0 0 0 3px #161617;
}
/* Rotation tray carries three buttons (Prev/Pause/Next) where Now Playing
   carries two. The panel's 260px width is fixed and must not grow, so the
   third button is paid for out of the tray's own gap/padding and the caps'
   type size rather than out of the panel -- and it's a .dpad-3 modifier,
   not a change to .dpad, so the Music tray keeps its original two-button
   sizing. Inner width works out to 228 - 16 (tray padding) - 12 (2 gaps)
   = 200px, i.e. ~66px per cap; the fascia captions below are sized to fit
   that same column ("Play/Pause" at 12px VT323 is ~60px). */
#hostpanel .dpad-3 { gap: 6px; padding: 8px 8px; }
#hostpanel .dpad-3 button {
  font-size: 14px; letter-spacing: 0; padding: 10px 2px;
}
#hostpanel .dpad-3-labels { gap: 6px; padding: 0 8px; }
#hostpanel .dpad-3-labels span { font-size: 12px; letter-spacing: 0; }
#hostpanel .btn-accent {
  width: 100%; text-align: center; text-transform: uppercase;
  font-size: 17px; letter-spacing: 1.5px; padding: 11px 10px;
  background: linear-gradient(180deg, #d1453a 0%, var(--hot) 55%, #7a2010 100%);
  border-color: #ff8a72 #4a0f08 #4a0f08 #ff8a72;
  color: #fff2df; text-shadow: 1px 1px 0 rgba(0,0,0,0.5);
}
#hostpanel .btn-accent:hover { border-color: var(--yellow); color: #fff; }
#hostpanel .muted { color: #c2c2c6; font-size: 15px; }
#hostpanel .status { margin-top: 6px; font-size: 22px; }
#hostpanel .status .on { color: #6fe08a; }
#hostpanel .status .off { color: #ff8a8a; }

#hostpanel #vol-slider, #hostpanel #music-seek {
  flex: 1; width: auto; cursor: pointer;
  accent-color: var(--hot);
}
#hostpanel .music-now { align-items: center; justify-content: space-between; }
#hostpanel .music-now .muted {
  flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--yellow-2); font-size: 16px;
}
#hostpanel #music-time { font-size: 14px; white-space: nowrap; color: #b0b0b4; }

/* ===== Mobile bottom-sheet remote =====
   All rules are scoped to body.touch-ui (set by wireHostPanel when pointer:coarse
   is detected) so none of this ever fires on desktop/OBS — the existing
   #hostpanel is the desktop path. FAB + sheet use the same brick-red/cream/gold
   palette as the board itself, not the panel's cool-grey plastic. */
#mobile-ctrl-btn { display: none; }
body.touch-ui #mobile-ctrl-btn {
  display: block;
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  z-index: 9000;
  background: linear-gradient(180deg, #d1453a, var(--hot));
  color: var(--yellow);
  border: 2px solid var(--yellow-2);
  border-radius: 999px;
  padding: 12px 32px;
  font-family: "VT323", "Courier New", monospace;
  font-size: 22px; letter-spacing: 3px;
  box-shadow: 0 4px 18px rgba(0,0,0,0.75), inset 0 1px 0 rgba(255,255,255,0.12);
  touch-action: manipulation; cursor: pointer;
  text-shadow: 1px 1px 0 rgba(0,0,0,0.5);
}
body.touch-ui #mobile-ctrl-btn:active {
  transform: translateX(-50%) translateY(2px);
  box-shadow: 0 2px 8px rgba(0,0,0,0.75);
}
/* Sheet itself always exists in DOM (display:block but off-screen via
   translateY) so the CSS transition can fire. Only shown on touch-ui. */
#mobile-ctrl-sheet {
  display: none;
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 9100;
  background: linear-gradient(180deg, #2a1510 0%, #1a0c08 100%);
  border-top: 3px solid var(--yellow-2);
  border-radius: 20px 20px 0 0;
  padding: 8px 20px 28px;
  transform: translateY(100%);
  transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
  max-height: 80dvh; overflow-y: auto;
  font-family: "VT323", "Courier New", monospace;
  color: var(--cream);
}
body.touch-ui #mobile-ctrl-sheet { display: block; }
#mobile-ctrl-sheet.open { transform: translateY(0); }

#mobile-ctrl-handle {
  width: 80px; height: 20px;
  background: transparent;
  margin: 0 auto 12px;
  cursor: pointer; touch-action: manipulation;
  position: relative;
}
#mobile-ctrl-handle::after {
  content: "";
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 40px; height: 2px;
  background: var(--yellow-2); border-radius: 99px;
}
.mc-section { margin-bottom: 12px; }
.mc-label {
  font-size: 12px; letter-spacing: 3px; text-transform: uppercase;
  color: var(--yellow-2); margin-bottom: 6px;
  text-shadow: 1px 1px 0 #000;
}
.mc-row { display: flex; gap: 8px; margin-bottom: 3px; }
.mc-row button {
  flex: 1; min-height: 44px;
  display: flex; align-items: center; justify-content: center;
  font-family: "VT323", "Courier New", monospace;
  font-size: 22px; line-height: 1; letter-spacing: 1px;
  background: linear-gradient(180deg, #55555a, #313134);
  color: var(--yellow);
  border: 2px solid; border-color: #6a6a6e #0a0a0b #0a0a0b #6a6a6e;
  border-radius: 8px;
  box-shadow: 0 3px 0 rgba(0,0,0,0.6);
  text-shadow: 1px 1px 0 #000;
  touch-action: manipulation; cursor: pointer;
}
.mc-row button:active { transform: translateY(2px); box-shadow: 0 1px 0 rgba(0,0,0,0.5); }
.mc-row-labels {
  display: flex; gap: 8px; margin-bottom: 10px;
}
.mc-row-labels span {
  flex: 1; text-align: center; font-size: 12px; letter-spacing: 0.5px;
  text-transform: uppercase; color: #e8e8ec; text-shadow: 1px 1px 0 #000;
}
#mc-track {
  font-size: 13px; color: var(--yellow-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  margin-bottom: 6px; text-shadow: 1px 1px 0 #000;
}
.mc-vol-row { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.mc-vol-label { font-size: 13px; color: #c2c2c6; letter-spacing: 1px; flex: 0 0 auto; }
#mc-vol { flex: 1; accent-color: var(--hot); cursor: pointer; }

#mobile-ctrl-backdrop {
  display: none; position: fixed; inset: 0; z-index: 9050;
  background: rgba(0,0,0,0.45);
}
body.touch-ui #mobile-ctrl-backdrop.open { display: block; background: transparent; }

/* ===== Severe Weather Alert ===== */
/* NWS-standard tiering, parsed from the alert's event name (tierFor() in
   alerts.js) — a different field than the Severe/Extreme severity filter
   that decides whether this segment appears at all (see app.js). */
.sev-wrap { flex: 1 1 auto; display: flex; flex-direction: column; gap: 20px; overflow: hidden; margin-top: 10px; }
.sev-card {
  background: linear-gradient(180deg, var(--blue-1), var(--blue-2));
  border: 4px solid var(--bevel-hi);
  border-left: 14px solid var(--bevel-hi);
  border-radius: 8px;
  padding: 22px 30px;
  box-shadow: inset 0 0 0 3px rgba(0,0,0,0.3), 0 4px 0 rgba(0,0,0,0.4);
}
.sev-card.t-warning  { border-left-color: var(--hot); }
.sev-card.t-watch    { border-left-color: var(--alert-watch); }
.sev-card.t-advisory { border-left-color: var(--alert-advisory); }
.sev-badge {
  display: inline-block; font-size: 28px; letter-spacing: 2px;
  padding: 4px 16px; border-radius: 4px; margin-bottom: 10px;
  color: #1a0603; background: var(--bevel-hi);
}
.sev-card.t-warning  .sev-badge { background: var(--hot); color: #fff; }
.sev-card.t-watch    .sev-badge { background: var(--alert-watch); color: #fff; }
.sev-card.t-advisory .sev-badge { background: var(--alert-advisory); }
.sev-event { font-size: 46px; color: #fff; text-shadow: 3px 3px 0 #1a0603; margin-bottom: 8px; }
.sev-headline {
  font-size: 34px; color: var(--cream); line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.sev-expires { font-size: 28px; color: var(--yellow-2); margin-top: 10px; }
.sev-more {
  font-size: 30px; color: var(--yellow-2); opacity: 0.85;
  margin-top: 4px; padding-top: 10px; border-top: 2px dashed rgba(202,161,107,0.25);
}
.sev-more a { color: inherit; text-decoration: none; }
.sev-more a:visited { color: inherit; }

/* Ticker item — same tier-color mapping as .sev-card, so an alert always
   reads as the same color whether seen on the ticker or the segment. */
.sev-ticker { font-weight: bold; color: var(--bevel-hi); }
.sev-ticker.t-warning  { color: var(--hot); }
.sev-ticker.t-watch    { color: var(--alert-watch); }
.sev-ticker.t-advisory { color: var(--alert-advisory); }

/* 4:3: shorter/wider canvas — same CSS multicol treatment as the Events
   Today list (.ev-today-list), since alert cards are row-shaped, not
   grid-shaped like the weekend columns. Cap drops via ALERT_MAX_SHOWN_4X3
   (app.js). */
#board.format-4x3 .sev-wrap { display: block; column-count: 2; column-gap: 32px; margin-top: 6px; }
#board.format-4x3 .sev-card { break-inside: avoid; margin-bottom: 16px; padding: 16px 22px; border-left-width: 10px; }
#board.format-4x3 .sev-badge { font-size: 22px; padding: 3px 12px; margin-bottom: 6px; }
#board.format-4x3 .sev-event { font-size: 32px; margin-bottom: 4px; }
#board.format-4x3 .sev-headline { font-size: 24px; -webkit-line-clamp: 3; }
#board.format-4x3 .sev-expires { font-size: 20px; margin-top: 6px; }
#board.format-4x3 .sev-more { column-span: all; }

/* ===== Event Screens ===== */

/* --- WEEKLY EDITORIAL (Brief + Editor's Pick) --- */
.wk-card {
  flex: 1 1 auto; min-height: 0; overflow: hidden;
  padding: 56px 62px; display: flex; flex-direction: column;
}
.wk-card-kicker {
  align-self: flex-start; background: var(--hot); color: #fff;
  font-size: 32px; letter-spacing: 2px; padding: 8px 20px;
  margin-bottom: 34px; box-shadow: 4px 4px 0 rgba(0,0,0,0.5);
}
.wk-card-source {
  color: var(--cream); opacity: 0.8; font-size: 28px; letter-spacing: 1px;
  margin-top: auto; padding-top: 34px;
  border-top: 2px dashed rgba(202,161,107,0.35);
}
.wk-card-source span { color: var(--yellow-2); }
.wk-brief-card { justify-content: center; }
.wk-brief-text {
  color: #fff; font-size: 53px; line-height: 1.28;
  text-shadow: 3px 3px 0 #1a0603;
}
.wk-pick-title {
  color: #fff; font-size: 72px; line-height: 1.05;
  text-shadow: 4px 4px 0 #1a0603; margin-bottom: 28px;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
  overflow: hidden;
}
.wk-pick-when { color: var(--yellow-2); font-size: 44px; line-height: 1.1; }
.wk-pick-venue { color: var(--cream); font-size: 42px; margin-top: 8px; }
.wk-pick-blurb {
  color: #fff; font-size: 39px; line-height: 1.25; margin-top: 36px;
  padding-top: 30px; border-top: 3px solid rgba(202,161,107,0.35);
}
.wk-pick-why {
  color: var(--cream); font-size: 33px; line-height: 1.22; margin-top: 20px;
}
.wk-pick-why strong { color: var(--yellow-2); }

#board.format-4x3 .wk-screen-title { font-size: 58px; margin-bottom: 14px; }
#board.format-4x3 .wk-card { padding: 28px 40px; }
#board.format-4x3 .wk-card-kicker { font-size: 24px; padding: 5px 14px; margin-bottom: 16px; }
#board.format-4x3 .wk-card-source { font-size: 20px; padding-top: 14px; }
#board.format-4x3 .wk-brief-text { font-size: 39px; line-height: 1.18; }
#board.format-4x3 .wk-pick-title { font-size: 54px; margin-bottom: 12px; -webkit-line-clamp: 2; }
#board.format-4x3 .wk-pick-when { font-size: 31px; }
#board.format-4x3 .wk-pick-venue { font-size: 30px; margin-top: 3px; }
#board.format-4x3 .wk-pick-blurb {
  font-size: 29px; line-height: 1.16; margin-top: 16px; padding-top: 14px;
}
#board.format-4x3 .wk-pick-why { font-size: 24px; margin-top: 10px; }

/* Category label chip */
.ev-cat { font-size: 28px; color: var(--hot); letter-spacing: 1px; margin-top: 2px; }

/* --- LOWELL TODAY (day-at-a-glance) --- */
.ev-today-list {
  flex: 1 1 auto; display: flex; flex-direction: column;
  gap: 6px; margin-top: 16px; overflow: hidden;
}
.ev-today-row {
  display: grid; grid-template-columns: 180px 1fr;
  align-items: start; padding: 8px 18px;
  border-bottom: 2px dashed rgba(202,161,107,0.25);
}
.ev-today-row .ev-time { color: var(--yellow-2); font-size: 40px; line-height: 1; }
.ev-today-row .ev-body {}
.ev-today-row .ev-name { color: #fff; font-size: 42px; line-height: 1.1; }
.ev-today-row .ev-venue { color: var(--cream); font-size: 30px; opacity: 0.8; margin-top: 2px; }
.ev-today-more {
  font-size: 28px; color: var(--yellow-2); opacity: 0.85;
  margin-top: 4px; padding-top: 10px; border-top: 2px dashed rgba(202,161,107,0.25);
}
.ev-today-more a { color: inherit; text-decoration: none; }
.ev-today-more a:visited { color: inherit; }

/* 4:3: a single vertical list of up to 8 rows won't fit a short canvas —
   2-col CSS multicol instead. .ev-today-list is flex (see above), and
   flex containers silently ignore column-count, so it must switch to
   display:block first or this would no-op. break-inside:avoid stops a
   row splitting across the column gap; column-span:all keeps the
   "+N more" footer as one full-width line instead of trapped in col 1. */
#board.format-4x3 .ev-today-list {
  display: block; column-count: 2; column-gap: 40px;
  margin-top: 8px;
}
#board.format-4x3 .ev-today-row {
  break-inside: avoid; grid-template-columns: 140px 1fr; padding: 10px 14px;
}
#board.format-4x3 .ev-today-row .ev-time { font-size: 32px; }
#board.format-4x3 .ev-today-row .ev-name { font-size: 34px; }
#board.format-4x3 .ev-today-row .ev-venue { font-size: 24px; }
#board.format-4x3 .ev-today-more { column-span: all; }
.ev-empty {
  flex: 1 1 auto; display: flex; align-items: center; justify-content: center;
  font-size: 52px; color: var(--cream); opacity: 0.65; text-align: center;
  line-height: 1.4;
}

/* "This Weekend In Lowell" explainer card (renderWeekendUnavailable) —
   .panel gives it the same bevel-border/blue-gradient treatment as
   .cc-grid/.ev-col elsewhere, max-width keeps it from stretching edge to
   edge on the wide 4:3 canvas. */
.weekend-empty-wrap {
  flex: 1 1 auto; display: flex; align-items: center; justify-content: center;
}
.weekend-empty-card {
  max-width: 860px; padding: 60px 70px; text-align: center;
}
.weekend-empty-message {
  color: #fff; font-size: 52px; line-height: 1.35; margin-bottom: 26px;
  text-shadow: 3px 3px 0 #1a0603;
}
.weekend-empty-sub {
  color: var(--cream); opacity: 0.85; font-size: 36px; line-height: 1.35;
  margin-bottom: 36px;
}
.weekend-empty-link {
  display: inline-block; background: var(--yellow); color: #1a0603;
  font-size: 38px; font-weight: bold; letter-spacing: 1px;
  text-decoration: none; padding: 16px 44px; border-radius: 8px;
  border: 4px solid;
  border-color: var(--bevel-hi) var(--bevel-lo) var(--bevel-lo) var(--bevel-hi);
  box-shadow: inset 0 0 0 2px rgba(0,0,0,0.25), 0 6px 0 rgba(0,0,0,0.4);
}
#board.format-4x3 .weekend-empty-card { max-width: 900px; padding: 40px 60px; }
#board.format-4x3 .weekend-empty-message { font-size: 44px; margin-bottom: 20px; }
#board.format-4x3 .weekend-empty-sub { font-size: 30px; margin-bottom: 26px; }
#board.format-4x3 .weekend-empty-link { font-size: 32px; padding: 14px 36px; }

/* --- THIS WEEKEND (3-column grid) --- */
.ev-weekend-grid {
  flex: 1 1 auto; display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 18px; margin-top: 16px; overflow: hidden;
}
.ev-col {
  display: flex; flex-direction: column; padding: 16px 14px; overflow: hidden;
}
.ev-col-head {
  color: var(--yellow); font-size: 58px; letter-spacing: 2px;
  border-bottom: 3px solid var(--bevel-hi); margin-bottom: 10px;
}
.ev-col-wx {
  display: flex; flex-direction: column; align-items: flex-start;
  font-size: 38px; color: var(--cream); margin-bottom: 12px;
  padding-bottom: 10px; border-bottom: 2px solid rgba(202,161,107,0.25);
  gap: 2px;
}
.ev-col-ic { display: inline-block; width: 52px; height: 52px; vertical-align: middle; }
.ev-col-temp { color: var(--yellow-2); font-size: 42px; }
.ev-col-cond { font-size: 34px; color: var(--cream); opacity: 0.9; }
.ev-col-events { display: flex; flex-direction: column; gap: 4px; flex: 1 1 auto; overflow: hidden; }
.ev-col-row {
  font-size: 28px; padding: 4px 0;
  border-bottom: 1px dashed rgba(202,161,107,0.2);
}
.ev-col-row .ev-col-time { color: var(--yellow-2); font-size: 24px; }
.ev-col-row .ev-col-title {
  color: #fff; display: block; font-size: 28px; line-height: 1.15;
  white-space: normal; overflow-wrap: break-word;
}
.ev-col-row .ev-col-venue {
  color: var(--cream); display: block; font-size: 22px; line-height: 1.15;
  opacity: 0.75; margin-top: 1px;
  white-space: normal; overflow-wrap: break-word;
}
.ev-col-empty { font-size: 30px; color: var(--cream); opacity: 0.5; }
.ev-col-more {
  font-size: 24px; color: var(--yellow-2); opacity: 0.85;
  margin-top: 6px; padding-top: 6px; border-top: 2px dashed rgba(202,161,107,0.25);
}

/* 4:3: 3-col structure is already landscape-shaped and columns get more
   width (1440 vs 1080), but each column has much less height to work
   with — retune padding/fonts down, cap dropped via WEEKEND_MAX_EVENTS_4X3. */
#board.format-4x3 .ev-weekend-grid { margin-top: 8px; }
#board.format-4x3 .ev-col { padding: 10px 14px; }
#board.format-4x3 .ev-col-head { font-size: 40px; margin-bottom: 6px; }
/* No per-day weather in 4:3 — the landscape column is short, the same hi/lo
   already appears on Extended Forecast, and the whole block's height is worth
   more as event rows. Portrait keeps it. Hidden rather than skipped in
   renderEventsWeekend() so the live Format toggle needs no re-render path. */
#board.format-4x3 .ev-col-wx { display: none; }
#board.format-4x3 .ev-col-row { font-size: 24px; padding: 3px 0; }
#board.format-4x3 .ev-col-row .ev-col-time { font-size: 20px; }
#board.format-4x3 .ev-col-row .ev-col-title { font-size: 24px; }
#board.format-4x3 .ev-col-row .ev-col-venue { font-size: 18px; }
