/* ---------------------------------------------------------------------------
   SELF-HOSTED FONTS

   Was two <link rel=preconnect> plus a render-blocking stylesheet request to
   fonts.googleapis.com, which then triggered a second request to
   fonts.gstatic.com for the files themselves. Three round trips to a third
   party before the first paint, on a site whose ranking case is speed.

   Served from our own origin instead: one hop, already-warm connection, and
   Cloudflare caches them at the edge for a year (see public/_headers).

   Variable fonts, so a single file covers every weight the design uses
   rather than one download per weight.

   font-display:swap means text paints immediately in the fallback and
   re-renders when the face lands. The alternative, `block`, gives a
   flash of invisible text that Lighthouse counts against you.

   unicode-range confines these to Latin — a Cyrillic visitor never downloads
   glyphs the page will not draw.
--------------------------------------------------------------------------- */
@font-face{
  font-family:'Inter';font-style:normal;font-display:swap;font-weight:100 900;
  src:url('/fonts/inter-latin-wght-normal.woff2') format('woff2-variations');
  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+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,
    U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Newsreader';font-style:normal;font-display:swap;font-weight:200 800;
  src:url('/fonts/newsreader-latin-opsz-normal.woff2') format('woff2-variations');
  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+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,
    U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Newsreader';font-style:italic;font-display:swap;font-weight:200 800;
  src:url('/fonts/newsreader-latin-opsz-italic.woff2') format('woff2-variations');
  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+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,
    U+2215,U+FEFF,U+FFFD;
}

/* Ported from the prototype. Same palette, same two-face type system:
   Newsreader for voice and data, Inter for interface. */
:root{
  --paper:#F7F5EF; --card:#FCFBF7; --ink:#242019; --rule:#8E8776;
  --hair:#D9D4C8; --muted:#5F594C; --accent:#A4232A; --shadow:rgba(36,32,25,.13);
  --sans:'Inter',ui-sans-serif,system-ui,-apple-system,'Segoe UI',Helvetica,Arial,sans-serif;
  --serif:'Newsreader',ui-serif,Georgia,serif;
  color-scheme:light;
}
[data-theme="dark"]{
  --paper:#171614; --card:#201F1C; --ink:#E4E0D6; --rule:#6A655C;
  --hair:#302E29; --muted:#A8A194; --accent:#3DE08A; --shadow:rgba(0,0,0,.55);
  color-scheme:dark;
}
*{box-sizing:border-box;margin:0;padding:0}
body{background:var(--paper);color:var(--ink);font-family:var(--sans);font-weight:500;
  font-size:17px;line-height:1.5;font-variant-numeric:tabular-nums lining-nums;
  -webkit-font-smoothing:antialiased;min-height:100vh;display:flex;flex-direction:column}
.wrap{width:100%;max-width:1120px;margin:0 auto;padding:0 22px}
a{color:inherit}
a:focus-visible,button:focus-visible,input:focus-visible{outline:2.5px solid var(--accent);outline-offset:3px}
.skip{position:absolute;left:-9999px;background:var(--ink);color:var(--paper);padding:12px 18px;z-index:100}
.skip:focus{left:12px;top:12px}

.mast{padding:24px 0 0}
.mh{display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;
  border-bottom:1.5px solid var(--ink);padding-bottom:9px}
