/* ==========================================================================
   Willamette Valley Weather - site stylesheet

   The visual target is the old iowastorms.com layout: a white masthead, a
   full-width stack of colour-coded alert bars directly under it, a pale-blue
   page field, a narrow left navigation rail of boxed link lists, and white
   content panels with thin borders. Everything below is written to reproduce
   that structure while still behaving on a phone, which the 2007 original
   did not have to do.

   Colour rules for alert bars live in the markup, not here: each bar's
   background comes from the alert_types table so the admin can change any
   of the 110+ NWS event colours without touching CSS.
   ========================================================================== */

:root {
    --page-bg:      #dbe6f0;
    --panel-bg:     #ffffff;
    --panel-border: #b9c9d8;
    --rail-bg:      #c9d9e8;
    --ink:          #16222d;
    --ink-soft:     #4a5b68;
    --link:         #6b2d8c;
    --link-hover:   #a3238e;
    --accent:       #123a6b;
    --accent-2:     #1d6fb8;
    --ok:           #1a7f42;
    --warn:         #a8690a;
    --bad:          #b3261e;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    background: var(--page-bg);
    color: var(--ink);
    font: 14px/1.45 Arial, Helvetica, "Liberation Sans", sans-serif;
}

a { color: var(--link); text-decoration: none; }
a:hover { color: var(--link-hover); text-decoration: underline; }

img { border: 0; max-width: 100%; }

.wrap { max-width: 1180px; margin: 0 auto; padding: 0 10px 30px; }

/* ---------- Masthead ---------------------------------------------------- */

