/* Copyright 2026 by John A Kline.  See LICENSE for your rights.
   The three imagery tabs -- Radar, Satellite, Sat. Bands -- in the panel
   grammar the celestial pages settled on and the forecast tabs, Highs/Lows
   and Graphs have carried since: white sections with a hairline border on
   the light page, a #367ba3 eyebrow, muted captions, everything sized in em
   so the phone layout scales with one .rs font-size rule under the phone
   query.

   THE TEXT LADDER IS NOT NEW.  ink / ink-2 / ink-3 / muted / faint are the
   five tiers css/forecast.css derived against measured grounds and
   css/liveseasons.css now carries as --ls-*; they are restated here under
   --rs-* only so this page family can be themed as a unit, and every value
   is a copy, not a fresh pick.  Do not hand-tune one.

   THEMING.  :root carries the light values, .theme-dark redefines them, and
   there is deliberately NO prefers-color-scheme rule -- until the site
   actually offers the switch a reader whose OS prefers dark must keep seeing
   these pages as designed.

   ---------------------------------------------------------------------
   THE PLATE, AND WHY THIS PAGE FAMILY NEEDS A TOKEN THE OTHERS DO NOT.

   Every other page the site has modernized draws its own content, so its
   dark theme is a stylesheet away.  These three are about ninety percent
   third-party raster that we do not generate and cannot recolor honestly:
   NWS RIDGE radar is a WHITE basemap, and GOES GeoColor and the sixteen ABI
   bands are near-BLACK.  Dropping both onto one card ground makes one of
   them a glare panel and the other invisible.

   So the image does not sit on the card.  It sits on a PLATE -- a framed
   ground of its own, whose color is chosen per source and is free to differ
   from the card behind it:

     .plate--lit    a source whose own image is light (RIDGE).  Stays a pale
                    neutral under .theme-dark: a lit panel inside a dark
                    instrument, which is what a radar console actually is.
                    The image is UNTOUCHED -- no filter -- so the dBZ scale
                    NWS prints along the bottom of the frame still means what
                    NWS says it means.
     .plate--night  a source whose own image is dark (GOES).  Goes DARKER
                    than the card under .theme-dark, so the image's own black
                    sky melts into the page instead of ending at a seam.

   Two other answers were built and rejected in review: dimming the radar
   with a brightness filter, and the usual invert+hue-rotate dark-map trick.
   The second is why neither shipped -- it repaints NWS's published
   reflectivity ramp into colors that are not the reflectivity ramp, while
   the legend inside the same image goes on claiming otherwise. */

:root {
  --rs-surface:#ffffff;
  --rs-ink:#222222;
  --rs-ink-2:#343434;
  --rs-ink-3:#3f4955;
  --rs-muted:#545e69;
  --rs-faint:#5e6574;   /* --ls-faint; see liveseasons.css */
  --rs-line:#999999;
  --rs-hair:#d8dde3;
  --rs-rule:#c2c8ce;
  --rs-accent:#246a92;
  --rs-link:#2b6897;
  --rs-tint:#f5f7f9;
  --rs-ok:#2f7d4f;
  --rs-warn:#8a6d03;
  --rs-bad:#a94442;
  --rs-head-rule:#666666;
  --rs-on-accent:#ffffff;

  /* the plates */
  --rs-plate-lit:#ffffff;
  --rs-plate-night:#ffffff;
  --rs-plate-line:#c2c8ce;

  /* the transport bar */
  --rs-track:#dfe4ea;
  --rs-track-fill:#246a92;
  --rs-btn:#f0f3f6;
  --rs-btn-line:#c2c8ce;
  --rs-btn-ink:#3f4955;
  --rs-btn-on:#246a92;

  /* The band families.  Four hues, one per ABI family, used only as an 8px
     dot beside a family heading -- never as a text color, so none of them
     has to clear a contrast bar on its own. */
  --rs-fam-vis:#b8860b;
  --rs-fam-nir:#7a5ea8;
  --rs-fam-wv:#2f7d70;
  --rs-fam-ir:#2f6ea8;
}