.mh-t{font-family:var(--serif);font-size:23px;font-weight:700;letter-spacing:-.022em;text-decoration:none}
.mh-t em{font-style:italic;color:var(--accent)}
.mh-r{display:flex;align-items:center;gap:14px;font-size:12px;font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.theme{background:none;border:none;color:var(--muted);cursor:pointer;width:32px;height:32px;
  display:grid;place-items:center}
.theme .ico{display:none;width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.9;
  stroke-linecap:round;stroke-linejoin:round}
.theme .ico-moon{display:block}
[data-theme="dark"] .theme .ico-moon{display:none}
[data-theme="dark"] .theme .ico-sun{display:block}

main{padding:44px 0 0;flex:1}
.crumb{font-size:12.5px;font-weight:600;color:var(--muted);margin-bottom:18px}
.crumb a{text-decoration:none}
.crumb a:hover{color:var(--accent)}
.crumb span{margin:0 6px;opacity:.6}

.hero{font-family:var(--serif);font-size:clamp(29px,5.4vw,66px);font-weight:700;
  letter-spacing:-.022em;line-height:1;margin:56px 0 24px}
.hero em{font-style:italic;color:var(--accent)}
.searchwrap{position:relative;max-width:720px}
#q{width:100%;font-family:var(--serif);font-size:clamp(21px,3.2vw,34px);font-weight:500;
  color:var(--ink);background:var(--card);border:2px solid var(--rule);border-radius:20px;
  padding:.5em 1em .54em .7em}
.ac{position:absolute;top:calc(100% + 8px);left:0;right:0;background:var(--paper);
  border:1.5px solid var(--rule);border-radius:14px;overflow:hidden;z-index:20;
  box-shadow:0 10px 30px var(--shadow)}
.ac a{display:block;padding:12px 16px;font-size:17px;font-weight:600;text-decoration:none}
.ac a+a{border-top:1px solid var(--hair)}
.ac a:hover{background:var(--ink);color:var(--paper)}
.tag{font-family:var(--serif);font-size:20px;font-weight:400;color:var(--muted);margin:28px 0 0}

h1.ch-name{font-family:var(--serif);font-size:clamp(27px,4.9vw,46px);font-weight:700;
  letter-spacing:-.025em;line-height:.95}
.ch-where{font-size:12px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);margin-top:10px;padding-bottom:11px;border-bottom:1px solid var(--rule)}
.lede{font-family:var(--serif);font-size:17px;font-weight:400;line-height:1.6;margin-top:16px}

.band{display:grid;grid-template-columns:repeat(2,1fr);border-top:2.5px solid var(--ink);
  border-bottom:1px solid var(--rule);margin-top:34px}
.band-c{padding:14px 22px 16px}
.band-c:first-child{padding-left:0}
.band-c+.band-c{border-left:1px solid var(--hair)}
.big{font-family:var(--serif);font-size:clamp(34px,5.8vw,54px);font-weight:700;
  letter-spacing:-.028em;line-height:.94;margin-top:10px}
.note{font-size:14.5px;color:var(--muted);margin-top:9px;line-height:1.5}

.sec{font-family:var(--serif);font-size:19px;font-weight:700;letter-spacing:-.02em;
  color:var(--accent);margin-top:44px;border-bottom:1px solid var(--rule);padding-bottom:10px}
.lbl{font-size:11.5px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}
.tbl{display:grid;grid-template-columns:repeat(4,1fr)}
.cell{padding:13px 18px 14px;border-bottom:1px solid var(--hair);border-left:1px solid var(--hair)}
.cell:nth-child(4n+1){padding-left:0;border-left:none}
.tbl.two{grid-template-columns:repeat(2,1fr)}
/* The four-up rule must be neutralised before the two-up one is applied, or
   the third cell of a two-column grid — which does not exist — keeps the
   exemption while the genuine first cell of row two does not. */
.tbl.two .cell:nth-child(4n+1){padding-left:18px;border-left:1px solid var(--hair)}
.tbl.two .cell:nth-child(2n+1){padding-left:0;border-left:none}
.val{font-family:var(--serif);font-size:26px;font-weight:600;letter-spacing:-.018em;
  line-height:1;margin-top:9px}
.tiny{font-size:13px;color:var(--muted);margin-top:7px;line-height:1.5;max-width:34ch}
.na{color:var(--muted)}

.links{list-style:none;display:grid;grid-template-columns:repeat(4,1fr);gap:0;margin-top:2px}
.links.wide{grid-template-columns:repeat(4,1fr)}
.links li{padding:11px 16px 12px;border-bottom:1px solid var(--hair);border-left:1px solid var(--hair);
  display:flex;flex-direction:column;gap:3px}
