/* Weather tab styles. Loaded in the order base, weather, sky, about. */
.place{display:flex;gap:12px;align-items:baseline;flex-wrap:wrap}
.place h2{font-size:clamp(2rem,5vw,3.75rem);letter-spacing:-.065em;margin:0;line-height:.95}
.place p{margin:0;color:var(--muted)}
.hero{display:block;margin-top:22px}
.now{padding:29px;position:relative;overflow:hidden;min-height:260px;display:grid;grid-template-columns:minmax(0,1.35fr) minmax(0,1fr);gap:24px 40px}
.now-main{min-width:0}
.now-summary{min-width:0;display:flex;flex-direction:column;justify-content:space-between;gap:18px}
.now:after{content:"";position:absolute;right:-52px;top:-60px;width:230px;height:230px;border:30px solid rgba(211,166,255,.14);border-radius:50%}
.temp-row{display:flex;align-items:center;gap:21px;margin-top:23px;position:relative;z-index:1}
.temp{font-size:clamp(4rem,9vw,6.8rem);letter-spacing:-.09em;line-height:.78;font-weight:720}
.condition{font-size:1.05rem;font-weight:680;margin-top:11px}
.feel{color:var(--muted);margin-top:6px;font-size:1rem}
.metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:31px;padding-top:20px;border-top:1px solid var(--line)}
.metrics span{display:block;color:var(--muted);font-size:1rem;margin-bottom:5px}
.metrics b{font-size:1rem}
.sunline{height:6px;background:linear-gradient(90deg,#a9c6e8 0 18%,var(--yellow) 42% 68%,#a9c6e8 85%);border-radius:99px;position:relative;margin:19px 2px 10px}
.sunline:after{content:"";position:absolute;top:50%;left:var(--sun,50%);width:12px;height:12px;border-radius:50%;background:#fff1bf;transform:translate(-50%,-50%);box-shadow:0 0 0 4px rgba(255,241,191,.13)}
.sun-times{display:flex;justify-content:space-between;color:var(--muted);font-size:1rem}
.weather-scene{position:relative;isolation:isolate;overflow:hidden}
.weather-scene::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;background-image:var(--scene-image,none);background-size:cover;background-position:center;background-repeat:no-repeat;filter:var(--picture-filter)}
.now.weather-scene::before{background-position:70% center}
.now.weather-scene:after{display:none}
.alert{border-radius:16px;padding:14px 16px;margin-top:18px;display:flex;gap:12px;align-items:flex-start}
#alertTitle,#alertText{overflow-wrap:anywhere}
.alert strong{display:block;font-size:1rem}
.alert p{margin:3px 0 0;font-size:1rem;line-height:1.35}
.alert .dot{width:9px;height:9px;border-radius:50%;margin-top:5px;flex:none}
.alert.state-warning{border:1px solid var(--warn-line);background:var(--warn-bg)}
.alert.state-warning strong{color:var(--warn-title)}
.alert.state-warning p{color:var(--warn-text)}
.alert.state-warning .dot{background:var(--warn-dot)}
.alert.state-loading,.alert.state-none{border:0;background:transparent;padding:14px 0}
.alert.state-loading strong,.alert.state-none strong{color:var(--ink)}
.alert.state-loading p,.alert.state-none p{color:var(--muted)}
.alert.state-loading .dot,.alert.state-none .dot{background:var(--muted)}
.alert.state-unavailable{border:1px dashed var(--quiet-line);background:var(--quiet-bg)}
.alert.state-unavailable strong{color:var(--ink)}
.alert.state-unavailable p{color:var(--muted)}
.alert.state-unavailable .dot{background:var(--muted)}
.hourly{display:block;overflow-x:auto;overflow-y:hidden;scrollbar-width:thin;scrollbar-color:rgba(var(--accent-rgb),.5) transparent}
.hour-track{position:relative;display:flex}
.hour-curve{position:absolute;left:0;top:44px;pointer-events:none}
.hour-curve path{fill:none;stroke:rgba(var(--accent-rgb),.42);stroke-width:3;stroke-linecap:round}
.rain{font-size:1rem;color:var(--rain-ink);margin-top:8px;min-height:16px}
.daily{display:grid;grid-template-columns:repeat(7,1fr);overflow:auto}
.day{padding:18px 13px;text-align:center;min-width:116px;border-right:1px solid var(--line)}
.day:last-child{border:0}
.day strong{font-size:1rem}
.day span{display:block;font-size:1.6rem;margin:13px 0}
.day small{color:var(--muted);font-size:1rem}
.highlow{margin-top:9px;font-size:1rem}
.range{position:relative;height:6px;border-radius:99px;background:var(--track);margin:10px 6px 4px}
.range i{position:absolute;top:0;bottom:0;border-radius:99px;background:linear-gradient(90deg,#7fd0ff,rgba(var(--accent-rgb),1),#ffb27a)}
.day.today{background:rgba(var(--accent-rgb),.14);box-shadow:inset 0 0 0 1px rgba(var(--accent-rgb),.45);border-radius:18px}
.day.today strong{color:var(--ink-strong)}
.day .rain{margin-top:6px}
.highlow em{font-style:normal;color:var(--muted);margin-left:5px}
/* Marcus, 2026-09-23: the 9 Weather details tiles (Feels like, Wind, Humidity, Dew point, Visibility, Pressure,
   UV index, Air quality, Cloud cover) made 4+4+1 rows at 4 columns, leaving Cloud cover alone in its own row. 3
   columns makes exactly 3 full rows; .day-parts (4 parts: morning/afternoon/evening/night, its own rule below)
   is unaffected -- 3 is right for this grid's own tile count, not a general column change. The phone/tablet
   2-column rule below 720px, shared with .day-parts, is untouched. */
.detail-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
/* Marcus, 2026-09-23: when a tile is missing (8 not 9) the last row was 2 tiles wide with a gap where the 9th
   would sit. A 6-track grid with each tile spanning 2 (still 3 per row, same widths as above) lets the last two
   tiles span 3 each -- filling the row evenly -- exactly when they are the row's only two: :nth-last-child(2)
   at a row-start position (3n+1) paired with :last-child at the following position (3n+2). 9 tiles (no orphan
   row) and a single leftover tile are both untouched by this selector on purpose. */
@media(min-width:721px){.detail-grid{grid-template-columns:repeat(6,1fr)}.detail-grid>*{grid-column:span 2}.detail-grid>:nth-last-child(2):nth-child(3n+1),.detail-grid>:last-child:nth-child(3n+2){grid-column:span 3}}
.day-parts{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}.part{padding:14px 16px}.part span{display:block;color:var(--muted);font-size:1rem}.part .icon{display:block;font-size:1.3rem;margin:4px 0}.part b{display:block;font-size:1.4rem;margin:2px 0}.part small{color:var(--muted);font-size:1rem;line-height:1.4}.parts-updated{margin:10px 0 0;color:var(--muted);font-size:1rem;line-height:1.4}
.detail{padding:17px}
.detail.has-photo,.part.has-photo{padding:0}
/* Marcus, 2026-09-23 (via EV Manager): a fixed band height instead of aspect-ratio, so every tile's photo
   band is the same height whatever the tile's own width -- including the full-row tile at 375px and the
   3-track desktop tiles, which are different widths from the ordinary 3-per-row tiles and previously grew or
   shrank the band along with them. 14vw (not 12) so the 170px ceiling is reached by 1280px, not ~1417px --
   deploy-gate's own guard expects every vw-sized clamp() on the page to have already hit its outer bound by
   1280, since several test files rely on that to treat 1920 as a repeat of 1280 rather than measure it too. */
.detail-photo{display:block;width:100%;height:clamp(110px,14vw,170px);overflow:hidden;border-radius:24px 24px 0 0}
.detail-photo img{width:100%;height:100%;object-fit:cover;display:block}
/* Marcus, 2026-09-24: the day-part tiles (Overnight/Tonight/Tomorrow night, etc.) show a bright daytime
   photo (mountain-lake-calm, the spares clouds-sun-breaks/windy-coast) next to a moon icon on a night part
   -- wrong. Darkened here, on the night part's own picture only, using the same `night` boolean
   renderDayParts() already computes for the moon/sun icon next to it (isNightHour against the place's own
   sunrise/sunset), so the picture and the icon can never disagree about whether a given part is night. A
   daytime card using the exact same underlying photo (e.g. mountain-lake-calm also used for Air quality on
   the Weather tab's detail tiles) is untouched -- the class is per-tile, not per-picture. CSS filter, no
   inline style (keeps the CSP script/style hashes unaffected) and no new image file. */
.detail-photo-night img{filter:brightness(.45) saturate(.7) hue-rotate(10deg)}
.detail.has-photo .detail-body{padding:17px}
.part.has-photo .part-body{padding:14px 16px}
.detail span{display:block;color:var(--muted);font-size:1rem;margin-bottom:7px}
.detail b{font-size:1.1rem}
.detail small{display:block;color:var(--ink-sub);font-size:1rem;margin-top:5px}
.detail b a{display:inline-flex;align-items:center;min-height:44px;color:var(--link);text-decoration:underline}
.scale{position:relative;height:6px;border-radius:99px;margin:10px 0 4px;background:linear-gradient(90deg,#5fd08a,#e6d45a 40%,#f5a04c 65%,#e5566b 85%,#a066e0)}
.scale i{position:absolute;top:-4px;width:4px;height:14px;border-radius:2px;background:var(--knob);box-shadow:0 0 0 2px var(--knob-ring);left:calc(var(--pos)*1%);transform:translateX(-50%)}
.meter{height:6px;border-radius:99px;background:var(--track);overflow:hidden;margin:10px 0 4px}
.meter i{display:block;height:100%;width:calc(var(--pos)*1%);background:linear-gradient(90deg,#7fd0ff,rgba(var(--accent-rgb),1))}
/* Pressure: a plain track with one reference tick (standard pressure, 1013.25 mb, in the middle) and a marker; the arrow after the number is the 3 hour change. */
.pressure-bar{position:relative;height:6px;border-radius:99px;margin:10px 0 4px;background:var(--track)}
.pressure-bar::before{content:'';position:absolute;left:50%;top:-2px;width:2px;height:10px;border-radius:1px;background:var(--track-mark);transform:translateX(-50%)}
.pressure-bar i{position:absolute;top:-4px;width:4px;height:14px;border-radius:2px;background:var(--knob);box-shadow:0 0 0 2px var(--knob-ring);left:calc(var(--pos)*1%);transform:translateX(-50%)}
.trend{width:13px;height:13px;margin-left:6px;vertical-align:-1px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}
.wind-arrow{width:20px;height:20px;vertical-align:-3px;margin-right:6px;fill:var(--wind-arrow)}
.astro{display:grid;grid-template-columns:repeat(5,1fr);overflow:auto}
.astro div{padding:17px;min-width:132px;border-right:1px solid var(--line)}
.astro div:last-child{border:0}
.astro span{display:block;color:var(--muted);font-size:1rem;margin-bottom:8px}
.astro b{font-size:1rem}
@media(max-width:720px){.astro{grid-template-columns:repeat(3,minmax(0,1fr));overflow:visible}.astro div{min-width:0;padding:10px 12px;border-right:1px solid var(--line);border-bottom:1px solid var(--line)}.astro div:nth-child(3n){border-right:0}.astro div:nth-last-child(-n+2){border-bottom:0}}
.astro small{display:block;color:var(--ink-sub);font-size:1rem;margin-top:5px}
.sunmoon{overflow:hidden;position:relative;isolation:isolate}
.sm-layer{position:absolute;inset:0;z-index:-1;pointer-events:none}
.sm-layer{filter:var(--picture-filter);background-size:cover;background-position:center bottom;background-repeat:no-repeat;opacity:0;transition:opacity .9s ease}
.sm-layer.on{opacity:1}
@media(prefers-reduced-motion:reduce){.sm-layer{transition:none}}
.sun-arc{padding:16px 18px 4px;text-align:center}
.sun-arc svg{display:block;width:100%;max-width:520px;height:auto;margin:0 auto}
.arc-track{fill:none;stroke:var(--chart-faint);stroke-width:3;stroke-dasharray:2 7;stroke-linecap:round}
.arc-done{fill:none;stroke:rgba(var(--accent-rgb),.95);stroke-width:4;stroke-linecap:round}
.horizon{stroke:var(--chart-horizon);stroke-width:2}
.sun-dot{fill:#fff1bf;stroke:rgba(255,241,191,.3);stroke-width:8}
.arc-text{color:var(--arc-text);font-size:1rem;margin:4px 0 10px}
.sunmoon .astro,.sunmoon .astro div{border:0;background:none;-webkit-backdrop-filter:none;backdrop-filter:none}
.sunmoon .arc-text{display:inline-block;padding:2px 10px}
.moon-icon{width:30px;height:30px;vertical-align:middle;margin-right:8px}
.moon-dark{fill:var(--moon-dark);stroke:var(--chart-horizon);stroke-width:1.5}
.moon-lit{fill:#f4efd2}
/* Marcus, 2026-09-23: on a phone (2 columns), 9 tiles give 2+2+2+2+1, leaving the last tile (Cloud cover) alone
   in its own half-width row. :last-child:nth-child(odd) matches only when the last tile also sits at an odd
   overall position -- true for 9 tiles (9th, orphaned) and false for the rare 8-tile case (8th, already paired,
   left exactly as it is) -- so this needs no tile count read anywhere, it falls out of the two selectors alone.
   Desktop's own rare 8-tile case (3 columns, 3+3+2) is a different media query, untouched. */
@media(max-width:720px){.detail-grid,.day-parts{grid-template-columns:repeat(2,1fr)}.detail-grid>:last-child:nth-child(odd){grid-column:1/-1}}
.data-sources{flex-basis:100%;border-top:1px solid var(--line);padding-top:12px;line-height:1.6}
.data-sources strong{display:block;font-size:1rem;letter-spacing:.02em;margin-bottom:2px}
.data-sources ul{list-style:none;margin:8px 0 0;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px 32px}
.data-sources li{padding-left:12px;border-left:2px solid rgba(var(--accent-rgb),.45);overflow-wrap:anywhere}
@media(max-width:720px){.data-sources ul{grid-template-columns:1fr}}
.data-sources a{text-decoration:underline;display:inline-block;box-sizing:border-box;max-width:100%;padding:15px 10px;margin:-15px -10px}
#footerAboutLink{display:inline-block;margin-top:10px;font-weight:600}
.privacy-note{flex-basis:100%;margin:0;line-height:1.6}
/* Marcus, 2026-09-23: the Windy row and the Install row above this each carry their own margin:14px 0 0
   (.windy-row), so the gap above them is even -- but .pwa-privacy is a plain sibling paragraph, not inside
   a flex container with its own gap (unlike .privacy-note, which sits inside the footer element's flex gap:18px and
   correctly needs margin:0), so it had none of its own and sat flush against whatever was above it. Same
   14px top margin as its siblings restores the even rhythm the screenshot showed missing. */
.pwa-privacy{margin:14px 0 0;line-height:1.6}
/* Marcus, 2026-09-22, live bug: this rule only ever set min-height, so the button had no explicit background or
   border and fell through to the browser's own default button chrome (a light gray/white system color) while
   still inheriting --ink (#f1edff, near-white -- meant for text on this page's own dark background) for its
   text color -- near-white text on a near-white background, contrast ~1.01:1. Matches .windy-row button's own
   pattern now (the sibling Install button right above it in the markup), a real transparent/bordered button on
   this dark page instead of the unstyled default. */
#clearSavedData{min-height:44px;padding:0 12px;color:var(--ink);background:transparent;border:1px solid var(--line);border-radius:8px;font:inherit;font-weight:700}
.place h2,.temp{font-family:Orbitron,"Segoe UI",sans-serif}
.place h2{font-size:clamp(1.75rem,4.4vw,3.2rem);letter-spacing:-.04em;line-height:1.05}
.temp{font-size:clamp(3.2rem,7vw,5.5rem);line-height:.9;font-weight:600}
@media(max-width:720px){.hero{grid-template-columns:1fr}.now{min-height:0;padding:24px}.daily,.hourly{border-radius:20px}.metrics{gap:5px}.metrics b{font-size:1rem}.place h2{font-size:2.35rem}.alert{margin-top:14px}}
#hourly:focus-visible,#daily:focus-visible,#astronomy:focus-visible{outline:2px solid rgb(var(--accent-rgb));outline-offset:2px}
.day small{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;overflow:hidden;line-height:1.3;height:2.6em;overflow-wrap:anywhere}
.now-note{margin-top:4px}
.warn-panel{margin-top:12px;padding:14px 16px;color:#e9e4f7;font-size:1rem;line-height:1.5;overflow-wrap:anywhere}
.warn-panel[hidden]{display:none}
.now .warn-panel{background:rgba(6,8,30,.8)}
@media(max-width:720px){.now{min-height:340px}}
.warn-panel strong{display:block;font-size:1rem}
.warn-panel span,.warn-panel p{display:block;margin:6px 0 0}
.warn-panel a{color:#e9e4f7;text-decoration:underline}
.warn-panel>a{display:inline-block;margin-top:6px}
#astronomy:focus-visible{outline:3px solid var(--focus);outline-offset:-3px}
.detail b{overflow-wrap:anywhere}
/* The conditions tile shows the whole scene centered near the horizon; the summary tile shows a zoomed crop of the lower left, so the two neighbours never look identical. */
.now.weather-scene::before{background-size:cover;background-position:70% 50%}
/* On a phone the tile is taller than 2:1, so cover shows only about 47% of the picture width; the landscape pictures are darkest on the left, so setScene gives THEM --picture-crop (10% 50%, measured with tools/regional-measure.mjs). The other pictures keep 70% 50%: a rule on the tile alone moved every picture on phones (found 2026-09-21 by the sun-bar surroundings check). */
@media (max-width:600px){.now.weather-scene::before{background-position:var(--picture-crop,70% 50%)}}
/* Text panels on the two scene tiles: a dark scrim keeps text at 4.5:1 or better over any scene, day or night. */
.now .temp-row>div:last-child,.now .metrics,.now .warn-panel,#weatherSummary>div{background:rgba(6,8,30,var(--panel-alpha,.8));-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-radius:16px}
.now .temp-row{margin-top:0}
.now .temp-row>div:last-child{padding:10px 12px;width:fit-content;max-width:100%;justify-self:start}
.now .metrics{padding:10px 12px;border-top:0;margin-top:20px;width:min(100%,340px);max-width:100%;gap:14px}
#weatherSummary>div{padding:10px 12px}
#weatherSummary>div:first-child{width:fit-content;max-width:min(100%,560px)}
#weatherSummary>div:last-child{width:min(100%,320px)}
.now,#weatherSummary{text-shadow:var(--ink-shadow);-webkit-text-stroke:3px rgba(0,0,0,.6);paint-order:stroke fill}
.now .temp-row>div:last-child,.now .metrics,#weatherSummary>div{background:none;-webkit-backdrop-filter:none;backdrop-filter:none;border-radius:0}
.now .warn-panel{text-shadow:none;-webkit-text-stroke:0}
#weatherSummary .sunline{box-shadow:0 0 0 2px #000,0 0 2px #000,0 1px 3px rgba(0,0,0,.9),0 0 8px rgba(0,0,0,.6)}
#weatherSummary .sunline:after{box-shadow:0 0 0 2px #000,0 0 2px #000,0 0 6px rgba(0,0,0,.9)}
.sunmoon .sun-arc svg,.sunmoon .moon-icon{filter:var(--ink-glow)}
@media(max-width:900px){.now{min-height:340px;grid-template-columns:1fr}}
.now .feel,.now .metrics span,#weatherSummary p,#weatherSummary .sun-times{color:#e9e4f7}
#weatherSummary h2{margin:0;font-size:1.2rem;letter-spacing:-.02em}
#weatherSummary p{line-height:1.5;margin:16px 0;font-size:1.2rem}
#weatherSummary .sun-times{font-size:1.2rem}
.radar summary{list-style:none;cursor:pointer;display:flex;align-items:center;justify-content:space-between;min-height:44px;margin:34px 0 13px}
.radar summary::-webkit-details-marker{display:none}
.radar summary:focus-visible{outline:2px solid rgb(var(--accent-rgb));outline-offset:2px;border-radius:12px}
.radar-hint{display:inline-flex;align-items:center;min-height:44px;padding:0 18px;border:1px solid var(--hint-line);border-radius:12px;background:var(--hint-bg);color:var(--ink);font-size:1rem;font-weight:700}
.radar-hint:after{content:"Show radar"}.radar[open] .radar-hint:after{content:"Hide radar"}.radar.estimate .radar-hint:after{content:"Show rain estimate"}.radar.estimate[open] .radar-hint:after{content:"Hide rain estimate"}
.radar.clouds .radar-hint:after{content:"Show clouds"}.radar.clouds[open] .radar-hint:after{content:"Hide clouds"}
.radar-card{overflow:hidden;max-width:820px}
/* Marcus, 2026-09-24: the small preview.svg thumbnail is replaced by a full-card photo background (one of 5
   AI-generated 3:1 scenes, chosen by day/night -- see radarPreviewScene() in app.js) -- the dark left side
   every picture ships with is where the text sits, not an overlay this page adds.
   #radarPreviewSource/#radarPreviewImg default to preview.svg in the static HTML (a visible flash of it is
   possible only before the first weather fetch resolves) and are only ever set forward to a real photo once
   weather data (and its sunrise/sunset) is available -- never back to the SVG placeholder.
   PLATFORM's contrast measurement, 2026-09-24: an aspect-ratio:3/1 container (matching the picture's own
   ratio, tried first so object-fit:cover would never need to crop) is a PREFERRED size only -- CSS still lets
   the box grow taller when its own content (the heading/note/button column) needs more room than the
   aspect-ratio gives, which is exactly what happens at 375px on mobile. Once the box is taller than 3:1,
   object-fit:cover's default centred crop removes equal slices from the picture's LEFT and RIGHT edges to
   fill that extra height -- cutting into the dark left side where the text sits, which is the real mechanism
   behind "the scene sliding under the text." object-position:left anchors the crop to the picture's own left
   edge instead, so any crop this box ever needs comes only from the right side, and the dark left side stays
   fully visible at every width -- confirmed by the ink contrast measurement across 375/768/1280/1920. */
.radar-preview{position:relative;overflow:hidden;isolation:isolate;min-height:150px;display:flex;align-items:center;padding:18px 22px;margin-top:20px}
.radar-preview-picture{position:absolute;inset:0;z-index:-1}
.radar-preview-picture img{display:block;width:100%;height:100%;object-fit:cover;object-position:left center;background:#11172c}
/* Manager, 2026-09-23: the preview picture is illustrative, not real data -- but a photo of rain or a radar
   sweep could still read as live radar, so this label states it outright. aria-hidden with the rest of the
   picture: alt="" already treats it as decorative, and the heading plus #rainviewerNote already say what it
   is for anyone using a screen reader; this is a sighted-only reinforcement, not new information. */
/* PLATFORM's HOLD, 2026-09-24: at left:10px the badge sat on top of the heading at 375x1.3 (the wider text
   column at that scale reaches under it). Moved to the picture's own side (right, not left) so it can never
   overlap the text column the heading/note/button occupy -- its own dark background still reads clearly on
   any of the 5 photos, on the picture side or the text side alike. */
.radar-preview-badge{position:absolute;right:10px;left:auto;top:10px;padding:2px 7px;border-radius:6px;background:rgba(6,8,30,.75);color:#e9e4f7;font-size:1rem;font-weight:700;line-height:1.3;z-index:1}
.radar-preview-body{position:relative;z-index:1;display:flex;flex-direction:column;justify-content:center;gap:6px;min-width:0;max-width:56%}
.radar-preview-body h2{margin:0;font-size:1.3rem;letter-spacing:-.02em}
.radar-preview-body p{margin:0;color:var(--muted);font-size:1rem;line-height:1.3}
.radar-preview-body button{align-self:flex-start;min-height:48px;padding:0 22px;border-radius:12px;border:1px solid rgba(var(--accent-rgb),.6);background:rgba(var(--accent-rgb),.18);color:var(--ink);font:inherit;font-weight:700;font-size:1.05rem;cursor:pointer}
.radar-preview-body button:hover{background:rgba(var(--accent-rgb),.28)}
@media(max-width:600px){.radar-preview{padding:12px 14px}.radar-preview-badge{right:6px;left:auto;top:6px;padding:1px 5px;font-size:1rem}.radar-preview-body{max-width:64%;gap:4px}.radar-preview-body h2{font-size:1.05rem}.radar-preview-body button{min-height:44px;padding:0 16px;font-size:1rem}}
.rainviewer-frame{aspect-ratio:4/3;padding:0}.rainviewer-frame iframe{display:block;width:100%;height:100%;border:0}
.radar-picture{position:relative;aspect-ratio:4/3;background:#0b1024}
.radar-picture svg{position:absolute;inset:0;width:100%;height:100%;display:block}
.radar-picture img{position:absolute;inset:0;width:100%;height:100%;display:block;object-fit:cover}
.radar-picture .rb-land{fill:#1b2447}.radar-picture .rb-lakes{fill:#0b1024}.radar-picture .rb-rivers path,.radar-picture .rb-roads path{fill:none;vector-effect:non-scaling-stroke;stroke-width:1}.radar-picture .rb-rivers path{stroke:#39588c}.radar-picture .rb-roads path{stroke:rgba(255,255,255,.22)}
.radar-city{position:absolute;transform:translate(-3px,-50%);white-space:nowrap;pointer-events:none;font-size:.72rem;font-weight:650;line-height:1;color:#fff;text-shadow:var(--ink-shadow);-webkit-text-stroke:3px rgba(0,0,0,.6);paint-order:stroke fill}.radar-city:before{content:"";display:inline-block;width:5px;height:5px;margin-right:4px;border-radius:50%;background:#fff;border:1px solid #000;vertical-align:middle}
.radar-marker{position:absolute;width:12px;height:12px;border-radius:50%;background:#fff;border:2px solid #000;transform:translate(-50%,-50%);box-shadow:0 0 6px #000}
.radar-stamp{position:absolute;left:14px;top:12px;margin:0;font-weight:700;color:#fff;text-shadow:var(--ink-shadow);-webkit-text-stroke:3px rgba(0,0,0,.6);paint-order:stroke fill}
.radar-info{padding:14px 18px 16px;display:grid;gap:10px}
.radar-info p,.radar-note{margin:0;font-size:1rem;color:var(--ink-soft)}
.radar-note{padding:18px}
.radar-controls{display:flex;flex-wrap:wrap;gap:8px}
.radar-controls button{min-height:44px;padding:0 16px;border-radius:12px;border:1px solid var(--line);background:transparent;color:inherit;font:inherit;cursor:pointer}
.radar-controls button[aria-pressed="true"]{background:rgba(var(--accent-rgb),.25);border-color:rgba(var(--accent-rgb),.7)}
.radar-legend{display:flex;flex-wrap:wrap;align-items:center;gap:4px 6px}.radar-legend i{display:inline-block;width:26px;height:10px;border-radius:2px}
.radar-credit{color:var(--muted);font-size:1rem}
.radar-credit a{color:var(--link);text-decoration:underline}
.radar-warn{color:var(--warn-ink)}
.windy-row{display:flex;flex-wrap:wrap;align-items:center;gap:6px 14px;margin:14px 0 0}.windy-row a{display:inline-flex;align-items:center;min-height:44px;padding:0 4px;color:var(--ink);font-weight:700}.windy-row span{color:var(--muted);font-size:1rem;line-height:1.4;flex:1 1 220px}
.windy-row button{min-height:44px;padding:0 12px;color:var(--ink);background:transparent;border:1px solid var(--line);border-radius:8px;font:inherit;font-weight:700}
/* Marcus, 2026-09-23: the install controls (Install Nalssi / Add to Home Screen) moved from a row in the
   weather tab's body into the header, next to the unit toggle -- see base.css for the .install-slot rules
   (header/shared, not weather-tab-owned) and pwa.js for the reserve-then-reveal logic. */
#offlineStatus{color:var(--muted);font-size:1rem;line-height:1.4}
/* Drifting clouds (Phase 1). A layer above the scene picture and below the text of the conditions tile, on the partly cloudy and overcast scenes only (weather.js sets data-clouds). Pure CSS and SVG: no file, no request.
   The layer is twice the tile wide and moves half its width on a slow loop with transform only, so nothing is laid out or painted again on each frame. It pauses when the tab is hidden or the tile is off screen (--cloud-state), and it does not exist when the visitor asks for reduced motion. */
.now.weather-scene[data-clouds]::after{display:block;content:"";position:absolute;top:0;bottom:0;left:0;right:auto;width:200%;height:auto;border:0;border-radius:0;z-index:-1;pointer-events:none;background-repeat:repeat-x;background-size:50% 100%;opacity:.42;animation:cloud-drift 110s linear infinite;animation-play-state:var(--cloud-state,running);will-change:transform}
.now.weather-scene[data-clouds="day"]::after{background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='1200' height='600' viewBox='0 0 1200 600' preserveAspectRatio='none'><defs><filter id='b' x='-30%' y='-60%' width='160%' height='220%'><feGaussianBlur stdDeviation='18'/></filter></defs><g filter='url(%23b)' fill='rgb(255,255,255)'><ellipse cx='250' cy='150' rx='150' ry='40'/><ellipse cx='155' cy='164' rx='90' ry='30'/><ellipse cx='342' cy='160' rx='100' ry='34'/><ellipse cx='220' cy='128' rx='84' ry='30'/><ellipse cx='720' cy='330' rx='188' ry='50'/><ellipse cx='601' cy='348' rx='113' ry='38'/><ellipse cx='835' cy='343' rx='125' ry='43'/><ellipse cx='683' cy='303' rx='105' ry='38'/><ellipse cx='1010' cy='120' rx='105' ry='28'/><ellipse cx='944' cy='130' rx='63' ry='21'/><ellipse cx='1074' cy='127' rx='70' ry='24'/><ellipse cx='989' cy='105' rx='59' ry='21'/><ellipse cx='430' cy='500' rx='120' ry='32'/><ellipse cx='354' cy='511' rx='72' ry='24'/><ellipse cx='504' cy='508' rx='80' ry='27'/><ellipse cx='406' cy='482' rx='67' ry='24'/></g></svg>")}
.now.weather-scene[data-clouds="night"]::after{background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='1200' height='600' viewBox='0 0 1200 600' preserveAspectRatio='none'><defs><filter id='b' x='-30%' y='-60%' width='160%' height='220%'><feGaussianBlur stdDeviation='18'/></filter></defs><g filter='url(%23b)' fill='rgb(176,190,224)'><ellipse cx='250' cy='150' rx='150' ry='40'/><ellipse cx='155' cy='164' rx='90' ry='30'/><ellipse cx='342' cy='160' rx='100' ry='34'/><ellipse cx='220' cy='128' rx='84' ry='30'/><ellipse cx='720' cy='330' rx='188' ry='50'/><ellipse cx='601' cy='348' rx='113' ry='38'/><ellipse cx='835' cy='343' rx='125' ry='43'/><ellipse cx='683' cy='303' rx='105' ry='38'/><ellipse cx='1010' cy='120' rx='105' ry='28'/><ellipse cx='944' cy='130' rx='63' ry='21'/><ellipse cx='1074' cy='127' rx='70' ry='24'/><ellipse cx='989' cy='105' rx='59' ry='21'/><ellipse cx='430' cy='500' rx='120' ry='32'/><ellipse cx='354' cy='511' rx='72' ry='24'/><ellipse cx='504' cy='508' rx='80' ry='27'/><ellipse cx='406' cy='482' rx='67' ry='24'/></g></svg>");opacity:.3}
/* The same drift on the landscape pictures (scene names regional/<type>-partly-cloudy|overcast-day|night), fainter than on the stock pictures (.42 day, .3 night). To make it stronger or fainter change the one number after --land-cloud-opacity. */
.now.weather-scene[data-scene^="regional/"][data-clouds]{--land-cloud-opacity:.2}
.now.weather-scene[data-scene^="regional/"][data-clouds]::after{opacity:var(--land-cloud-opacity)}
@keyframes cloud-drift{from{transform:translate3d(0,0,0)}to{transform:translate3d(-50%,0,0)}}
@media(prefers-reduced-motion:reduce){.now.weather-scene[data-clouds]::after{display:none;animation:none}}
/* Rain in the next 12 hours: a plain card under the conditions tile (no picture behind it, so it keeps its box). Six hours to a row on a phone, twelve in a row from 720px; no sideways scrolling. */
.rain-strip{position:relative;overflow:hidden;padding:14px 16px 16px;margin-top:18px}
.rain-strip-picture{position:absolute;inset:0;z-index:-2}
.rain-strip-picture img{display:block;width:100%;height:100%;object-fit:cover;background:#11172c}
.rain-strip-scrim{position:absolute;inset:0;z-index:-1;background:rgba(6,8,30,.62)}
.rain-strip-body{position:relative;z-index:1}
@media(max-width:720px){.rain-strip{margin-top:14px}}
/* Driving notes card (drivingNotes in daytiles.js): up to two short lines, sized like the rain strip. */
.drive-card{padding:14px 16px;margin-top:18px}
@media(max-width:720px){.drive-card{margin-top:14px}}
.drive-card h2{margin:0;font-size:1rem;display:flex;flex-wrap:wrap;gap:6px 10px;align-items:baseline}
.drive-card h2 small{font-size:1rem;font-weight:600;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:1px 9px}
.drive-notes{margin:8px 0 6px;padding-left:1.2em;font-size:1rem;line-height:1.4}
.drive-notes li+li{margin-top:4px}
.drive-card .src{margin:0;font-size:1rem;color:var(--muted)}
.rain-strip h2{margin:0;font-size:1rem;display:flex;flex-wrap:wrap;gap:6px 10px;align-items:baseline}
.rain-strip h2 small{font-size:1rem;font-weight:600;color:var(--muted);border:1px solid var(--line);border-radius:999px;padding:1px 9px}
.rain-strip .sum{margin:6px 0 12px;font-size:1rem}
/* Marcus, 2026-09-24 (via EV Manager): each hour showed a bar and word (the forecast AMOUNT) above a % (the
   CHANCE of any rain), so "Trace 26%" beside "Light rain 53%" read as a contradiction. Now one row per hour under
   visible "Amount" and "Chance of any rain" headings, with the bar lying flat. Rows at every width, measured: the
   12 columns were 32/42/45/80px wide inside at 320/375/768/1280 (the page caps the strip near 1098px), while
   "Moderate" is 71/81/92px at text scale 1/1.15/1.3, so columns spilled into their neighbours at every width up to
   1280 x1.3 and no width the page reaches fits all 12 (12 x 96 + 11 x 8 = 1240px). The head and the rows share one
   set of column tracks through subgrid, so each heading sits over its own column at any text scale. */
.rain-table{display:grid;grid-template-columns:max-content minmax(min-content,1fr) fit-content(3.5rem);column-gap:10px;max-width:36rem}
.rain-head,.rain-hours,.rain-hours li{display:grid;grid-column:1/-1;grid-template-columns:subgrid}
.rain-head{color:var(--muted);font-size:1rem;line-height:1.25;padding:0 4px 6px;border-bottom:1px solid var(--line);align-items:end}
.rain-hours{list-style:none;margin:0;padding:0}
.rain-hours li{align-items:center;padding:5px 4px;border-bottom:1px solid var(--line);min-width:0}
.rain-hours time{font-size:1rem;color:var(--muted);white-space:nowrap}
.rain-hours .amount{display:flex;flex-wrap:wrap;align-items:center;gap:2px 8px;min-width:0}
/* a fixed bar width, the same in every row, so bar length compares amounts across hours (a bar that grew to fill its row
   was longer wherever that hour's word was shorter) */
.rain-hours .bar{flex:0 0 2rem;height:10px;display:flex;align-items:center}
@media(min-width:720px){.rain-hours .bar{flex-basis:8rem}}
/* Marcus, 2026-09-24: on wide screens the 12 rows sit as two halves side by side (the first six hours on the
   left, the next six on the right), each under its own headings, so the card fills its width at about the height
   the 12 columns had. The DOM stays one list of 12 hours in order, so a screen reader still reads them in time
   order; "row dense" lets the second six (placed in the right-hand tracks) fill rows 1-6 beside the first six.
   1080px is where two halves measured to fit at text scale 1.3 with no row wrapping. */
.rain-head .second{display:none}
@media(min-width:1080px){
.rain-table{grid-template-columns:max-content minmax(min-content,1fr) fit-content(5rem) 1.5rem max-content minmax(min-content,1fr) fit-content(5rem);max-width:none}
.rain-head .second{display:block}
.rain-hours{grid-auto-flow:row dense}
.rain-hours li:nth-child(-n+6){grid-column:1/4}
.rain-hours li:nth-child(n+7){grid-column:5/8}
.rain-hours .bar{flex-basis:6rem}
}
.rain-hours .bar i{display:block;height:100%;border-radius:2px 4px 4px 2px;background:var(--rain-bar);border:1px solid var(--rain-bar-edge)}
.rain-hours .none .bar i,.rain-hours .unknown .bar i{width:100%;height:2px;background:transparent;border:0;border-bottom:2px dotted var(--bar-none);border-radius:0}
.rain-hours .unknown .bar i{border-bottom-style:dashed}
/* PLATFORM, 2026-09-24: a trace is the smallest amount, so it is a short tick at the start of the track (10%,
   under a third of Light), not a full-width line that read as long as Heavy. Mixed, freezing and snow hours carry no
   rain-rate class, so they get a fixed round swatch in their own pattern instead of a length on the rain scale. */
.rain-hours .trace .bar i{width:10%;height:2px;background:transparent;border:0;border-bottom:2px solid var(--bar-trace);border-radius:0}
.rain-hours .lvl-light .bar i{width:30%}.rain-hours .lvl-moderate .bar i{width:62%}.rain-hours .lvl-heavy .bar i{width:100%}
.rain-hours .snow .bar i,.rain-hours .mix .bar i,.rain-hours .freezing .bar i{flex:0 0 10px;width:10px;height:10px;border-radius:50%}
.rain-hours .snow .bar i{background:repeating-linear-gradient(135deg,var(--snow-light) 0 4px,var(--snow-dark) 4px 6px)}
.rain-hours .mix .bar i{background:linear-gradient(90deg,var(--rain-bar) 0 50%,var(--snow-light) 50% 100%)}
.rain-hours .freezing .bar i{background:repeating-linear-gradient(45deg,var(--rain-bar) 0 4px,var(--freeze-light) 4px 6px)}
.rain-hours .word{font-size:1rem;line-height:1.25}
.rain-hours .chance{font-size:1rem;color:var(--muted)}
.rain-strip .rain-read{margin:8px 0 0;font-size:1rem;line-height:1.4}
.rain-strip .key{display:flex;flex-wrap:wrap;gap:6px 16px;margin:8px 0 0;padding:0;list-style:none;color:var(--muted);font-size:1rem}
.rain-strip .src{margin:10px 0 0;color:var(--muted);font-size:1rem}
/* Marcus, 2026-09-23: the Rain/Snow/Freezing rain/Trace key and the MANOBS amounts line were always open, adding
   height most visitors never read. Collapsed behind a <summary> now; the WeatherAPI credit (.src, above) stays
   outside and always visible, since the provider terms require it on screen, not one click away. */
.rain-legend{margin:12px 0 0}
.rain-legend summary{cursor:pointer;list-style:none;display:inline-flex;align-items:center;min-height:44px;color:var(--muted);font-size:1rem;font-weight:600}
.rain-legend summary::-webkit-details-marker{display:none}
.rain-legend summary:focus-visible{outline:3px solid var(--focus);outline-offset:2px;border-radius:8px}
/* PLATFORM, 2026-09-23: the / "" alt text tells assistive tech this glyph has no meaning of its own to announce --
   "How to read this" is already the summary's whole accessible name; without it, some screen readers would read
   the triangle glyph itself (e.g. "black right-pointing small triangle") before the label. Older Safari and
   Firefox do not parse the alt-text syntax at all and drop the whole declaration as invalid, which would lose
   the arrow entirely -- the plain content:"▸" right before it is the fallback those browsers keep; a browser
   that does understand the alt-text form uses the second (valid, later) declaration instead. */
.rain-legend summary:before{content:"▸";content:"▸" / "";display:inline-block;margin-right:6px;transition:transform .15s}
.rain-legend[open] summary:before{transform:rotate(90deg)}
.rain-legend .manobs{margin:8px 0 0;color:var(--muted);font-size:1rem}
/* Falling rain: Marcus's drop picture repeats every 700 x 350 px on a layer that starts 350px above the tile and slides down 350px per loop, transform only, above the picture and below the text, on the rain scenes only (weather.js sets data-rain). Opacity is --rain-opacity: .9 ordinary, .65 light, .9 heavy and thunderstorm; change the three numbers to make the drops stronger or fainter. It pauses with the clouds (--cloud-state) and does not exist for reduced motion. */
.now.weather-scene[data-rain]::after{display:block;content:"";position:absolute;top:-350px;bottom:0;left:0;right:auto;width:100%;height:auto;border:0;border-radius:0;z-index:-1;pointer-events:none;background:url("/assets/rain/rain-overlay.webp") repeat 0 0/700px 350px;opacity:var(--rain-opacity,.9);animation:rain-fall 1.2s linear infinite;animation-play-state:var(--cloud-state,running);will-change:transform}
.now.weather-scene[data-rain="light"]{--rain-opacity:.65}.now.weather-scene[data-rain="heavy"]{--rain-opacity:.9}
@keyframes rain-fall{from{transform:translate3d(0,0,0)}to{transform:translate3d(0,350px,0)}}
@media(prefers-reduced-motion:reduce){.now.weather-scene[data-rain]::after{display:none;animation:none}}
/* MeteoAlarm step 3 (Style Guide v1.0): the warning colour is a left-border stripe and a small dot only --
   text stays on the theme's own card background (var(--ink)/var(--muted)), so the mandated hex colours never
   have to double as a text-contrast background. */
#meteoalarmSection{margin-top:18px}
.ma-warning{padding:18px;margin-top:14px;border-left:6px solid var(--ma-color,var(--line))}
.ma-warning:first-child{margin-top:0}
.ma-warning h3{margin:0 0 6px;font-size:1.15rem;display:flex;align-items:center;gap:8px}
.ma-warning h3::before{content:"";display:inline-block;width:10px;height:10px;border-radius:50%;background:var(--ma-color,var(--muted));flex:none}
.ma-meta{margin:2px 0;font-size:1rem;color:var(--muted)}
.ma-issued{font-size:1rem}
.ma-upcoming{display:inline-block;margin:6px 0 0;padding:2px 9px;border-radius:999px;border:1px solid var(--line);font-size:1rem;color:var(--muted)}
.ma-areas{margin:8px 0 0;font-size:1rem;color:var(--muted)}
.ma-areas summary{cursor:pointer;color:var(--link)}
.ma-text{margin-top:10px}
.ma-text p{margin:6px 0;font-size:1rem;line-height:1.45}
.ma-text b{display:block;font-size:1rem;color:var(--muted);margin-top:10px}
.ma-langs{margin-top:10px}
.ma-langs summary{cursor:pointer;color:var(--link);font-size:1rem}
.ma-empty,.ma-partial{color:var(--muted);font-size:1rem}
.ma-stale{margin:0 0 12px;padding:8px 12px;border-radius:10px;border:1px dashed var(--quiet-line);background:var(--quiet-bg);color:var(--muted);font-size:1rem}
.ma-links{margin:14px 0 0}
.ma-links a{color:var(--link)}
.ma-disclaimer{margin:6px 0 0;color:var(--muted);font-size:1rem;line-height:1.4}
