/* Fixed 1920x1080 broadcast canvas - the capture pipeline drives the real
   display size (Xvfb's own resolution), so nothing here is responsive; same
   assumption every GD-rendered scene image already makes. */

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  width: 1920px;
  height: 1080px;
  overflow: hidden;
  background: #060b12;
  font-family: 'Arial Narrow', Arial, 'Helvetica Neue', sans-serif;
  color: #f2f5f9;
}

#app {
  position: relative;
  width: 1920px;
  height: 1080px;
}

/* ---- background video layer ------------------------------------------ */

/* Two stacked elements, not one <video loop> - a native loop restart is an
   instant frame-0 jump-cut, and there's no attribute/CSS that softens that
   on a single element. channel.js keeps a hidden standby video preloaded
   and seeked to 0, then crossfades opacity into it a couple seconds before
   the visible one reaches its end (see crossfadeBackgroundTo()) - same
   "two independent layers" idea the Yellow/Red theme tint above already
   uses for the same reason, just applied to loop *and* state-change
   transitions here instead of only state changes. */
.bg-video {
  position: absolute;
  inset: 0;
  width: 1920px;
  height: 1080px;
  object-fit: cover;
  z-index: 0;
  opacity: 0;
  transition: opacity 1.4s ease;
}
.bg-video.bg-video-active { opacity: 1; }

#bg-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(4,10,20,0.55) 0%, rgba(4,10,20,0.72) 100%);
  z-index: 1;
}

/* Yellow/Red/Purple Mode background wash - see channel.js's
   applyAlertMode(). Independent layers (not one that swaps color) so a
   direct transition between any two crossfades cleanly instead of
   jump-cutting the color the instant a single shared layer's background
   value changes. Purple (Tornado Emergency - see computeAlertMode()'s own
   doc comment for why it outranks Red) reuses this same pattern rather
   than repurposing Red's own layer, since a Yellow<->Purple transition
   (a Tornado Watch upgraded straight to a Tornado Emergency) needs to
   crossfade too, not just Red<->Purple. */
#bg-theme-tint-yellow,
#bg-theme-tint-red,
#bg-theme-tint-purple {
  position: absolute;
  inset: 0;
  z-index: 2;
  opacity: 0;
  transition: opacity 1.4s ease;
  pointer-events: none;
}
#bg-theme-tint-yellow { background: linear-gradient(180deg, rgba(255,196,0,0.22), rgba(150,110,0,0.34)); }
#bg-theme-tint-purple { background: linear-gradient(180deg, rgba(200,38,211,0.24), rgba(120,20,130,0.36)); }
#bg-theme-tint-red    { background: linear-gradient(180deg, rgba(255,40,40,0.20), rgba(120,10,10,0.34)); }
body.theme-yellow #bg-theme-tint-yellow { opacity: 1; }
body.theme-red    #bg-theme-tint-red    { opacity: 1; }
body.theme-purple #bg-theme-tint-purple { opacity: 1; }

/* Frosted rain-on-glass look, toggled on during active severe weather -
   see channel.js's applyBackgroundState(). A blurred, darkened wash over
   the whole frame rather than per-panel, matching the second mockup's
   overall moodier look during the tornado warning rather than an isolated
   panel effect. */
#bg-glass {
  position: absolute;
  inset: 0;
  z-index: 3;
  opacity: 0;
  background: radial-gradient(ellipse at 30% 20%, rgba(120,150,190,0.10), transparent 60%);
  backdrop-filter: blur(1.5px) brightness(0.9);
  transition: opacity 1.2s ease;
  pointer-events: none;
}
body.severe #bg-glass { opacity: 1; }

.frame {
  position: relative;
  z-index: 4;
  width: 1920px;
  height: 1080px;
  padding: 20px;
  display: grid;
  grid-template-columns: 460px 1fr;
  grid-template-rows: 148px 92px 1fr 268px;
  gap: 16px;
}

.panel {
  background: rgba(12, 24, 40, 0.62);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 10px;
  backdrop-filter: blur(6px);
  box-shadow: 0 6px 24px rgba(0,0,0,0.35);
  overflow: hidden;
}

/* ---- logo -------------------------------------------------------------- */