.links li:nth-child(4n+1){padding-left:0;border-left:none}
.links a{font-family:var(--serif);font-size:17px;font-weight:600;text-decoration:none;letter-spacing:-.015em}
.links a:hover{color:var(--accent)}
.links span{font-size:12px;color:var(--muted);font-weight:500}
.more{margin-top:14px;font-size:14px}
.more a{color:var(--accent);font-weight:600;text-decoration:none}

.statetable{margin-top:20px;overflow-x:auto}
.statetable table{width:100%;border-collapse:collapse;font-size:15px}
.statetable th{text-align:left;font-size:10.5px;font-weight:800;letter-spacing:.11em;
  text-transform:uppercase;color:var(--muted);padding:0 12px 8px 0;border-bottom:1.5px solid var(--rule)}
.statetable td{padding:10px 12px 10px 0;border-bottom:1px solid var(--hair)}
.statetable td a{font-family:var(--serif);font-weight:600;text-decoration:none;font-size:16px}
.statetable td a:hover{color:var(--accent)}
.statetable .n{text-align:right;font-variant-numeric:tabular-nums}

.ad{margin-top:34px;border:1px solid var(--rule);background:var(--card);min-height:96px;
  display:grid;place-items:center;padding:18px}
.ad-note{font-size:11.5px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}

/* 142px of top padding = the original 42 plus 100. Padding rather than
   margin: margin-top is already `auto`, which is what pushes the footer
   to the bottom of a short page, and overwriting it would break that. */
footer{margin-top:auto;padding:142px 0 32px}
.foot-in{border-top:1px solid var(--rule);padding-top:14px;display:flex;
  justify-content:space-between;gap:16px;flex-wrap:wrap;font-size:12px;font-weight:600;
  letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}

@media (max-width:820px){
  .tbl,.links,.links.wide{grid-template-columns:repeat(2,1fr)}
  .cell:nth-child(4n+1),.links li:nth-child(4n+1){padding-left:18px;border-left:1px solid var(--hair)}
  .cell:nth-child(2n+1),.links li:nth-child(2n+1){padding-left:0;border-left:none}
}
@media (max-width:600px){
  .wrap{padding:0 16px}
  .band{grid-template-columns:1fr}
  .band-c{padding:14px 0 16px;border-left:none!important;border-top:1px solid var(--hair)}
  .band-c:first-child{border-top:none}
}
@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}

/* Comparison winner highlight. Colour alone can't carry meaning for colour-blind
   readers, so the weight changes too. */
.statetable td.win{color:var(--accent);font-weight:700}
.statetable th a{text-decoration:none}
.tiny.caveat{margin-top:12px;max-width:70ch;font-size:12.5px}
/* The two-column variant is gone. It had correct borders but cells twice the
   width of every other section, which is what made it look like a different
   component. Blank cells fill the row instead. */
.cell.blank{min-height:0}
.val.sm{font-size:18px;line-height:1.35}


/* ---------- CHARTS ----------
   Deliberately not Chart.js. That's 208KB minified / 70KB gzipped and it
   draws to <canvas>, which is a pixel buffer: no text in the DOM, nothing for
   a crawler to read, nothing for a screen reader to announce. This site's
   entire ranking argument is that its numbers are real text in the markup, so
   replacing figures with canvas would trade the thing that makes it rank for
   a nicer picture.

   These are CSS bars driven by a width percentage. The numbers stay as text
   next to them — the chart is an aid to reading the figure, never a
   replacement for it. Themed with var(), so the dark-mode toggle just works;
   canvas would need every chart destroyed and redrawn on each switch.
   Total cost: no requests, no library, a few hundred bytes of CSS. */