.theme-dark {
  --rs-surface:#111834;
  --rs-ink:#e9e4d4;
  --rs-ink-2:#ded9c9;
  --rs-ink-3:#d4cfbf;
  --rs-muted:#b2bae1;
  --rs-faint:#b2bae1;   /* --ls-faint; see liveseasons.css */
  --rs-line:#2a3358;
  --rs-hair:#232b4c;
  --rs-rule:#2a3358;
  --rs-accent:#99c2dd;
  --rs-link:#99c2dd;
  --rs-tint:#151c38;
  --rs-ok:#7acb99;
  --rs-warn:#ebae6b;
  --rs-bad:#ffa08d;
  --rs-head-rule:#2f3a63;
  --rs-on-accent:#0a0f22;

  /* Deliberately NOT dark.  See the plate note at the top of this file. */
  --rs-plate-lit:#dfe4ea;
  --rs-plate-night:#05070f;
  --rs-plate-line:#2a3358;

  --rs-track:#232b4c;
  --rs-track-fill:#99c2dd;
  --rs-btn:#19203e;
  --rs-btn-line:#2a3358;
  --rs-btn-ink:#c8c3b4;
  --rs-btn-on:#99c2dd;

  --rs-fam-vis:#d8a93a;
  --rs-fam-nir:#a58fd0;
  --rs-fam-wv:#4fa895;
  --rs-fam-ir:#5b8fc4;
}

/* THE [hidden] GUARD, AND IT IS NOT OPTIONAL HERE.
   The browser hides [hidden] with a UA rule of specificity (0,1,0), which
   ANY class rule setting display beats.  .rs .tbtn{display:grid} is (0,2,0),
   so the LOAD LOOP button carried hidden=true and went on rendering anyway
   -- 121 px of "LOADING 100%" that never went away on any of the three
   pages.  Several things on these pages are shown and hidden from
   javascript (the live telemetry rows, the health pill, the alert line,
   this button); this makes the attribute mean what it says for all of
   them, and for whatever gets added next. */
.rs [hidden]{display:none !important}

/* ---- the panel ------------------------------------------------------ */
.rs{padding:1.625rem 1.25rem 2.125rem;margin-top:1.125rem;font-size:min(calc(1rem + var(--ls-step)), max(1rem, var(--ls-cap)));color:var(--rs-ink);
    text-align:left;font-family:'Open Sans',arial,sans-serif}
.rs .rswrap{max-width:77.5rem;margin:0 auto;display:flex;flex-direction:column;gap:1.625rem}
.rs section{background:var(--rs-surface);border:.0625rem solid var(--rs-line);
            padding:1.125rem 1.25rem 1rem}
.rs .eyebrow{color:var(--rs-accent);font-weight:600;font-size:.8em;
             letter-spacing:.08em;text-transform:uppercase;margin:0 0 .75rem;
             border:0;padding:0}
.rs .caption{color:var(--rs-muted);font-size:.85em;margin:.875rem 0 0;max-width:86ch;
             line-height:1.55}
.rs .caption b{color:var(--rs-ink-3)}
.rs .cfoot{color:var(--rs-muted);font-size:.82em;border-top:.0625rem solid var(--rs-rule);
           padding-top:.625rem;margin-top:1rem}
.rs a{color:var(--rs-link);text-decoration:none}
.rs a:hover{text-decoration:underline}

.rs .chead{display:flex;justify-content:space-between;align-items:flex-end;gap:1.5rem;
           border-bottom:.125rem solid var(--rs-head-rule);padding-bottom:.5rem}
.rs h2.ctitle{margin:0;font-size:2.1em;font-weight:600;color:var(--rs-accent);
              letter-spacing:.02em}
.rs h2.ctitle .over{color:var(--rs-muted);font-weight:400}
.rs .sub{color:var(--rs-muted);font-size:.95em;padding-bottom:.25rem;text-align:right}
/* The 15-minute image-refresh countdown lives in the subtitle, and each
   page's *_updater.inc writes this note into it while a check is in flight.
   --rs-muted, NOT --rs-faint: this is small, spaced-out type, and muted is
   the tier the rest of the page uses for a de-emphasized note on the card.
   (An earlier version of this comment justified the choice by claiming
   .chead sits outside any <section> and renders on the page ground
   #e3e3e3.  It does not -- .chead is inside the <section> in all three
   .inc files, on --rs-surface.  The color was right, the reason was not.) */

/* The transport glyph sprite: <defs> only, so it paints nothing and must
   take no space.  Not display:none -- Safari will not resolve a <use> into
   a hidden tree. */
.rs-sprite{position:absolute;width:0;height:0;overflow:hidden}

/* ---- the plate ------------------------------------------------------
   The frame the third-party raster sits in.  grid + place-items keeps the
   image centered whatever its aspect, and the padding is the mat. */