.logo-panel {
  grid-column: 1;
  grid-row: 1;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 22px;
  background: linear-gradient(135deg, #0d3a66, #133c5c 60%, #0a2b4a);
}

.badge svg { display: block; }

/* ---- Yellow/Red Mode "blue block" tinting -------------------------------
   Applies to the radar title bar (.map-header) and the two blue panels
   (.logo-panel, .currently-panel) - see channel.js's applyAlertMode(). Two
   stacked overlay layers per element (::before for yellow, ::after for
   red), each independently opacity-faded, so the transition is a real
   crossfade in every direction (normal<->yellow, normal<->red, and a
   direct yellow<->red) rather than an abrupt color swap. Real content
   inside these three elements gets position:relative + z-index so it
   stays readable above the overlays instead of being painted under them
   the way an absolutely-positioned pseudo-element normally would sit atop
   any non-positioned sibling regardless of DOM order. */
.logo-panel, .currently-panel, .map-header {
  position: relative;
}

.logo-panel > *, .currently-panel > *, .map-header > * {
  position: relative;
  z-index: 1;
}

.logo-panel::before, .currently-panel::before, .map-header::before,
.logo-panel::after,  .currently-panel::after,  .map-header::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0;
  transition: opacity 1.4s ease;
  pointer-events: none;
}