.chart{margin:14px 0 26px;display:flex;flex-direction:column;gap:0}
.crow{display:grid;grid-template-columns:11.5em 1fr auto;align-items:center;gap:14px;
  padding:9px 0;border-bottom:1px solid var(--hair)}
.crow:last-child{border-bottom:none}
.clab{font-family:var(--sans);font-size:12px;font-weight:700;letter-spacing:.07em;
  text-transform:uppercase;color:var(--muted)}
/* overflow is visible, not hidden, so the benchmark tick below can overhang
   the rounded ends of the track. The bar carries its own border-radius, so
   nothing needs clipping anyway. */
.ctrack{position:relative;height:12px;background:var(--hair);border-radius:7px;overflow:visible}
.cbar{position:absolute;inset:0 auto 0 0;background:var(--accent);border-radius:7px;
  min-width:3px;transition:width .5s cubic-bezier(.22,1,.36,1)}
/* The benchmark is a TICK, not a second bar.
   It was a muted bar behind the value bar, which worked only while the city
   was below average — the moment the city exceeded the benchmark the value
   bar painted straight over it and the comparison disappeared, precisely in
   the cases where the reader most needs it. A tick sits on top and stays
   visible whichever side of the average the city falls. */
.cref{position:absolute;top:-3px;bottom:-3px;width:2px;background:var(--ink);
  border-radius:1px;transform:translateX(-1px);z-index:2}
.cval{font-family:var(--serif);font-size:19px;font-weight:600;letter-spacing:-.02em;
  white-space:nowrap;min-width:4.6em;text-align:right}
.cval sup{font-size:.62em;font-weight:600;vertical-align:baseline;margin-left:.14em;color:var(--muted)}
.csub{grid-column:2/4;font-family:var(--sans);font-size:11.5px;color:var(--muted);
  margin-top:-4px;padding-bottom:2px}

/* Position of one value on a range — used for "safer than N of M". The scale
   is the population of comparable cities; the pin is this city. */
.scale{position:relative;height:26px;margin:16px 0 6px}
.scale-t{position:absolute;top:11px;left:0;right:0;height:5px;border-radius:3px;
  background:linear-gradient(90deg,var(--rule),var(--accent))}
.scale-p{position:absolute;top:3px;width:3px;height:21px;border-radius:2px;
  background:var(--ink);transform:translateX(-1.5px)}
