
/* ═══════════════════════════ design system ═══════════════════════════ */
:root{
  /* surfaces - layered elevation, each step subtly lighter than the page */
  --bg:#0a0c10; --bg-2:#0d1016; --surface:#12151d; --surface-2:#171b25; --surface-3:#1d2230;
  --line:#232838; --line-soft:#1a1e2a;
  /* text */
  /* --fg-3 is the tertiary text token: table headers, .sub, .dim, timestamps.
     Kept at >=4.5:1 against every surface step, --surface-3 included. */
  --fg:#e8ecf4; --fg-2:#9aa4b8; --fg-3:#8590a8;
  /* one accent, used sparingly */
  --accent:#2dd4bf; --accent-2:#22d3ee; --accent-dim:rgba(45,212,191,.12);
  /* semantic */
  --ok:#34d399; --warn:#fbbf24; --danger:#f87171; --info:#60a5fa;
  --chip-fg:#a5f3ea;
  --grid:rgba(255,255,255,.045);
  color-scheme:dark;
  /* spacing scale 4/8/12/16/24/32 */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s6:24px; --s8:32px;
  --r:14px; --r-sm:10px; --r-xs:7px;
  --shadow:0 1px 2px rgba(0,0,0,.4), 0 8px 24px -12px rgba(0,0,0,.6);
  --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
  --sans:-apple-system,BlinkMacSystemFont,"Inter","Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --t:170ms cubic-bezier(.2,.7,.3,1);
}
html[data-theme="light"]{
  --bg:#f4f6fa; --bg-2:#eef1f6; --surface:#ffffff; --surface-2:#f7f9fc; --surface-3:#e8ecf3;
  --line:#d5dbe6; --line-soft:#e0e5ee;
  --fg:#11151d; --fg-2:#46536a; --fg-3:#5f6b80;
  /* the teal that reads fine on a dark page is only ~3.5:1 on white, so the
     light theme uses a darker shade for links, chips and accented text */
  --accent:#0f766e; --accent-2:#0284c7; --accent-dim:rgba(15,118,110,.1);
  --ok:#047857; --warn:#a45307; --danger:#c81e1e; --info:#2563eb;
  /* a shade darker again than --accent: chip text sits on accent-dim, which
     lightens it, and chips appear on --bg-2 and --surface-3 as well as white */
  --chip-fg:#0d6a62;
  --grid:rgba(17,21,29,.07);
  color-scheme:light;
  --shadow:0 1px 2px rgba(16,24,40,.06), 0 8px 24px -12px rgba(16,24,40,.14);
}
html[data-theme="light"] body{
  background-image:
    radial-gradient(1100px 520px at 10% -10%, rgba(13,148,136,.07), transparent 62%),
    radial-gradient(820px 420px at 96% -14%, rgba(2,132,199,.05), transparent 60%);
}
html[data-theme="light"] header,
html[data-theme="light"] nav.sections{background:rgba(255,255,255,.86)}
html[data-theme="light"] tbody tr:nth-child(even){background:rgba(17,21,29,.025)}
/* the dark theme's near-black label on a bright teal fill inverts here: the
   light accent is dark, so the fill needs white text and a darker hover */
html[data-theme="light"] .btn-a{color:#fff}
html[data-theme="light"] .btn-a:hover:not(:disabled){background:#0b5f58;border-color:#0b5f58}
*{box-sizing:border-box}
/* Author `display` on .badge/.btn outranks the UA stylesheet's [hidden] rule,
   so the attribute alone would not hide those elements. */
[hidden]{display:none!important}
/* --hdr and --nav are measured from the real elements at runtime (see
   trackStickyHeights). They must not be hardcoded: the header wraps to two or
   three rows on narrow screens, and a fixed offset there parks the section nav
   underneath it - invisible, since the header sits one z-index higher. */
html{scroll-behavior:smooth; scroll-padding-top:calc(var(--hdr,61px) + var(--nav,53px) + 18px)}
body{
  margin:0; background:var(--bg); color:var(--fg); font-family:var(--sans);
  font-size:14px; line-height:1.55; -webkit-font-smoothing:antialiased;
  font-variant-numeric:tabular-nums;
  background-image:
    radial-gradient(1100px 520px at 10% -10%, rgba(45,212,191,.10), transparent 62%),
    radial-gradient(820px 420px at 96% -14%, rgba(34,211,238,.07), transparent 60%);
  background-attachment:fixed;
}
a{color:var(--accent); text-decoration:none}
a:hover{text-decoration:underline}
button{font:inherit; color:inherit}
/* Structural iconography. Emoji were doing this job, but they render at a
   different weight, size and colour on every platform and ignore
   currentColor, so a row of them reads as noise rather than as one set.
   These inherit colour and scale with the type they sit next to. */
.ico{width:1em;height:1em;flex:none;fill:none;stroke:currentColor;stroke-width:1.75;
  stroke-linecap:round;stroke-linejoin:round;vertical-align:-.14em}
.ico-sprite{position:absolute;width:0;height:0;overflow:hidden}
:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-radius:var(--r-xs)}
.wrap{max-width:1240px; margin:0 auto; padding:0 var(--s6) 80px}
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
    clip:rect(0 0 0 0);white-space:nowrap;border:0}
.skip{position:fixed;top:-60px;left:var(--s4);z-index:100;background:var(--surface-2);
      padding:var(--s2) var(--s3);border-radius:var(--r-xs);transition:top var(--t)}
.skip:focus{top:var(--s2)}

/* ═══════════════════════════ header ═══════════════════════════ */
header{position:sticky; top:0; z-index:40; background:rgba(10,12,16,.86);
  backdrop-filter:blur(14px) saturate(1.4); border-bottom:1px solid var(--line-soft)}
.hd{max-width:1240px;margin:0 auto;padding:var(--s3) var(--s6);
    display:flex;align-items:center;gap:var(--s4);flex-wrap:wrap}
/* the control row wraps to a second line on narrow screens once the install
   button appears (the hd-r gets ~40px wider); reserving the wrapped height
   and the button's slot from first paint keeps the header from shifting the
   page below it (CLS). Note [hidden] can't be overridden - the UA sheet uses
   display:none !important - so the button toggles a .show class instead. */
#install{display:none}
#install.show{display:inline-flex}
@media (max-width:620px){
  .hd{min-height:110px}
  #install{display:inline-flex;visibility:hidden}
  #install.show{visibility:visible}
}
/* Touch sizing (see the pointer:coarse block further down) puts the wrapped
   control row at 44px, so the reserve above under-shoots by ~9px and the CLS
   it exists to prevent comes back. 12px padding x2 + 36px brand + 16px gap
   + 44px controls = 124. Keep this in step with .btn's min-height. */
@media (max-width:620px) and (pointer:coarse){ .hd{min-height:124px} }
.brand{display:flex;align-items:center;gap:var(--s3);min-width:0}
.mark{width:36px;height:36px;border-radius:11px;flex:none;box-shadow:var(--shadow)}
.brand h1{margin:0;font-size:16px;font-weight:680;letter-spacing:-.02em}
.brand p{margin:0;font-size:11.5px;color:var(--fg-3)}
.hd-r{margin-left:auto;display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap}
.badge{display:inline-flex;align-items:center;gap:var(--s2);padding:6px var(--s3);
  border-radius:99px;border:1px solid var(--line);background:var(--surface);
  font-size:11.5px;font-family:var(--mono);color:var(--fg-2);white-space:nowrap}
.badge b{color:var(--fg);font-weight:600}
.badge .pulse{width:6px;height:6px;border-radius:50%;background:var(--ok);flex:none}
.badge.off .pulse{background:var(--warn)}
.btn{display:inline-flex;align-items:center;gap:var(--s2);padding:8px var(--s3);
  border-radius:var(--r-sm);border:1px solid var(--line);background:var(--surface-2);
  cursor:pointer;font-size:13px;font-weight:550;transition:var(--t);white-space:nowrap}
.btn:hover:not(:disabled){background:var(--surface-3);border-color:#2e3548;transform:translateY(-1px)}
.btn:active:not(:disabled){transform:translateY(0)}
.btn:disabled{opacity:.55;cursor:not-allowed}
.btn-a{background:var(--accent);border-color:var(--accent);color:#04241f;font-weight:650}
.btn-a:hover:not(:disabled){background:#3ee0cc;border-color:#3ee0cc}
.btn-ico{width:36px;height:36px;padding:0;justify-content:center;font-size:15px}
.spin{display:inline-block;animation:sp 900ms linear infinite}
@keyframes sp{to{transform:rotate(360deg)}}
#bar{height:2px;background:transparent;overflow:hidden}
#bar i{display:block;height:100%;width:0;transition:width 400ms ease;
  background:linear-gradient(90deg,var(--accent),var(--accent-2))}
/* floating back-to-top button */
#topbtn{position:fixed;right:18px;bottom:18px;z-index:60;width:44px;height:44px;
  border-radius:14px;border:1px solid var(--line);background:var(--surface-2);
  color:var(--fg);font-size:17px;cursor:pointer;display:grid;place-items:center;
  box-shadow:0 8px 24px rgba(0,0,0,.35);opacity:0;visibility:hidden;
  transform:translateY(8px);transition:var(--t)}
#topbtn.show{opacity:1;visibility:visible;transform:none}
#topbtn:hover{background:var(--surface-3);border-color:var(--accent);color:var(--accent)}

/* sticky section nav */
nav.sections{position:sticky;top:var(--hdr,61px);z-index:39;background:rgba(10,12,16,.86);
  backdrop-filter:blur(14px);border-bottom:1px solid var(--line-soft)}
nav.sections ul{max-width:1240px;margin:0 auto;padding:var(--s2) var(--s6);
  display:flex;gap:var(--s1);list-style:none;overflow-x:auto;scrollbar-width:none}
nav.sections ul::-webkit-scrollbar{display:none}
nav.sections a{display:flex;align-items:center;gap:var(--s2);padding:7px var(--s3);
  border-radius:var(--r-sm);color:var(--fg-3);font-weight:550;font-size:13px;
  white-space:nowrap;transition:var(--t);border:1px solid transparent}
nav.sections a:hover{color:var(--fg-2);background:var(--surface);text-decoration:none}
nav.sections a[aria-current="true"]{color:var(--fg);background:var(--surface-2);border-color:var(--line)}
.dot{width:6px;height:6px;border-radius:50%;background:var(--fg-3);flex:none;transition:var(--t)}
.dot.ok{background:var(--ok)} .dot.err{background:var(--danger)}
.dot.load{background:var(--warn);animation:pulse 1.1s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.2}}
/* live traffic marquee: a slim ticker strip under the nav, amber-accented
   like the warnings so it reads as "heads up", scrolling continuously and
   pausing on hover/focus. Two duplicated .traffic-run spans translate 0→-50%
   for a seamless loop; the run is 2x content by construction. */