.logo-panel::before      { background: linear-gradient(135deg, #6b5108, #7c600e 60%, #57420a); }
.currently-panel::before { background: linear-gradient(180deg, rgba(150,120,20,0.60), rgba(80,62,10,0.80)); }
.map-header::before      { background: linear-gradient(90deg, #4a3a08, #6a5410 55%, #4a3a08); }

.logo-panel::after      { background: linear-gradient(135deg, #6b1210, #7c1614 60%, #500e0c); }
.currently-panel::after { background: linear-gradient(180deg, rgba(150,25,20,0.60), rgba(80,12,10,0.80)); }
.map-header::after      { background: linear-gradient(90deg, #3a0a0a, #5a1414 55%, #3a0a0a); }

body.theme-yellow .logo-panel::before,
body.theme-yellow .currently-panel::before,
body.theme-yellow .map-header::before { opacity: 1; }

body.theme-red .logo-panel::after,
body.theme-red .currently-panel::after,
body.theme-red .map-header::after { opacity: 1; }

/* Purple (Tornado Emergency) reuses the ::after slot Red's rules already
   define above rather than adding a fourth pseudo-element (CSS only gives
   an element ::before and ::after) - safe since Red and Purple are
   mutually exclusive states (see computeAlertMode()), so this can never
   fight with Red's own ::after opacity rule for the same element at the
   same time. */
body.theme-purple .logo-panel::after      { background: linear-gradient(135deg, #5a0f66, #6c1478 60%, #3f0a4a); opacity: 1; }
body.theme-purple .currently-panel::after { background: linear-gradient(180deg, rgba(180,20,190,0.55), rgba(90,12,100,0.80)); opacity: 1; }
body.theme-purple .map-header::after      { background: linear-gradient(90deg, #34073b, #57125e 55%, #34073b); opacity: 1; }

.logo-text .station-name {
  font-size: 34px;
  font-weight: 800;
  letter-spacing: 0.5px;
  line-height: 1.05;
}

.logo-text .tagline {
  font-size: 26px;
  font-weight: 800;
  color: #d9b23c;
  line-height: 1.1;
}

/* ---- sponsor ad slot ---------------------------------------------------- */
/* Named .sponsor-panel, not .ad-panel - confirmed live 2026-08-05 that
   "ad-panel" is a generic cosmetic-filter match in Brave Shields/uBlock/
   EasyList (anything that looks like "ad-*" gets hidden client-side by
   default, regardless of whether it's a real third-party ad), silently
   dropping this panel on any viewer with an ad blocker even though it's
   entirely first-party sponsor content this station controls. */

.sponsor-panel {
  grid-column: 1;
  grid-row: 2;
  background: linear-gradient(180deg, #f4d23a, #e0b520);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: #7a0f14;
  border-color: rgba(0,0,0,0.15);
}

.sponsor-name {
  font-family: 'Brush Script MT', 'Segoe Script', cursive;
  font-size: 32px;
  font-weight: 700;
  text-shadow: 1px 1px 0 rgba(255,255,255,0.25);
}

.sponsor-sub {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 2px;
  margin-top: 2px;
}

/* ---- currently panel ---------------------------------------------------- */

.currently-panel {
  grid-column: 1;
  grid-row: 3;
  background: linear-gradient(180deg, rgba(40,90,150,0.55), rgba(15,40,75,0.75));
  padding: 20px 26px;
  display: flex;
  flex-direction: column;
}

.currently-label-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.currently-label {
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 3px;
  color: #8fc4f5;
}

.currently-obs-time {
  font-size: 14px;
  color: #9aa5b0;
  text-align: right;
  white-space: nowrap;
}

.currently-city {
  font-size: 40px;
  font-weight: 800;
  margin-top: 4px;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.45s ease, transform 0.45s ease;
}

.currently-main {
  display: flex;
  align-items: center;
  gap: 18px;
  margin-top: 6px;
}

.condition-icon {
  width: 96px;
  height: 96px;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.45s ease, transform 0.45s ease;
}

.currently-temp {
  font-size: 88px;
  font-weight: 800;
  line-height: 1;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.45s ease, transform 0.45s ease;
}

.currently-panel.fade-in .currently-city,
.currently-panel.fade-in .condition-icon,
.currently-panel.fade-in .currently-temp,
.currently-panel.fade-in .currently-wind,
.currently-panel.fade-in .currently-stats {
  opacity: 1;
  transform: none;
}

/* Own boxed area beside the condition icon/temp (not a stat-row line) -
   modeled directly on a real broadcast wind graphic: big MPH number on the
   left, a circular compass with a direction needle (or "CLM" when calm)
   on the right, both inside one dark panel set off from the temperature. */
.currently-wind {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 8px 16px;
  margin-left: 4px;
  border-radius: 10px;
  background: rgba(6,14,26,0.55);
  border-left: 2px solid rgba(255,255,255,0.18);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.45s ease, transform 0.45s ease;
}

.currently-wind[hidden] { display: none; }

.currently-wind-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 2px;
  color: #9fbedd;
}

.currently-wind-row {
  display: flex;
  align-items: center;
  gap: 14px;
}

.currently-wind-speed-block,
.currently-wind-compass-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.1;
}

.currently-wind-speed {
  font-size: 34px;
  font-weight: 800;
}

.currently-wind-unit {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1.5px;
  color: #9fbedd;
  margin-top: 2px;
}

.currently-wind-compass {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: rgba(0,0,0,0.35);
  display: flex;
  align-items: center;
  justify-content: center;
}

.currently-wind-compass svg { display: block; color: #d9b23c; }

.currently-wind-compass-calm {
  font-size: 12px;
  font-weight: 800;
  color: #eaf2fb;
}

.currently-wind-dir {
  font-size: 14px;
  font-weight: 800;
  margin-top: 4px;
}

.currently-stats {
  margin-top: auto;
  font-size: 18px;
  line-height: 1.85;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.45s ease, transform 0.45s ease;
}

.currently-stats .stat-row {
  display: flex;
  justify-content: space-between;
  border-bottom: 1px solid rgba(255,255,255,0.08);
  padding: 2px 0;
}

.currently-stats .stat-label { color: #9fbedd; }
.currently-stats .stat-value { font-weight: 700; }

.pressure-trend {
  display: inline-block;
  font-size: 20px;
  line-height: 1;
  vertical-align: middle;
  margin-left: 3px;
}

/* ---- mini alerts panel --------------------------------------------------- */

.alerts-mini-panel {
  grid-column: 1;
  grid-row: 4;
  position: relative;
  background: linear-gradient(180deg, rgba(30,120,80,0.55), rgba(10,50,35,0.75));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  transition: background 0.6s ease;
}

.alerts-mini-panel.has-alerts {
  background: linear-gradient(180deg, rgba(150,30,30,0.6), rgba(70,10,10,0.8));
}

.mini-label {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 3px;
  color: #cfe8dd;
}

.mini-event {
  font-size: 16px;
  font-weight: 700;
  padding: 0 16px;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-top: 6px;
}

/* The per-alert mini map (AlertMiniMapScene's output) fills the whole
   panel; a thin caption strip along the bottom names which alert is
   currently shown, since the map itself has no title bar by design. */
.mini-map-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.mini-map-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 6px 12px;
  background: rgba(0,0,0,0.55);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- main map panel ------------------------------------------------------ */

.map-panel {
  grid-column: 2;
  grid-row: 1 / span 3;
  display: flex;
  flex-direction: column;
  background: #0a1826;
}

.map-header {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 14px 22px;
  background: linear-gradient(90deg, #0c1f33, #14314f 55%, #0c1f33);
  border-bottom: 1px solid rgba(255,255,255,0.08);
}

.map-badge { flex: 0 0 auto; }

.map-title-block { flex: 1 1 auto; min-width: 0; }

.map-title {
  font-size: 30px;
  font-weight: 800;
}

.map-subtitle {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 2px;
  color: #9fbedd;
  margin-top: 2px;
}

.legend-bar {
  margin-top: 6px;
  height: 8px;
  width: 260px;
  border-radius: 4px;
  background: linear-gradient(90deg, #4fc3f7, #4caf50, #ffeb3b, #ff9800, #f44336, #d63aff);
}

.map-clock-block {
  flex: 0 0 auto;
  text-align: right;
}

.map-clock {
  font-size: 26px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.map-date {
  font-size: 13px;
  color: #9fbedd;
  letter-spacing: 1px;
}

.map-body {
  flex: 1 1 auto;
  position: relative;
  overflow: hidden;
}


.map-body img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---- bottom rotator: forecast strip / warnings ---------------------------- */

.bottom-panel {
  grid-column: 2;
  grid-row: 4;
  position: relative;
  padding: 0;
}

.bottom-forecast {
  position: absolute;
  inset: 0;
  padding: 14px 20px;
  display: flex;
  flex-direction: column;
  opacity: 1;
  transition: opacity 0.5s ease;
}

.bottom-forecast.hidden { opacity: 0; pointer-events: none; }

.forecast-city-label {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 2px;
  color: #9fbedd;
  margin-bottom: 6px;
}

.forecast-columns {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 12px;
}

.forecast-col {
  background: rgba(60,100,170,0.28);
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 8px 4px;
  transition: background 1.4s ease;
}

/* Yellow/Red Mode also tints the forecast boxes, per the user's explicit
   call - even though they're unlikely to show alongside a real Watch/
   Warning in production, the admin panel's manual override can force one
   on while forecast boxes are on screen, so they need to react too. Same
   color language as the "blue block" panels elsewhere on the page. */
body.theme-yellow .forecast-col { background: rgba(150,120,20,0.38); }
body.theme-red    .forecast-col { background: rgba(150,25,20,0.38); }
body.theme-purple .forecast-col { background: rgba(150,20,160,0.38); }

.forecast-col .fc-label {
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 1px;
  text-align: center;
}

.forecast-col img {
  width: 44px;
  height: 44px;
  margin: 4px 0;
}

.forecast-col .fc-temp {
  font-size: 26px;
  font-weight: 800;
}

.forecast-col .fc-wind {
  font-size: 12px;
  color: #cfe0f0;
  margin-top: 2px;
}

.bottom-warning {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  opacity: 0;
  transition: opacity 0.5s ease;
}

.bottom-warning.visible { opacity: 1; }

.new-alert-bar {
  flex: 0 0 auto;
  background: #f4d23a;
  color: #1a1a1a;
  font-size: 17px;
  font-weight: 900;
  letter-spacing: 2px;
  text-align: center;
  padding: 6px 0;
}

.warning-top {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 24px;
  padding: 10px 24px;
  background: rgba(20,10,10,0.55);
}

.warning-areas {
  flex: 1 1 auto;
  font-size: 20px;
  font-weight: 700;
  min-width: 0;
}

.warning-tags {
  flex: 0 0 auto;
  display: flex;
  gap: 10px;
}

.warning-tags .tag {
  background: #101010;
  border: 1px solid rgba(255,255,255,0.15);
  border-radius: 6px;
  padding: 6px 14px;
  text-align: center;
  min-width: 96px;
}

.warning-tags .tag .tag-label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1px;
  color: #b9c6d6;
}

.warning-tags .tag .tag-value {
  font-size: 17px;
  font-weight: 800;
}

.warning-expires {
  flex: 0 0 auto;
  font-size: 17px;
  font-weight: 700;
  color: #ffd9d9;
}

.warning-ticker {
  height: 40px;
  /* Set per-alert by channel.js from config/warnings.php's official NWS
     color for that event type (via AlertResolver::styleFor()) - the
     fallback here only shows before the first alert has rendered. */
  background: var(--alert-color-dark, #7a0f14);
  overflow: hidden;
  position: relative;
  display: flex;
  align-items: center;
}

.ticker-track {
  white-space: nowrap;
  font-size: 19px;
  font-weight: 700;
  padding-left: 1920px;
  /* animation-duration is set per-alert in JS (channel.js's
     startTickerScroll()) so scroll SPEED stays constant regardless of
     message length - a fixed duration here would make long alerts race
     past unreadably fast and short ones crawl. animation-play-state
     starts paused; JS unpauses once the duration is computed and
     applied, avoiding one frame of default-duration motion first. */
  animation: ticker-scroll linear infinite;
  animation-play-state: paused;
}

@keyframes ticker-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-100%); }
}

.warning-banner {
  flex: 0 0 auto;
  /* --alert-color/--alert-color-text set per-alert by channel.js - see the
     .warning-ticker comment above. Text color is picked for contrast
     against whatever the event's official color happens to be (e.g. a
     Tornado Watch's yellow needs dark text, a Flash Flood Warning's dark
     red needs light text). */
  background: linear-gradient(180deg, var(--alert-color, #d81c1c), var(--alert-color-dark, #a11010));
  color: var(--alert-color-text, #fff);
  font-size: 30px;
  font-weight: 900;
  letter-spacing: 3px;
  text-align: center;
  padding: 8px 0;
  animation: banner-pulse 1.6s ease-in-out infinite;
}

@keyframes banner-pulse {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.18); }
}

/* Elevated-warning treatment (Tornado Emergency / Confirmed Tornado
   Warning / Flash Flood Emergency / Destructive Severe Thunderstorm
   Warning - see ChannelDataBuilder::detectElevatedAlert()) - a
   continuously-scrolling diagonal chevron/hazard-stripe texture over the
   banner's own solid color, plus a brighter/faster pulse than every other
   warning gets. Modeled on the constant-motion "alert tape" look real
   broadcast lower-thirds use for their highest-tier warnings (the user's
   own reference: a Fox Weather Alert bar) - built from a description and
   a still image, not a frame-by-frame copy of any particular graphic, so
   treat the exact stripe angle/speed as a starting point to refine.
   z-index:-1 on the stripe layer (not 0) is deliberate: .warning-banner's
   text is a plain, unwrapped text node (channel.js just sets
   .textContent), and per the normal CSS stacking order an in-flow text
   node paints *above* a positioned z-index:0 sibling regardless of DOM
   order - a positive/zero z-index here would have sat over the text, not
   under it. -1 keeps the stripes between the banner's own solid-color
   background and the text, exactly like a texture on the background
   rather than a layer over the words, without needing to wrap the text
   in its own element just to give it a z-index to win with. */
.warning-banner.elevated {
  position: relative;
  overflow: hidden;
  animation: banner-pulse-elevated 0.9s ease-in-out infinite;
}

.warning-banner.elevated::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: repeating-linear-gradient(
    135deg,
    rgba(255,255,255,0.22) 0px,
    rgba(255,255,255,0.22) 22px,
    rgba(255,255,255,0) 22px,
    rgba(255,255,255,0) 44px
  );
  animation: chevron-scroll 1.1s linear infinite;
  pointer-events: none;
}

@keyframes chevron-scroll {
  from { background-position: 0 0; }
  to   { background-position: -62.2px 0; }
}

@keyframes banner-pulse-elevated {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.35); }
}

/* ---- map-panel forecast/map-rotation iframe ---------------------------- */

/* Sits in the same .map-body container as #radarImage (see
   scheduleMapRotation() in channel.js) - shown instead of the radar image
   during the forecast-graphic portion of the rotation, never as a
   separate full-screen layer. */
#mapFrame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