.rs .plate{border:.0625rem solid var(--rs-plate-line);display:grid;place-items:center;
           padding:.625rem;border-radius:.1875rem}
/* The image is sized in the FRAME, not by its own pixels: every NOAA
   product on these pages is 600 px wide, and a bare max-width left it at
   600 CSS px on a device-width page, so on a portrait iPad it kept its
   desktop width and crushed the rail beside it until the page ran wider
   than the screen (872 on an iPad Pro 11, measured).  37.5rem is 600 at
   1124 and wider, so no desktop moves. */
.rs .plate img{display:block;width:37.5rem;max-width:100%;height:auto}
.rs .plate--lit{background:var(--rs-plate-lit)}
.rs .plate--night{background:var(--rs-plate-night)}

/* ---- the transport bar ----------------------------------------------
   One grammar on all three pages.  The loop starts on its own, as it
   always has; what the GIFs this replaced refused everyone was the ability
   to stop it, step it or scrub it, and a reader who has asked for reduced
   motion gets the still and the load button instead. */
.rs .transport{display:flex;align-items:center;gap:.625rem;margin-top:.75rem;
               flex-wrap:wrap}
.rs .tbtn{background:var(--rs-btn);border:.0625rem solid var(--rs-btn-line);
          color:var(--rs-btn-ink);width:2.125rem;height:1.875rem;display:grid;
          place-items:center;cursor:pointer;padding:0;border-radius:.1875rem}
.rs .tbtn:hover{border-color:var(--rs-btn-on);color:var(--rs-btn-on)}
.rs .tbtn svg{width:.875rem;height:.875rem;fill:currentColor}
.rs .tbtn.wide{width:auto;padding:0 .625rem;font:600 .78em/1 'Open Sans',arial,sans-serif;
               letter-spacing:.05em}
.rs .tbtn[aria-pressed="true"]{background:var(--rs-btn-on);
                               border-color:var(--rs-btn-on);color:var(--rs-on-accent)}
/* A real <input type=range> so the scrubber is keyboard-operable and
   announced; the hairline behind it is decoration. */
.rs .scrub{flex:1;min-width:11.25rem;position:relative;height:1.875rem;display:flex;
           align-items:center}
.rs .scrub input[type=range]{width:100%;margin:0;accent-color:var(--rs-track-fill);
                             background:transparent;position:relative;z-index:2}
.rs .ticks{position:absolute;left:0;right:0;top:50%;height:.0625rem;
           background:var(--rs-track);z-index:1}
.rs .stamp{margin-left:auto;font-size:.86em;color:var(--rs-ink-2);
           font-variant-numeric:tabular-nums;white-space:nowrap;
           min-width:15.5em;text-align:right}
.rs .stamp .rel{color:var(--rs-muted)}
.rs .stamp .now{color:var(--rs-ok);font-weight:600}

/* ---- the context rail -----------------------------------------------
   `auto 1fr`: the frame column takes the raster's natural width and the
   rail takes the rest, so the image is never resampled. */
.rs .withrail{display:grid;grid-template-columns:auto 1fr;gap:1.625rem;
              align-items:start;margin-top:1.125rem}
.rs .rail{min-width:0}
.rs .meta{margin:0;display:grid;grid-template-columns:auto 1fr;gap:.375rem .875rem;
          font-size:.86em}
.rs .meta dt{color:var(--rs-muted);font-size:.92em;letter-spacing:.04em;
             text-transform:uppercase;white-space:nowrap}
.rs .meta dd{margin:0;color:var(--rs-ink-2);font-variant-numeric:tabular-nums}
/* ---- the radar's live health ----------------------------------------
   The instrument rail carries its own state rather than wearing an alert
   bar above it.  What this replaced was a <div> a cron wrote with
   color:#8a6d03 baked in -- markup no stylesheet could override and the
   page had to undress before it could style it at all.

   THREE STATES, and they are the READER's three, not the alarm's:
     .pill--ok    working
     .pill--warn  working, NWS has something flagged.  This is the common
                  case, not an exception -- "Receiver" is a standing alarm
                  on healthy radars nationwide (KMUX, KDAX, KBBX and KLOT
                  all reported it while this was written), which is why it
                  is a quiet note on the heading and not a banner.
     .pill--down  not operating, or the Level II feed has gone stale.  This
                  one earns the sentence underneath it, because it is the
                  only case where the picture above may not be current. */