.traffic-band{display:flex;align-items:center;gap:var(--s3);max-width:1240px;margin:0 auto;
  padding:var(--s1) var(--s6);overflow:hidden;border-bottom:1px solid var(--line-soft);
  background:linear-gradient(90deg,rgba(251,191,36,.08),transparent 55%)}
.traffic-tag{flex:none;display:flex;align-items:center;justify-content:center;width:22px;height:22px;
  border-radius:var(--r-sm);background:var(--warn-dim);color:var(--warn);
  border:1px solid color-mix(in srgb,var(--warn) 30%,transparent)}
.traffic-mq{flex:1;overflow:hidden;white-space:nowrap;font-size:12.5px;color:var(--fg-2);
  mask-image:linear-gradient(90deg,transparent,#000 4%,#000 96%,transparent)}
.traffic-run{display:inline-block;padding-right:3.2em;animation:mq 90s linear infinite;
  will-change:transform}
.traffic-item{display:inline-block;padding:2px 0}
.traffic-item b{color:var(--fg);font-weight:600;font-variant-numeric:tabular-nums;margin-right:.35em}
.traffic-sep{display:inline-block;margin:0 .9em;color:var(--warn);font-size:9px;vertical-align:middle}
.traffic-link{color:var(--accent);text-decoration:none;font-weight:600;margin-left:.3em;
  padding:0 3px;border-radius:var(--r-sm);background:var(--accent-dim)}
.traffic-link:hover{text-decoration:underline;color:var(--fg)}
.traffic-band:hover .traffic-run,.traffic-band:focus-within .traffic-run{animation-play-state:paused}
@keyframes mq{to{transform:translateX(-50%)}}
/* Active-alert count on a nav item. Only Warnings uses it today; it is set
   from renderHazards()'s own alertN, so the nav and the tile can never
   disagree. Hidden at zero - a "0" badge reads as an alert at a glance. */
.nav-badge{min-width:17px;height:17px;padding:0 5px;border-radius:99px;flex:none;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--danger);color:#fff;font-size:10.5px;font-weight:700;
  font-family:var(--mono);line-height:1}
.nav-badge[hidden]{display:none}

/* ═══════════════════════════ hero ═══════════════════════════ */
.hero{padding:var(--s8) 0 var(--s6)}
.hero h2{margin:0 0 var(--s2);font-size:30px;font-weight:720;letter-spacing:-.035em;line-height:1.15}
.hero p{margin:0;color:var(--fg-2);font-size:14.5px;max-width:62ch}
/* Above 1100px the copy caps out at 62ch and would leave the right half of
   the hero empty, so the Travel Outlook band moves up beside it and the daily
   brief fills the space under the copy. Below that the band goes back to a
   full-width block under the copy. */
.hero .travel-band{margin-top:var(--s6)}
@media (min-width:1100px){
  .hero{display:flex;gap:var(--s8);align-items:flex-start}
  .hero-copy{flex:1 1 0;min-width:0}
  .hero .travel-band{flex:1.15 1 0;min-width:0;margin-top:6px}
}
.hero-loc{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;min-height:40px}
.hero-loc .loc-chip{text-decoration:none}
.hero-loc .loc-chip:hover{text-decoration:none;border-color:rgba(45,212,191,.55)}
/* Location chip and postcode search are one control group, so they sit
   together on the left. They used to be pushed to opposite ends of a full
   width row, which read as two unrelated widgets with a gap between them. */
.hero-loc-row{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;
  margin-top:var(--s4)}
.hero-loc-row .pos-util{margin-top:0}
/* The brief lives in the hero's copy column on desktop (filling the space
   under the location row) and directly under the KPI row on small screens. */
.hero .brief{margin-top:var(--s4)}
/* compact postcode utility */
.pos-util{position:relative;display:flex;align-items:center;gap:var(--s2);max-width:420px;flex:1;min-width:240px}
.pos-util .pos-ico{font-size:13px;color:var(--fg-3)}
.pos-util .inp{flex:1;min-width:0;padding:8px var(--s3);border-radius:var(--r-sm);
  border:1px solid var(--line);background:var(--surface);color:var(--fg);
  font-size:13px;transition:var(--t)}
.pos-util .inp:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(45,212,191,.18)}
.pos-util .pos-count{font-size:11px;font-family:var(--mono);color:var(--fg-3);white-space:nowrap}
.pos-panel{position:absolute;top:calc(100% + 6px);left:0;right:0;z-index:50;
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r);
  box-shadow:0 12px 32px rgba(0,0,0,.45);max-height:300px;overflow:auto}
.pos-panel table{width:100%;border-collapse:collapse}
.pos-panel td{padding:7px var(--s3);font-size:12.5px;border-bottom:1px solid var(--line-soft)}
.pos-panel td.num{font-family:var(--mono);font-weight:650;white-space:nowrap}
.pos-panel tr:last-child td{border-bottom:none}
.pos-panel tr:hover td{background:var(--surface-3)}
.pos-panel .state{text-align:center;color:var(--fg-3);padding:var(--s4)}
/* In-section KPI cards (fuel, groceries, tourism, health, …): a labelled value
   with a sub-line. The hero KPI strip is gone - Travel Outlook holds that slot
   now - so these rules serve the sections only; there is no .kpis grid. */
.kpi{background:linear-gradient(180deg,var(--surface),var(--bg-2));
  border:1px solid var(--line-soft);border-radius:var(--r);padding:var(--s4);
  box-shadow:var(--shadow);transition:var(--t)}
.kpi:hover{transform:translateY(-2px);border-color:var(--line)}
.kpi .lab{font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;
  color:var(--fg-3);font-weight:650;display:inline-flex;align-items:center;gap:5px}
.kpi .lab .ico{width:13px;height:13px;stroke-width:2}
/* the value/sub shimmer while a KPI is still pending - sized to the type it
   replaces so cards do not resize when the number lands */
.kpi-load{pointer-events:none}
.skel-val{display:block;height:28px;width:62%;margin:6px 0 0;border-radius:7px}
.skel-sub{display:block;height:17px;width:82%;margin:5px 0 0}
.kpi-load{min-height:123px}
.kpi .val{font-size:25px;font-weight:700;letter-spacing:-.03em;margin-top:6px;line-height:1.1}
.kpi .sub{font-size:11.5px;color:var(--fg-3);margin-top:var(--s1)}
.kpi-t{display:flex;align-items:center;justify-content:space-between;gap:var(--s2)}
.up{color:var(--danger)} .down{color:var(--ok)} .neutral{color:var(--fg-3)}
/* ── latest exchange rate (dropdown + hero) ── */
.fx-row{display:flex;align-items:center;justify-content:space-between;gap:var(--s4);flex-wrap:wrap}
.fx-pick{display:flex;align-items:baseline;gap:var(--s3);flex-wrap:wrap}
.fx-code{font-size:12px;font-weight:700;letter-spacing:.06em;color:var(--fg-2);
  text-transform:uppercase;background:var(--bg-2);border:1px solid var(--line);
  padding:3px 9px;border-radius:var(--r-xs)}
.fx-hero{display:flex;align-items:baseline;gap:var(--s3);flex-wrap:wrap}
.fx-hero .val{font-size:32px;font-weight:700;letter-spacing:-.03em;line-height:1}
.fx-hero .chg{font-size:13.5px;font-weight:650}
.fx-pick .sub{font-size:12px;color:var(--fg-3)}
.fx-select{font:inherit;font-size:13.5px;padding:7px 12px;border-radius:var(--r-sm);
  border:1px solid var(--line);background:var(--bg-2);color:var(--fg);cursor:pointer}

/* ═══════════════════════════ sections ═══════════════════════════ */
section{padding-top:var(--s8)}
.sec-h{display:flex;align-items:flex-start;gap:var(--s3);margin-bottom:var(--s4);flex-wrap:wrap}
/* A sub-block inside a section (flood risk under Weather & Warnings):
   separated from the section body above with padding + a soft rule. */
.sec-sub{margin-top:var(--s8);padding-top:var(--s6);border-top:1px solid var(--line-soft)}
.sec-ico{width:38px;height:38px;border-radius:11px;flex:none;display:grid;place-items:center;
  background:var(--accent-dim);border:1px solid rgba(45,212,191,.22);font-size:18px}
.sec-h h3{margin:0;font-size:20px;font-weight:680;letter-spacing:-.025em}
.sec-h p{margin:var(--s1) 0 0;color:var(--fg-2);font-size:13.5px;max-width:76ch}
.sec-time{margin-left:auto;flex:none;font-size:11px;font-family:var(--mono);color:var(--fg-3);
  background:var(--bg-2);border:1px solid var(--line-soft);border-radius:99px;padding:4px 10px;
  white-space:nowrap}
