/* Self-hosted, latin-only subset (this site's UI text is English-only) —
   one variable-font file covers the whole 400–700 weight range used
   everywhere on the site, so there's a single font request total,
   same-origin, cached alongside the rest of the static assets instead of
   depending on Google Fonts' CDN. One family throughout (differentiated
   by weight/size only) reads more deliberate/premium than mixing two.

   Inter specifically because this is a dense data screen: it's drawn for
   UI at small sizes, so the 12px heatmap cells stay legible, and its
   figures have a proper tabular set — see .num below. */
@font-face{
  font-family:"Inter";font-style:normal;font-weight:400 700;font-display:swap;
  src:url("/fonts/inter.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

:root{
  --bg:#F3F6FB; --surface:#FFFFFF; --surface-2:#FBFCFE;
  --ink:#101725; --muted:#6B7688; --line:#E7ECF4;
  --accent:#6C7BF0; --accent-soft:#EEF0FE;
  --shadow:0 1px 2px rgba(16,23,37,.04), 0 12px 30px -12px rgba(16,23,37,.14);
  --shadow-lift:0 1px 2px rgba(16,23,37,.05), 0 22px 46px -18px rgba(16,23,37,.22);
  --radius:22px; --temp:#4C6EF0;
}
[data-theme="dark"]{
  --bg:#0A0E18; --surface:#121926; --surface-2:#0F1622;
  --ink:#EAF0FA; --muted:#8B95A9; --line:#1F2838; --accent-soft:#191F33;
  --shadow:0 1px 2px rgba(0,0,0,.4), 0 18px 40px -16px rgba(0,0,0,.6);
  --shadow-lift:0 1px 2px rgba(0,0,0,.45), 0 28px 56px -20px rgba(0,0,0,.7);
}
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
body{background:var(--bg);color:var(--ink);font-family:"Inter",system-ui,sans-serif;line-height:1.5;
  -webkit-font-smoothing:antialiased;transition:background .5s ease,color .5s ease;min-height:100vh}
/* Readings aren't set in a second family — weight, size and tighter
   tracking carry the contrast instead. What they do need is tabular
   figures: Inter's proportional digits vary in width, so without this a
   value ticking 9 → 10 shifts everything after it, and a column of
   readings stops lining up. Applied to .num and to the spots that show a
   changing number without carrying that class. */
.num{font-variant-numeric:tabular-nums;letter-spacing:-.02em}
.g-axis span,.g-hl,.g-tip,.tcell .tv,.tcell .tc,.hm-axis .hx,.hmrow .dhl,.hcell{
  font-variant-numeric:tabular-nums}
.wrap{max-width:1280px;margin:0 auto;padding:22px 24px 30px}
.page{display:block}
.side-l,.rail{display:none}
.rail-title{font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin:2px 4px 4px}
.ad-slot{background:var(--surface);border:1px dashed var(--line);border-radius:18px;box-shadow:var(--shadow);
  display:flex;align-items:center;justify-content:center;color:var(--muted);font-size:12px;letter-spacing:.14em;text-transform:uppercase;flex:1;min-height:250px}
.ph{flex:1;background:var(--surface);border:1px dashed var(--line);border-radius:18px;box-shadow:var(--shadow);
  display:flex;align-items:center;justify-content:center;text-align:center;color:var(--muted);font-size:12.5px;line-height:1.5;min-height:120px}
.rail-note{color:var(--muted);font-size:11px;text-align:center;letter-spacing:.04em}
/* margin-top:auto pins this to the bottom of the sidebar's flex column
   regardless of how many stations are listed above it. */
.raw-metar{margin-top:auto;background:var(--surface);border:1px solid var(--line);border-radius:14px;
  padding:14px 15px;box-shadow:var(--shadow)}
.raw-metar-label{font-size:11.5px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px}
/* The one place that isn't Inter: a raw METAR is genuinely fixed-width
   material (each group sits in its own column), so it takes the platform
   monospace rather than a second bundled font — no extra download for a
   single block of text. */
.raw-metar-text{font-family:ui-monospace,"SF Mono","Cascadia Mono","Roboto Mono",Menlo,Consolas,monospace;
  font-size:14px;line-height:1.55;color:var(--ink);word-break:break-word;white-space:pre-wrap}
.raw-metar-updated{font-family:"Inter";font-size:11px;color:var(--muted);margin-top:8px}
/* Narrower-layout copy of the sidebar's raw-METAR box — side-l (and its
   copy) is display:none below 1200px, so this one takes over there. */
.raw-metar-mobile{background:var(--surface);border:1px solid var(--line);border-radius:14px;
  padding:14px 15px;box-shadow:var(--shadow);margin-bottom:16px}
@media (min-width:1200px){.raw-metar-mobile{display:none}}
.stations-v{display:flex;flex-direction:column;gap:8px}
.side-l .st{display:flex;flex-direction:row;align-items:baseline;justify-content:space-between;gap:8px;width:100%;text-align:left;
  background:var(--surface);border:1px solid var(--line);border-radius:13px;padding:9px 13px;color:var(--muted);
  box-shadow:var(--shadow);cursor:pointer;transition:transform .15s ease,border-color .2s ease,background .2s ease}
.side-l .st.active{color:var(--ink);background:var(--accent-soft);border-color:color-mix(in srgb,var(--accent) 45%,var(--line))}
.side-l .st .nm{font-weight:600;font-size:13.5px;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.side-l .st .code{font-family:"Inter";font-weight:600;font-size:10.5px;color:var(--muted);letter-spacing:.04em;flex:0 0 auto}
.side-l .st.active::before{content:none}

/* nav */
.nav{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px}
.brand{display:flex;align-items:center;gap:10px;font-weight:600;font-size:18px;letter-spacing:-.01em}
.brand .mark{width:26px;height:26px;flex:0 0 auto}
.nav-mid{display:flex;justify-content:center;min-width:0}
.nav-right{display:flex;align-items:center;gap:10px}
.live{display:inline-flex;align-items:center;gap:7px;font-size:13px;color:var(--muted);background:var(--surface);border:1px solid var(--line);padding:7px 12px;border-radius:999px;box-shadow:var(--shadow)}
.dot{width:8px;height:8px;border-radius:50%;background:#22c17b;position:relative}
.dot::after{content:"";position:absolute;inset:-4px;border-radius:50%;background:#22c17b;opacity:.45;animation:pulse 2s ease-out infinite}
.viewers{display:inline-flex;align-items:center;gap:7px;font-size:13px;color:var(--muted);background:var(--surface);border:1px solid var(--line);padding:7px 12px;border-radius:999px;box-shadow:var(--shadow)}
.viewers .num{font-family:"Inter";color:var(--ink);font-weight:600}
.vdot{width:8px;height:8px;border-radius:50%;background:var(--accent);position:relative}
.vdot::after{content:"";position:absolute;inset:-4px;border-radius:50%;background:var(--accent);opacity:.4;animation:pulse 2s ease-out infinite}
@keyframes pulse{0%{transform:scale(.6);opacity:.5}100%{transform:scale(2.4);opacity:0}}
.icon-btn{width:40px;height:40px;display:grid;place-items:center;cursor:pointer;background:var(--surface);border:1px solid var(--line);border-radius:12px;color:var(--ink);box-shadow:var(--shadow);transition:transform .18s ease}
.icon-btn:active{transform:scale(.96)}.icon-btn svg{width:18px;height:18px}
/* Docs menu — the anchor for the absolutely-positioned panel below, so
   it hangs off this button rather than the page. */
.docs-wrap{position:relative;flex:0 0 auto}
/* Labelled rather than icon-only: "Docs" is not a shape anyone
   recognises the way a bell or a moon is, and an unlabelled icon here
   would just be a mystery button. Same 40px height as the toggles beside
   it so the toolbar row stays level. */
.docs-btn{width:auto;padding:0 13px;gap:7px;grid-auto-flow:column;align-items:center;
  font:inherit;font-size:13.5px;font-weight:600;color:var(--ink)}
.docs-btn svg{width:16px;height:16px;flex:0 0 auto}
.docs-wrap.open .icon-btn{background:var(--accent-soft);border-color:color-mix(in srgb,var(--accent) 45%,var(--line))}
.docs-menu{position:absolute;top:calc(100% + 8px);right:0;z-index:30;min-width:264px;
  background:var(--surface);border:1px solid var(--line);border-radius:14px;padding:6px;
  box-shadow:var(--shadow-lift);display:flex;flex-direction:column;gap:2px}
.docs-menu[hidden]{display:none}
.docs-menu a{display:block;padding:9px 11px;border-radius:10px;text-decoration:none;color:var(--ink);transition:background .15s ease}
.docs-menu a strong{display:block;font-size:13.5px;font-weight:600;line-height:1.3}
.docs-menu a span{display:block;font-size:12px;color:var(--muted);margin-top:2px;line-height:1.35}
.docs-menu a:hover,.docs-menu a:focus-visible{background:var(--accent-soft);outline:none}
/* On a narrow screen the button is close to the right edge and a
   right-anchored panel would run off it, so pin the panel to the
   viewport's edges instead of the button's. */
@media (max-width:520px){
  .docs-menu{position:fixed;left:12px;right:12px;top:auto;min-width:0}
}
/* max-width + overflow-x turn this into a horizontal scroll strip instead
   of silently overflowing the viewport. Without this, five station tabs
   plus city-name labels don't fit narrow phones — CSS falls back to
   "safe" start-alignment when a centered flex item overflows, which is
   what made the row look shoved to one side with the last tab clipped
   at the screen edge rather than truly centered. */
.stations{display:inline-flex;gap:8px;max-width:100%;overflow-x:auto;touch-action:pan-x;background:var(--surface);border:1px solid var(--line);padding:6px;border-radius:14px;box-shadow:var(--shadow);
  -webkit-overflow-scrolling:touch;overscroll-behavior-x:contain;scrollbar-width:none}
.stations::-webkit-scrollbar{display:none}
/* z-index:0 (not just position:relative) matters here: it makes this
   button its own stacking context, so the active pill's z-index:-1
   below stays contained and only goes behind THIS button's own text —
   without it, the -1 escapes to the nearest ancestor that does have one
   and can end up behind opaque content elsewhere on the page instead. */
.st{border:0;background:transparent;color:var(--muted);cursor:pointer;font:inherit;font-weight:500;padding:8px 16px;border-radius:10px;font-size:14px;position:relative;z-index:0;transition:color .2s ease;white-space:nowrap;flex:0 0 auto}
.st .code{font-family:"Inter";font-weight:600}
.st.active{color:var(--ink)}
/* An absolutely-positioned box paints above normal-flow siblings by
   default regardless of DOM order, so z-index:-1 is required here (not
   optional) to put the highlight behind the label text instead of
   covering it. */
.st.active::before{content:"";position:absolute;inset:0;background:var(--accent-soft);border-radius:10px;z-index:-1;animation:tabin .25s ease}
@keyframes tabin{from{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}

.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow)}
.grid{display:flex;flex-direction:column;gap:16px}

/* ---- HERO (top, full width) — two independent sibling cards: the
   existing "current conditions" card at 75%, and the forecast card at
   25% right next to it (not nested inside one shared card). ---- */
/* calc() (not a bare 75%) because .hero-row's own 16px gap sits on top of
   these two cards' basis — 75%+25% with flex-shrink:0 on both left no
   room for that gap, so the row overflowed 16px past the page edge.
   Splitting the gap's width out of each side's basis lands on exactly
   100% again. */
.hero-row{display:flex;gap:16px}
.hero-wrap{flex:0 0 calc(75% - 8px);min-width:0}
.hero{padding:18px 28px 14px;display:flex;flex-direction:column}
.hero-top{display:flex;gap:16px}
/* max-width caps this column on wide/ultra-wide screens — without it the
   basis keeps growing past what the (now compact) content needs, leaving
   a big dead gap before the graph starts. The 52% basis (vs. a narrower
   share) is because the three-column sidebar layout leaves .hero-top
   itself fairly narrow, and long station names need the extra room to
   avoid clipping there even with a small font. */
.hero-info{flex:0 0 52%;max-width:480px;display:flex;flex-direction:column;justify-content:center;min-width:0}
.place{display:flex;align-items:baseline;gap:8px;flex-wrap:nowrap;margin-bottom:6px;min-width:0}
.place h1{font-size:18px;font-weight:600;letter-spacing:-.01em;min-width:0;flex:0 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.place .code{font-family:"Inter";font-weight:600;font-size:12px;color:var(--muted);background:var(--accent-soft);padding:3px 8px;border-radius:7px;flex:0 0 auto}
.bigrow{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;flex-wrap:wrap}
.bigtemp{display:flex;align-items:flex-start;gap:6px}
.bigtemp .val{font-family:"Inter";font-weight:700;font-size:84px;line-height:.82;letter-spacing:-.045em;color:var(--temp);transition:color .6s ease}
.bigtemp .deg{font-family:"Inter";font-weight:600;font-size:26px;color:var(--muted);margin-top:6px}
.units{display:inline-flex;background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:4px;box-shadow:var(--shadow)}
.units button{border:0;background:transparent;cursor:pointer;font:inherit;font-weight:600;font-size:13.5px;color:var(--muted);padding:6px 12px;border-radius:8px;transition:color .2s ease,background .2s ease}
.units button.on{color:var(--ink);background:var(--accent-soft)}
.alt{color:var(--muted);font-size:15px;margin-top:8px}.alt .num{color:var(--ink);font-weight:600}
.updated{margin-top:0;color:var(--muted);font-size:13.5px;display:flex;align-items:center;gap:9px}
.hmeta{display:flex;align-items:center;gap:12px;margin-top:8px;flex-wrap:wrap}
.updated .fresh{width:7px;height:7px;border-radius:50%;background:var(--temp)}
.flash .fresh{animation:flash 1.6s ease-out}
@keyframes flash{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--temp) 70%,transparent)}100%{box-shadow:0 0 0 14px transparent}}

.hero-graph{flex:1;position:relative;min-height:160px;border-radius:16px;overflow:hidden}
/* Explicit width AND height (not left/right + top/bottom) are required
   here: the svg has a viewBox, which makes it a "replaced element" —
   browsers size those from their intrinsic aspect ratio on whichever of
   width/height is left "auto", ignoring the corresponding offsets
   entirely. Leaving width to resolve from left+right (with height
   explicit) silently computed width from the ratio instead — the svg
   was rendering ~40% wider than its container and getting clipped by
   overflow:hidden, which also fed the hover handler a curve position
   that didn't match what was actually visible. Setting both dimensions
   explicitly sidesteps the ratio calculation on both axes. */
.hero-graph svg{position:absolute;top:0;left:0;width:100%;height:calc(100% - 22px)}
.g-axis{position:absolute;left:0;right:0;bottom:0;height:20px}
.g-axis span{position:absolute;bottom:0;font-family:"Inter";font-size:13.5px;font-weight:600;color:color-mix(in srgb,var(--ink) 62%,var(--muted));white-space:nowrap}
.spark path.line{fill:none;stroke:var(--temp);stroke-width:3;stroke-linecap:round;stroke-linejoin:round;filter:drop-shadow(0 8px 16px color-mix(in srgb,var(--temp) 38%,transparent))}
.spark path.fill{fill:url(#sparkfill);stroke:none;opacity:.16}
.g-hl{position:absolute;left:12px;top:8px;font-family:"Inter";font-size:15px;color:var(--muted)}
.g-hl b{color:var(--ink);font-weight:600}

/* ---- Forecast card (25% column, right of the current-conditions group) ---- */
.hero-forecast{flex:0 0 calc(25% - 8px);min-width:0;display:flex;flex-direction:column;align-items:center;
  padding:14px 12px 12px;text-align:center}
.fc-head{display:flex;align-items:center;justify-content:space-between;width:100%;gap:8px}
.fc-label{font-size:10px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;white-space:nowrap}

/* "Coming soon" placeholder — orbiting dots + blinking ellipsis, both
   transform/opacity only (cheap, compositor-only animation). */
.fc-soon{display:flex;flex-direction:column;align-items:center;justify-content:center;flex:1;gap:12px;padding:8px 0}
.fc-soon-orbit{position:relative;width:40px;height:40px}
.fc-soon-orbit span{position:absolute;top:50%;left:50%;width:7px;height:7px;margin:-3.5px;border-radius:50%;background:var(--accent);animation:fcOrbit 1.6s linear infinite}
.fc-soon-orbit span:nth-child(1){animation-delay:0s;opacity:1}
.fc-soon-orbit span:nth-child(2){animation-delay:-.53s;opacity:.6}
.fc-soon-orbit span:nth-child(3){animation-delay:-1.06s;opacity:.3}
.fc-soon-text{font-size:12px;font-weight:600;color:var(--muted);display:flex;align-items:baseline}
.fc-ellipsis{display:inline-flex;gap:2px;margin-left:3px}
.fc-ellipsis i{width:3px;height:3px;border-radius:50%;background:var(--muted);font-style:normal;animation:fcBlink 1.4s ease-in-out infinite}
.fc-ellipsis i:nth-child(2){animation-delay:.2s}
.fc-ellipsis i:nth-child(3){animation-delay:.4s}
@keyframes fcOrbit{0%{transform:rotate(0deg) translateX(16px) rotate(0deg)}100%{transform:rotate(360deg) translateX(16px) rotate(-360deg)}}
@keyframes fcBlink{0%,80%,100%{opacity:.2}40%{opacity:1}}

/* stat strip inside hero */
/* Grid (not flex) so this reflows to more rows as columns run out of
   room instead of squeezing all six into one row and clipping values
   like "Scattered" or "1000 hPa" — a fixed breakpoint would need to
   guess the exact width six items stop fitting at; auto-fit doesn't. */
.hstats{display:grid;grid-template-columns:repeat(auto-fit,minmax(88px,1fr));gap:10px 16px;margin-top:12px;border-top:1px solid var(--line);padding-top:10px}
.hstat{padding:0 12px;min-width:0;border-left:1px solid var(--line)}
.hstat:first-child{border-left:0;padding-left:2px}
.hstat .k{font-size:11.5px;color:var(--muted);font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Wraps (doesn't truncate) — "Sky"/"Weather" values like "Broken clouds"
   are two words and were getting cut to "Broken cl…" at normal column
   widths; wrapping to a second line reads fine and never loses text. */
.hstat .v{margin-top:4px;font-family:"Inter";font-weight:600;font-size:17px;line-height:1.3}

/* ---- section head ---- */
.side-head{display:flex;align-items:center;justify-content:space-between;margin:0 4px 10px}
.sec-label{font-size:13px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.08em}
.side-head .cnt{font-family:"Inter";font-size:12.5px;color:var(--muted)}

/* ---- today 24h — horizontal strip ---- */
/* contain:content isolates this scroller's layout/paint from the rest of
   the page (and vice versa) — overflow already clips it visually, so this
   is a free hint, not a behavior change. Scrolling here shouldn't have to
   wait on anything happening elsewhere on the page, or force it to redo
   work either. */
.strip{display:flex;gap:10px;overflow-x:auto;padding:6px 2px 12px;scroll-behavior:smooth;contain:content;
  -webkit-overflow-scrolling:touch;overscroll-behavior-x:contain}
.strip::-webkit-scrollbar{height:9px}
.strip::-webkit-scrollbar-thumb{background:var(--line);border-radius:99px}
.strip{scrollbar-width:thin;scrollbar-color:var(--line) transparent}
.nowbtn{border:1px solid var(--line);background:var(--surface);color:var(--accent);font:inherit;font-weight:600;font-size:12.5px;padding:5px 11px;border-radius:9px;cursor:pointer;box-shadow:var(--shadow);transition:transform .15s ease}
.nowbtn:active{transform:scale(.96)}
.nowbtn:disabled{opacity:.35;cursor:not-allowed;pointer-events:none}
.tcell{flex:0 0 auto;min-width:90px;text-align:center;background:var(--surface-2);border:1px solid var(--line);
  border-radius:16px;padding:13px 10px 11px;transition:transform .18s ease,box-shadow .18s ease}
.tcell.now{background:color-mix(in srgb,var(--temp) 13%,var(--surface-2));border-color:var(--temp);box-shadow:0 0 0 2px color-mix(in srgb,var(--temp) 28%,transparent)}
.tcell.now .tt{color:var(--ink);font-weight:600}
.tcell .tt{font-size:11.5px;color:var(--muted);font-weight:500;white-space:nowrap}
.tcell .tv{margin-top:6px;font-family:"Inter";font-weight:700;font-size:22px;line-height:1}
.tcell .tc{font-family:"Inter";font-size:12px;color:var(--muted);margin-top:3px}
.tcell .ta{font-size:11px;color:var(--muted);margin-top:4px;height:12px}

/* ---- 7 day — hourly heatmap ---- */
.hm-inner{min-width:860px;padding:10px 18px 8px}
.hm-axis,.hmrow{display:grid;grid-template-columns:54px repeat(24,1fr) 70px;gap:4px;align-items:center}
.hm-axis{margin-bottom:7px}
.hm-axis .hx{font-size:12px;color:var(--muted);text-align:center;font-family:"Inter";font-weight:600}
.hmrow{margin-bottom:4px}
.hmrow .dd{font-size:13px;color:var(--muted)}
.hmrow.today .dd{color:var(--ink);font-weight:600}
/* Fixed (not aspect-ratio) height — aspect-ratio tied it to the grid's
   1fr column width, which on a wide card meant tall enough cells to push
   the whole heatmap into scrolling. */
.hcell{height:36px;border-radius:6px;display:flex;align-items:center;justify-content:center;
  font-family:"Inter";font-weight:600;font-size:12px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.35);
  transition:transform .1s ease}
.hmrow .dhl{justify-self:end;font-family:"Inter";font-size:11.5px;white-space:nowrap}
.hmrow .dhl b{font-weight:600}.hmrow .dhl i{font-style:normal;color:var(--muted);margin-left:5px}
.hm-legend{display:flex;align-items:center;gap:8px;justify-content:flex-end;margin-top:4px}
.hm-leg-txt{font-size:12px;font-weight:600;letter-spacing:.01em;display:inline-flex;align-items:center;gap:4px}
.hm-leg-txt .num{font-weight:700;color:inherit}
.hm-leg-txt.lo{color:#3f6bf0}
.hm-leg-txt.hi{color:#e0402f}
.hm-legend .bar{width:88px;height:7px;border-radius:99px;background:linear-gradient(90deg,#3f6bf0,#3e93ef,#2bb3a3,#eaa22e,#e8623c,#e0402f);box-shadow:inset 0 0 0 1px rgba(16,23,37,.05)}

.scroll{overflow:auto;contain:content;-webkit-overflow-scrolling:touch;overscroll-behavior:contain}
.scroll::-webkit-scrollbar{width:10px}
.scroll::-webkit-scrollbar-thumb{background:var(--line);border-radius:99px;border:3px solid var(--surface)}
.scroll{scrollbar-width:thin;scrollbar-color:var(--line) transparent}

.rise{opacity:0;transform:translateY(14px);animation:rise .6s cubic-bezier(.22,.61,.36,1) forwards}
@keyframes rise{to{opacity:1;transform:none}}

/* ---------- DESKTOP: T-layout, one page ---------- */
@media (min-width:960px){
  /* overflow-y:auto (not hidden) is the fix for a real zoom bug: at 100%
     zoom the content already fits in 100vh so no scrollbar ever appears,
     but at 110%+ browser zoom 100vh renders taller than the actual
     viewport — with overflow:hidden that extra content was clipped and
     completely unreachable by scroll. This keeps the single-screen look
     in the normal case while giving zoomed-in users a way to reach it. */
  html,body{overflow-y:auto;overflow-x:hidden;height:100%}
  .wrap{height:100vh;display:flex;flex-direction:column;padding:16px 30px 16px}
  .nav{margin-bottom:12px}
  .grid{flex:1;min-height:0;display:flex;flex-direction:column;gap:14px}
  /* hero-row (not hero-wrap) now, since hero-wrap moved one level deeper
     — it's .hero-row that's the direct child of this column flex .grid,
     controlling height-basis here; hero-wrap's own flex property is for
     its width-share of .hero-row (a row-direction flex), a different axis. */
  .hero-row{flex:0 0 auto}
  .hero{padding:11px 28px 20px}
  /* hero-info's own content (place name + big temp + alt/hmeta) is taller
     than this floor, so it's what actually sizes hero-graph via stretch —
     this min-height only matters if hero-info's content ever shrinks
     below it. */
  .hero-graph{min-height:80px}
  .bigtemp .val{font-size:80px}
  .bigtemp .deg{font-size:24px;margin-top:5px}
  .alt{margin-top:6px;font-size:14.5px}
  .hmeta{margin-top:6px}
  .hstats{margin-top:11px;padding-top:11px}
  .hstat .v{font-size:16.5px}
  .col-today{flex:0 0 auto}
  .side-head{margin:0 4px 7px}
  /* Fixed height (not auto) so the horizontal scrollbar — which only
     appears on stations with enough hourly readings to overflow the
     strip's width — doesn't grow this section and steal vertical space
     from the heatmap below it. Always-reserved scrollbar track instead
     of a height that changes per station's data. */
  .strip{padding:6px 2px 10px;height:134px}
  .tcell{padding:11px 10px 9px}
  .tcell .tv{font-size:22px;margin-top:6px}
  .col-days{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
  .col-days .card{flex:1;min-height:0;overflow:auto}
  .foot{margin-top:8px}
}
/* Between these widths the sidebar is hidden, which moves its raw-METAR
   box and the mobile ad slot (~190px together) into the main column —
   more than the one-screen layout has room for. The fixed 100vh height
   meant that overflow had nowhere to go: .col-days is the flex item that
   absorbs leftover space, so it was squeezed to zero height and pushed
   below the fold, with the page unable to scroll because the document
   was still exactly viewport-height. The 7-day heatmap simply vanished.

   Releasing the fixed height here lets the page scroll normally instead,
   which keeps every section at its natural size — nothing is resized,
   the layout just stops pretending it fits on one screen when it
   doesn't. Above 1200px the sidebar is back, the content fits, and the
   single-screen layout continues to apply unchanged. */
/* .98 rather than a whole pixel: viewport widths aren't necessarily
   integers (fractional device pixel ratios, browser zoom), and at a
   width like 1199.5px a `max-width:1199px` here would match neither this
   block nor the `min-width:1200px` one below — leaving that sliver of
   widths with no layout rules at all. */
@media (min-width:960px) and (max-width:1199.98px){
  html,body{height:auto}
  .wrap{height:auto;min-height:100vh}
  .grid{flex:none}
  .col-days{flex:none}
  .col-days .card{flex:none;min-height:0;overflow:visible}
}
@media (min-width:960px) and (max-height:770px){
  .wrap{padding:12px 30px 12px}
  .bigtemp .val{font-size:74px}
  .hero-graph{min-height:108px}
  .hero{padding:18px 34px 14px}
  .tcell .tv{font-size:19px}
  .hcell{height:26px}      /* short window — a little more compact than the 34px default */
}
@media (min-width:1200px){
  /* overflow-y:auto (not hidden) is the fix for a real zoom bug: at 100%
     zoom the content already fits in 100vh so no scrollbar ever appears,
     but at 110%+ browser zoom 100vh renders taller than the actual
     viewport — with overflow:hidden that extra content was clipped and
     completely unreachable by scroll. This keeps the single-screen look
     in the normal case while giving zoomed-in users a way to reach it. */
  html,body{overflow-y:auto;overflow-x:hidden;height:100%}
  .nav-mid{display:none}
  .page{display:flex;width:100%;height:100vh;align-items:stretch}
  .side-l{display:flex;flex-direction:column;gap:12px;flex:0 0 15%;max-width:15%;height:100vh;padding:22px 14px 18px}
  .wrap{flex:0 0 70%;max-width:70%;min-width:0;margin:0;height:100vh}
  .rail{display:flex;flex-direction:column;gap:12px;flex:0 0 15%;max-width:15%;height:100vh;padding:22px 14px 18px}
}
/* .98 for the same reason as the 1199.98px boundary above: a fractional
   viewport width of 959.5px would otherwise fall between this block and
   the min-width:960px one and get neither layout. */
@media (max-width:959.98px){
  .nav{flex-wrap:wrap;align-items:center}
  /* No flex-basis:100% here — nav-right stays on the brand's row as long
     as it fits (which it does down to phone widths); it only drops to
     its own line if there's truly no room, instead of always being
     forced onto a second row even when there was space to share one. */
  .nav-right{flex-wrap:nowrap;gap:8px}
  .nav-mid{order:3;flex-basis:100%;margin-top:12px}
  .hero-row{flex-direction:column}
  .hero-wrap{flex:none}
  .hero-top{flex-direction:column;gap:16px}
  .hero-forecast{flex:none;width:100%}
  .hero-info{flex:none}
  /* Full station name, not an ellipsis. "Chaudhary Charan Singh Intl
     Airport Lucknow" is far wider than a phone line, and clipping the end
     hid the part that names the city — the one word the reader is looking
     for. The hero is content-sized at this width (no 100vh lock like the
     desktop layout), so a second line costs a few pixels of card height
     rather than pushing anything off-screen. Kept at the same font size —
     the name just needs permission to wrap.

     Dropping the flex row for a plain block is what lets the code badge
     sit inline right after the last word instead of being stranded on a
     line of its own: as a flex item beside a full-width h1 it had
     nowhere else to go. */
  .place{display:block}
  .place h1{display:inline;white-space:normal;overflow:visible;text-overflow:clip;line-height:1.3}
  .place .code{display:inline-block;margin-left:8px}
  .hero-graph{min-height:180px}
  .hero-graph svg{top:26px;height:calc(100% - 48px)}      /* room for H/L above the graph */
  .hide-touch{display:none}      /* "hover a cell" means nothing on touch; drop it so the day pager + range label fit */
  /* Square cells here specifically — this section's grid is horizontally
     scrollable at a fixed min-width (not fluid like on desktop), so each
     column lands on a fixed, predictable width and aspect-ratio can't run
     away the way it did on wide desktop screens (that's why desktop keeps
     a fixed height instead). */
  .hcell{height:auto;aspect-ratio:1/1;font-size:10.5px}
  .hm-axis .hx{font-size:11px}
  .hmrow .dd{font-size:12px}
  .hmrow .dhl{font-size:10.5px}
  /* Row layout squeezed the disclaimer text into a narrow column to make
     room for the button group beside it. Stacking gives the paragraph
     its full width back and puts the buttons in their own row below. */
  .foot{flex-direction:column;align-items:flex-start;gap:12px}
  .foot>span:first-child{flex:none;width:100%}
  .foot-links{flex-wrap:wrap;justify-content:center;width:100%}
}
@media (max-width:680px){
  /* Below this width the stat row wraps to multiple rows (auto-fit runs
     out of columns), where a left border reads as a stray line at the
     start of each wrapped row rather than a divider between items. */
  .hstat{border-left:0}
  /* auto-fit(minmax(88px,1fr)) was landing on 3 columns for 5 items —
     an orphaned 2-item last row where "Pressure"'s value ("1020 hPa")
     didn't have room and wrapped to two lines. A fixed 2-column grid
     gives every value column enough width to stay on one line. */
  .hstats{grid-template-columns:repeat(2,1fr)}
  .hstat .v{white-space:nowrap}
  /* :first-child's special 2px padding-left made sense as the leading
     edge of one single row — in this 2-column grid "Wind" is just the
     top item of the left column, so it needs the same 12px as every
     other item below it, not a smaller one-off value. */
  .hstat:first-child{padding-left:12px}
}
@media (max-width:560px){
  .bigtemp .val{font-size:82px}
  .wrap{padding:16px 16px 34px}
  .hero{padding:20px 20px 18px}
  .hm-inner{min-width:760px}
  .viewers,.live,.units{padding:6px 9px}
  .viewers,.live{font-size:12px}
}
@media (max-width:480px){
  .viewers .lbl{display:none}
  .units button{padding:6px 9px;font-size:12.5px}
  .nav-right{gap:7px}
}
@media (prefers-reduced-motion:reduce){*{animation-duration:.001ms !important;transition-duration:.001ms !important}}
.hcell.empty{background:var(--line);color:transparent}
.empty-note{padding:26px 18px;color:var(--muted);font-size:13.5px;text-align:center}
/* interactive graph */
.g-cursor{position:absolute;top:0;bottom:20px;width:1px;background:color-mix(in srgb,var(--temp) 55%,transparent);opacity:0;pointer-events:none;transition:opacity .12s;z-index:2}
.g-dot{position:absolute;width:12px;height:12px;border-radius:50%;background:var(--temp);border:2px solid var(--surface);box-shadow:0 2px 7px rgba(0,0,0,.28);transform:translate(-50%,-50%);opacity:0;pointer-events:none;transition:opacity .12s;z-index:3}
.g-tip{position:absolute;transform:translate(-50%,-135%);background:var(--ink);color:var(--surface);padding:6px 10px;border-radius:10px;font-family:"Inter";font-size:13px;font-weight:600;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .12s;z-index:4;box-shadow:0 6px 18px rgba(0,0,0,.22)}
.g-tip .tip-t{opacity:.65;font-weight:500;margin-left:7px}
.hm-tip{position:fixed;left:0;top:0;transform:translate(-50%,-100%) scale(.9) translateY(4px);
  background:var(--ink);color:var(--surface);padding:7px 11px;border-radius:11px;
  font-family:"Inter";font-size:12.5px;font-weight:600;white-space:nowrap;
  box-shadow:0 10px 26px rgba(0,0,0,.26);pointer-events:none;opacity:0;z-index:9999;will-change:transform,opacity;
  transition:opacity .13s ease,transform .17s cubic-bezier(.22,.61,.36,1)}
.hm-tip.show{opacity:1;transform:translate(-50%,-100%) scale(1) translateY(-6px)}
.hm-tip::after{content:"";position:absolute;left:50%;bottom:-5px;transform:translateX(-50%);border:5px solid transparent;border-top-color:var(--ink);border-bottom:0}
.st-search{position:relative;margin:2px 2px 10px}
.st-search svg{position:absolute;left:11px;top:50%;transform:translateY(-50%);width:15px;height:15px;color:var(--muted);pointer-events:none}
.st-search input{width:100%;box-sizing:border-box;border:1px solid var(--line);background:var(--surface);color:var(--ink);
  font:inherit;font-size:13.5px;border-radius:12px;padding:9px 12px 9px 33px;box-shadow:var(--shadow);outline:none;
  transition:border-color .18s ease,box-shadow .18s ease}
.st-search input::placeholder{color:var(--muted)}
.st-search input:focus{border-color:color-mix(in srgb,var(--accent) 55%,var(--line));box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 14%,transparent)}
.st-empty{display:none;color:var(--muted);font-size:12.5px;text-align:center;padding:8px 4px}
/* Search + ad inventory for the phone/tablet layout — the desktop sidebar
   (with its own search) only exists at 1200px+, so anything narrower had
   neither a search box nor any ad slot at all. */
.mobile-search-wrap{margin-bottom:14px}
.mobile-search-wrap .st-search{margin:0}
@media (min-width:1200px){.mobile-search-wrap{display:none}}
.ad-slot-mobile{display:flex;align-items:center;justify-content:center;background:var(--surface);
  border:1px dashed var(--line);border-radius:18px;box-shadow:var(--shadow);color:var(--muted);
  font-size:12px;letter-spacing:.14em;text-transform:uppercase;min-height:90px;margin:16px 0}
@media (min-width:1200px){.ad-slot-mobile{display:none}}
.foot{margin-top:16px;color:var(--muted);font-size:12.5px;display:flex;flex-direction:row;align-items:center;justify-content:space-between;gap:16px;text-align:left;flex-wrap:wrap}
.foot-links{display:inline-flex;gap:8px;flex-wrap:nowrap;justify-content:flex-end;flex:0 0 auto}
.foot>span:first-child{flex:1;min-width:0}
.foot-links a,.foot-links button{color:var(--ink);text-decoration:none;font:inherit;font-size:12px;font-weight:600;border:1px solid var(--line);
  background:var(--surface);padding:6px 13px;border-radius:999px;box-shadow:var(--shadow);cursor:pointer;
  transition:transform .15s ease,border-color .2s ease}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:8px}

/* ---- bug report modal ---- */
.modal-overlay{position:fixed;inset:0;background:rgba(16,23,37,.45);display:flex;align-items:center;
  justify-content:center;padding:20px;z-index:10000}
.modal-overlay[hidden]{display:none}
.modal-card{background:var(--surface);border-radius:20px;box-shadow:var(--shadow-lift);padding:22px;
  max-width:400px;width:100%;max-height:90vh;overflow:auto}
.modal-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}
.modal-head h2{font-family:"Inter";font-size:19px;letter-spacing:-.01em}
.modal-head .icon-btn{width:34px;height:34px}.modal-head .icon-btn svg{width:16px;height:16px}
.modal-sub{color:var(--muted);font-size:13.5px;margin-bottom:14px}
.modal-card textarea,.modal-card input{display:block;width:100%;box-sizing:border-box;border:1px solid var(--line);
  background:var(--surface-2);color:var(--ink);font:inherit;font-size:13.5px;border-radius:12px;padding:10px 12px;
  outline:none;resize:vertical;margin-bottom:10px;transition:border-color .18s ease,box-shadow .18s ease}
.modal-card textarea:focus,.modal-card input:focus{border-color:color-mix(in srgb,var(--accent) 55%,var(--line));
  box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 14%,transparent)}
.modal-card textarea::placeholder,.modal-card input::placeholder{color:var(--muted)}
.modal-actions{display:flex;justify-content:flex-end}
.modal-status{font-size:13px;margin-top:10px;min-height:18px;color:var(--muted)}
.modal-status.ok{color:#22c17b}
.modal-status.err{color:#e0402f}

/* Hover-only lift/scale effects — gated so touch taps don't get a hover
   state stuck on until the next tap elsewhere (classic mobile "sticky
   hover" jank). :active below still gives touch users tap feedback. */
@media (hover:hover) and (pointer:fine){
  .side-l .st:hover{transform:translateY(-1px)}
  .icon-btn:hover{transform:translateY(-1px)}
  .nowbtn:hover{transform:translateY(-1px)}
  .tcell:hover{transform:translateY(-3px);box-shadow:var(--shadow-lift)}
  .hcell:hover{transform:scale(1.25);box-shadow:0 0 0 2px var(--surface),0 5px 12px rgba(0,0,0,.22);position:relative;z-index:2}
  .foot-links a:hover,.foot-links button:hover{transform:translateY(-1px);border-color:color-mix(in srgb,var(--accent) 45%,var(--line))}
}