.rs .railh--pill{display:flex;align-items:center;gap:.625rem;
                 justify-content:space-between;flex-wrap:wrap;
                 white-space:nowrap}
/* nowrap + wrap: the heading keeps its own line rather than being crushed
   by the pill.  At 1124 the rail is about 300 px and "Operating .
   Receiver" takes half of it, which broke "THE INSTRUMENT" across two
   lines while every other rail heading sat on one.  The pill drops under
   it instead. */
.rs .pill--inst{font-size:.92em;font-weight:600;letter-spacing:.02em;
          text-transform:none;padding:.125rem .5625rem;border-radius:.625rem;
          border:.0625rem solid;white-space:nowrap}
.rs .pill--inst.pill--ok{color:var(--rs-ok);border-color:var(--rs-ok);
              background:var(--rs-tint)}
.rs .pill--inst.pill--warn{color:var(--rs-warn);border-color:var(--rs-warn);
                background:var(--rs-tint)}
.rs .pill--inst.pill--down{color:var(--rs-on-accent);border-color:var(--rs-bad);
                background:var(--rs-bad)}
.rs .alertline{font-size:.88em;line-height:1.55;margin:0 0 .875rem;
               padding:.5rem .75rem;border-radius:.1875rem;background:var(--rs-tint);
               border-left:.1875rem solid var(--rs-bad);color:var(--rs-ink)}
/* The pattern number itself, after the plain-language reading of it. */
.rs .vcpno{color:var(--rs-faint)}

.rs .railh{color:var(--rs-accent);font-weight:600;font-size:.8em;
           letter-spacing:.08em;text-transform:uppercase;margin:1.375rem 0 .5rem}
.rs .railh:first-child{margin-top:0}
.rs .rail p{margin:0 0 .625rem;font-size:.88em;line-height:1.6;color:var(--rs-ink-2)}

/* ---- the contact sheet ----------------------------------------------
   All sixteen bands as live 300-px thumbnails, at once, grouped into the
   four spectral families.  This is the argument sixteen bare two-digit
   numbers could never make: you pick a band by looking at it, and you can
   see for yourself that the six reflected-light channels go dark after
   sunset.

   EIGHT columns, which is not arbitrary: the families hold 2, 4, 3 and 7
   channels, so at eight across every family is exactly one row. */
.rs .sheetgroups{display:flex;flex-direction:column;gap:1.125rem}
.rs .glabel{display:flex;align-items:center;gap:.4375rem;color:var(--rs-muted);
            font-size:.72em;letter-spacing:.08em;text-transform:uppercase;
            margin:0 0 .5rem;padding-bottom:.375rem;
            border-bottom:.0625rem solid var(--rs-hair)}
.rs .gdot{width:.5rem;height:.5rem;border-radius:50%;flex:none}
.rs .bgroup--vis .gdot{background:var(--rs-fam-vis)}
.rs .bgroup--nir .gdot{background:var(--rs-fam-nir)}
.rs .bgroup--wv  .gdot{background:var(--rs-fam-wv)}
.rs .bgroup--ir  .gdot{background:var(--rs-fam-ir)}
/* align-items:start: a button centers its own content, so a tile whose
   label wraps to one line fewer than its row's carried its image down by
   half the difference (7.5px on the desktop, 5px on the phone).  Pinned to
   the top of the row, every image in a row starts on one line. */
.rs .sheet{display:grid;grid-template-columns:repeat(8,1fr);gap:.75rem;align-items:start}
.rs .tile{background:transparent;border:0;padding:0;cursor:pointer;font:inherit;
          text-align:left;color:var(--rs-ink-2);display:block}
/* 1/.95 with object-position:top crops NOAA's own caption strip off the
   bottom: every tile carries the same stamp, it is illegible at this size,
   and the tile has a label of its own underneath. */
.rs .tile .timg{background:var(--rs-plate-night);border:.0625rem solid var(--rs-plate-line);
                display:block;width:100%;aspect-ratio:1/.95;object-fit:cover;
                object-position:top;border-radius:.125rem}
.rs .tile .tlab{display:block;font-size:.72em;line-height:1.3;margin-top:.3125rem;
                color:var(--rs-muted)}
.rs .tile .tno{color:var(--rs-faint);font-variant-numeric:tabular-nums;
               margin-right:.4em}
.rs .tile:hover .timg{border-color:var(--rs-accent)}