.sec-time.loading{color:var(--warn)}
.sec-time.error{color:var(--danger)}
details.meta{margin-top:var(--s2)}
details.meta summary{cursor:pointer;font-size:12px;color:var(--fg-2);display:inline-flex;
  align-items:center;gap:4px;user-select:none}
details.meta summary:hover{color:var(--fg)}
details.meta ul{margin:var(--s2) 0 0;padding-left:18px;color:var(--fg-3);font-size:12px}
details.meta code{font-family:var(--mono);font-size:12px;color:var(--fg-2)}
details.meta p{margin:var(--s2) 0 0;color:var(--fg-3);font-size:12px;max-width:76ch}

.card{background:var(--surface);border:1px solid var(--line-soft);border-radius:var(--r);
  box-shadow:var(--shadow);overflow:hidden;transition:var(--t)}
.card:hover{border-color:var(--line)}
.card-h{padding:var(--s3) var(--s4);border-bottom:1px solid var(--line-soft);
  display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap}
.card-h h4{margin:0;font-size:13.5px;font-weight:640}
.card-h .sub{font-size:11.5px;color:var(--fg-3);font-family:var(--mono)}
.card-h .right{margin-left:auto;display:flex;gap:var(--s2);align-items:center;flex-wrap:wrap}
.card-b{padding:var(--s4)}
.grid{display:grid;gap:var(--s3)}
.g2{grid-template-columns:repeat(2,1fr)} .g3{grid-template-columns:repeat(3,1fr)}
.g4{grid-template-columns:repeat(4,1fr)} .g5{grid-template-columns:repeat(5,1fr)}
.mb{margin-bottom:var(--s3)}
.chart{position:relative;height:300px}
.chart.tall{height:340px}
details.dt{margin-top:var(--s2);border-top:1px dashed var(--line-soft);padding-top:var(--s2)}
details.dt summary{cursor:pointer;font-size:11.5px;color:var(--fg-2);user-select:none}
details.dt summary:hover{color:var(--fg)}
details.dt .dt-body{margin-top:var(--s2)}
/* health: one state picker drives every chart in the section, so it lives in
   a bar above them rather than being repeated in four card headers */
.hbar{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;
  background:var(--surface);border:1px solid var(--line-soft);border-radius:var(--r);
  padding:var(--s3) var(--s4);box-shadow:var(--shadow)}
.hbar .hstamp{font-size:11.5px;color:var(--fg-3);font-family:var(--mono)}
.hbar .right{margin-left:auto;display:flex;gap:var(--s2);align-items:center}
.hbar label{font-size:11.5px;color:var(--fg-2);white-space:nowrap}
.hbar select{width:auto;max-width:240px}
.credit{margin-top:var(--s3);font-size:11.5px;color:var(--fg-3)}
.credit a{color:var(--fg-2)}
/* stops tables: map link instead of raw coordinates */
a.maplink{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:550;
  color:var(--accent);text-decoration:none;white-space:nowrap;padding:3px 8px;
  border:1px solid rgba(45,212,191,.25);border-radius:99px;transition:var(--t)}
a.maplink:hover{background:rgba(45,212,191,.12);text-decoration:none;border-color:rgba(45,212,191,.5)}
/* trend radar cards */
.radar-grid{display:grid;gap:var(--s3);grid-template-columns:repeat(2,1fr)}
@media (max-width:820px){.radar-grid{grid-template-columns:1fr}}
.radar-card{position:relative}
.radar-card .rank{flex:none;width:26px;height:26px;border-radius:8px;display:grid;place-items:center;
  background:var(--bg-2);border:1px solid var(--line);font-family:var(--mono);
  font-size:11.5px;font-weight:700;color:var(--fg-2)}
.radar-card h4{margin:0;font-size:14px;line-height:1.35}
.radar-card .fc-badge{flex:none;font-size:10.5px;font-weight:650;padding:3px 8px;border-radius:99px;
  border:1px solid var(--line);color:var(--fg-2);background:var(--bg-2);text-transform:uppercase;letter-spacing:.04em}
.radar-card .fc-badge.ok{color:#34d399;border-color:rgba(52,211,153,.4);background:rgba(52,211,153,.08)}
.radar-card .fc-badge.err{color:#f87171;border-color:rgba(248,113,113,.4);background:rgba(248,113,113,.08)}
.radar-debunk{border-color:rgba(248,113,113,.35)}
.radar-meta{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;margin:6px 0 var(--s2);font-size:12px}
.radar-meta .pill{font-size:10.5px}
.radar-meta .dim{font-size:12px}
.fc-verdict{flex:none;font-size:11px;font-weight:650;color:var(--fg);background:var(--surface-2);
  border:1px solid var(--line);padding:3px 8px;border-radius:99px;text-transform:capitalize}
.fc-reason{margin:0 0 var(--s2);font-size:12.5px;color:var(--fg-2);line-height:1.5}
.radar-src{display:flex;justify-content:space-between;gap:var(--s2);padding:5px 0;font-size:12.5px}
.radar-src a{color:var(--accent);text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.radar-src a:hover{text-decoration:underline}
.radar-src .dim{flex:none}
/* trend radar carousel band */
/* Daily briefing band. Deliberately quieter than the radar band above it:
   this is a summary of numbers already on the page, not a headline of its own,
   and machine-written text should not shout. */
.brief{background:var(--bg-2);border:1px solid var(--line-soft);
  border-radius:var(--r);padding:var(--s4);margin:0 0 var(--s2)}
.brief-h{display:flex;align-items:baseline;gap:var(--s3);flex-wrap:wrap;
  margin-bottom:var(--s3)}
.brief-h h3{margin:0;font-size:15px;font-weight:660;letter-spacing:-.02em}
.brief p{margin:var(--s1) 0 0;font-size:13px;line-height:1.5;color:var(--fg-2)}
#wx-prose{margin-top:var(--s2)}
#wx-prose .brief-h{align-items:center;gap:var(--s2)}
#wx-prose .sec-ico{width:34px;height:34px;border-radius:10px}
/* Today in brief carries the same badge as the card above it: the two sit in
   one stack, and only one of them having an icon read as an oversight. The
   header is baseline-aligned for its wrapping note, so the badge centres
   itself rather than hanging off the text baseline. */
#brief-band .brief-h{align-items:center;gap:var(--s2)}
#brief-band .sec-ico{width:34px;height:34px;border-radius:10px;flex:none}
/* Animated sky behind the next-hours card. Two pseudo-element layers over the
   normal card background: ::before is a slow-drifting colour wash keyed to the
   WMO class, ::after the motion (falling drops, drifting cloud, sun glow).
   Both are tints - the card keeps var(--bg-2) underneath, so the same rules
   read correctly in either theme and the body text keeps its contrast. All of
   it is CSS, so the global prefers-reduced-motion block freezes it flat. */
#wx-prose{position:relative;overflow:hidden;isolation:isolate;
  --wx-a:transparent;--wx-b:transparent;--wx-wash:0;--wx-drop:rgba(147,197,253,.55)}
#wx-prose>*{position:relative;z-index:1}
#wx-prose::before,#wx-prose::after{content:"";position:absolute;z-index:0;
  pointer-events:none;background-repeat:repeat}
#wx-prose::before{inset:0;opacity:var(--wx-wash);background-size:220% 220%;
  background-image:linear-gradient(125deg,var(--wx-a),transparent 58%,var(--wx-b));
  animation:wxWash 20s ease-in-out infinite}
@keyframes wxWash{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
/* the motion layer is oversized so the rain slant never bares a corner */
#wx-prose::after{inset:-30%;opacity:0}
html[data-theme="light"] #wx-prose{--wx-drop:rgba(37,99,235,.4)}

/* clear / partly: warm wash with a soft sun glow that breathes */
#wx-prose.wx-clear{--wx-a:rgba(251,191,36,.3);--wx-b:rgba(56,189,248,.18);--wx-wash:.55}
#wx-prose.wx-partly{--wx-a:rgba(251,191,36,.18);--wx-b:rgba(125,155,190,.22);--wx-wash:.55}
#wx-prose.wx-clear::after,#wx-prose.wx-partly::after{opacity:.7;
  background-image:radial-gradient(circle at 84% 16%,rgba(253,224,71,.35),transparent 42%);
  animation:wxGlow 9s ease-in-out infinite}
@keyframes wxGlow{0%,100%{opacity:.45}50%{opacity:.85}}
/* overcast / fog: grey wash with a cloud band drifting sideways. The 50%-wide
   repeating layer means a 0→50% position shift loops seamlessly. */
#wx-prose.wx-overcast{--wx-a:rgba(148,163,184,.28);--wx-b:rgba(100,116,139,.2);--wx-wash:.6}
#wx-prose.wx-fog{--wx-a:rgba(203,213,225,.3);--wx-b:rgba(148,163,184,.22);--wx-wash:.6}
#wx-prose.wx-overcast::after,#wx-prose.wx-fog::after{opacity:.55;background-size:50% 100%;
  background-image:radial-gradient(60% 45% at 30% 40%,rgba(226,232,240,.3),transparent 70%),
    radial-gradient(45% 35% at 75% 65%,rgba(226,232,240,.22),transparent 70%);
  animation:wxCloud 26s linear infinite}
@keyframes wxCloud{to{background-position:50% 0}}
/* drizzle / rain / showers / storm: falling drops. Two layers at different
   tile sizes and speeds give depth; each tile height equals its travel per
   cycle, so the loop has no seam. */