.scale-e{display:flex;justify-content:space-between;font-family:var(--sans);
  font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
@media (max-width:640px){
  .crow{grid-template-columns:1fr auto;gap:8px 12px}
  .ctrack{grid-column:1/3;order:3}
  .csub{grid-column:1/3}
}


/* A slot holding a real banner drops the placeholder chrome. The dashed card
   frame exists to make an EMPTY slot legible; around an actual creative it
   just boxes the artwork in a second, competing border. */
.ad-filled{border:none;background:none;padding:0;min-height:0;display:block}
.ad-a{display:block;line-height:0;border-radius:4px;overflow:hidden;
  transition:opacity .15s ease}
.ad-a:hover{opacity:.92}
.ad-a img{width:100%;max-width:970px;height:auto;display:block;margin:0 auto}

/* A section with an ad rail beside it.

   minmax(0,1fr) for the content column, not 1fr: plain 1fr refuses to shrink
   below its contents, and the climate bars contain nowrap text, so the grid
   would push past the page width instead of letting the bars narrow. Same
   trap as the forecast columns.

   align-items:center is the vertical centring — the rail sits against the
   middle of the data, not the top of it.

   The rail collapses below 900px rather than at a phone breakpoint. A 300px
   ad next to a data column only works while the data column still has room
   to be legible; squeezed under ~560px the bars stop being readable long
   before the layout technically breaks. */
.withad{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:36px;align-items:center}
.withad-main{min-width:0}
.withad-ad{width:340px;display:flex;justify-content:center;
  position:relative;top:-100px}
.ad-300{margin-top:0;width:300px;height:250px;padding:0;min-height:0;
  display:grid;place-items:center;text-align:center;line-height:1.7}
/* A filled 300x250 has no placeholder text to centre, and the creative is
   exactly the box size — so drop the grid centring and let the image be the
   box. Without this the <a> is a grid item sized to its content and the
   rounded corner clips against a 250px track it does not fill. */
.ad-300.ad-filled{display:block;overflow:hidden;border-radius:4px}
.ad-300.ad-filled .ad-a,.ad-300.ad-filled img{width:300px;height:250px;display:block}

@media (max-width:900px){
  .withad{grid-template-columns:minmax(0,1fr);gap:26px}
  /* top:0 is load-bearing. Relative offsets are invisible to layout, so the
     -100px above would survive the stack and drop the ad straight through
     the climate tiles it is supposed to sit below. */
  .withad-ad{width:auto;display:flex;justify-content:center;position:static;top:0}
}

/* ===========================================================================
   CITY HEADER, FORECAST STRIP AND WEATHER ICONS

   Ported verbatim from the single-page prototype so the deployed pages
   match the design that was actually iterated on. Same selectors, same
   values — copied rather than re-derived on purpose, because a near-miss
   reimplementation is exactly how these two drifted apart to begin with.
=========================================================================== */
/* ---------- CITY HEADER ---------- */
.cityhead{display:none;padding:0 0 2px}
.cityhead.on{display:block}
.city{display:flex;justify-content:space-between;align-items:flex-end;gap:16px;flex-wrap:wrap;
  border-bottom:1px solid var(--rule);padding-bottom:11px}
.ch-name{font-size:clamp(27px,4.9vw,46px);font-weight:800;letter-spacing:-.045em;line-height:.95}   /* -15% */
.ch-where{font-size:12px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-top:10px;line-height:1.5}
.tools{display:flex;gap:20px;padding-bottom:2px}
.tbtn{font-family:inherit;font-size:12.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  background:none;border:none;border-bottom:2px solid transparent;color:var(--muted);
  padding:5px 0 5px;cursor:pointer;transition:.12s;min-height:28px}
.tbtn:hover{color:var(--ink)}
/* A readout, not a control — same type as the toggles beside it, but no
   pointer, no hover, no underline.

   The extra left margin rides on top of the flex gap, so °F and °C stay
   tightly paired at 20px while the clock sits 44px away. They're the same
   size and weight, and without the separation the three read as one row of
   three buttons — which invites a click on something that isn't clickable. */
.tinfo{font-size:12.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);min-height:28px;display:inline-flex;align-items:center;padding:5px 0;
  margin-left:24px}
.tinfo b{color:var(--ink);font-weight:700;margin-left:.55em}
.tinfo:empty{display:none}
.tbtn.act{color:var(--ink);border-bottom-color:var(--accent)}
.lede{font-size:17px;line-height:1.6;color:var(--ink);margin-top:14px}
/* The 68ch cap is gone so the text runs almost to the divider, as asked.
   Measured honestly: at the 1120px container this column gives ~766px of text,
   which at 17px Newsreader is roughly 90 characters per line — above the 45–75
   band where reading speed peaks. A deliberate trade of measure for the fuller
   look. Re-adding `max-width:80ch` here splits the difference if it reads long. */

/* Current temperature sits beside the description rather than competing with
   the two money figures for the headline band.

   Grid, not flex. With flex the block was sized to its own content and simply
   stopped wherever the text ran out, leaving dead space to its right and no
   consistent edge to read against. A fixed second column anchors it to the
   page edge, and centring the column's contents puts the label, icon, figure
   and caption on one shared axis instead of four different ones. */
.intro{display:grid;grid-template-columns:minmax(0,1fr) 290px;margin-top:14px}
.intro .lede{margin-top:0;padding-right:20px}
.nowtemp{
  border-left:1px solid var(--hair);
  padding:6px 0 10px 30px;
  display:flex;flex-direction:column;
  align-items:center;justify-content:center;   /* centred both ways */
  text-align:center;
}
.nowtemp .lbl{margin:0}
.nowtemp .wxi{width:46px;height:46px;stroke-width:1.5;stroke:var(--accent);
  display:block;margin:10px 0 2px}