/* ---- which one is showing above -------------------------------------
   This has to carry TWO jobs, and the first version carried neither well
   enough.  It says which of sixteen similar-looking thumbnails is the one
   in the frame above -- several of the infrared channels are near
   identical at this size -- and on a phone it is the ONLY confirmation
   that a tap registered at all, because there is no hover.  A 2px ring in
   the accent color was too quiet for both.

   Sized in em so the phone layout, which scales the type up, gets a
   proportionally heavier ring rather than the same hairline. */
.rs .tile[aria-pressed="true"] .timg{border-color:var(--rs-accent);
                                     outline:.22em solid var(--rs-accent);
                                     outline-offset:.06em}
.rs .tile[aria-pressed="true"] .tlab{color:var(--rs-accent);font-weight:700}
/* The channel number becomes a filled chip: a second, differently-shaped
   cue, so the selection survives being looked at by someone who cannot
   pick the accent color out from the border colors around it. */
.rs .tile[aria-pressed="true"] .tno{background:var(--rs-accent);
                                    color:var(--rs-on-accent);
                                    border-radius:.125rem;padding:.05em .35em;
                                    margin-right:.35em}
/* Tap feedback, on a device with no hover: the press itself is answered
   immediately, before the new still has finished loading. */
.rs .tile:active .timg{border-color:var(--rs-accent);
                       outline:.22em solid var(--rs-accent);
                       outline-offset:.06em}

/* ---- the phone layout ----------------------------------------------
   Under the width query, and NOT a narrow layout: the phone keeps the same
   1124-px frame as a wider screen (the root rule in css/liveseasons.css
   scales it) and sets the type larger.  One override does the whole family. */
@media (max-width: 600px){.rs{font-size:min(calc(1.875rem + var(--ls-step)), max(1.875rem, var(--ls-cap)))}}
@media (max-width: 600px){.rs .rswrap{gap:2.125rem}}
@media (max-width: 600px){.rs section{padding:1.5rem 1.625rem 1.375rem}}
@media (max-width: 600px){.rs .withrail{grid-template-columns:1fr}}
@media (max-width: 600px){.rs .sheet{grid-template-columns:repeat(4,1fr);gap:1rem}}
@media (max-width: 600px){.rs .tbtn{width:4rem;height:3.5rem}}
@media (max-width: 600px){.rs .tbtn svg{width:1.625rem;height:1.625rem}}
@media (max-width: 600px){.rs .stamp{min-width:0}}
/* The phone fills its column with the frame.  It is handed NOAA's 1200-px
   source rather than the desktop's 600, so this is a DOWNSCALE -- the phone
   page this replaced upscaled a 600-px source 1.75x out of a 4-to-5 MB GIF. */
@media (max-width: 600px){.rs .plate img{width:100%}}

/* PHONE TEXT SIZES MEASURED ON AN IPHONE.  Under the old 1124 pin, iOS
   Safari enlarged this family's small text on its own; these are the
   sizes it showed (screen px at 402 wide, as rem of the scaled frame),
   so the device-width page reads the same.  Rules Safari left alone are
   not restated. */
@media (max-width: 600px){.rs.rs .rail p{font-size:min(calc(2.291rem + var(--ls-step)), max(2.291rem, var(--ls-cap)))}}
@media (max-width: 600px){.rs.rs .cfoot{font-size:min(calc(1.788rem + var(--ls-step)), max(1.788rem, var(--ls-cap)))}}
@media (max-width: 600px){.rs.rs .railh{font-size:min(calc(1.824rem + var(--ls-step)), max(1.824rem, var(--ls-cap)))}}
@media (max-width: 600px){.rs.rs .glabel{font-size:min(calc(1.608rem + var(--ls-step)), max(1.608rem, var(--ls-cap)))}}
@media (max-width: 600px){.rs.rs .eyebrow{font-size:min(calc(1.828rem + var(--ls-step)), max(1.828rem, var(--ls-cap)))}}
@media (max-width: 600px){.rs.rs .caption{font-size:min(calc(1.688rem + var(--ls-step)), max(1.688rem, var(--ls-cap)))}}
@media (max-width: 600px){.rs.rs .meta dd{font-size:min(calc(1.852rem + var(--ls-step)), max(1.852rem, var(--ls-cap)))}}
@media (max-width: 600px){.rs.rs .railh--pill{font-size:min(calc(1.499rem + var(--ls-step)), max(1.499rem, var(--ls-cap)))}}