#wx-prose.wx-drizzle{--wx-a:rgba(96,165,250,.2);--wx-b:rgba(71,85,105,.22);--wx-wash:.6}
#wx-prose.wx-rain,#wx-prose.wx-showers{--wx-a:rgba(59,130,246,.26);--wx-b:rgba(51,65,85,.28);--wx-wash:.65}
#wx-prose.wx-storm{--wx-a:rgba(129,140,248,.3);--wx-b:rgba(30,41,59,.34);--wx-wash:.7}
#wx-prose.wx-snow{--wx-a:rgba(191,219,254,.26);--wx-b:rgba(148,163,184,.2);--wx-wash:.55}
#wx-prose.wx-drizzle::after,#wx-prose.wx-rain::after,
#wx-prose.wx-showers::after,#wx-prose.wx-storm::after{
  background-image:radial-gradient(1px 7px at 50% 50%,var(--wx-drop),transparent 72%),
    radial-gradient(1px 5px at 50% 50%,var(--wx-drop),transparent 72%);
  background-size:17px 26px,29px 38px;transform:rotate(9deg);
  animation:wxFall 1s linear infinite}
@keyframes wxFall{to{background-position:0 26px,0 38px}}
#wx-prose.wx-drizzle::after{opacity:.4;animation-duration:1.6s}
#wx-prose.wx-rain::after,#wx-prose.wx-showers::after{opacity:.6}
#wx-prose.wx-storm::after{opacity:.75;animation-duration:.7s}
/* Lightning: a double blink once every 8s, riding on the same wash layer.
   wxWash drives background-position and wxFlash opacity/filter, so the two
   compose. Deliberately two short blinks rather than a repeating strobe -
   long enough to read as a storm, far too brief and infrequent to nag. */
#wx-prose.wx-storm::before{animation:wxWash 20s ease-in-out infinite,wxFlash 8s linear infinite}
@keyframes wxFlash{
  0%,88%,100%{opacity:var(--wx-wash);filter:none}
  88.6%{opacity:1;filter:brightness(2.6) saturate(.5)}
  89.4%{opacity:var(--wx-wash);filter:none}
  90.2%{opacity:.95;filter:brightness(2) saturate(.6)}
  91%{opacity:var(--wx-wash);filter:none}}
#wx-prose.wx-snow::after{opacity:.5;
  background-image:radial-gradient(2px 2px at 50% 50%,rgba(255,255,255,.7),transparent 70%);
  background-size:26px 34px;animation:wxSnow 6s linear infinite}
@keyframes wxSnow{to{background-position:12px 34px}}
/* Holiday + school chips: one slim row inside the Travel Outlook band. */
.hol-row{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;
  margin:var(--s2) 0}
/* Travel Outlook: peak travel periods as a scroll-snap carousel, the same
   interaction as the radar and hazard-alert decks. It was a five-row stacked
   list, which in the hero column ran taller than the copy beside it; one row
   of cards fits the same five periods in a third of the height. Impact badges
   are traffic-light colour-coded and the dates line is mono so the cards
   scan as a timeline left to right. */
.t-carousel{position:relative}
.t-track{display:flex;gap:var(--s3);overflow-x:auto;scroll-snap-type:x mandatory;
  scrollbar-width:none;list-style:none;margin:0;padding:0 0 2px}
.t-track::-webkit-scrollbar{display:none}
/* Impact is carried by the card, not by a chip in front of the title: a pill
   of varying width pushed every driver name to a different x and the column
   of titles read as ragged. Left rule + tint, the same language the weather
   alert cards use. */
.t-row{flex:0 0 268px;scroll-snap-align:start;display:flex;flex-direction:column;
  gap:6px;padding:var(--s3) var(--s4);background:var(--bg-2);
  border:1px solid var(--line-soft);border-left:4px solid var(--fg-3);
  border-radius:var(--r)}
.t-row.t-extreme{border-left-color:#f87171;
  background:linear-gradient(180deg,rgba(248,113,113,.09),var(--bg-2) 60%)}
.t-row.t-high{border-left-color:#fbbf24;
  background:linear-gradient(180deg,rgba(251,191,36,.09),var(--bg-2) 60%)}
.t-row.t-moderate{border-left-color:#34d399;
  background:linear-gradient(180deg,rgba(52,211,153,.07),var(--bg-2) 60%)}
.t-ctl{display:flex;align-items:center;justify-content:flex-end;gap:var(--s2);
  margin-top:var(--s2)}
.t-dates{display:flex;align-items:center;gap:var(--s2);font-family:var(--mono);
  font-size:11.5px;color:var(--fg-3);line-height:1.5}
.t-mid{min-width:0}
.t-driver{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;
  font-size:13.5px;margin-bottom:3px}
.t-driver b{font-weight:700}
/* The level still has to be readable without colour vision, so the word
   survives - as small type on the dates line, where it cannot shift the
   title. */
.t-imp{font-family:var(--mono);font-size:10px;font-weight:700;
  text-transform:uppercase;letter-spacing:.04em;margin-left:auto}