.nowtemp .big{font-size:clamp(48px,6.5vw,72px);margin:0}   /* +25% */
.nowtemp .note{max-width:24ch;margin:9px 0 0}
@media (max-width:820px){
  .intro{grid-template-columns:1fr}
  .intro .lede{padding-right:0}
  .nowtemp{border-left:none;border-top:1px solid var(--hair);
    padding:16px 0 0;align-items:flex-start;text-align:left}
  .nowtemp .wxi{margin-left:0}
  .nowtemp .note{margin-left:0}
}

/* ---------- FORECAST ---------- */
/* minmax(0,1fr), not 1fr. Plain `1fr` means minmax(AUTO,1fr), so a column
   refuses to shrink below its content — and .tiny is white-space:nowrap, so
   "Partly cloudy · 0%" was forcing the third column wider than the other two
   and throwing the dividers out of alignment. minmax(0,…) lets the track go
   narrower than its contents, which is what makes the thirds actually equal. */
.fc{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));border-top:2.5px solid var(--ink);margin-top:24px}
/* Condensed: each day is a single horizontal line rather than a stacked cell. */
.fc-d{display:flex;align-items:center;gap:12px;padding:11px 20px;
  border-bottom:1px solid var(--hair);border-left:1px solid var(--hair);min-height:0}
.fc-d:first-child{padding-left:0;border-left:none}
.fc-d .lbl{flex:none;min-width:5.4em}
.fc-v{font-size:22px;font-weight:700;letter-spacing:-.03em;line-height:1;margin:0;flex:none}
.fc-v i{font-size:.62em;font-style:normal;font-weight:600;color:var(--muted);margin-left:4px}
/* Shrinkable with an ellipsis rather than nowrap alone — now that the track
   can go narrow, the long descriptions need somewhere to give. */
.fc-d .tiny{margin:0 0 0 auto;text-align:right;white-space:nowrap;
  min-width:0;overflow:hidden;text-overflow:ellipsis}
@media (max-width:820px){.fc{grid-template-columns:1fr}
  .fc-d{padding-left:0;border-left:none!important}}

/* ---------- WEATHER ICONS ---------- */
.wxi{width:1em;height:1em;stroke:currentColor;fill:none;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round;flex:none}
.band-c .wxi{width:46px;height:46px;stroke-width:1.5;margin-bottom:4px;display:block}
/* Tile icon. Muted rather than accent — it labels the measurement, it isn't
   the measurement, and at full ink weight it competed with the number. */
/* Icon to the left of the label/value stack rather than above it, so every
   label in a row still starts on the same line. */
.cell.has-ico{display:flex;align-items:flex-start;gap:13px}
.cell.has-ico .cell-b{min-width:0;flex:1}
.cell .wxi.tico{width:27px;height:27px;stroke-width:1.6;color:var(--muted);
  flex:none;margin-top:2px;display:block}

/* The sunrise/sunset arrow. Sized in em so it tracks the number beside it;
   stroke-width is the point of using SVG at all. */
.arw{width:1.15em;height:1.15em;stroke:currentColor;fill:none;stroke-width:2.4;
  stroke-linecap:round;stroke-linejoin:round;display:inline-block;
  vertical-align:-.19em;margin:0 .1em}
/* This was the misalignment. There were two competing `.fc-d .wxi` rules —
   one up in the forecast block setting margin:0, this one setting
   margin-top:10px. Same specificity, so the later one won and shoved every
   forecast icon 10px below the centre line of a flex row that was already
   align-items:center. The duplicate is gone; this is the only rule now. */
.fc-d .wxi{width:30px;height:30px;margin:0;flex:none;display:block}