.masthead {
    background: #fff;
    border: 1px solid #9aa8b4;
    margin: 10px 0 0;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 10px 14px;
}
.masthead .mh-logo img { height: 74px; width: auto; display: block; }
.masthead .mh-title { flex: 1; text-align: center; }
.masthead .mh-title h1 {
    margin: 0;
    font: 700 30px/1.1 Georgia, "Times New Roman", serif;
    letter-spacing: .5px;
    color: #1a2430;
    text-transform: uppercase;
}
.masthead .mh-title .mh-tag {
    margin: 4px 0 0;
    font-size: 12px;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 1.5px;
}
.masthead .mh-badge {
    background: #0b1a2a;
    color: #fff;
    font: 700 11px/1.2 Arial, sans-serif;
    text-align: center;
    padding: 8px 14px;
    border-radius: 4px;
    text-transform: uppercase;
    letter-spacing: .5px;
    white-space: nowrap;
}
.masthead .mh-badge-channel { display: block; color: #7fd0ff; font-size: 16px; }

/* Link to the live broadcast stream (watch.gmrsweather.com), opened in the
   shared on-page modal (see .modal-channel below) by the delegated
   .js-channel-watch click handler in site.js - a real popup window can't be
   made to hide its own address bar in any modern browser, so this stays
   on-page instead. text-decoration:none overrides the browser default
   underline a bare <a> would otherwise get, now that this badge is a link
   instead of a plain div. Flex row (not the plain text-align:center every
   other .mh-badge uses) so the dot sits to the left of the copy block and
   is centered on the badge's full height, not just its own first line -
   padding widens automatically with the extra "Click to Watch" line below. */
.masthead .mh-badge-live {
    display: flex;
    align-items: center;
    gap: 8px;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
}
.masthead .mh-badge-live:hover { background: #14283f; }
.masthead .mh-badge-copy { display: flex; flex-direction: column; align-items: center; }
.masthead .mh-badge-dot {
    flex: 0 0 auto;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #ff3b30;
    box-shadow: 0 0 4px #ff3b30;
    animation: mh-badge-blink 1.4s ease-in-out infinite;
}
.masthead .mh-badge-cta {
    display: block;
    margin-top: 3px;
    color: #ffcf4d;
    font-size: 10px;
    letter-spacing: .5px;
}
@keyframes mh-badge-blink {
    0%, 100% { opacity: 1; box-shadow: 0 0 6px 1px #ff3b30; }
    50% { opacity: .35; box-shadow: 0 0 2px #ff3b30; }
}

.hero {
    margin: 8px 0 0;
    border: 1px solid #9aa8b4;
    background: #fff;
    overflow: hidden;
}
.hero img { display: block; width: 100%; height: auto; }

/* ---------- Alert bars -------------------------------------------------- */

.alertbars { margin: 10px 0 0; }

/* Bar geometry follows the gmrsweather.com warning bars exactly: a coloured
   label block on the left, a white body to its right, the whole thing one
   flat row with no gap between the two halves. The original is a fixed
   800px table with a 275px label cell and a 518px body cell; those
   proportions are kept as a ratio here so the bar fills whatever width it's
   given and collapses to stacked blocks on a phone, which the 2007 table
   could not do. Typography matches too: Verdana bold for the label,
   Arial for the sentence. */
.alertbar {
    display: flex;
    align-items: stretch;
    margin: 0 0 3px;
    border: 1px solid var(--bar-color, #999);
    background: #fff;
    cursor: pointer;
    width: 100%;
    padding: 0;
    text-align: left;
    font: inherit;
    color: inherit;
}
.alertbar:hover { box-shadow: 0 0 0 2px rgba(0, 0, 0, .18); }
.alertbar:focus-visible { outline: 3px solid #0b62b8; outline-offset: 1px; }

.alertbar .ab-label {
    background: var(--bar-color, #999);
    color: var(--bar-text, #000);
    font: bold 13px/1.2 Verdana, Geneva, Arial, sans-serif;
    text-transform: uppercase;
    padding: 7px 10px;
    flex: 0 0 34.4%;             /* 275 / 800, as in the original table */
    min-width: 250px;
    display: flex;
    align-items: center;
    gap: 8px;
}
.alertbar .ab-body {
    flex: 1;
    background: #fff;
    padding: 7px 12px;
    display: flex;
    align-items: center;
    gap: 12px;
    font: 11px/1.4 Arial, Helvetica, sans-serif;
    color: #123a6b;
}
.alertbar .ab-body .ab-sentence { text-decoration: underline; }
.alertbar .ab-count {
    display: inline-block;
    background: #123a6b;
    color: #fff;
    border-radius: 10px;
    padding: 1px 8px;
    font: 700 11px/1.6 Arial, sans-serif;
    margin-left: 8px;
}
.alertbar.is-test .ab-label::after {
    content: "TEST";
    margin-left: 10px;
    background: #000;
    color: #ff0;
    padding: 1px 6px;
    font-size: 10px;
    border-radius: 3px;
}

.alerts-none {
    background: #fff;
    border: 2px solid #7ea86a;
    padding: 10px 14px;
    margin-bottom: 4px;
    color: #2c5c1e;
    font-weight: 700;
}

/* Scrolling summary line, the way a cable weather crawl reads. */
.crawl {
    background: #0b1a2a;
    color: #fff;
    overflow: hidden;
    white-space: nowrap;
    margin: 6px 0 0;
    border: 1px solid #061220;
}
.crawl > span {
    display: inline-block;
    padding: 6px 0;
    padding-left: 100%;
    animation: crawl 40s linear infinite;
    font-weight: 700;
    font-size: 13px;
    letter-spacing: .3px;
}
.crawl:hover > span { animation-play-state: paused; }
@keyframes crawl { to { transform: translateX(-100%); } }

/* ---------- Page grid --------------------------------------------------- */

.layout {
    display: grid;
    grid-template-columns: 210px 1fr;
    gap: 12px;
    margin-top: 12px;
    align-items: start;
}

/* ---------- Left rail --------------------------------------------------- */

.rail { background: var(--rail-bg); padding: 8px; }
.rail h2 {
    margin: 10px 0 6px;
    padding: 6px 8px;
    background: var(--accent);
    font: 700 16px/1.2 Georgia, serif;
    color: #f3f8fc;
    text-shadow: 0 1px 0 #6f8ba5;
    text-align: center;
}
.rail-box {
    background: #fff;
    border: 1px solid var(--panel-border);
    padding: 8px 10px;
    margin: 0 0 10px;
}
.rail-box h3 {
    margin: 6px 0 4px;
    font: 700 13px/1.3 Arial, sans-serif;
    color: #16222d;
}
.rail-box ul { list-style: none; margin: 0 0 8px; padding: 0; }
.rail-box li { padding: 1px 0; }
.rail-box li::before { content: "\00b7"; color: #7a8a97; margin-right: 5px; }
.rail-box a { font-size: 13px; }

.zulu { width: 100%; border-collapse: collapse; font-size: 12px; }
.zulu td { padding: 2px 4px; border-bottom: 1px solid #e6edf3; }
.zulu td:last-child { text-align: right; font-family: "DejaVu Sans Mono", Consolas, monospace; }

/* ---------- Panels ------------------------------------------------------ */

.panel {
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    padding: 14px 16px;
    margin: 0 0 12px;
}
.panel > h2 {
    margin: 0 0 10px;
    font: 700 20px/1.2 Arial, Helvetica, sans-serif;
    color: #10202e;
}
.panel > h3 { margin: 14px 0 8px; font-size: 15px; }
.panel-tight { padding: 10px 12px; }

.muted { color: var(--ink-soft); }
.small { font-size: 12px; }
.right { text-align: right; }
.center { text-align: center; }
.nowrap { white-space: nowrap; }

/* ---------- Current conditions rotator ---------------------------------- */

.cond {
    display: grid;
    grid-template-columns: minmax(180px, 240px) 1fr;
    gap: 16px;
    align-items: start;
}
/* Same light-blue treatment as the forecast strip cards (see .fcast .fc)
   and the same reason: the condition icons are white/near-white shapes on a
   transparent background and need real contrast under them, not off-white.
   .cond-now-night is a shade darker, same day/night distinction as
   .fcast .fc-night, set from is_daytime in inc/weather.php's
   city_conditions(). */
.cond-now { border: 2px solid #b3261e; background: #8ec5ea; padding: 10px; text-align: center; }
.cond-now.cond-now-night { background: #6fa8d4; }
.cond-now img { width: 64px; height: 64px; }
.cond-temp { font: 700 40px/1 Arial, sans-serif; color: #0d2b4a; }
.cond-temp small { display: block; font: 400 16px/1.4 Arial, sans-serif; color: #2c4a63; }
.cond-text { font-weight: 700; margin-top: 4px; color: #0d2b4a; }

.cond-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 18px; }
.cond-grid dl { display: grid; grid-template-columns: 1fr auto; margin: 0; }
.cond-grid dt, .cond-grid dd {
    margin: 0;
    padding: 4px 8px;
    border-bottom: 1px solid #eef2f6;
    font-size: 13px;
}
.cond-grid dt { color: var(--ink-soft); background: #fbfdff; }
.cond-grid dd { text-align: right; font-weight: 700; }

.cond-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.cond-city { font: 700 20px/1.2 Arial, sans-serif; }
.cond-obs { font-size: 12px; color: var(--ink-soft); }

.rotator-dots { display: flex; gap: 6px; justify-content: center; margin-top: 10px; flex-wrap: wrap; }
.rotator-dots button {
    border: 1px solid #9db2c4;
    background: #fff;
    color: var(--ink-soft);
    font-size: 11px;
    padding: 2px 9px;
    border-radius: 10px;
    cursor: pointer;
}
.rotator-dots button.active { background: var(--accent-2); border-color: var(--accent-2); color: #fff; font-weight: 700; }

/* ---------- Forecast strip ---------------------------------------------- */

/* Day periods on top, night periods below, same calendar day sharing a
   column - see forecast_columns() in inc/weather.php. grid-auto-flow:column
   fills each generated column top-to-bottom (day, then night) before
   starting the next, so the two rows stay paired without any explicit
   row/column indices in the markup. Columns don't wrap - a day/night pair
   has to stay together - so the strip scrolls horizontally instead once it
   runs out of width, same pattern as most weather sites' forecast rows. */
.fcast {
    display: grid;
    grid-auto-flow: column;
    grid-template-rows: auto auto;
    grid-auto-columns: minmax(100px, 1fr);
    gap: 8px;
    overflow-x: auto;
    padding-bottom: 3px;
}
/* Not off-white: the condition icons (assets/img/wx/*.svg) are white/near-
   white shapes on a transparent background with no card of their own, so
   they need real contrast under them. A light, clearly-saturated blue gives
   that contrast without going all the way to a dark card. */
.fcast .fc {
    border: 1px solid #5a99c9;
    background: #8ec5ea;
    text-align: center;
    padding: 8px 6px;
}
/* The one column that can lead with a night period and no day above it
   ("Tonight", when there's no "Today" left to show) - an empty cell here is
   what shifts every later column one slot to the right instead of forcing
   Tonight to share a column with the next day. */
.fcast .fc-empty { border: none; background: none; }
.fcast .fc-night { background: #6fa8d4; }
.fcast .fc-name { font-weight: 700; font-size: 12px; min-height: 2.4em; color: #0d2b4a; }
.fcast .fc img { width: 44px; height: 44px; margin: 4px auto; display: block; }
.fcast .fc-short { font-size: 11px; color: #2c4a63; min-height: 3em; }
.fcast .fc-temp { font-weight: 700; font-size: 15px; }
.fcast .fc-temp.hi { color: #b3261e; }
.fcast .fc-temp.lo { color: #0d3868; }
.fcast .fc-pop { font-size: 11px; color: #0e5a7a; }

.city-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.city-tabs button {
    border: 1px solid #9db2c4;
    background: #eef4f9;
    color: #16222d;
    padding: 5px 11px;
    border-radius: 14px;
    font-size: 12px;
    font-weight: 700;
    cursor: pointer;
}
.city-tabs button.active { background: var(--accent-2); border-color: var(--accent-2); color: #fff; }

/* ---------- Imagery ----------------------------------------------------- */

.imgrow { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* Radar & satellite page: every map full width, one per row. */
.imgrow.imgrow-single { grid-template-columns: 1fr; }
.imgcard { border: 1px solid var(--panel-border); background: #fff; }
.imgcard .ic-head {
    background: #123a6b;
    color: #fff;
    font: 700 13px/1.3 Arial, sans-serif;
    padding: 6px 10px;
    display: flex;
    justify-content: space-between;
    gap: 8px;
}
.imgcard .ic-head a { color: #b9dcff; font-weight: 400; font-size: 12px; }
.imgcard img { display: block; width: 100%; height: auto; background: #eef2f6; }

/* ---------- Warning map ------------------------------------------------- */

.mapwrap { display: grid; grid-template-columns: 1fr 260px; gap: 14px; align-items: start; max-width: 520px; }
.mapbox { border: 1px solid var(--panel-border); background: #f7fafd; padding: 6px; }
.mapbox svg { display: block; width: 100%; height: auto; }
/* Base geography. Forecast and fire zones are real, named places with their
   own boundaries - not a subdivision of a county - so alerts against them
   still shade and label normally (see the alert layer below). But the base,
   nothing-going-on map reads as one thing: county outlines only. Zone shapes
   stay in the DOM (the alert layer is built from these same elements) but
   draw nothing until they're actually under an alert. */
.mapbox .area, .mapbox .county { stroke: #33475a; stroke-width: 1.1; cursor: default; transition: filter .12s; }
.mapbox .layer-county .area { stroke: #33475a; stroke-width: 1.3; }
.mapbox .layer-fire .area { stroke: #b08050; stroke-width: .9; stroke-dasharray: 3 2; }
.mapbox .layer-forecast .area, .mapbox .layer-fire .area { fill: none; stroke: none; }
.mapbox .area.has-alert, .mapbox .county.has-alert { cursor: pointer; }
.mapbox .area.has-alert:hover, .mapbox .county.has-alert:hover { filter: brightness(1.12); }
.mapbox .area.has-alert:focus-visible { outline: 2px solid #0b62b8; }
/* The alert layer is appended last so severity decides what sits on top.
   No stroke - an alert is just its coloured area, no border. */
.mapbox .alert-layer .area { stroke: none; }
/* County-outline overlay (templates/warningmap.php) - a second, stroke-only
   pass of every county shape, appended right after the alert layer so
   county lines stay visible even where a solid alert fill would otherwise
   bury them. Deliberately not the same rule as .layer-county .area above
   (thinner, slate grey) - this one is the thick, unmissable black boundary
   that's meant to read on top of everything, alert or no alert. No fill, no
   pointer-events - it's a line, not a click target; the real county shape
   underneath still handles clicks and the hover tooltip. */
.mapbox .county-outline-layer .county-outline { fill: none; stroke: #000; stroke-width: 2; stroke-linejoin: round; pointer-events: none; }
.mapbox .state-line, .mapbox .coast-line { fill: none; stroke: #0b3a6b; stroke-linecap: round; }
.mapbox .state-line { stroke-width: 4; }
.mapbox .coast-line { stroke-width: 2.2; }
/* City anchor points - a curated few (Admin > Cities > "Map"), not every
   rotation/forecast city. White-halo technique (paint-order/stroke below)
   so a dot and its name stay legible sitting on a filled alert shape -
   which is exactly why the renderer paints this group last, after the
   alert and county-outline layers. Size (font-size, both stroke-widths) is
   set per-element by the generator from Admin > Settings > Imagery > "City
   label size" / "City dot size", not fixed here - this rule only covers
   what doesn't change with size. */
.mapbox .city-dot { fill: #14202b; stroke: #fff; }
.mapbox .city-label {
    font-weight: 700;
    font-family: Arial, sans-serif;
    line-height: 1;
    fill: #14202b;
    pointer-events: none;
    text-anchor: start;
    paint-order: stroke;
    stroke: rgba(255, 255, 255, .85);
}

/* Legend rows follow the National Weather Service's own warning-map legend,
   which is also what gmrsweather.com's *map.php fragments render: a 17x10px
   swatch with a 1px black border, then the event name in small Arial, rows
   packed tight with no separators. Restrained on purpose - the legend sits
   next to the map and should read as part of it, not as a second UI. */
.legend { border: 1px solid var(--panel-border); background: #fff; }
.legend h3 { margin: 0; background: #123a6b; color: #fff; font: 700 12px/1.3 Arial, sans-serif; padding: 5px 9px; }
.legend ul { list-style: none; margin: 0; padding: 6px 8px; }
.legend li { margin: 0; }
.legend button {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    border: 0;
    background: transparent;
    padding: 2px 2px;
    cursor: pointer;
    font: 11px/1.45 Arial, Helvetica, sans-serif;
    text-align: left;
    color: #000;
}
.legend button:hover { background: #eef4fa; }
.legend button:hover .lg-name { text-decoration: underline; }
.legend .swatch {
    width: 17px;
    height: 10px;
    border: 1px solid #000;
    flex: 0 0 auto;
}
.legend .lg-count { margin-left: auto; color: var(--ink-soft); font-size: 10px; white-space: nowrap; }
.legend .legend-empty { padding: 8px; color: var(--ink-soft); font: 11px/1.4 Arial, sans-serif; }
.legend .legend-note { padding: 6px 9px; margin: 0; border-top: 1px solid #e6edf3; }

/* ---------- Modal ------------------------------------------------------- */

.modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(8, 18, 28, .6);
    display: none;
    z-index: 900;
    padding: 20px;
    overflow-y: auto;
}
.modal-backdrop.open { display: block; }
.modal {
    background: #fff;
    max-width: 860px;
    margin: 0 auto;
    border: 1px solid #0b1a2a;
    box-shadow: 0 12px 40px rgba(0, 0, 0, .4);
}
.modal-head {
    background: var(--modal-color, #123a6b);
    color: var(--modal-text, #fff);
    padding: 10px 14px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}
.modal-head h2 { margin: 0; font: 700 18px/1.2 Arial, sans-serif; text-transform: uppercase; }
.modal-head button {
    background: rgba(0, 0, 0, .25);
    border: 0;
    color: inherit;
    font: 700 20px/1 Arial, sans-serif;
    width: 30px;
    height: 30px;
    cursor: pointer;
    border-radius: 4px;
}
.modal-body { padding: 14px 16px; max-height: 70vh; overflow-y: auto; }
/* "Larger View" image lightbox (radar & satellite page): nearly the full viewport. */
.modal.modal-wide { max-width: 1800px; }
.modal.modal-wide .modal-body { padding: 0; max-height: calc(100vh - 100px); }
.modal.modal-wide .modal-body img { width: 100% !important; max-width: none !important; }

/* "24/7 Weather Channel" badge -> live stream, embedded on-page (see the
   .js-channel-watch handler in site.js for why this is a modal rather than
   a popup window). The whole popup - not just the video - is held to 16:9
   (.modal-channel-box, toggled by Modal.show()'s opts.channel in site.js):
   the header bar with the title and the X carves its own fixed height out
   of the top of that rectangle, and the video fills whatever's left under
   it. That's deliberately different from a plain aspect-ratio iframe, which
   would make the VIDEO 16:9 but the header's height on top of it would
   leave the dialog as a whole taller and narrower than a 16:9 box actually
   reads as. The fullscreen button overlays its top-right corner rather
   than sitting in the ordinary document flow, so it's reachable without
   the stream itself shifting position under it. .modal-channel itself (not
   the iframe) is what actually goes fullscreen - see the :fullscreen rule
   and site.js' own comment for why. */
.modal.modal-channel-box {
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: 1280px;
    aspect-ratio: 16 / 9;
    max-height: calc(100vh - 40px);
}
.modal.modal-channel-box .modal-head { flex: 0 0 auto; }
.modal.modal-channel-box .modal-body {
    flex: 1 1 auto;
    min-height: 0;
    padding: 0;
    max-height: none;
}
.modal-channel { position: relative; }
.modal.modal-channel-box .modal-channel { height: 100%; }
.modal-channel iframe {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
    background: #000;
}
.modal-channel-fullscreen {
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 2;
    background: rgba(11, 26, 42, .85);
    color: #fff;
    border: 0;
    padding: 7px 14px;
    border-radius: 4px;
    font: 700 11px/1.2 Arial, sans-serif;
    text-transform: uppercase;
    letter-spacing: .5px;
    cursor: pointer;
    opacity: 1;
    transition: opacity .4s ease;
}
.modal-channel-fullscreen:hover { background: #0b1a2a; }
/* The few-seconds-of-no-movement fade (site.js' channelIdleArm()) - applied
   any time the channel modal is open, windowed or fullscreen alike.
   pointer-events:none while faded so the invisible button can't swallow a
   click (or, more to the point, the mousemove the hover-catcher below it
   needs to bring it back). */
.modal-channel-fullscreen.is-idle { opacity: 0; pointer-events: none; }

/* Mousemove/touch catcher for the fade-timer above - see its own comment in
   site.js. Fullscreen ONLY: the channel is a real page with its own
   interactive UI (its sound-permission prompt on load, at least), so an
   always-on overlay over the iframe swallows clicks meant for it - which is
   exactly what made that prompt unclickable and broke the channel's audio
   entirely. Fullscreen is the one case worth the tradeoff anyway, since
   there's no surrounding chrome left to generate a mousemove from once the
   iframe fills the whole screen. Invisible and inert outside fullscreen -
   its SIZE still needs to be zero there too, not just display:none as a
   formality. */
.modal-channel-hover-catcher { display: none; }

/* Fullscreen mode: .modal-channel is the fullscreened element itself (see
   site.js), so IT needs the full-viewport sizing, not the iframe - the
   modal-backdrop/.modal wrapper isn't part of the fullscreen element and
   its max-height/aspect-ratio would otherwise still cap things. */
.modal-channel:fullscreen {
    width: 100vw;
    height: 100vh;
    background: #000;
    display: flex;
    align-items: center;
}
.modal-channel:fullscreen iframe { max-height: none; aspect-ratio: auto; width: 100%; height: 100%; }
.modal-channel:fullscreen .modal-channel-hover-catcher {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 1;
}
.modal-body .alert-item { border: 1px solid #d8e1e9; margin: 0 0 14px; }
.modal-body .alert-item > h3 {
    margin: 0;
    background: #eef4fa;
    border-bottom: 1px solid #d8e1e9;
    padding: 8px 12px;
    font: 700 14px/1.3 Arial, sans-serif;
}
/* Not scoped under .modal-body - these render identically whether the alert
   card is inside the modal (api/alerts.php) or standalone on warnings.php,
   which reads the exact same normalised alert list. */
/* Keep separately issued alerts distinct, especially consecutive-night products. */
.alert-item .ai-timing { padding: 12px; background: #f0f5f2; border-bottom: 1px solid #d8e1e9; border-left: 4px solid #45775b; color: #244b35; font-size: 13px; line-height: 1.6; }
.alert-item .ai-timing-upcoming { background: #eaf3fc; border-left-color: #2567a3; color: #153a61; }
.alert-item .ai-event { margin: 0 0 4px; font-size: 16px; color: inherit; }
.alert-item .ai-timing-status { display: block; font-size: 15px; margin-bottom: 3px; }
.alert-item .ai-text { padding: 10px 12px; font-size: 13px; }
.alert-item .ai-text p { margin: 0 0 9px; }
.alert-item .ai-instruction {
    background: #fff7e6;
    border-top: 1px solid #f0dfb8;
    padding: 10px 12px;
    font-size: 13px;
}
.ai-counties {
    display: grid;
    grid-template-columns: minmax(140px, 260px) 1fr;
    gap: 12px;
    align-items: start;
    padding: 8px 12px;
    font-size: 12px;
    background: #f4f8fb;
    border-bottom: 1px solid #eef2f6;
}
.ai-counties-list { min-width: 0; }
.ai-until { margin-bottom: 6px; font-size: 12px; }
.ai-until b { color: var(--ink-soft); font-weight: 700; }
.ai-counties span {
    display: inline-block;
    background: #fff;
    border: 1px solid #c3d3e0;
    border-radius: 3px;
    padding: 1px 7px;
    margin: 2px 3px 0 0;
}
/* Fills the left grid column at whatever width that column resolves to. */
.ai-map-col { min-width: 0; }
/* The per-alert Leaflet map (initLeafletMaps() in assets/js/site.js,
   geometry from api/alert_geometry.php) - real political boundaries via OSM
   tiles, the alert's own shape drawn in its own colour (data-color) on top.
   Height follows via aspect-ratio so it's always square. Dragging and
   scroll-zoom are disabled (site.js), so the buttons below in
   .ai-leaflet-zoom are the only way to zoom it - deliberately kept off the
   map itself since it's already small. */
.ai-leaflet {
    width: 100%;
    aspect-ratio: 1;
    background: #eef2f6;
    border: 1px solid #c3d3e0;
    border-radius: 3px;
}
.ai-leaflet .leaflet-control-container { display: none; }
.ai-leaflet-zoom {
    display: flex;
    gap: 6px;
    margin-top: 6px;
}
.ai-leaflet-zoom .btn { flex: 1; text-align: center; }
@media (max-width: 480px) {
    .ai-counties { grid-template-columns: 1fr; }
}
.modal-loading { padding: 30px; text-align: center; color: var(--ink-soft); }

/* On/off switch for admin settings that submit immediately on change (see
   admin/ipaws.php) - the input stays a real checkbox for label/keyboard
   semantics, just visually replaced by .switch-track. */
.switch-row {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 14px;
    cursor: pointer;
}
.switch {
    position: relative;
    display: inline-block;
    width: 40px;
    height: 22px;
    flex: none;
}
.switch input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}
.switch-track {
    position: absolute;
    inset: 0;
    background: #b9c9d8;
    border-radius: 22px;
    transition: background .15s;
}
.switch-track::before {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 18px;
    height: 18px;
    background: #fff;
    border-radius: 50%;
    box-shadow: 0 1px 2px rgba(0,0,0,.35);
    transition: transform .15s;
}
.switch input:checked + .switch-track { background: var(--ok); }
.switch input:checked + .switch-track::before { transform: translateX(18px); }
.switch input:focus-visible + .switch-track { outline: 2px solid var(--accent-2); outline-offset: 2px; }

/* ---------- Forms / buttons --------------------------------------------- */

.btn, button.btn, a.btn {
    display: inline-block;
    background: var(--accent-2);
    border: 1px solid #14588f;
    color: #fff;
    padding: 7px 15px;
    font: 700 13px/1.3 Arial, sans-serif;
    border-radius: 3px;
    cursor: pointer;
    text-decoration: none;
}
.btn:hover { background: #1a63a4; color: #fff; text-decoration: none; }
.btn.secondary { background: #eef4f9; color: #16222d; border-color: #9db2c4; }
.btn.danger { background: #b3261e; border-color: #8d1c16; }
.btn.small { padding: 4px 10px; font-size: 12px; }

label.field { display: block; margin: 0 0 12px; font-weight: 700; font-size: 13px; }
label.field span.hint { display: block; font-weight: 400; color: var(--ink-soft); font-size: 12px; margin-top: 2px; }
input[type=text], input[type=email], input[type=password], input[type=number], input[type=url], select, textarea {
    display: block;
    width: 100%;
    padding: 8px;
    margin-top: 4px;
    border: 1px solid #9db2c4;
    border-radius: 3px;
    font: 14px/1.4 Arial, sans-serif;
    background: #fff;
    color: var(--ink);
}
textarea { min-height: 130px; resize: vertical; }
input[type=color] { width: 46px; height: 30px; padding: 2px; border: 1px solid #9db2c4; }
.inline-form { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; }
.inline-form label.field { margin-bottom: 0; }

/* Forum WYSIWYG editor (assets/vendor/suneditor/, initForumEditor() in
   assets/js/site.js) - restyled to match this app's plain inputs (same
   border colour/radius/font as textarea above) rather than looking like a
   dropped-in widget. Was Quill's "snow" theme (.ql-*) through 2026-10-08;
   see initForumEditor()'s own comment for why it was dropped. */
.forum-editor { margin-top: 4px; }
.forum-editor .sun-editor {
    border: 1px solid #9db2c4;
    border-radius: 3px;
    font-family: Arial, sans-serif;
}
.forum-editor .se-toolbar {
    border-bottom: 1px solid #9db2c4;
    background: #f4f8fb;
}
.forum-editor .se-wrapper-wysiwyg {
    font: 14px/1.4 Arial, sans-serif;
    min-height: 130px;
}
.forum-editor .se-wrapper-wysiwyg img { max-width: 100%; border-radius: 3px; }

/* A stored post's own images (not the editor - see forum_body_html() in
   inc/forums.php) - same overflow guard, this app has no server-side resize
   step so a phone photo comes through at its full original width. */
.p-body img { max-width: 100%; border-radius: 3px; }

fieldset { border: 1px solid var(--panel-border); padding: 12px 14px; margin: 0 0 14px; }
legend { font-weight: 700; padding: 0 6px; }

table.grid { width: 100%; border-collapse: collapse; font-size: 13px; }
table.grid th, table.grid td { border: 1px solid #dde5ec; padding: 6px 8px; text-align: left; vertical-align: middle; }
table.grid th { background: #eef4fa; font-weight: 700; }
table.grid tr:nth-child(even) td { background: #fafcfe; }
table.grid .num { text-align: right; }

table.sortable th[data-sort] { cursor: pointer; user-select: none; }
table.sortable th[data-sort]:hover { background: #e0ecf7; }
table.sortable th.sort-asc::after  { content: '\25B2'; margin-left: 5px; font-size: 9px; }
table.sortable th.sort-desc::after { content: '\25BC'; margin-left: 5px; font-size: 9px; }

/* ---------- Earthquakes -------------------------------------------------- */

.quake-map { width: 100%; height: 420px; border: 1px solid #dbe4ec; border-radius: 3px; margin-bottom: 14px; }
.quake-legend { display: flex; gap: 18px; align-items: center; font-size: 12px; margin-bottom: 10px; }
.quake-dot { display: inline-block; width: 11px; height: 11px; border-radius: 50%; margin-right: 5px; vertical-align: middle; }
.quake-dot-low  { background: #e2a53a; }
.quake-dot-high { background: #c0392b; }
tr.quake-high td { font-weight: 700; }

.flash { padding: 10px 14px; margin: 0 0 12px; border: 1px solid; font-weight: 700; }
.flash.ok   { background: #e9f7ee; border-color: #8fcfa6; color: #14603a; }
.flash.warn { background: #fff6e5; border-color: #e6c483; color: #8a5b00; }
.flash.err  { background: #fdecea; border-color: #e8a49e; color: #93211a; }

.notice-stale { background: #fff6e5; border: 1px solid #e6c483; color: #8a5b00; padding: 7px 12px; font-size: 12px; margin: 0 0 10px; }

/* ---------- Forums / chat ----------------------------------------------- */

.forum-list { width: 100%; border-collapse: collapse; }
.forum-list th { background: #123a6b; color: #fff; text-align: left; padding: 7px 10px; font-size: 13px; }
.forum-list td { border-bottom: 1px solid #e2e9ef; padding: 9px 10px; vertical-align: top; }
.forum-list .f-name { font-weight: 700; font-size: 14px; }
.forum-list .f-desc { color: var(--ink-soft); font-size: 12px; }
.forum-list .f-stats { text-align: center; white-space: nowrap; font-size: 12px; color: var(--ink-soft); }

.post { border: 1px solid #b9c9d8; border-radius: 5px; overflow: hidden; margin: 0 0 18px; display: grid; grid-template-columns: 160px 1fr; }
.post .p-author { background: #eef4fa; padding: 10px; border-right: 1px solid #dbe4ec; font-size: 12px; }
.post .p-author .p-avatar { display: block; width: 64px; height: 64px; object-fit: cover; border-radius: 5px; border: 1px solid #dbe4ec; margin-bottom: 6px; }
.post .p-author .p-name { font-weight: 700; font-size: 14px; display: block; margin-bottom: 3px; }
.post .p-body { padding: 12px 14px; background: #fff9ed; min-width: 0; }
.post .p-head { font-size: 12px; color: var(--ink-soft); border-bottom: 1px solid #e3d8c1; padding-bottom: 7px; margin-bottom: 10px; }
.post .p-sig { margin-top: 12px; padding-top: 8px; border-top: 1px dashed #cfd9e2; color: var(--ink-soft); font-size: 12px; }

.chat-shell { display: grid; grid-template-columns: 1fr 190px; gap: 12px; align-items: start; }
.chat-log {
    height: 460px;
    overflow-y: auto;
    border: 1px solid var(--panel-border);
    background: #fff;
    padding: 8px 10px;
    font-size: 13px;
}
.chat-log .cm { padding: 3px 0; border-bottom: 1px solid #f2f6f9; }
.chat-log .cm .cm-time { color: #8b9aa6; font-size: 11px; margin-right: 6px; font-family: "DejaVu Sans Mono", monospace; }
.chat-log .cm .cm-user { font-weight: 700; color: #123a6b; margin-right: 5px; }
.chat-log .cm.sys { color: var(--ink-soft); font-style: italic; }
.chat-who { border: 1px solid var(--panel-border); background: #fff; }
.chat-who h3 { margin: 0; background: #123a6b; color: #fff; font-size: 13px; padding: 6px 9px; }
.chat-who ul { list-style: none; margin: 0; padding: 6px 9px; font-size: 12px; }
.chat-form { display: flex; gap: 8px; margin-top: 8px; }
.chat-form input { margin-top: 0; }

/* ---------- Footer ------------------------------------------------------ */

.footer {
    margin-top: 16px;
    padding: 12px 4px;
    border-top: 2px solid #9aa8b4;
    font-size: 12px;
    color: var(--ink-soft);
    text-align: center;
}
.footer a { color: #2a5a8c; }

/* ---------- Admin ------------------------------------------------------- */

.admin-nav { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 14px; }
.admin-nav a {
    border: 1px solid #9db2c4;
    background: #eef4f9;
    color: #16222d;
    padding: 7px 13px;
    border-radius: 16px;
    font-size: 13px;
    font-weight: 700;
}
.admin-nav a.active { background: var(--accent-2); border-color: var(--accent-2); color: #fff; }
.admin-nav a:hover { text-decoration: none; background: #dce9f4; }
.admin-nav a.active:hover { background: #1a63a4; }

.pill { display: inline-block; padding: 1px 9px; border-radius: 11px; font-size: 11px; font-weight: 700; }
.pill.on  { background: #dcf3e3; color: #166a34; }
.pill.off { background: #eceff1; color: #5a6b78; }
.pill.hot { background: #fde3e0; color: #93211a; }

.swatch-cell { display: flex; align-items: center; gap: 7px; }
.swatch-cell .sw { width: 34px; height: 18px; border: 1px solid #33475a; }

.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 14px; }
.stat {
    border: 1px solid var(--panel-border);
    background: #fff;
    padding: 10px 12px;
}
.stat .s-n { font: 700 24px/1.1 Arial, sans-serif; color: #10202e; }
.stat .s-l { font-size: 12px; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .5px; }

.filter-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 10px; }
.filter-chips button {
    border: 1px solid #9db2c4; background: #fff; color: #16222d;
    padding: 3px 10px; border-radius: 12px; font-size: 12px; cursor: pointer;
}
.filter-chips button.active { background: #123a6b; border-color: #123a6b; color: #fff; }

/* ---------- Responsive -------------------------------------------------- */

@media (max-width: 900px) {
    .layout { grid-template-columns: 1fr; }
    .rail { order: 2; }
    .mapwrap, .imgrow, .cond, .chat-shell { grid-template-columns: 1fr; }
    .cond-grid { grid-template-columns: 1fr; }
    .masthead { flex-wrap: wrap; justify-content: center; }
    .masthead .mh-title h1 { font-size: 21px; }
    .alertbar { flex-direction: column; }
    .alertbar .ab-label { min-width: 0; }
    .post { grid-template-columns: 1fr; }
    .post .p-author { border-right: 0; border-bottom: 1px solid #dbe4ec; }
}

@media print {
    .rail, .crawl, .admin-nav, .chat-shell { display: none; }
    body { background: #fff; }
}