.t-imp.t-extreme{color:#f87171}
.t-imp.t-high{color:#fbbf24}
.t-imp.t-moderate{color:#34d399}
/* The outlook text is English only, in both UI languages: the collector
   prompts Gemini for one English sentence per period. A machine-translated
   second line under every card doubled the height of a hero block whose
   whole point was to be compact. */
.t-note{font-size:12.5px;color:var(--fg-2);line-height:1.5;margin-top:2px}
/* A period covering today keeps its impact colour and gets a ring instead -
   painting it amber would make a live "quiet" period look like a peak. The
   NOW marker on the dates line and the banner above say the rest. */
.t-row.t-cur{box-shadow:0 0 0 2px rgba(251,191,36,.35)}
.t-now{font-family:var(--mono);font-size:9.5px;font-weight:700;
  color:#111;background:#fbbf24;padding:1px 6px;border-radius:999px;
  margin-right:4px;letter-spacing:.05em}
.t-curband{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;
  background:rgba(251,191,36,.1);border:1px solid rgba(251,191,36,.35);
  border-radius:var(--r);padding:var(--s3) var(--s4);margin-bottom:var(--s3);
  font-size:13px}
.t-curband b{display:inline-flex;align-items:center;gap:6px;color:#fbbf24}
.t-curband span{color:var(--fg-2)}
.t-curband .ico{width:15px;height:15px}
.t-tips{margin-top:var(--s3);border:1px solid var(--line-soft);
  border-radius:var(--r);background:var(--bg-2)}
.t-tips summary{cursor:pointer;font-size:12.5px;font-weight:650;
  padding:var(--s3) var(--s4);display:flex;align-items:center;gap:6px;
  color:var(--fg-1)}
.t-tips ul{margin:0;padding:0 var(--s4) var(--s3) var(--s4);list-style:disc;
  padding-left:calc(var(--s4) + 16px)}
.t-tips li{font-size:12.5px;color:var(--fg-2);line-height:1.5;margin-top:6px}
/* One card per swipe on a phone, where 232px strands half of the next card at
   the edge. 100% is the track's own width, so it never overshoots the way a
   100vw-minus-padding guess does. */
@media (max-width:620px){ .t-row{flex-basis:100%} }
.hol-chip{display:inline-flex;align-items:center;gap:6px;
  font-family:var(--mono);font-size:11.5px;line-height:1;
  padding:5px 10px;border-radius:999px;border:1px solid var(--line-soft);
  background:var(--bg-2);color:var(--fg-2)}
.hol-chip.hol-next{color:var(--fg-1)}
.hol-chip.hol-school{color:var(--fg-3)}
.hol-chip b{font-weight:700}
.hol-chip.hol-today{border-color:rgba(251,191,36,.45);
  background:rgba(251,191,36,.08);color:#fbbf24}
/* Chips that open a list. They are <button>, so reset the inherited control
   styling and mark them as pressable; the caret is the only affordance that
   separates them from the static "Today" chip. */
button.hol-chip{font-family:var(--mono);cursor:pointer;transition:var(--t)}
button.hol-chip:hover{border-color:var(--line);color:var(--fg)}
button.hol-chip:focus-visible{outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px var(--accent-dim)}
button.hol-chip::after{content:"▾";font-size:9px;opacity:.6;margin-left:1px}
button.hol-chip[aria-expanded="true"]::after{content:"▴"}
.hol-pop{position:relative;display:inline-flex}
.hol-panel{position:absolute;top:calc(100% + 6px);left:0;z-index:40;
  min-width:250px;max-width:min(340px,86vw);padding:var(--s2);
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-sm);box-shadow:0 12px 30px rgba(0,0,0,.28)}
html[data-theme="light"] .hol-panel{box-shadow:0 12px 30px rgba(17,21,29,.12)}
.hol-panel-h{margin:0 0 4px;padding:0 6px;font-size:10.5px;font-weight:700;
  letter-spacing:.04em;text-transform:uppercase;color:var(--fg-3)}
.hol-panel ul{list-style:none;margin:0;padding:0}
.hol-panel li{display:flex;align-items:baseline;justify-content:space-between;
  gap:var(--s3);padding:6px;border-radius:6px;font-size:12px}
.hol-panel li+li{border-top:1px solid var(--line-soft)}
.hol-panel .hol-n{color:var(--fg-1);line-height:1.35}
.hol-panel .hol-m{flex:none;font-family:var(--mono);font-size:10.5px;
  color:var(--fg-3);white-space:nowrap}
.hol-empty{margin:0;padding:6px;font-size:12px;color:var(--fg-3)}
/* Near the right edge the panel would hang off-screen; on narrow viewports
   pin it to the row's left edge instead of the chip's. */
@media (max-width:560px){
  .hol-pop{position:static}
  .hol-panel{left:var(--s4);right:var(--s4);max-width:none;min-width:0}
}
/* District ethnicity mini-bar: a stacked 6-segment bar, one per ETHNIC
   group, width = share of the district's population. 160 rows render as
   plain spans - no canvas, no layout thrash. */
.ethbar{display:inline-flex;height:7px;width:88px;border-radius:4px;
  overflow:hidden;vertical-align:middle;margin-left:6px;
  background:var(--line-soft)}
.ethseg{height:100%;display:block;min-width:0}
/* A whole card collapsed behind its own header (the Places constituency
   tables). Safari draws its native marker unless it is explicitly removed -
   `list-style:none` alone leaves a triangle sitting next to our own hint. */
.acc-sum{cursor:pointer;list-style:none}
.acc-sum::-webkit-details-marker{display:none}
.acc-hint::after{content:"\25BE  " attr(data-closed)}
details[open] > .acc-sum .acc-hint::after{content:"\25B4  " attr(data-open)}
/* Affluent-vs-poorest comparison strip (Places view). */
.cmp-row{display:grid;grid-template-columns:1fr auto 1fr;gap:var(--s3);
  align-items:stretch}
.cmp{border:1px solid var(--line-soft);border-radius:var(--r2);
  padding:var(--s3) var(--s4);background:var(--bg-2)}
.cmp-tag{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--fg-3);margin-bottom:6px}
.cmp-name{font-size:14px;font-weight:650;color:var(--fg-1)}
.cmp-st{color:var(--fg-3);font-weight:500}
.cmp-val{font-family:var(--mono);font-size:21px;font-weight:700;
  margin-top:2px}
.cmp-aff .cmp-val{color:#34d399}
.cmp-poor .cmp-val{color:#fbbf24}
.cmp-sub{font-size:11px;color:var(--fg-3);margin-top:4px}
.cmp-gap{display:flex;flex-direction:column;justify-content:center;
  align-items:center;gap:2px;min-width:52px}
.cmp-gap-v{font-family:var(--mono);font-size:18px;font-weight:700;
  color:var(--fg-1)}
.cmp-gap-l{font-size:10px;color:var(--fg-3);text-transform:uppercase;
  letter-spacing:.06em}
@media (max-width:620px){ .cmp-row{grid-template-columns:1fr}
  .cmp-gap{flex-direction:row;min-width:0;padding:4px 0} }
.brief-note{font-family:var(--mono);font-size:11px;color:var(--fg-3)}
.brief-list{list-style:none;margin:0;padding:0;display:grid;gap:var(--s2)}
.brief-list li{display:flex;align-items:flex-start;gap:var(--s2);font-size:13.5px;
  line-height:1.5;color:var(--fg-2)}
.brief-list li::before{content:"";flex:0 0 5px;width:5px;height:5px;margin-top:7px;
  border-radius:50%;background:var(--accent)}
.brief-list a{color:inherit;text-decoration:none;border-bottom:1px solid transparent}
.brief-list a:hover{color:var(--fg);border-bottom-color:var(--line)}
.radar-band{background:linear-gradient(180deg,var(--surface),var(--bg-2));
  border:1px solid var(--line-soft);border-radius:var(--r);padding:var(--s4);
  box-shadow:var(--shadow);margin:0 0 var(--s2)}
.radar-band-h{display:flex;align-items:flex-start;gap:var(--s3);flex-wrap:wrap;margin-bottom:var(--s3)}
.radar-band-t{display:flex;align-items:flex-start;gap:var(--s3);min-width:0}
.radar-band-t h3{margin:0;font-size:18px;font-weight:680;letter-spacing:-.025em}
.radar-band-t p{margin:var(--s1) 0 0;color:var(--fg-2);font-size:12.5px;max-width:70ch}
.radar-band-ctl{margin-left:auto;display:flex;align-items:center;gap:var(--s2)}
.radar-count{font-family:var(--mono);font-size:11.5px;color:var(--fg-3);white-space:nowrap}
/* collection stamp sits left of the slide counter, divided so the two
   monospaced runs never read as one string */
#radar-when:not(:empty){padding-right:var(--s2);margin-right:2px;
  border-right:1px solid var(--line)}
.radar-track{display:flex;gap:var(--s3);overflow-x:auto;scroll-snap-type:x mandatory;
  scrollbar-width:none;padding-bottom:2px}
.radar-track::-webkit-scrollbar{display:none}
/* These tracks hide their scrollbar, so a card clipped at the right edge is
   the only hint that the row scrolls - and it reads as a layout bug rather
   than an affordance. The mask fades the cut instead. data-end is set by the
   scroll handler so the fade disappears once you reach the end. */
.radar-track,.wx-track,.t-track,.metro-scroll,nav.sections ul{
  -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 56px),transparent);
  mask-image:linear-gradient(90deg,#000 calc(100% - 56px),transparent)}
.radar-track[data-end="1"],.wx-track[data-end="1"],.t-track[data-end="1"],
.metro-scroll[data-end="1"],nav.sections ul[data-end="1"]{-webkit-mask-image:none;mask-image:none}
.radar-slide{flex:0 0 320px;scroll-snap-align:start;display:flex;gap:var(--s3);
  align-items:flex-start;text-align:left;cursor:pointer;background:var(--surface);
  border:1px solid var(--line-soft);border-radius:var(--r);padding:var(--s4);
  font:inherit;color:inherit;transition:var(--t);min-height:118px}
.radar-slide:hover{transform:translateY(-2px);border-color:var(--line);box-shadow:var(--shadow)}
.radar-slide:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.radar-slide .rank{flex:none;width:26px;height:26px;border-radius:8px;display:grid;place-items:center;
  background:var(--bg-2);border:1px solid var(--line);font-family:var(--mono);
  font-size:11.5px;font-weight:700;color:var(--fg-2)}
.radar-slide h4{margin:0;font-size:13.5px;line-height:1.35;font-weight:640}
.radar-slide .rs-body{min-width:0}
.radar-slide .rs-body p{margin:3px 0 0;font-size:12px}
.radar-slide .radar-meta{margin-top:var(--s2)}
.radar-slide .fc-badge{font-size:10px}
/* detail modal */
.rd-back{position:fixed;inset:0;z-index:80;background:rgba(4,6,10,.72);
  backdrop-filter:blur(4px);display:grid;place-items:center;padding:var(--s4)}
.rd-card{max-width:600px;width:100%;max-height:84vh;overflow:auto;position:relative;
  background:var(--surface);border:1px solid var(--line-soft);border-radius:var(--r);
  box-shadow:0 24px 64px rgba(0,0,0,.5)}
.rd-x{position:absolute;top:14px;right:14px;z-index:2;width:32px;height:32px;border-radius:10px;
  border:1px solid var(--line);background:var(--surface-2);color:var(--fg-2);font-size:13px;
  cursor:pointer;display:grid;place-items:center;transition:var(--t)}
.rd-x:hover{background:var(--surface-3);color:var(--fg);border-color:var(--line)}
.rd-head{display:flex;align-items:flex-start;gap:var(--s3);padding:var(--s6) var(--s6) var(--s4);
  border-bottom:1px solid var(--line-soft)}
.rd-head .rank{flex:none;width:30px;height:30px;border-radius:9px;display:grid;place-items:center;
  background:var(--bg-2);border:1px solid var(--line);font-family:var(--mono);
  font-size:13px;font-weight:700;color:var(--fg-2)}
.rd-head h4{margin:0;font-size:17px;line-height:1.35;letter-spacing:-.015em;font-weight:680;
  padding-right:34px}
.rd-titles{min-width:0}
.rd-en{margin:4px 0 0;color:var(--fg-3);font-size:12.5px;line-height:1.45}
.rd-head .pill{flex:none;margin:2px 44px 0 0;text-transform:capitalize}
.rd-body{padding:var(--s4) var(--s6) var(--s6)}
.rd-verdict{display:flex;align-items:center;gap:var(--s3);padding:var(--s3) var(--s4);
  border-radius:var(--r);border:1px solid var(--line-soft);margin-bottom:var(--s4)}
.rd-verdict .rd-vicon{font-size:20px;line-height:1}
.rd-vlab{font-size:10.5px;text-transform:uppercase;letter-spacing:.07em;color:var(--fg-3);font-weight:650}
.rd-vval{font-size:15px;font-weight:700;letter-spacing:.02em}
.rd-verdict.ok{background:rgba(52,211,153,.07);border-color:rgba(52,211,153,.35)}
.rd-verdict.ok .rd-vval{color:#34d399}
.rd-verdict.err{background:rgba(248,113,113,.07);border-color:rgba(248,113,113,.35)}
.rd-verdict.err .rd-vval{color:#f87171}
.rd-verdict.warn{background:rgba(251,191,36,.07);border-color:rgba(251,191,36,.35)}
.rd-verdict.warn .rd-vval{color:#fbbf24}
.rd-verdict .fc-badge{margin-left:auto}
.rd-reason{margin:0 0 var(--s3);font-size:13.5px;line-height:1.6;color:var(--fg-2)}
.rd-sb{display:inline-flex;align-items:center;gap:5px;font-size:12.5px;font-weight:600;
  color:var(--accent);text-decoration:none;margin-bottom:var(--s3)}
.rd-sb:hover{text-decoration:underline}
.rd-src-h{margin:var(--s3) 0 var(--s2);font-size:12px;color:var(--fg-2)}
.rd-srcs{margin:0;padding-left:20px;display:grid;gap:var(--s2)}
.rd-srcs li{font-size:12.5px;line-height:1.45}
.rd-srcs a{color:var(--fg);text-decoration:none;font-weight:550}
.rd-srcs a:hover{color:var(--accent);text-decoration:underline}
.rd-srcs .dim{display:block;font-size:11px;font-family:var(--mono)}
@media (max-width:560px){.radar-slide{flex-basis:calc(100vw - 64px)}}
/* LRT & MRT metro diagram */
.metro-wrap{display:flex;flex-direction:column;gap:var(--s3);margin-top:var(--s6)}
.metro-head{display:flex;align-items:baseline;gap:var(--s3);flex-wrap:wrap;padding:var(--s2) 2px 0}
.metro-head h4{margin:0}
.metro-legend{display:flex;align-items:center;gap:var(--s4);padding:0 2px;
  font-size:11px;color:var(--fg-2)}
.ml-item{display:inline-flex;align-items:center;gap:7px}
.ml-dot{width:9px;height:9px;border-radius:99px;background:var(--fg-2);flex:none;
  border:1.5px solid var(--bg);box-shadow:0 0 0 1px var(--fg-2)}
.ml-ring{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;
  border-radius:99px;border:1.5px solid var(--fg-2);flex:none}
.ml-ring .ml-dot{width:6px;height:6px;border:none;box-shadow:none}
.metro-scroll{display:flex;gap:var(--s4);overflow-x:auto;padding:var(--s2) 2px var(--s4);
  scroll-snap-type:x proximity}
.metro-col{flex:none;scroll-snap-align:start;border:1px solid var(--line-soft);
  border-radius:var(--r);background:var(--bg-2);overflow:hidden}
.metro-line-h{display:flex;align-items:center;gap:7px;padding:10px 12px;
  border-bottom:1px solid var(--line-soft);position:sticky;top:0;background:var(--bg-2);z-index:1}
.metro-line-dot{width:11px;height:11px;border-radius:99px;flex:none}
.metro-line-name{font-size:12.5px;font-weight:680;white-space:nowrap}
.metro-line-n{font-size:10.5px;color:var(--fg-3);font-family:var(--mono);margin-left:auto;white-space:nowrap}
.metro-svg{display:block}
.metro-stn{cursor:pointer}
.metro-stn .metro-lbl{font-size:9px;fill:var(--fg-2);font-weight:600;font-family:inherit}
.metro-stn .metro-lbl-s{font-size:7.5px;fill:var(--fg-3);font-family:var(--mono)}
.metro-stn:hover .metro-lbl{fill:var(--accent)}
.metro-stn:hover circle[r="4.5"]{stroke:#fff;stroke-width:2.2}
.metro-stn:focus{outline:none}
.metro-stn:focus circle[r="4.5"]{stroke:#fff;stroke-width:2.2}
/* live flights board */
.fids-meta{display:flex;align-items:center;gap:var(--s2);margin-bottom:var(--s2);
  font-size:11px;font-family:var(--mono);color:var(--fg-3)}
.live-dot{width:7px;height:7px;border-radius:50%;background:var(--ok);flex:none;
  box-shadow:0 0 0 0 rgba(52,211,153,.5);animation:livepulse 2s ease-out infinite}
@keyframes livepulse{0%{box-shadow:0 0 0 0 rgba(52,211,153,.5)}70%{box-shadow:0 0 0 7px rgba(52,211,153,0)}100%{box-shadow:0 0 0 0 rgba(52,211,153,0)}}
.fs-badge{display:inline-block;font-size:10.5px;font-weight:650;padding:3px 9px;border-radius:99px;
  border:1px solid var(--line);color:var(--fg-2);background:var(--bg-2);white-space:nowrap;text-transform:uppercase;letter-spacing:.03em}
.fs-badge.ok{color:#34d399;border-color:rgba(52,211,153,.4);background:rgba(52,211,153,.08)}
.fs-badge.warn{color:#fbbf24;border-color:rgba(251,191,36,.4);background:rgba(251,191,36,.08)}
.fs-badge.err{color:#f87171;border-color:rgba(248,113,113,.4);background:rgba(248,113,113,.08)}
@media (max-width:720px){
  #fids-card .card-h .right{width:100%;justify-content:flex-start}
  #fids-q{flex:1;min-width:0}
}
/* live map + vehicle cards */
.lvmap{position:relative;z-index:0;height:340px;background:var(--bg-2)}
html[data-theme="dark"] .lvmap .leaflet-tile-pane{filter:invert(1) hue-rotate(180deg) brightness(.95) contrast(.9) saturate(.7)}
.lvmap .leaflet-control-attribution{background:rgba(0,0,0,.55);color:var(--fg-3);font-size:10px}
.lvmap .leaflet-control-attribution a{color:var(--fg-2)}
/* Weather "Now" card: live current conditions + today line + the location map. */
.wx-now{padding-bottom:var(--s2)}
.wx-now-row{display:flex;align-items:center;gap:var(--s2)}
.wx-cond{font-size:26px;line-height:1}
.wx-temp{font-size:26px;font-weight:720;letter-spacing:-.03em;color:var(--fg)}
.wx-condlab{font-size:13px;font-weight:600;color:var(--fg-2)}
.wx-meta{display:flex;gap:var(--s2);flex-wrap:wrap;margin-top:4px;
  font-size:11.5px;color:var(--fg-2);font-family:var(--mono)}
.wx-today{margin-top:4px;font-size:12px;color:var(--fg-2)}
.wx-today b{font-weight:650;color:var(--fg)}
.wx-now .dim{margin:var(--s1) 0 0}
#wx-map{height:230px}
.wx-hours-wrap{margin-top:var(--s2)}
.wx-hours-h{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:650;
  color:var(--fg-2);margin-bottom:6px}
.wx-hours-h .ico{width:15px;height:15px;color:var(--fg-3)}
.wx-hours{display:flex;gap:6px;overflow-x:auto;scrollbar-width:none;
  padding-bottom:2px}
.wx-hours::-webkit-scrollbar{display:none}
.wx-h{flex:0 0 auto;display:inline-flex;align-items:center;gap:4px;font-size:11.5px;
  font-family:var(--mono);padding:4px 9px;border-radius:999px;
  border:1px solid var(--line-soft);background:var(--bg-2);color:var(--fg-2);white-space:nowrap}
.wx-h b{font-weight:700;color:var(--fg-1)}
.wx-h i{font-style:normal;font-weight:700;color:var(--info)}
.wx-h.wx-rain{border-color:color-mix(in srgb,var(--info) 45%,transparent);
  background:color-mix(in srgb,var(--info) 10%,transparent)}
.wx-h.wx-maybe{border-color:color-mix(in srgb,var(--info) 25%,transparent)}
.wxp{font-size:12.5px;line-height:1.5;min-width:150px}
.wxp b{display:block;margin-bottom:2px}
.wxp-cond{display:block;font-weight:650}
.wxp-meta{color:var(--fg-3);font-size:11.5px;font-family:var(--mono)}
.vgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s3)}
.vcard .card-h h4{margin-right:auto}
.vcard .vrow{display:flex;justify-content:space-between;gap:var(--s2);font-size:12.5px;padding:3px 0;color:var(--fg-2)}
/* live trains: compact chips with hover/focus tooltips instead of cards */
.vchips{display:flex;flex-wrap:wrap;gap:var(--s2)}
.vchip{position:relative;display:inline-flex;align-items:center;gap:6px;padding:4px 11px;
  border-radius:99px;border:1px solid var(--line);background:var(--bg-2);
  font-size:12px;color:var(--fg-2);cursor:help;transition:var(--t);outline:none}
.vchip:hover,.vchip:focus-visible{border-color:var(--accent);color:var(--fg)}
/* route chips: count badge + active (filtered) state - the Live section's
   Rapid feed aggregates 800+ buses into one chip per route */
.vchip .cnt{min-width:18px;padding:0 5px;border-radius:99px;background:var(--accent-dim);
  color:var(--chip-fg);font-size:10.5px;font-weight:700;line-height:16px;text-align:center}
.vchip.on{border-color:var(--accent);background:var(--accent-dim);color:var(--fg)}
.vchip.on .cnt{background:var(--accent);color:#06121a}
.vchip .tip{position:absolute;bottom:calc(100% + 10px);left:50%;transform:translateX(-50%) translateY(4px);
  width:max-content;max-width:min(280px,calc(100vw - 24px));padding:10px 12px;z-index:40;
  background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-sm);box-shadow:var(--shadow);
  opacity:0;visibility:hidden;pointer-events:none;transition:var(--t)}
.vchip:hover .tip,.vchip:focus .tip,.vchip:focus-within .tip{opacity:1;visibility:visible;transform:translateX(-50%) translateY(0);pointer-events:auto}
.vchip .tip .vrow{display:flex;justify-content:space-between;gap:var(--s2);font-size:12px;padding:2px 0;white-space:nowrap}
.vchip .tip .link-btn{display:inline-block;margin-top:6px}
@media (max-width:480px){ .vchip .tip{width:auto;white-space:normal} }

/* controls */
.seg{display:inline-flex;background:var(--bg-2);border:1px solid var(--line-soft);
  border-radius:var(--r-sm);padding:2px;gap:2px}
.seg button{padding:5px 11px;border:0;background:none;border-radius:var(--r-xs);
  font-size:11.5px;font-weight:600;color:var(--fg-3);cursor:pointer;transition:var(--t)}
.seg button:hover{color:var(--fg-2)}
.seg button[aria-pressed="true"]{background:var(--surface-3);color:var(--fg)}
.inp{width:100%;padding:9px var(--s3);border-radius:var(--r-sm);border:1px solid var(--line);
  background:var(--bg-2);color:var(--fg);font-size:13px;transition:var(--t)}
.inp::placeholder{color:var(--fg-3)}
.inp:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-dim)}
.chip{padding:4px 10px;border-radius:99px;border:1px solid var(--line);background:var(--bg-2);
  cursor:pointer;font-size:11.5px;color:var(--fg-3);transition:var(--t);white-space:nowrap}
.chip:hover{color:var(--fg-2);border-color:#2e3548}
.chip[aria-pressed="true"]{background:var(--accent-dim);border-color:rgba(45,212,191,.45);color:var(--chip-fg)}
.chips{display:flex;gap:var(--s2);flex-wrap:wrap}
.pager{display:flex;justify-content:flex-end;margin-top:var(--s2);min-height:26px}
.pager-inner{display:inline-flex;align-items:center;gap:var(--s2)}
.pg{padding:3px 10px;border-radius:8px;border:1px solid var(--line);background:var(--bg-2);
  color:var(--fg-2);font-size:12px;cursor:pointer}
.pg:disabled{opacity:.4;cursor:default}
.pg-count{font-size:12px;color:var(--fg-3)}
.badge{display:inline-block;margin-left:6px;padding:1px 6px;border-radius:99px;font-size:10px;
  color:var(--fg-3);border:1px solid var(--line);vertical-align:middle}
.swatch{display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:6px;vertical-align:middle}

/* tables */
.tw{overflow-x:auto;-webkit-overflow-scrolling:touch}
.scroll-y{max-height:430px;overflow-y:auto}
table{width:100%;border-collapse:collapse;font-size:13px}
thead th{position:sticky;top:0;z-index:1;background:var(--surface-2);text-align:left;
  padding:9px var(--s3);font-size:10.5px;text-transform:uppercase;letter-spacing:.07em;
  color:var(--fg-3);font-weight:650;border-bottom:1px solid var(--line);white-space:nowrap}
th.sortable{cursor:pointer;user-select:none;transition:var(--t)}
th.sortable:hover{color:var(--fg-2)}
th .arrow{opacity:.4;margin-left:3px}
th[aria-sort] .arrow{opacity:1;color:var(--accent)}
td{padding:9px var(--s3);border-bottom:1px solid var(--line-soft);white-space:nowrap}
/* Item names ("KUBIS BULAT (TEMPATAN) · 1kg") are long and this table sits in
   a half-width card, so let the name column wrap instead of forcing the whole
   row into a horizontal scroll. */
#px-movers td:first-child{white-space:normal;min-width:9.5rem}
tbody tr:nth-child(even){background:rgba(255,255,255,.014)}
tbody tr:last-child td{border-bottom:none}
tbody tr.sel{background:var(--accent-dim)}
tbody tr.sel td:first-child{box-shadow:inset 3px 0 0 var(--accent)}
.num{text-align:right;font-family:var(--mono);font-variant-numeric:tabular-nums}
.wrapcell{white-space:normal;min-width:210px;color:var(--fg-2)}
.pick{background:none;border:0;color:var(--fg);font:inherit;font-weight:550;padding:7px 9px;
  margin:-7px -9px;border-radius:6px;cursor:pointer;text-align:left;white-space:nowrap;
  transition:color var(--t)}
.pick:hover{color:var(--accent)}
tr.sel .pick{color:var(--chip-fg)}
.mono{font-family:var(--mono)} .dim{color:var(--fg-3)}
.pill{display:inline-block;padding:2px 8px;border-radius:99px;font-size:10.5px;
  font-weight:650;font-family:var(--mono)}
.tag{display:inline-block;padding:1px 6px;border-radius:5px;font-size:9.5px;font-weight:700;
  letter-spacing:.05em;text-transform:uppercase;background:var(--surface-3);color:var(--fg-3)}

/* Hazard status strip: one tile per live hazard (weather / earthquake /
   flood). The three used to be two cards plus a whole sub-section, each
   rendering a full body even when clear - which on a normal day is all three.
   A tile is a <details>, so the quiet state is one short row and the detail
   only exists once you ask for it. */
.hz-strip{display:grid;gap:var(--s3);
  grid-template-columns:repeat(auto-fit,minmax(240px,1fr));align-items:start}
.hz-tile{background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r2);padding:var(--s3) var(--s4)}
.hz-tile.hz-ok{background:linear-gradient(180deg,rgba(52,211,153,.05),var(--surface) 60%)}
.hz-tile.hz-on{background:linear-gradient(180deg,rgba(251,191,36,.08),var(--surface) 60%);
  border-color:rgba(251,191,36,.4)}
/* An open tile spans the strip - the flood map and the warning carousel need
   the full width, and a tall tile in one column would strand the other two. */
.hz-strip details.hz-tile[open]{grid-column:1/-1}
.hz-sum{cursor:pointer;list-style:none}
.hz-sum::-webkit-details-marker{display:none}
.hz-top{display:flex;align-items:center;gap:var(--s2);color:var(--fg-3)}
.hz-top .ico{width:15px;height:15px}
.hz-lab{font-size:11px;letter-spacing:.06em;text-transform:uppercase;font-weight:650}
.hz-mark{margin-left:auto;font-size:13px}
.hz-ok .hz-mark{color:var(--ok)} .hz-on .hz-mark{color:var(--warn)}
.hz-val{font-size:21px;font-weight:700;letter-spacing:-.01em;margin-top:6px}
.hz-sub{font-size:11.5px;color:var(--fg-3);margin-top:2px}
.hz-hint{color:var(--accent);font-weight:600;white-space:nowrap}
.hz-hint::after{content:"\00b7  " attr(data-closed) "  \25BE"}
details.hz-tile[open] .hz-hint::after{content:"\00b7  " attr(data-open) "  \25B4"}
.hz-body{margin-top:var(--s3);padding-top:var(--s3);border-top:1px solid var(--line-soft)}
.hz-body .lvmap{border-radius:var(--r2)}
/* alerts - weather warnings strip */
.alert{border:1px solid var(--line-soft);border-left:4px solid var(--warn);
  border-radius:var(--r);background:var(--surface);padding:var(--s4);box-shadow:var(--shadow)}
.alert h4{margin:0 0 var(--s2);font-size:13.5px;font-weight:650;display:flex;
  align-items:center;gap:var(--s2);flex-wrap:wrap}
.alert h4 .alert-ico{flex:none;font-size:15px;line-height:1}
.alert p{margin:0;color:var(--fg-2);font-size:12.5px}
.alert .meta{margin-top:var(--s2);font-family:var(--mono);font-size:10.5px;color:var(--fg-3)}
.alert.storm{border-left-color:var(--warn);
  background:linear-gradient(180deg,rgba(251,191,36,.07),var(--surface) 55%)}
.alert.rain,.alert.wind{border-left-color:var(--info);
  background:linear-gradient(180deg,rgba(96,165,250,.07),var(--surface) 55%)}
.alert.heat{border-left-color:var(--danger);
  background:linear-gradient(180deg,rgba(248,113,113,.08),var(--surface) 55%)}
.alert.adv{border-left-color:var(--fg-3)}
.alert.info{border-left-color:var(--ok);
  background:linear-gradient(180deg,rgba(52,211,153,.06),var(--surface) 55%)}
.wx-other{margin-top:var(--s3)}
.wx-other summary{cursor:pointer;font-size:12px;color:var(--fg-2);padding:var(--s2) 0;user-select:none}
/* weather warnings carousel */
.wx-carousel{position:relative}
.wx-track{display:flex;gap:var(--s3);overflow-x:auto;scroll-snap-type:x mandatory;
  scrollbar-width:none;padding-bottom:2px}
.wx-track::-webkit-scrollbar{display:none}
.wx-track .alert{flex:0 0 340px;scroll-snap-align:start;margin:0}
.wx-ctl{display:flex;align-items:center;justify-content:flex-end;gap:var(--s2);margin-top:var(--s2)}
@media (max-width:560px){.wx-track .alert{flex-basis:calc(100vw - 64px)}}
/* air quality comparison grid - one cell per city, worst first */
.aq-grid{display:grid;gap:var(--s2);
  grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}
.aq-cell{background:var(--surface);border:1px solid var(--line-soft);
  border-radius:var(--r);padding:var(--s3) var(--s4)}
.aq-cell .aq-name{font-size:12px;font-weight:650;color:var(--fg)}
.aq-cell .aq-val{font-size:22px;font-weight:750;letter-spacing:-.01em;margin-top:2px}
.aq-cell .aq-sub{font-size:10.5px;color:var(--fg-3);margin-top:2px}
/* quiet green "no active warnings" chip */
.chip-ok{background:rgba(52,211,153,.1);border-color:rgba(52,211,153,.32);color:var(--ok)}
/* small card footnote */
.note{display:flex;gap:var(--s2);align-items:flex-start;margin-top:var(--s3);
  font-size:11.5px;line-height:1.5;color:var(--fg-3)}
.mini-h{font-size:12px;font-weight:650;margin:var(--s4) 0 var(--s2);color:var(--fg-2)}

/* states */
.state{padding:38px var(--s6);text-align:center;color:var(--fg-3);font-size:13px}
.state .big{font-size:26px;margin-bottom:var(--s2);opacity:.6}
.state strong{display:block;color:var(--fg-2);font-size:13.5px;margin-bottom:var(--s1)}
.state.err strong{color:#fca5a5}
.skel{background:linear-gradient(90deg,var(--surface) 25%,var(--surface-3) 50%,var(--surface) 75%);
  background-size:200% 100%;animation:sh 1.5s infinite;border-radius:6px;height:12px;margin:11px 0}
/* chart-height placeholder: most section bodies lead with a 250-300px chart,
   so the loading state reserves that space to avoid a layout shift when the
   real chart swaps in. Height tracks the real .chart breakpoint. */
.skel-chart{height:300px;margin-top:18px}
/* table-height placeholder: the tall tables (scroll-y) cap at 430px */
.skel-table{height:430px}
/* details-row placeholder: cards show a 'View data table' disclosure
   under the chart; reserving it keeps the card height stable */
.skel-details{width:30%;height:14px;margin-top:16px}
@media (max-width:620px){ .skel-chart{height:250px} }
@keyframes sh{0%{background-position:200% 0}100%{background-position:-200% 0}}
.fade{opacity:0;transform:translateY(8px);animation:fade 420ms var(--t) forwards}
@keyframes fade{to{opacity:1;transform:none}}

/* location chip */
.loc{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap}
.loc-chip{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border-radius:99px;
  background:var(--accent-dim);border:1px solid rgba(45,212,191,.3);color:var(--chip-fg);font-size:12px;font-weight:600}
.loc-chip.off{background:var(--surface-2);border-color:var(--line);color:var(--fg-3)}
.link-btn{background:none;border:0;color:var(--accent);cursor:pointer;font-size:11.5px;
  padding:2px 4px;border-radius:4px}
.link-btn:hover{text-decoration:underline}

footer{border-top:1px solid var(--line-soft);margin-top:56px;padding:var(--s6) 0;
  color:var(--fg-3);font-size:12px}
.foot{display:flex;gap:var(--s6);align-items:flex-start;justify-content:space-between;flex-wrap:wrap}
.foot > div{max-width:74ch}
.bmc{display:inline-flex;align-items:center;gap:var(--s2);padding:9px var(--s4);flex:none;
  border-radius:var(--r-sm);background:#fbbf24;color:#1a1400;font-weight:700;font-size:13px;
  border:1px solid #fbbf24;transition:var(--t);white-space:nowrap;box-shadow:var(--shadow)}
.bmc:hover{background:#fcd34d;border-color:#fcd34d;transform:translateY(-1px);text-decoration:none}
@media (max-width:620px){ .foot{flex-direction:column-reverse;gap:var(--s4)} .bmc{width:100%;justify-content:center} }

@media (max-width:1000px){ .g4,.g5{grid-template-columns:repeat(2,1fr)} .vgrid{grid-template-columns:repeat(2,1fr)} }
@media (max-width:860px){ .g2,.g3{grid-template-columns:1fr} }
@media (max-width:620px){
  .wrap{padding:0 var(--s4) 64px} .hd{padding:var(--s3) var(--s4)}
  /* the hero location line wraps once the selected-area chip and the search
     field sit side by side; reserving the wrapped height avoids a CLS when
     the location status fills in. Hero-loc takes its own line at this width
     so the search field never re-wraps when the chip swaps to a button. */
  .hero-loc-row{min-height:85px}
  .hero-loc{flex:1 1 100%}
  nav.sections ul{padding:var(--s2) var(--s4)}
  .g4{grid-template-columns:1fr}
  .g5{grid-template-columns:repeat(2,1fr)}
  .hero{padding:var(--s6) 0 var(--s4)} .hero h2{font-size:23px}
  .kpi .val{font-size:21px} .chart{height:250px} .brand p{display:none}
  .vgrid{grid-template-columns:1fr} .lvmap{height:260px}
  /* the header has to stay one short row here - two sticky bars plus a wrapped
     header would eat a fifth of a phone screen. The timestamp keeps only its
     progress count, and the display toggles collapse to icons. */
  .hd{gap:var(--s3)}
  .hd-r{gap:6px}
  #stamp{padding:5px 10px}
  #stamp-t .stamp-time{display:none}
  #lang,#textsize,#theme{padding:8px 10px}
  #install span{display:none}
}
/* Touch sizing below takes both stacked children of .hero-loc-row to 44px
   (44 + 12px gap + 44), so the 85px reserve set above under-shoots by 15 and
   the wrap it exists to absorb shifts the hero copy anyway. Keep in step with
   .hero-loc/.inp in the coarse block. */
@media (max-width:620px) and (pointer:coarse){ .hero-loc-row{min-height:100px} }
@media (pointer:coarse){
  .btn{padding:11px 16px;min-height:44px;min-width:44px}
  /* .btn-ico is a fixed square, so the padding bump above cannot reach it -
     it stayed at 36px while every other control here was already sized. */
  .btn-ico{width:44px;height:44px}
  /* The nav is the primary way around the page on a phone and its rows were
     ~31px; the sticky offsets read --nav from a ResizeObserver, so growing
     it re-measures rather than desyncing scroll-padding. */
  nav.sections a{min-height:44px}
  .inp{min-height:44px}
  .seg button{min-height:40px;padding:6px 14px}
  .chip{min-height:40px;padding:9px 14px;display:inline-flex;align-items:center}
  .pick{min-height:44px;padding:11px 9px}
  /* the small text-styled controls: map links in the earthquake and stops
     tables, the location links, and every disclosure summary. All well under
     24px tall at their type size, so the hit area comes from padding. */
  a.maplink{min-height:36px;padding:8px 12px}
  .link-btn{min-height:36px;padding:7px 8px;display:inline-flex;align-items:center}
  /* The location controls are the exception to the 36px above. They are the
     page's primary CTA - and they render as .link-btn precisely in the
     denied/unavailable states, i.e. the moment the user has to hit one to
     recover - so they get the full 44px. The 36px compromise stays for the
     genuinely secondary text links (table map links, disclosure summaries),
     where inflating every row would cost more than it buys.
     .hero-loc's own reserve moves with them, or the taller control reflows
     the hero copy below it. */
  .hero-loc{min-height:44px}
  .hero-loc .link-btn, #wx-loc .link-btn{min-height:44px;padding:9px 10px}
  details.meta summary,details.dt summary,.wx-other summary{min-height:36px;
    padding:9px 0;align-items:center}
  details.dt summary,.wx-other summary{display:inline-flex}
}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important}
  .kpi:hover,.btn:hover{transform:none}
}
/* larger text mode */
html[data-text="large"] body{font-size:16px}
html[data-text="large"] .hero h2{font-size:34px}
html[data-text="large"] .hero p{font-size:16px}
html[data-text="large"] .sec-h h3{font-size:22px}
html[data-text="large"] .sec-h p{font-size:15px}
html[data-text="large"] .kpi .val{font-size:28px}
html[data-text="large"] .kpi .lab{font-size:11.5px}
html[data-text="large"] .card-h h4{font-size:15px}
html[data-text="large"] table{font-size:14px}

/* ═══════════════ on-device summary (Chrome Prompt API) ═══════════════
   Progressive enhancement, desktop Chrome 148+ only. Every node below is
   created by mountAI() and never exists in the served HTML, so browsers
   without globalThis.LanguageModel render the page exactly as before. */
.ai-btn{flex:none;font-size:11.5px;padding:5px 10px}
/* .sec-time already claims margin-left:auto; the button sits before it and
   needs its own auto margin so the pair stays right-aligned when the
   timestamp is still empty (it is, until a section finishes loading). */
.ai-btn{margin-left:auto}
.ai-btn + .sec-time{margin-left:var(--s2)}
.ai-panel{flex-basis:100%;margin-top:var(--s3);border:1px solid var(--line-soft);
  border-left:2px solid var(--accent);border-radius:var(--r-sm);
  background:var(--surface);padding:var(--s3) var(--s4);font-size:13.5px;
  color:var(--fg-2);line-height:1.6;max-width:76ch}
.ai-panel[hidden]{display:none}
.ai-panel p{margin:0 0 var(--s2)}
.ai-panel p:last-child{margin-bottom:0}
.ai-panel ul{margin:0;padding-left:1.2em}
.ai-panel li{margin-bottom:var(--s1)}
.ai-note{display:block;margin-top:var(--s3);padding-top:var(--s2);
  border-top:1px dashed var(--line-soft);font-size:11px;font-family:var(--mono);
  color:var(--fg-3)}
.ai-err{color:var(--danger)}
/* Download progress: the first run pulls a multi-GB model. */
.ai-prog{display:block;height:3px;border-radius:2px;background:var(--surface-3);
  overflow:hidden;margin-top:var(--s2)}
.ai-prog i{display:block;height:100%;background:var(--accent);width:0;
  transition:width .2s linear}
@media (prefers-reduced-motion:reduce){.ai-prog i{transition:none}}
html[data-text="large"] .ai-panel{font-size:15px}

/* ── Election results section ─────────────────────────────────────── */
.wchip{display:inline-flex;align-items:center;gap:6px;font-weight:600;white-space:nowrap}
.wchip i{width:10px;height:10px;border-radius:3px;flex:none}
.el-bar{display:flex;align-items:stretch;gap:2px;height:14px;border-radius:3px;overflow:hidden;
  background:rgba(128,128,128,.14);min-width:90px}
.el-bar .cand{flex:1;display:block;min-width:2px}
.el-bar .cand i{display:block;height:100%}
/* Seat search + state filter in the card header. .inp is width:100% by
   default, which stretches inside the flex .right row; cap it and match the
   select's metrics so the pair reads as one control group. */
#election-q{width:210px;flex:none;font-size:13.5px;padding:7px var(--s3);height:34px}
#election-state{max-width:170px}
#election-state:focus-visible{outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px var(--accent-dim)}
@media (max-width:560px){
  #election-q{width:100%}
  #election-state{max-width:none;flex:1}
}
.hv{display:flex;flex-direction:column;gap:10px}
.hv-row{display:grid;grid-template-columns:130px 1fr 44px;align-items:center;gap:10px}
.hv-lab{display:flex;align-items:center;gap:7px;font-size:12.5px;font-weight:600;white-space:nowrap}
.hv-lab i{width:10px;height:10px;border-radius:3px;flex:none}
.hv-bar{height:10px;border-radius:3px;background:rgba(128,128,128,.14);overflow:hidden}
.hv-bar i{display:block;height:100%;border-radius:3px}
.hv-num{font-size:12.5px;font-family:var(--mono);color:var(--fg-2);text-align:right}
