/* Edmonton Daily Brief — "Blue Hour"
   At 53.5°N the sun grazes rather than sets; Nov–Feb the city spends 90+ minutes
   a day in civil twilight and that sky is not black, it is a saturated luminous
   blue. That is the ground. A chromatic ground kills the near-black-plus-one-pop
   cliché at the root: a neon accent has nowhere to shout from.

   Data hues come from that sky and that river — ice, river blue, brass, rose,
   pale mint. The palette is cold-committed: every ramp runs the blue↔yellow axis,
   which is the only axis surviving both protanopia and deuteranopia.

   RULES
   - Dark is canonical. Light is the reprint. No theme toggle exists.
   - --accent NEVER encodes a data value. Nine simultaneously-discriminable hues
     do not exist for a dichromat (measured ceiling: ΔE 12.9), so the ninth signal
     is separated by FORM: a 1px full-height rule, a 2px focus ring, a 4px cap.
   - --rule / --rule-strong are FURNITURE: panel edges, table hairlines, seams
     backed by a fill. They sit at 1.47:1 / 2.01:1 and are deliberately below the
     3:1 non-text floor, which is only legal because they never carry meaning
     alone. Anything a reader must TRACE to understand content — a chart axis, a
     ward boundary, a scanning-table row rule, a diverging midpoint — uses
     --ink-3 (5.23:1 dark / 5.02:1 light). Enforced by tools/check-design.py.
   - Every categorical lane owns a mark SHAPE as well as a hue.
   - No cards, no radii, no shadows, no app bar.
*/

:root {
  color-scheme: light;

  /* THE STOCK IS LIGHT BY DEFAULT.
     This palette existed for months behind `prefers-color-scheme: light`, so
     whether a reader got a newspaper or a dark screen was decided by an
     operating-system setting that has nothing to do with this paper — and most
     people never touch it. A morning paper is off-white with black ink; that is
     what the object IS, not a style. Dark is now the deliberate choice a reader
     makes for the late edition, at [data-theme="dark"] below. ------------- */
  --ground:#F4F1E9; --surface:#EAE6DA; --surface-2:#DED9C9;
  --rule:#BFB6A0; --rule-strong:#8E846C;   /* were #C9C2B0/#A69D88 — the rules
                                              read washy on cream at the values
                                              that looked right over navy. */
  --ink:#14181C; --ink-2:#3D474F; --ink-3:#5C6970;
  --accent:#0C6B4C; --link:#12557E;  /* accent was #0E7A57: 4.27 on --surface */
  --ok:#0A6E74; --warn:#826306; --danger:#9E2B2E;  /* warn was #8A6A08 = 4.49 */
  --lane-weather:#2E6C93; --lane-transit:#1B4F87;
  --lane-council:#7A5411; --lane-safety:#8E2E4E; --lane-dev:#2F6B45;
  --spruce-hover:#0B3A59;

  /* the lamp warms the head of the sheet and the vignette settles its edges.
     Both were tuned against navy: a .28 black vignette on cream reads as dirt,
     and a warm glow at .075 over warm stock is not there at all. */
  --lamp: rgba(255,252,242,.60); --vignette: rgba(120,104,78,.13);
  --wash-day:.075; --wash-twi:.10; --wash-temp:0;
  --day-fill:#C08A2A; --twi-fill:#4A5C86;
  --tooth-blend: multiply, multiply;
  --plate-wash: rgba(120,104,78,.05);

  /* legacy aliases — the existing component layer still speaks these */
  --paper:var(--ground); --paper-2:var(--surface);
  --ink-muted:var(--ink-3);
  --spruce:var(--link);
  --alert:var(--danger); --filed:var(--warn);

  --serif:"Newsreader","Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
  --sans:"Source Sans 3",system-ui,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;

  /* prose scale · 1.25 from 1rem — generous, for reading */
  --t-p0:1rem; --t-p1:1.25rem; --t-p2:1.5625rem;
  --t-p3:1.953rem; --t-p4:2.441rem; --t-p5:3.052rem;
  /* data scale · 1.125 from 0.875rem — tight, so eight steps fit a panel */
  --t-d-2:0.691rem; --t-d-1:0.778rem; --t-d0:0.875rem;
  --t-d1:0.984rem; --t-d2:1.107rem; --t-d3:1.246rem;
  --t-d4:1.62rem; --t-d5:2.05rem;
  --t-d-3:0.614rem;                       /* agate floor, ≈10px */
  /* headline ladder — the three sizes the news well actually uses,
     named once so a rule cannot quietly invent a fourth */
  --h-l:2.25rem; --h-m:1.5rem; --h-s:1.125rem;

  --s-1:2px; --s-2:4px; --s-3:8px; --s-4:12px; --s-5:16px;
  --s-6:24px; --s-7:32px; --s-8:48px; --s-9:64px; --s-10:96px;

  /* THE STOCK.
     This was two radial dots at 4% alpha — invisible, and the page read as a
     screen rather than as paper. Real tooth now: a fine fractal grain for the
     surface and a long-strand fibre for the weave, both drawn as SVG
     turbulence and inlined as data URIs because the CSP forbids remote images.
     Warm cream, so ink sits on stock rather than on glass. */
  --grain: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22200%22%20height%3D%22200%22%3E%20%3Cfilter%20id%3D%22g%22%3E%3CfeTurbulence%20type%3D%22fractalNoise%22%20baseFrequency%3D%220.9%22%20numOctaves%3D%224%22%20stitchTiles%3D%22stitch%22%2F%3E%20%3CfeColorMatrix%20type%3D%22saturate%22%20values%3D%220%22%2F%3E%3C%2Ffilter%3E%20%3Crect%20width%3D%22200%22%20height%3D%22200%22%20filter%3D%22url%28%23g%29%22%20opacity%3D%220.34%22%2F%3E%3C%2Fsvg%3E");
  --tooth: var(--grain), var(--fibre);
  --fibre: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22340%22%20height%3D%22340%22%3E%20%3Cfilter%20id%3D%22f%22%3E%3CfeTurbulence%20type%3D%22fractalNoise%22%20baseFrequency%3D%220.05%200.13%22%20numOctaves%3D%222%22%20stitchTiles%3D%22stitch%22%2F%3E%20%3CfeColorMatrix%20type%3D%22saturate%22%20values%3D%220%22%2F%3E%3C%2Ffilter%3E%20%3Crect%20width%3D%22340%22%20height%3D%22340%22%20filter%3D%22url%28%23f%29%22%20opacity%3D%220.22%22%2F%3E%3C%2Fsvg%3E");
}


:root[data-theme="dark"] {
  color-scheme: dark;

  /* THE LATE EDITION. Chosen, never inherited from the OS. ---------- */
  --ground:#0B1A24; --surface:#10222E; --surface-2:#162C3A;
  --rule:#21394A; --rule-strong:#2F4E63;
  --ink:#EFE7D6;      /* 14.38:1 — ivory, not screen-white */
  --ink-2:#C6BCA8;    /*  9.40:1 */
  --ink-3:#948D7C;    /*  5.36:1 */
  --accent:#5FE3B0;   /* 11.03:1 — chrome only, never data */
  --link:#86C8F5;     /*  9.76:1 */
  --ok:#12B4BD; --warn:#D9B44A; --danger:#F17F80;
  --lane-weather:#AEE3FA; --lane-transit:#3D93EF; --lane-council:#D69B2B;
  --lane-safety:#DC6B92; --lane-dev:#C4EFD2;
  --spruce-hover:#B6DEFA;

  --lamp: rgba(255,226,168,.075); --vignette: rgba(0,0,0,.28);
  --wash-day:.10; --wash-twi:.16; --wash-temp:.13;
  --day-fill:var(--lane-weather); --twi-fill:var(--lane-transit);
  --tooth-blend: soft-light, soft-light;
  --plate-wash: rgba(255,226,168,.028);
}

*, *::before, *::after { box-sizing: border-box; }

html {
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
  background-color: var(--ground);
  color: var(--ink);
}

body {
  margin: 0;
  /* uncoated tooth over the blue hour. Longhand, not the `background`
     shorthand — the shorthand resets background-image to none. */
  background-color: var(--ground);
  background-image: var(--tooth);
  background-size: 200px 200px, 340px 340px;
  background-blend-mode: var(--tooth-blend);
  color: var(--ink);
  font-variant-numeric: tabular-nums lining-nums;
  font: 400 var(--t-p0)/1.5 var(--serif);
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
}

[hidden] { display: none !important; }

::selection { background: var(--spruce); color: var(--paper); }

a {
  color: var(--spruce);
  text-decoration: underline;
  text-underline-offset: 0.15em;
  text-decoration-thickness: 1px;
}
a:hover { color: var(--spruce-hover); }
a:focus-visible,
select:focus-visible,
input:focus-visible {
  outline: 2px solid var(--spruce);
  outline-offset: 2px;
}

.skip {
  position: absolute;
  left: -9999px;
  background: var(--spruce);
  color: var(--paper);
  font: 600 var(--t-d-1)/1 var(--sans);
  padding: 0.5rem 0.75rem;
  z-index: 20;
}
.skip:focus { left: 0.5rem; top: 0.5rem; }

.sheet {
  width: min(76rem, 100%);
  margin: 0 auto;
  padding: 1.25rem 1.25rem 4.5rem;
}

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

.masthead {
  text-align: center;
  padding: 0.25rem 0 0;
}

.flag {
  margin: 0 0 0.5rem;
  font: 700 var(--t-d-3)/1.3 var(--sans);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--spruce);
}

.wordmark {
  margin: 0;
  color: var(--ink);
  line-height: 0.9;
}

.lockup-city {
  display: block;
  font: 700 2.375rem/0.9 var(--serif);
  font-variation-settings: "opsz" 72;
  letter-spacing: 0.085em;
  text-transform: uppercase;
}

.lockup-title {
  display: block;
  margin-top: 0.2rem;
  font: italic 500 1.375rem/1 var(--serif);
  font-variation-settings: "opsz" 36;
  letter-spacing: 0.02em;
}

.rules {
  margin: 0.85rem 0 0.7rem;
}
.rules div:first-child { height: 3px; background: var(--ink); }
.rules div:last-child { height: 1px; background: var(--ink); margin-top: 3px; }




.how { display: inline; }

/* ------------------------------------------------------------------ */
/* Folio: area filter                                                  */
/* ------------------------------------------------------------------ */

.folio {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.25rem 0 0;
  padding: 0.7rem 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}

.folio label {
  flex: none;
  font: 700 var(--t-d-2)/1 var(--sans);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.folio select {
  flex: 1;
  min-width: 0;
  font: 600 var(--t-d0)/1 var(--sans);
  color: var(--ink);
  background-color: transparent;
  border: 1px solid var(--rule);
  border-radius: 0;
  padding: 0.5rem 1.75rem 0.5rem 0.6rem;
  -webkit-appearance: none;
  appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 0.9rem center, right 0.6rem center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

.folio .count {
  flex: none;
  margin: 0;
  font: 400 var(--t-d-1)/1 var(--sans);
  color: var(--ink-muted);
  white-space: nowrap;
}

/* ------------------------------------------------------------------ */
/* Beta                                                                */
/* ------------------------------------------------------------------ */

.beta {
  display: flex;
  gap: 0.625rem;
  align-items: baseline;
  margin: 1rem 0 0;
  padding: 0.65rem 0.75rem;
  background: var(--paper-2);
  border-left: 3px solid var(--alert);
  font: 400 var(--t-d-1)/1.45 var(--sans);
  color: var(--ink);
}
.beta p { margin: 0; }
.beta-tag {
  flex: none;
  font: 700 var(--t-d-3)/1.45 var(--sans);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--alert);
}

/* ------------------------------------------------------------------ */
/* Weather fixture                                                     */
/* ------------------------------------------------------------------ */

.wx {
  display: grid;
  align-content: start;
  grid-template-columns: auto 1fr;
  grid-template-areas:
    "label label"
    "temp  body"
    "temp  meta"
    "src   src";
  column-gap: 1.25rem;
  row-gap: 0.25rem;
  margin: 1.5rem 0 0;
  padding: 0.9rem 0 1rem;
  border-top: 1px solid var(--ink);
  border-bottom: 1px solid var(--ink);
  background:
    linear-gradient(var(--paper-2), var(--paper-2)) padding-box;
}

.wx-label {
  grid-area: label;
  margin: 0 0 0.35rem;
  font: 700 var(--t-d-3)/1 var(--sans);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
}

.wx-now { grid-area: temp; align-self: start; }

.wx-temp {
  margin: 0;
  font: 600 3.25rem/0.85 var(--serif);
  font-variation-settings: "opsz" 72;
  font-variant-numeric: lining-nums tabular-nums;
  letter-spacing: -0.03em;
}
.wx-temp span {
  font-size: 0.45em;
  letter-spacing: 0;
  vertical-align: super;
}

.wx-place {
  margin: 0.2rem 0 0;
  font: 500 var(--t-d-1)/1.2 var(--sans);
  letter-spacing: 0.04em;
  color: var(--ink-muted);
}

.wx-body {
  grid-area: body;
  margin: 0;
  font: 400 var(--t-d0)/1.4 var(--serif);
  font-variation-settings: "opsz" 18;
}

.wx-meta {
  grid-area: meta;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
  margin: 0.35rem 0 0;
  padding: 0;
  list-style: none;
  font: 600 var(--t-d-1)/1.3 var(--sans);
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--ink);
}
.wx-meta li + li { position: relative; }
.wx-src {
  grid-area: src;
  margin: 0.55rem 0 0;
  font: 500 var(--t-d-1)/1.3 var(--sans);
}

.wx--alert {
  border-top-color: var(--alert);
  border-bottom-color: var(--alert);
}
.wx--alert .wx-label { color: var(--alert); }

/* ------------------------------------------------------------------ */
/* Pack: sections after weather                                        */
/* ------------------------------------------------------------------ */

.pack { margin-top: 0.25rem; }

.sec {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0 0 0.15rem;
}
/* ══ THE DESK HEAD — one style for every desk and instrument heading ═════
   There were six competing heading rules, four sizes, four trackings, and a
   font rule for .lab-a that existed only inside .apparatus — so three of the
   ten instrument headings fell to the browser's default 2em serif. That is why
   Business was huge and "What the city is building" had no hierarchy.
   The base rule is on h2 ITSELF: a forgotten class now renders as a desk head,
   never as a UA default. .desk-h is the marker the tests key on. */
h2 { margin: 0; font: 700 var(--t-d-1)/1.2 var(--sans);
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink); }
.desk-h { display: block; }
.desk-h--gate { font: 600 var(--h-m)/1.15 var(--serif); letter-spacing: 0;
  text-transform: none; }
.sec h2 { margin: 0; }
/* the flex row a heading sits in, with its hairline and its right-hand stamp */
.instr-h, .ch-h { display: flex; align-items: baseline; gap: 0.6rem;
  margin: 0 0 0.35rem; padding-bottom: 0.3rem;
  border-bottom: 1px solid var(--rule-strong); }
.instr-h .stamp, .instr-h .mk-when { margin-inline-start: auto;
  font: 500 var(--t-d-2)/1 var(--sans); letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--ink-3); }
.sec::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--ink);
}

section.has-alert .sec h2 { color: var(--alert); }
section.has-alert .sec::after { background: var(--alert); }

.pack > section { margin-top: 2rem; }

.pack > section:first-child { margin-top: 1.75rem; }

/* ------------------------------------------------------------------ */
/* Items                                                               */
/* ------------------------------------------------------------------ */

.item {
  margin: 0;
  padding: 1rem 0 1.1rem;
  border-top: 1px solid var(--rule);
}

.item h3 {
  margin: 0 0 0.4rem;
  font: 600 var(--h-s)/1.2 var(--serif);
  font-variation-settings: "opsz" 36;
  font-variant-numeric: lining-nums tabular-nums;
  letter-spacing: -0.012em;
  text-wrap: balance;
  hyphens: none;
  color: var(--ink);
}

.body {
  margin: 0 0 0.55rem;
  font: 400 var(--t-p0)/1.45 var(--serif);
  font-variation-settings: "opsz" 18;
  font-variant-numeric: oldstyle-nums;
  text-wrap: pretty;
}

.why {
  margin: 0 0 0.55rem;
  font: italic 400 var(--t-d1)/1.4 var(--serif);
  color: var(--ink);
}
.why-k {
  margin-right: 0.4em;
  font: 700 var(--t-d-2)/1.4 var(--sans);
  font-style: normal;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.why-k::after { content: " —"; }

.sources {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  margin: 0;
  padding: 0;
  list-style: none;
}
.sources li {
  font: 500 var(--t-d-1)/1.35 var(--sans);
}
.sources li + li::before {
  content: "·";
  margin: 0 0.45em;
  color: var(--ink-muted);
  font-weight: 400;
}

.filed-mark {
  font: 600 var(--t-d-2)/1.35 var(--sans) !important;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--filed);
}
.filed-mark::before { color: var(--rule); }

/* Lead — one per edition, generated on the highest-score
   non-weather, non-stub publish item */
.item--lead {
  padding-top: 0.85rem;
}
.item--lead h3 {
  margin-bottom: 0.6rem;
  font-size: var(--h-l);
  line-height: 1.12;
  font-variation-settings: "opsz" 72;
  letter-spacing: -0.022em;
}
.item--lead .body {
  font-size: var(--t-d2);
  line-height: 1.42;
  max-width: 38em;
}

/* Stubs — a format, not a fault. Same measure. No fill. */
.item--stub {
  padding: 0.85rem 0 0.95rem 0.85rem;
  border-top: 1px solid var(--rule);
  border-left: 2px solid var(--ink);
}
.stub-flag {
  margin: 0 0 0.3rem;
  font: 700 var(--t-d-3)/1.3 var(--sans);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.stub-note {
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--ink-muted);
}
.stub-note::before {
  content: " · ";
  letter-spacing: 0;
}
.item--stub h3 {
  margin: 0 0 0.45rem;
  font-size: var(--t-p0);
  font-weight: 600;
  line-height: 1.25;
}

/* Templated / filed */
.item--filed h3 { font-variant-numeric: lining-nums tabular-nums; }

/* Docket (rezonings, multi-filing templated bodies) */
.docket {
  margin: 0 0 0.55rem;
  padding: 0;
  list-style: none;
  font: 400 var(--t-d0)/1.4 var(--sans);
}
.docket li {
  padding: 0.4rem 0;
  border-top: 1px solid var(--rule);
}
.docket li:first-child { border-top: 0; padding-top: 0.15rem; }
.docket-id {
  display: inline-block;
  margin-right: 0.45rem;
  font: 700 var(--t-d-2)/1.4 var(--sans);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--filed);
}
.docket-when {
  display: block;
  font-size: var(--t-d-1);
  color: var(--ink-muted);
}

/* Votes */
.votes {
  margin: 0 0 0.55rem;
  padding: 0;
  list-style: none;
}
.votes li {
  font: 400 var(--t-d0)/1.45 var(--sans);
  margin: 0 0 0.3rem;
  padding-left: 0;
}
.vote-outcome {
  display: inline-block;
  min-width: 4.75rem;
  margin-right: 0.4rem;
  font: 700 var(--t-d-2)/1.45 var(--sans);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--filed);
}

/* ------------------------------------------------------------------ */
/* Sports fixture                                                      */
/* ------------------------------------------------------------------ */

.scoreboard {
  padding: 0.85rem 0 1rem;
  border-top: 1px solid var(--rule);
}
.score-line {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.25rem 0;
  font-variant-numeric: lining-nums tabular-nums;
}
.score-team {
  font: 600 var(--h-s)/1.2 var(--serif);
  font-variation-settings: "opsz" 36;
}
.score-n {
  font: 600 1.75rem/1 var(--serif);
  font-variation-settings: "opsz" 72;
  letter-spacing: -0.03em;
}
.score-meta {
  margin: 0.4rem 0 0;
  font: 400 var(--t-d-1)/1.35 var(--sans);
  color: var(--ink-muted);
}

#sports .item h3 { font-size: var(--t-d2); }

/* ------------------------------------------------------------------ */
/* Sponsor                                                             */
/* ------------------------------------------------------------------ */

.sponsor {
  margin: 2rem 0 0;
  padding: 1rem 0 1.1rem;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  background: var(--paper-2);
  padding-left: 1rem;
  padding-right: 1rem;
}
.sponsor-kicker {
  margin: 0 0 0.35rem;
  font: 700 var(--t-d-3)/1 var(--sans);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.sponsor .name {
  margin: 0 0 0.25rem;
  font: 600 var(--t-d1)/1.3 var(--sans);
  color: var(--ink);
}
/* the blanket .sponsor p rule is gone: every child is typed individually,
   and the blanket one overrode .sponsor-text's colour by source order */

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

.colophon {
  margin-top: 2.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--ink);
  font: 400 var(--t-d-1)/1.55 var(--sans);
  color: var(--ink-muted);
}
.colophon p { margin: 0 0 0.65rem; }
.colophon p:last-child { margin: 0; }
.colophon strong {
  font-weight: 700;
  color: var(--ink);
}

/* ------------------------------------------------------------------ */
/* Desktop — a real page, not a wider phone                            */
/* ------------------------------------------------------------------ */

@media (min-width: 36rem) {
  .lockup-city { font-size: 3.25rem; }
  .lockup-title { font-size: 1.625rem; }
  .wx-temp { font-size: 4rem; }
}

@media (min-width: 48rem) {
  .sheet { padding: 1.75rem 2rem 5rem; }

  .lockup-city { font-size: 3.75rem; letter-spacing: 0.12em; }

  .how { display: inline; }

  .wx {
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      "label label label"
      "temp  body  meta"
      "temp  src   meta";
    align-items: start;
    column-gap: 1.75rem;
    padding: 1rem 0 1.1rem;
  }
  .wx-meta {
    display: grid;
    gap: 0.25rem 0;
    margin: 0;
    text-align: right;
    justify-items: end;
  }
  .wx-body { max-width: 42em; }

  .item--lead h3 { font-size: 2.75rem; }
}

@media (min-width: 64rem) {
  .lockup-city { font-size: 4.5rem; }
  .lockup-title { font-size: 1.75rem; margin-top: 0.3rem; }
  .flag { letter-spacing: 0.28em; }
  .dateline { font-size: var(--t-d-1); }

  .item--lead h3 {
    font-size: 2.75rem;
    max-width: 18ch;
  }

  /* Multicolumn well. Lead section spans the page like A1. */
  /* GRID, not multicolumn. Multicolumn balances by HEIGHT, and every .item
     carries break-inside: avoid, so one tall unbreakable section near a
     boundary cannot be split and the browser pushes a void to the column
     instead. On a real edition that left the middle column empty from y=941
     to y=2363 — a screen and a half of nothing between two full columns.
     A grid places sections in stable rows: a tall one takes more of its own
     row and pushes the next row down, and nothing moves sideways. */
  /* Multicolumn, not grid. A grid row is as tall as its tallest member, so a
     short section beside a long one left ~900px of nothing beneath it — the
     same void, relocated. Multicolumn FLOWS: the blocker was break-inside on
     the section, which is gone, so sections now split across columns the way
     a newspaper's do and only the article stays whole. */
  .pack {
    columns: 2;
    column-gap: 2.5rem;
    column-rule: 1px solid var(--rule);
  }
  .pack > .frontpkg,
  .pack > .quiet { column-span: all; }
  /* The sponsor no longer spans: the owner asked for a card, not a banner
     across the middle of the page. .frontpkg and .quiet still span. */

  /* Sections flow across columns the way a newspaper's do; it is the article
     that must not split. Keeping break-inside:avoid on the section made a
     979px City Hall block unbreakable, so it could not enter column two and
     every section stacked in column one. */
  .pack > section {
    margin-top: 0;
    margin-bottom: 1.75rem;
    padding-top: 0;
  }

  .pack .item,
  .pack .scoreboard,
  .pack .sponsor {
    break-inside: avoid;
  }

  /* A section header stranded at the foot of a column is an orphan. */
  .pack > section > .sec {
    break-after: avoid;
  }
  .pack > section:first-child { margin-top: 1.5rem; }

  section:has(.item--lead) {
    column-span: all;
    display: grid;
    grid-template-columns: 1.55fr 1fr;
    column-gap: 2.5rem;
    margin: 1.5rem 0 1.75rem;
    padding-bottom: 1.5rem;
    border-bottom: 1px solid var(--ink);
  }
  section:has(.item--lead) .sec { grid-column: 1 / -1; }
  section:has(.item--lead) .item--lead { border-top: 0; padding-top: 0.5rem; }
  section:has(.item--lead) .item:not(.item--lead) {
    border-top: 0;
    border-left: 1px solid var(--rule);
    padding: 0.5rem 0 0 1.5rem;
  }
  section:has(.item--lead):not(:has(.item:not(.item--lead))) {
    grid-template-columns: 1fr;
  }

  .sponsor {
    margin-top: 0.25rem;
    margin-bottom: 1.5rem;
  }
}

@media (min-width: 80rem) {
  .sheet { width: min(84rem, 100%); }
  .pack { columns: 3; column-gap: 2.25rem; }
  .item--lead h3 { font-size: 3rem; }
}

/* ------------------------------------------------------------------ */
/* Dark — not an invert. Warm night stock.                             */
/* ------------------------------------------------------------------ */

/* The old dark override lived here. Dark is now the canonical :root, so an
   override would fight the ground. .wx keeps its surface fill in both modes. */
.wx { background: var(--surface); }
.item--stub { border-left-color: var(--warn); }

/* ------------------------------------------------------------------ */
/* Print — people will take this to a council meeting                  */
/* ------------------------------------------------------------------ */

@media print {
  :root {
    --ground: #fff; --surface: #fff; --surface-2: #fff;
    --ink: #000; --ink-2: #222; --ink-3: #444;
    --rule: #bbb; --rule-strong: #888;
    --accent: #000; --link: #000; --spruce-hover: #000;
    --ok: #000; --warn: #444; --danger: #000;
    --lane-weather: #666; --lane-transit: #444; --lane-council: #555;
    --lane-safety: #333; --lane-dev: #777;
    --tooth: none;
  }
  body { background: #fff; color: #000; font-size: 10pt; }
  .skip, .folio, .beta { display: none !important; }
  .sheet { width: 100%; max-width: none; padding: 0; }
  .pack { columns: 2; column-gap: 1.5pc; column-rule: 0.5pt solid #000; }
  a { color: #000; text-decoration: none; }
  .sources a::after { content: " (" attr(href) ")"; font-size: 7pt; }
  .item--lead h3 { font-size: 18pt; }
  .lockup-city { font-size: 22pt; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* --- desk navigation --- */
.desks { margin: 1rem 0 0; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
.desks ul {
  display: flex; flex-wrap: wrap; gap: 0 1.25rem;
  margin: 0; padding: 0.5rem 0; list-style: none;
}
.desks a {
  font: 700 var(--t-d-2)/1.8 var(--sans); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-muted); text-decoration: none;
}
.desks a:hover { color: var(--spruce); }
.desks a.on { color: var(--ink); box-shadow: inset 0 -2px 0 var(--spruce); }

/* --- weather + what's ahead band --- */
.band { display: grid; gap: 0; margin-top: 1.25rem; }
@media (min-width: 48rem) {
  .band { grid-template-columns: 1.6fr 1fr; column-gap: 2.5rem; align-items: start; }
  /* was stretch: .wx is itself a grid with auto rows, so it inherited whatever
     height the What's-ahead rail happened to be — the weather box grew when the
     calendar got busy. */
  .band .wx { margin-top: 0; }
  .band .ahead { border-left: 1px solid var(--rule); padding-left: 2.5rem; }
}
.ahead { padding: 0.9rem 0; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
@media (min-width: 48rem) { .ahead { border-top: 1px solid var(--rule); } }
.ahead-label { margin: 0 0 0.5rem; }
.ahead-list { margin: 0; padding: 0; list-style: none; }
.ahead-list li {
  display: grid; grid-template-columns: 5.25rem 1fr; gap: 0.5rem;
  padding: 0.3rem 0; border-top: 1px solid var(--rule);
  font: 400 var(--t-d-1)/1.35 var(--sans);
}
.ahead-list li:first-child { border-top: 0; }
.ahead-date { font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.ahead-list a { color: var(--ink); text-decoration: none; }
.ahead-list a:hover { color: var(--spruce); text-decoration: underline; }
.ahead-note {
  grid-column: 2; font: 400 var(--t-d-2)/1.3 var(--sans); color: var(--ink-muted);
}

/* --- section (desk) pages --- */
.masthead--sub { padding-top: 0.5rem; }
.masthead--sub .flag a { color: var(--spruce); text-decoration: none; }
.masthead--sub .lockup-title { font-size: 2rem; font-style: normal; font-weight: 600; }
.pack--single { columns: 1 !important; }
.pack--single .sec { margin-top: 2rem; }

/* --- sponsor: house style, not a banner --- */
.sponsor .name {
  margin: 0 0 0.15rem; font: 700 var(--t-d-1)/1.2 var(--sans);
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink);
}
.sponsor-hed {
  margin: 0 0 0.35rem; font: 600 1.25rem/1.2 var(--serif);
  letter-spacing: -.01em; color: var(--ink);
}
.sponsor-text { margin: 0 0 0.5rem; font: 400 var(--t-d0)/1.45 var(--sans); color: var(--ink-muted); }
.sponsor-cta {
  font: 700 var(--t-d-1)/1 var(--sans); letter-spacing: 0.06em;
  color: var(--spruce); text-decoration: none;
  border-bottom: 2px solid var(--spruce); padding-bottom: 2px;
}
.sponsor-cta:hover { color: var(--spruce-hover); border-color: var(--spruce-hover); }

/* --- Alberta mode toggle --- */
body.alberta-on .ab-toggle { color: var(--spruce); }
/* A standing mark so nobody mistakes the restyled copy for the record. It is a
   real element rather than generated content because it has to carry a link. */
.ab-note { display: none; }
body.alberta-on .ab-note {
  display: block; grid-column: 1 / -1;
  margin: 0.75rem 0 0; padding: 0.55rem 0.8rem;
  background: var(--paper-2); border-left: 3px solid var(--spruce);
  font: 400 var(--t-d-1)/1.5 var(--sans); color: var(--ink-2);
}
.ab-note a { color: var(--spruce); }

/* The gate. Alberta mode does not turn on from a single click. */
.ab-gate {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  padding: 1.25rem; background: rgba(0, 0, 0, .62);
  overflow-y: auto;
}
.ab-gate[hidden] { display: none; }
.ab-gate-card {
  max-width: 34rem; background: var(--paper);
  border: 1px solid var(--rule-strong); border-top: 4px solid var(--spruce);
  padding: 1.5rem 1.5rem 1.25rem;
  box-shadow: 0 18px 48px rgba(0, 0, 0, .35);
}
.ab-gate-card h2 {
  margin: 0 0 var(--t-d-1); font: 700 1.375rem/1.15 var(--serif);
  letter-spacing: -.01em; color: var(--ink);
}
.ab-gate-card p { margin: 0 0 var(--t-d-1); font: 400 var(--t-d1)/1.55 var(--sans); color: var(--ink-2); }
.ab-gate-card strong { color: var(--ink); }
.ab-gate-fine { font-size: var(--t-d-1) !important; color: var(--ink-3) !important; }
.ab-gate-row { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1.1rem; }
.ab-gate-row button {
  font: 700 var(--t-d-1)/1 var(--sans); letter-spacing: 0.04em;
  padding: 0.7rem 1rem; cursor: pointer; border: 1px solid var(--rule-strong);
  background: none; color: var(--ink-2);
}
.ab-gate-yes { background: var(--spruce) !important; color: var(--paper) !important;
               border-color: var(--spruce) !important; }
.ab-gate-row button:hover { border-color: var(--spruce); color: var(--ink); }
.ab-gate-yes:hover { background: var(--spruce-hover) !important; color: var(--paper) !important; }
body.ab-gated { overflow: hidden; }

/* Held plain, and said so. A reader who does not know the register exists reads
   a missing joke as the machine giving up, which is the wrong lesson.

   It said "kept plain", which sounded like a decision. This selector is true of
   TWO different things: an item in the verbatim register, deliberately untouched
   because someone is before a court, and an item whose restyle was thrown out by
   a fidelity gate. Nothing here can tell them apart, so the badge no longer
   implies it knows which. "Plain copy" is the one thing that is true of both and
   the only thing the reader needs from a badge: what you are reading here is the
   neutral text. Distinguishing them properly needs a register attribute on the
   article, which is the Alberta session's to emit.

   Keyed off .is-record, which alberta.js sets per item on every swap, NOT off
   an attribute name. It was `:not([data-ab-b])`, and when the renditions became
   locale-keyed (data-r-en-ab-b) that selector stopped matching anything real
   and started matching EVERYTHING — so a page where all 15 items were
   successfully voiced shipped with "plain copy" stamped on every one of them.
   A class the script maintains cannot rot the next time an id changes. */
body.alberta-on article.item.is-record:not(.item--stub) > h3::after {
  content: "no comment"; display: inline-block; vertical-align: middle;
  margin-left: 0.5rem; padding: 0.1rem 0.35rem;
  font: 700 var(--t-d-3)/1.3 var(--sans); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-3); border: 1px solid var(--rule);
}

/* The quip is a comment, not the story. It is set apart so nobody reads one or
   two lines of a guy's opinion as the paper reporting something — the headline
   above it and the source link below it are the record. */
body.alberta-on article.item > p.quip {
  font: 400 var(--t-d2)/1.5 var(--sans);
  color: var(--ink-2);
  border-left: 3px solid var(--spruce);
  padding-left: 0.75rem;
  margin: 0 0 0.6rem;
}
p.quip[hidden] { display: none; }

/* --- public alert: a copy, never the channel of record --- */
.item--alert {
  background: var(--paper-2);
  border-left: 4px solid var(--alert);
  padding: 1rem 1.1rem;
  margin-left: calc(var(--s5, 1.25rem) * -1);
  margin-right: calc(var(--s5, 1.25rem) * -1);
}
.alert-flag {
  margin: 0 0 0.35rem; font: 700 var(--t-d-2)/1 var(--sans);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--alert);
}
.item--alert h3 { color: var(--ink); }
.alert-notice {
  margin: 0.6rem 0 0.5rem; padding-top: 0.6rem;
  border-top: 1px solid var(--rule);
  font: 400 var(--t-d-1)/1.45 var(--sans); color: var(--ink-muted);
}
.alert-notice strong { color: var(--alert); }

/* --- newsletter signup --- */
.sr-only {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}
.hp { position:absolute; left:-9999px; }
.signup {
  margin: var(--s8, 2.5rem) 0 0; padding: 1.5rem 0 0;
  border-top: 3px solid var(--ink);
}
.signup-h { margin: 0 0 0.4rem; }
.signup-lede {
  margin: 0 0 1rem; max-width: 34rem;
  font: 400 var(--t-d1)/1.5 var(--sans); color: var(--ink-muted);
}
.signup-lede strong { color: var(--ink); }
.signup-row { display: flex; flex-wrap: wrap; gap: 0.5rem; max-width: 30rem; }
.signup-row input {
  flex: 1 1 14rem; min-width: 0;
  font: 400 var(--t-d1)/1 var(--sans); color: var(--ink);
  background: var(--paper); border: 1px solid var(--ink);
  border-radius: 0; padding: 0.6rem 0.7rem;
}
.signup-row button {
  flex: none; cursor: pointer;
  font: 700 var(--t-d-1)/1 var(--sans); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--paper); background: var(--spruce);
  border: 1px solid var(--spruce); border-radius: 0; padding: 0.65rem 1.1rem;
}
.signup-row button:hover { background: var(--spruce-hover); border-color: var(--spruce-hover); }
.signup-editions { border: 0; margin: 0.9rem 0 0; padding: 0; max-width: 34rem; }
.signup-editions label {
  display: flex; gap: 0.5rem; align-items: baseline; margin-bottom: 0.4rem;
  font: 400 var(--t-d0)/1.4 var(--sans); color: var(--ink-muted); cursor: pointer;
}
.signup-editions strong { color: var(--ink); }
.signup-editions input { accent-color: var(--spruce); margin-top: 0.2rem; }
.signup-fine {
  margin: 0.9rem 0 0; max-width: 34rem;
  font: 400 var(--t-d-1)/1.45 var(--sans); color: var(--ink-muted);
}
.signup-msg { margin: 0.6rem 0 0; font: 500 var(--t-d-1)/1.4 var(--sans); }
.signup-msg.is-ok { color: var(--spruce); }
.signup-msg.is-err { color: var(--alert); }

/* ── THE CITY TIME STRIP ───────────────────────────────────────────────
   The reader samples a MOMENT, not a series — which is why daylight,
   temperature and precipitation share one block instead of three lanes, and
   why every link lives in the readout panel, not on the marks. The strip is
   the index; the panel is the document.

   Marks are coloured by SEVERITY, not by lane: four values instead of nine,
   each also carrying a shape (bar vs dot vs cell), so hue is never the sole
   channel. Lane identity comes from the row label, which is a word.

   A lane with zero rows keeps its full height and its label. "Nothing today"
   and "this lane is broken" must never look the same. */
.citytime { margin: 1.5rem 0 0; padding-top: 0.9rem; border-top: 2px solid var(--ink); }
.ct-head { display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 0.5rem 1rem; margin-bottom: 0.6rem; }
.ct-h { margin: 0;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink); }
.ct-zooms { display: flex; gap: 0.9rem; }
.ct-z { appearance: none; background: none; border: 0; padding: 0.15rem 0; cursor: pointer;
  font: 500 var(--t-d-2)/1 var(--sans); letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink-3); border-bottom: 2px solid transparent; }
.ct-z:hover { color: var(--ink); }
.ct-z[aria-pressed="true"] { color: var(--ink); font-weight: 700;
  border-bottom-color: var(--accent); }  /* accent as FORM: a 2px cap, never data */
.ct-z:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.ct-plot { position: relative; touch-action: pan-y; cursor: crosshair;
  border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); padding: 0.3rem 0; }
.ct-plot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ct-svg { display: block; width: 100%; height: auto; }

/* --- sky: daylight wash, temperature, precipitation --- */
.ct-day  { fill: var(--day-fill); opacity: var(--wash-day); }
.ct-twi  { fill: var(--twi-fill); opacity: var(--wash-twi); }
/* Hairline and broken, not a dashboard's ruled grid: on cream stock a solid
   1px line at every five degrees is a stronger mark than the temperature it
   is there to help you read. */
.ct-grid { stroke: var(--rule); stroke-width: 0.75; stroke-dasharray: 1 3;
  opacity: .85; }
.ct-templine { fill: none; stroke: var(--lane-weather); stroke-width: 1.6;
  stroke-linejoin: round; stroke-linecap: round; }
/* The stretch before the forecast begins is MODELLED, not forecast — see
   _sky() in render.py — so it is drawn broken. A solid line over hours
   nobody reported would be the drawing telling a lie the data does not. */
.ct-templine--est { stroke-dasharray: 3 3; opacity: .65; }
.ct-tempfill, .ct-range { fill: var(--lane-weather); opacity: var(--wash-temp);
  stroke: none; }
.ct-tempmean { fill: none; stroke: var(--lane-weather); stroke-width: 1.5;
  stroke-dasharray: 4 3; }
.ct-pop { fill: var(--lane-transit); opacity: .34; }
.ct-nohz { fill: url(#ct-hatch); opacity: .5; }
.ct-hatchline { stroke: var(--ink-3); stroke-width: .6; }

/* --- rules and ticks. Axis and ticks are TRACED, so --ink-3, not --rule.
   Every one of these was a full-weight 1px line, and eight kinds of
   full-weight line in one 350px block is what "hard" means. They are
   hairlines now; the hierarchy is carried by opacity, not by four different
   strokes all shouting. --- */
.ct-axis { stroke: var(--ink-3); stroke-width: 0.75; opacity: .8; }
.ct-sep  { stroke: var(--rule); stroke-width: 0.75; opacity: .7; }
.ct-tick { stroke: var(--ink-3); stroke-width: 0.75; opacity: .38; }
.ct-tick--maj { opacity: .72; stroke-width: 0.9; }

/* --- marks: severity carries the hue, shape carries the redundancy --- */
/* A bar was a solid saturated block with cream lettering on it — which is a
   chart's idiom, and on cream stock it read as a strip of highlighter. Now
   it is drawn the way a newspaper draws a span: a pale tint inside a
   hairline rule, with the lettering in ink. Legible on either stock. */
.ct-bar { fill-opacity: .18; stroke-width: 0.9; stroke-opacity: .6;
  stroke-linejoin: round; }
.ct-dot { fill-opacity: 1; stroke: none; }
.ct-mk--info   .ct-bar, .ct-mk--info   .ct-dot { fill: var(--lane-transit);
  stroke: var(--lane-transit); }
.ct-mk--warn   .ct-bar, .ct-mk--warn   .ct-dot { fill: var(--warn);
  stroke: var(--warn); }
.ct-mk--danger .ct-bar, .ct-mk--danger .ct-dot { fill: var(--danger);
  stroke: var(--danger); }
.ct-mk--act    .ct-bar, .ct-mk--act    .ct-dot { fill: var(--ok);
  stroke: var(--ok); }
.ct-mk { opacity: .82; }
.ct-mk.is-on { opacity: 1; }
.ct-mk.is-on .ct-bar, .ct-mk.is-on .ct-dot { stroke: var(--ink); stroke-width: 1.2; }
.ct-mk.is-on .ct-bar { fill-opacity: .34; }
.ct-count { fill: var(--lane-transit); opacity: .5; }
.ct-aq { fill: var(--ink-3); opacity: .5; }

/* --- lettering ---------------------------------------------------------
   THE SANS IS A LABEL FACE, NOT A MODULE FACE. A newspaper sets a chart in
   its text face and keeps the grotesque for the standing head over a column
   (BUSINESS) and the agate under an entry. This strip had every word in it —
   event titles, hour numbers, degrees — at 8-9px sans tracked 0.09em, which
   is exactly why it read scrunched beside the serif page around it. What
   stays sans is the row and band names, because those ARE standing heads.
   Everything a reader reads as language or as a figure is Newsreader.

   `.ct-tlab` and `.ct-sun` had NO RULE AT ALL. citytime.js has always emitted
   those two class names; the stylesheet declared `.ct-rulelab`, which nothing
   has ever used. So the hour numbers along the ruler and the sunrise/sunset
   glyphs inherited the body serif at 16px, against the 8-9px of everything
   around them — and the sun glyphs, whose baseline is set three pixels ABOVE
   the ruler lane, carried that whole ascent up into the band overhead. The
   dead rule sitting in the same selector list is what made the stylesheet
   look like it had them covered. Same failure as the `.ct-empty` / `.ct-emptylab` mixup
   documented below, one class over, shipped at the same time. */
/* The strip's lettering is the paper's, not a dashboard's. These two were
   the last sans faces inside the plot, sitting beside serif numerals and
   serif event labels, which is what read as off-brand. */
.ct-lanelab, .ct-bandlab {
  font-family: var(--serif); font-size: 9.5px; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; fill: var(--ink-2); }
.ct-bandlab { font-size: 8.5px; letter-spacing: 0.18em; fill: var(--ink); }
/* Hours, weekdays and degrees: figures in the paper's own voice. */
.ct-tlab { font-family: var(--serif); font-size: 10.5px; font-weight: 400;
  letter-spacing: 0.01em; fill: var(--ink-3); }
.ct-sun  { font-family: var(--serif); font-size: 9.5px; fill: var(--ink-3); }
.ct-gridlab { font-family: var(--serif); font-size: 9.5px; font-weight: 400;
  letter-spacing: 0; text-transform: none; fill: var(--ink-3); }
/* `.ct-empty` is the LINE ruled across a lane with nothing in it, and
   `.ct-emptylab` is the note sitting on it. This rule styled the line with
   font properties — which do nothing to a <line> — and left it with no
   stroke, so it never drew; and it never reached the label at all, which
   fell back to the body serif at 16px and floated in the middle of the
   plot like a sentence someone had dropped there. The comment eight lines
   below, on .ct-mklab, describes this exact failure: "leave it unstyled
   and the browser inherits 16px serif". It was live one class over. */
.ct-empty { stroke: var(--rule); stroke-width: 1; }
.ct-emptylab { font-family: var(--serif); font-size: 10.5px; font-weight: 400;
  font-style: italic; letter-spacing: 0; text-transform: none;
  fill: var(--ink-3); }
.ct-nowlab { font-family: var(--serif); font-size: 9.5px; font-weight: 600;
  letter-spacing: 0.13em; text-transform: uppercase; fill: var(--ink-2); }

/* the playhead: the accent as a 1px full-height rule. It is the ninth signal,
   separated by FORM because a ninth discriminable hue does not exist. */
.ct-head-line { stroke: var(--accent); stroke-width: 1; shape-rendering: crispEdges; }
.ct-head-halo { stroke: var(--ground); stroke-width: 3; opacity: .85; }
.ct-head-cap  { fill: var(--accent); }
.ct-nowline { stroke: var(--ink-3); stroke-width: 0.9; stroke-dasharray: 1 4;
  opacity: .6; }
.ct-alertwash { fill: var(--danger); opacity: .1; }

/* --- readout: the document half. Every fact here is a link. --- */
.ct-out { min-height: 6rem; padding: 0.7rem 0 0.2rem;
  font: 400 var(--t-d1)/1.55 var(--sans); color: var(--ink-2); }
.ct-out a { color: var(--link); }
.ct-out .ct-when, .ct-out b { color: var(--ink); font-weight: 700; }
.ct-out .ct-rel { color: var(--ink-3); margin-inline-start: 0.5em; font-weight: 500; }
.ct-out .ct-lane-h { display: block; margin-top: 0.45rem;
  font: 600 var(--t-d-3)/1.4 var(--sans); letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--ink-3); }
.ct-note { margin: 0.2rem 0 0; font: italic 400 var(--t-d-1)/1.45 var(--serif); color: var(--ink-3); }
.ct-tbtn { appearance: none; background: none; border: 0; padding: 0.4rem 0; cursor: pointer;
  font: 500 var(--t-d-2)/1 var(--sans); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--link); text-decoration: underline; text-underline-offset: 0.2em; }
/* The table is the timeline AS A DOCUMENT — printable, searchable, read
   line by line. That is body copy, so it is set like body copy. Only the
   column heads stay sans, because a column head is a standing head. */
.ct-table table { width: 100%; border-collapse: collapse; font: 400 var(--t-d0)/1.45 var(--serif); }
.ct-table th, .ct-table td { text-align: left; padding: 0.3rem 0.6rem 0.3rem 0;
  border-bottom: 1px solid var(--ink-3); }   /* traced rule: --ink-3, not --rule */
.ct-table th { font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-3); }

@media (prefers-reduced-motion: reduce) { .citytime * { transition: none !important; } }
@media print {
  /* The strip is a scrubbable instrument; on paper it is a picture of a control
     nobody can move, so it goes and the table it summarises stays. The toggle
     button is built by JS as .ct-tabtoggle — .ct-tbtn was an earlier name and
     hiding it was hiding nothing. */
  .ct-zooms, .ct-tabtoggle, .ct-plot { display: none; }
  /* JS sets hidden on the table once it collapses it; print must override the
     attribute, not just the class. */
  .ct-table, .ct-table[hidden] { display: block !important; }
  .ct-table table { width: 100%; }
  .ct-note { color: #000; }
}

/* Mark lettering: the event's own title, so the text face. clip() in
   citytime.js no longer trusts a hand-fitted constant for the width of a
   character — it measures this rule as rendered, and falls back to 0.62 of
   whatever font-size is declared HERE when the SVG has not been laid out yet.
   The size is therefore still load-bearing, in both directions: change it and
   the clip budget changes with it, but remove the rule and the browser
   inherits the 16px body serif and every label overruns its bar. */
.ct-mklab { font-family: var(--serif); font-size: 10.5px; font-weight: 500;
  letter-spacing: 0; fill: var(--ink); }
.ct-chev { font-family: var(--serif); font-size: 12px; font-weight: 400;
  fill: var(--ink-2); }
.ct-more { font-family: var(--serif); font-size: 10px; font-weight: 400;
  font-style: italic; letter-spacing: 0; text-transform: none;
  fill: var(--ink-3); }

/* ── COUNCIL DIVISIONS ─────────────────────────────────────────────────
   A bar makes you eyeball a ratio. Council is thirteen people, so the honest
   encoding is thirteen marks you can count — filled for, hollow against — with
   the majority rule drawn at seat seven, because seven is what carries a
   motion. Fill and outline differ in FORM, so the split survives with hue
   removed entirely. */
.divisions { margin: 1.5rem 0 0; padding-top: 0.9rem; border-top: 2px solid var(--ink); }
.dv-lede { margin: 0 0 0.9rem; font: 400 var(--t-p0)/1.5 var(--serif); color: var(--ink-2); }
.dv-lede b { color: var(--ink); }
.dv { padding: 0.75rem 0 0.85rem; border-top: 1px solid var(--rule); }
.dv:first-of-type { border-top: 0; }
.dv-what { margin: 0 0 0.3rem; font: 600 var(--t-d2)/1.3 var(--serif); color: var(--ink); }
.dv-tally { margin: 0 0 0.45rem; font: 500 var(--t-d-1)/1.3 var(--sans); color: var(--ink-3); }
.dv-tally b { color: var(--ink); font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; font-size: var(--t-d-2); }
.dv-n { color: var(--ink); font-weight: 700; margin-inline-start: 0.4em;
  font-variant-numeric: lining-nums tabular-nums; }
.dv-margin { margin-inline-start: 0.5em; padding: 0.05rem 0.3rem;
  font: 700 var(--t-d-3)/1.4 var(--sans); letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ground); background: var(--warn); }

.dv-dots { position: relative; display: grid; gap: 3px;
  grid-template-columns: repeat(13, minmax(0, 1fr)); max-width: 22rem;
  margin: 0 0 0.45rem; padding: 0 0 0.55rem; list-style: none; }
.dv-dot { position: relative; aspect-ratio: 1; min-height: 12px; }
.dv-dot--for     { background: var(--ok); }
.dv-dot--against { background: none; box-shadow: inset 0 0 0 2px var(--danger); }
.dv-dot--absent  { background: none; box-shadow: inset 0 0 0 1px var(--rule-strong); }
/* the majority rule sits on the boundary after seat 7 and is labelled, so it
   is never a bare hairline carrying meaning alone */
.dv-major { position: absolute; top: -2px; bottom: 10px;
  left: calc(var(--seat) / 13 * 100%); width: 0;
  border-left: 1px solid var(--ink-2); }
.dv-major::after { content: "7 carries"; position: absolute; left: 4px; bottom: -1px;
  font: 600 var(--t-d-3)/1 var(--sans); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-3); white-space: nowrap; }
/* dot names now use the shared tooltip system, not a CSS ::after */
.dv-quote { margin: 0.35rem 0 0; font: italic 400 var(--t-d0)/1.45 var(--serif);
  color: var(--ink-3); }
.dv-note { margin: 0.9rem 0 0; font: 400 var(--t-d-1)/1.55 var(--sans); color: var(--ink-3);
  max-inline-size: 70ch; }
.dv-note a { color: var(--link); }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* ── FACETS ────────────────────────────────────────────────────────────
   Tappable words with a rule, not chips: a rounded pill reads as an app, a
   word with a rule under it reads as a newspaper index. State is carried by
   strike-through AND colour, so it survives with hue removed entirely.
   Everything ships visible; the script only hides. */
.fx-k { font: 500 var(--t-d-2)/1.5 var(--sans); letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--ink-3); }
.fx { appearance: none; background: none; border: 0; padding: 0.1rem 0; cursor: pointer;
  font: 600 var(--t-d-1)/1.4 var(--sans); letter-spacing: 0.04em; color: var(--ink);
  border-bottom: 2px solid transparent; }
button.fx:hover { border-bottom-color: var(--rule-strong); }
.fx[aria-pressed="true"] { border-bottom-color: var(--accent); }
.fx[aria-pressed="false"] { color: var(--ink-3); text-decoration: line-through;
  text-decoration-thickness: 1px; border-bottom-color: transparent; }
.fx:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.fx-n { margin-inline-start: 0.35em; font-size: var(--t-d-3); font-weight: 700;
  color: var(--ink-3); font-variant-numeric: lining-nums tabular-nums; }
.fx[aria-pressed="false"] .fx-n { color: var(--rule-strong); }
.fx-count { margin-inline-start: auto; font: 500 var(--t-d-2)/1.5 var(--sans);
  letter-spacing: 0.06em; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.fx-reset { appearance: none; background: none; border: 0; padding: 0.1rem 0; cursor: pointer;
  font: 600 var(--t-d-2)/1.5 var(--sans); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--link); text-decoration: underline; text-underline-offset: 0.2em; }
@media print { .facets { display: none; } }

/* ── LOCATOR MAP ───────────────────────────────────────────────────────
   Two registers on one drawing. The basemap is a hand-drawn schematic,
   because the pipeline has no boundary geometry and a fake one would be worse
   than an honest diagram. The markers are real coordinates projected onto it.
   Items resolved only to a neighbourhood are listed beside the map, never
   pinned — "somewhere in Balwin" is true, a dot on a corner is not. */
.locator { margin: 1.5rem 0 0; padding-top: 0.9rem; border-top: 2px solid var(--ink); }
.cm-wrap { display: grid; gap: 1rem 2rem; }
@media (min-width: 60rem) { .cm-wrap { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); } }
.cm-stage { position: relative; }
.citymap { display: block; width: 100%; height: auto; max-height: 30rem; cursor: grab;
  --cm-k: 1; touch-action: none; }
.citymap.is-grabbing { cursor: grabbing; }
.citymap:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.cm-valley { fill: none; stroke: var(--lane-transit); opacity: .16; stroke-width: 15; }
.cm-bank   { fill: none; stroke: var(--lane-transit); opacity: .3; stroke-width: 6.4; }
.cm-chan   { fill: none; stroke: var(--lane-weather); opacity: .55; stroke-width: 3.2; }
.cm-core { fill: var(--ink); opacity: .17; }
.cm-inst { fill: var(--ink); opacity: .1; }
.cm-case { fill: none; stroke: var(--ground); stroke-width: 3; }
.cm-case--ring { stroke-width: 3.6; }
.cm-case--rail { stroke-width: 3.6; }
.cm-road { fill: none; stroke: var(--ink-3); stroke-width: 1.15; }
.cm-ring { fill: none; stroke: var(--ink-2); stroke-width: 1.75; }
/* three LRT lines, three dash patterns: the pattern carries the distinction,
   hue is the third channel, so it survives deuteranopia and mono print */
.cm-lrt { fill: none; stroke-width: 1.9; }
.cm-lrt--c { stroke: var(--ink); }
.cm-lrt--m { stroke: var(--ok); stroke-dasharray: 6 2.6; }
.cm-lrt--v { stroke: var(--warn); stroke-dasharray: 2 2.4; }

.cm-lab, .cm-water-lab { paint-order: stroke fill; stroke: var(--ground);
  stroke-width: 2.4; stroke-linejoin: round; }
.cm-lab { font-family: var(--sans); font-size: calc(8.4px * var(--cm-k));
  font-weight: 640; letter-spacing: 0.085em; text-transform: uppercase; fill: var(--ink); }
.cm-lab--stop { fill: var(--ink-3); font-weight: 560; }
.cm-lab--road { fill: var(--ink-3); }
.cm-water-lab { font-family: var(--serif); font-style: italic;
  font-size: calc(8.4px * var(--cm-k)); fill: var(--lane-weather); opacity: .9; }
/* zoomed out, the small labels go rather than pile up */
.citymap[data-z="far"] .cm-lab--stop { opacity: .7; }

.cm-mk { cursor: pointer; }
/* `fill` and `stroke` are INHERITED properties, and that is the only way to
   reach inside a <use> shadow tree — a descendant selector such as
   `.cm-mk--permit .mk-o` never matches the cloned content, so the glyph would
   render with the default black fill on a near-black ground and vanish. */
.cm-mk--permit   { stroke: var(--warn);   fill: var(--warn); }
.cm-mk--docket   { stroke: var(--ok);     fill: var(--ok); }
.cm-mk--incident { stroke: var(--danger); fill: var(--danger); }
.cm-mk--transit  { stroke: var(--lane-transit); fill: var(--lane-transit); }
.cm-mk--road     { stroke: var(--ink);    fill: var(--ink); }
.cm-mk:hover, .cm-mk:focus { stroke: var(--ink); }
.cm-mk:focus-visible { outline: none; stroke: var(--accent); fill: var(--accent); }
.cm-val { font-family: var(--sans); font-size: calc(7.4px * var(--cm-k));
  font-weight: 700; fill: var(--ink); paint-order: stroke fill;
  stroke: var(--ground); stroke-width: calc(2.2 * var(--cm-k)); }
.citymap[data-z="far"] .cm-val { display: none; }

.cm-tools { position: absolute; top: 6px; right: 6px; z-index: 2; display: flex; gap: 3px; }
.cm-btn { appearance: none; cursor: pointer; min-width: 26px; height: 26px; padding: 0 0.4rem;
  font: 700 var(--t-d-1)/1 var(--sans); color: var(--ink);
  background: var(--surface); border: 1px solid var(--rule-strong); border-radius: 0; }
.cm-btn--w { width: auto; font-size: var(--t-d-3); letter-spacing: 0.08em; text-transform: uppercase; }
.cm-btn:hover { background: var(--surface-2); border-color: var(--ink-3); }
.cm-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.cm-tip { position: absolute; left: 0; right: 0; bottom: 0; margin: 0; z-index: 2;
  padding: 0.4rem 0.55rem; font: 500 var(--t-d-1)/1.35 var(--sans);
  color: var(--ground); background: var(--ink); }

.cm-rail { align-self: start; }
.cm-list { margin: 0.35rem 0 0; padding: 0; list-style: none; }
.cm-list li { padding: 0.35rem 0; border-top: 1px solid var(--rule);
  font: 400 var(--t-d-1)/1.35 var(--sans); }
.cm-list li:first-child { border-top: 0; }
.cm-where { display: block; font: 600 var(--t-d-3)/1.5 var(--sans); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3); }
.cm-list a { color: var(--ink); text-decoration: none; }
.cm-list a:hover { color: var(--link); text-decoration: underline; }

.cm-legend { display: flex; flex-wrap: wrap; gap: 0.3rem 1.2rem; margin: 0.7rem 0 0;
  padding: 0; list-style: none; font: 500 var(--t-d-2)/1.6 var(--sans);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-3); }
.cm-legend li { display: flex; align-items: center; gap: 0.4rem; }
.cm-sw { width: 15px; height: 15px; flex: none; }
.cm-sw { stroke: var(--ink-3); fill: var(--ink-3); }
.cm-cap { margin: 0.5rem 0 0; font: 400 var(--t-d-1)/1.55 var(--sans); color: var(--ink-3);
  max-inline-size: 76ch; }
@media print { .cm-tools, .cm-tip { display: none; } }

/* ── TOOLTIPS ──────────────────────────────────────────────────────────
   A real element, not title=. Hover and focus both drive it, Escape
   dismisses it, and it is only ever a gloss — anything actionable is a link
   in the flow, because a tooltip that hides a URL is a trap. */
.tt { position: absolute; z-index: 40; max-width: 26rem; padding: 0.45rem 0.6rem;
  font: 400 var(--t-d-1)/1.45 var(--sans); color: var(--ground); background: var(--ink);
  pointer-events: none; }
.tt[hidden] { display: none; }
.fig { color: var(--ink); font-weight: 700; cursor: help;
  border-bottom: 1px dotted var(--ink-3); }
.fig:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.fig-note { display: block; margin-top: 0.3rem; color: var(--ink-3);
  font-size: var(--t-d-2); font-style: italic; }

/* ── THE FOLD ──────────────────────────────────────────────────────────
   The masthead dissolving into grain as it scrolls away — ink lifting off
   the stock. Decoration only: the real nameplate stays in the DOM and the
   canvas is inert. Off entirely under reduced-motion or a coarse pointer. */
.dissolve { position: absolute; inset-inline-start: 0; top: 0; z-index: 1;
  pointer-events: none; }
.masthead { position: relative; }
.masthead .wordmark, .masthead .flag, .masthead .rules, .masthead .dateline {
  position: relative; z-index: 2; }
.masthead .wordmark { will-change: opacity; }
@media (prefers-reduced-motion: reduce) { .dissolve { display: none; }
  .masthead .wordmark { opacity: 1 !important; } }
@media print { .dissolve { display: none; }
  .masthead .wordmark { opacity: 1 !important; } }

/* The timeline table ships visible and is collapsed by JS, so a reader without
   JS gets the whole window as a document rather than an empty frame. */
.ct-table { margin-top: 0.6rem; overflow-x: auto; }
.ct-table caption { text-align: left; padding-bottom: 0.35rem;
  font: 500 var(--t-d-2)/1.4 var(--sans); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3); }
.ct-table td a { color: var(--link); }
.ct-tabtoggle { appearance: none; background: none; border: 0; padding: 0.4rem 0;
  cursor: pointer; font: 500 var(--t-d-2)/1 var(--sans); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--link);
  text-decoration: underline; text-underline-offset: 0.2em; }
.ct-tabtoggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.ct-empty-note { margin: 0.4rem 0 0; font: italic 400 var(--t-d0)/1.5 var(--serif);
  color: var(--ink-3); }

/* ── DIGEST ────────────────────────────────────────────────────────────
   Headline-and-link items on a section page. A stub is a pointer, so it gets
   the shape of a pointer: one dense scannable line, not an article block.
   Rendering twelve of these like articles drowns the written pieces above. */
.dg-note { margin: 0.4rem 0 0.7rem; max-inline-size: 68ch;
  font: 400 var(--t-d-1)/1.5 var(--sans); color: var(--ink-3); }
.digest { margin: 0; padding: 0; list-style: none; }
.dg-row { display: grid; grid-template-columns: 3.2rem minmax(0, 1fr) auto;
  gap: 0.1rem 0.8rem; align-items: baseline;
  padding: 0.45rem 0; border-top: 1px solid var(--rule); }
.dg-row:first-child { border-top: 0; }
.dg-when { font: 600 var(--t-d-2)/1.5 var(--sans); color: var(--ink-3);
  font-variant-numeric: lining-nums tabular-nums; }
.dg-row:not(:has(.dg-when)) .dg-head { grid-column: 1 / 3; }
.dg-head { font: 400 var(--t-p0)/1.35 var(--serif); color: var(--ink);
  text-decoration: none; }
.dg-head:hover { color: var(--link); text-decoration: underline;
  text-underline-offset: 0.15em; }
.dg-src { justify-self: end; white-space: nowrap;
  font: 500 var(--t-d-2)/1.6 var(--sans); letter-spacing: 0.04em; color: var(--ink-3); }
.dg-more { margin-inline-start: 0.35em; padding: 0 0.25em;
  font-weight: 700; color: var(--ground); background: var(--ink-3); }
@media (max-width: 34rem) {
  .dg-row { grid-template-columns: 3.2rem minmax(0, 1fr); }
  .dg-src { grid-column: 2; justify-self: start; }
}

/* Controls citytime.js builds at runtime. Styled here rather than inline in the
   script so they follow the palette and stay overridable. */
.ct-pin, .ct-next {
  appearance: none; background: none; border: 0; padding: 0.1rem 0.25rem;
  cursor: pointer; font: 600 var(--t-d-2)/1.4 var(--sans); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--link);
}
.ct-pin { border: 1px solid var(--rule-strong); }
.ct-pin[aria-pressed="true"] { color: var(--ground); background: var(--accent);
  border-color: var(--accent); }
.ct-next { text-decoration: underline; text-underline-offset: 0.2em; }
.ct-pin:hover, .ct-next:hover { color: var(--ink); }

/* ── CONDITIONS ────────────────────────────────────────────────────────
   What a reader needs before the news, in sentences. This replaced a 937px
   scrubbable timeline that reported "nothing scheduled" on five of seven
   lanes — absence is not worth a chart. The instrument still exists below the
   news for anyone who wants the day plotted. */
/* nowrap keeps the short facts whole — "ETS 4 detours" should not break across
   a line — but the weather item is a SENTENCE now, and nowrap on a sentence
   pushed 169px of horizontal overflow onto a 375px screen. */
.cd-alert { color: var(--danger) !important; }

/* ── APPARATUS ─────────────────────────────────────────────────────────
   Everything instrumental, below the news rather than in front of it. A front
   page ranks; it does not hand the reader controls before it has told them
   what happened. */
.apparatus { margin-top: 2.5rem; padding-top: 1rem; border-top: 3px solid var(--ink); }
.app-lede { margin: 0 0 0.5rem; max-inline-size: 62ch;
  font: italic 400 var(--t-p0)/1.5 var(--serif); color: var(--ink-3); }
.colophon .desks { margin: 0 0 0.8rem; border-top: 0;
  border-bottom: 1px solid var(--rule); }


/* ── SECOND TIER ───────────────────────────────────────────────────────
   Ranks 2 to 4. The pipeline already decides an order; rendering every front
   item at one size discarded that judgement, so a homicide and a pool repair
   arrived at the reader identically. Three weights, not one:
   lead 2.75rem / second 1.5rem / rest 1.125rem. */
.item--second h3 {
  font-size: var(--h-m);
  line-height: 1.16;
  letter-spacing: -0.018em;
  font-variation-settings: "opsz" 40;
  margin-bottom: 0.5rem;
}
.item--second .body { font-size: var(--t-d2); line-height: 1.45; }
.item--second { padding-top: 1.1rem; }
@media (min-width: 64rem) {
  .item--second h3 { font-size: 1.75rem; }
}

/* Named divisions. The minority is the story — on a 7-6 the six names are what
   a reader came for — so it leads, and the majority follows it. Both sides are
   named: the vote marks used to carry the majority on hover, and when they went
   the only route to it went too.

   The label column needs slack as well as a width. min-width alone sized it to
   exactly fit the longest label, so "Against" filled its box to the edge and
   ran straight into the first name: "AgainstE. Rutherford". */
/* Two columns, label and names, so a roll that runs to a second line keeps its
   names aligned instead of restarting under the label. Thirteen surnames wrap
   at almost any width, so this is the normal case, not the edge one.

   Done as a grid rather than a hanging indent: text-indent pulls only the first
   line, which put the label and the first name on top of each other. */
.dv-roll { display: grid; grid-template-columns: 4.95rem minmax(0, 1fr);
  margin: 0.1rem 0 0.35rem; max-inline-size: 62ch;
  font: 400 var(--t-d0)/1.5 var(--sans); color: var(--ink-2); }
.dv-roll-k { padding-right: 0.75rem; padding-top: 0.18rem;
  font: 600 var(--t-d-3)/1.5 var(--sans); letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--ink-3); }
.dv-mover { margin: 0; font: 400 var(--t-d-1)/1.5 var(--sans); color: var(--ink-3); }
.dv-nomotion { font-style: italic; color: var(--ink-3); }
.dv--split .dv-what { font-weight: 700; }
.dv-dot[data-tip] { cursor: help; }
.dv-dot[data-tip]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── MAP MODE ──────────────────────────────────────────────────────────
   A map earns a section when it is the fastest way to answer "does this
   affect where I live". Below about eight located items it loses that race to
   a list of neighbourhood names, so it shrinks to a locator beside the list.
   The threshold does the deciding; when more items get geocoded the map grows
   back without anyone touching this file. */
@media (min-width: 60rem) {
  .cm-wrap--locator { grid-template-columns: minmax(0, 15rem) minmax(0, 1fr); }
  .cm-wrap--map     { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); }
}
.locator--locator .citymap { max-height: 17rem; }
.locator--locator .cm-legend { font-size: var(--t-d-3); gap: 0.25rem 0.8rem; }
.locator--locator .cm-list li { padding: 0.3rem 0; }
.locator--map .citymap { max-height: 30rem; }

/* ── THE FRONT PACKAGE ─────────────────────────────────────────────────
   The lead and the stories beside it, above the desks. Promoting the lead's
   whole SECTION reordered the paper every morning and pushed an active health
   advisory below road salt; the lead floats here instead and the desks keep a
   fixed order a reader can learn. */
.frontpkg { display: grid; gap: 1.25rem 2.25rem; margin: 0 0 1.75rem;
  padding-bottom: 1.25rem; border-bottom: 2px solid var(--ink); }
@media (min-width: 60rem) {
  .frontpkg { grid-template-columns: minmax(0, 1.75fr) minmax(0, 1fr); }
  .fp-rail { border-inline-start: 1px solid var(--rule); padding-inline-start: 2.25rem; }
}
.frontpkg .item { border-top: 0; padding-top: 0; }
.fp-rail .item + .item { margin-top: 1rem; padding-top: 1rem;
  border-top: 1px solid var(--rule); }
.frontpkg .kicker { margin: 0 0 0.3rem; font: 700 var(--t-d-3)/1.3 var(--sans);
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-3); }

/* An item missing something a reader needs says so, where they can see it.
   Sorting it quietly downward was the one dishonest move on the page. */
.defect { margin: 0.5rem 0 0.6rem; padding: 0.5rem 0.7rem;
  border-inline-start: 3px solid var(--warn); background: var(--surface);
  font: 400 var(--t-d0)/1.5 var(--sans); color: var(--ink-2); }
.defect-k { display: block; margin-bottom: 0.1rem;
  font: 700 var(--t-d-3)/1.4 var(--sans); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--warn); }
.defect a { color: var(--link); }

/* ── QUIET DAY ─────────────────────────────────────────────────────────
   A thin page is a real outcome, not a breakage. Saying so is the difference
   between a short edition and one that looks broken — and it is something a
   paper can say that a dashboard never has. */
.quiet { margin: 0 0 1.5rem; padding: 0.9rem 1.1rem;
  border-top: 2px solid var(--ink); border-bottom: 1px solid var(--rule);
  background: var(--surface); }
.quiet-h { margin: 0 0 0.25rem; font: 600 1.25rem/1.25 var(--serif); color: var(--ink); }
.quiet-b { margin: 0; max-inline-size: 66ch;
  font: 400 var(--t-d1)/1.55 var(--sans); color: var(--ink-2); }

/* ── WHAT'S ON ─────────────────────────────────────────────────────────
   The answer to "if I finish this brief and still don't know what's on
   tonight, you're one of my tabs." Grouped by day, because a listings page is
   read by day and never by relevance. Category is a mark AND a word AND a hue,
   so the colour is the decorative channel and the listing survives mono print. */
.events { margin: 2rem 0 0; padding-top: 0.9rem; border-top: 2px solid var(--ink); }
.ev-legend { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.3rem 1rem;
  margin: 0 0 0.6rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--rule); }
.ev-leg { appearance: none; background: none; border: 0; padding: 0.1rem 0; cursor: pointer;
  font: 600 var(--t-d-1)/1.4 var(--sans); letter-spacing: 0.04em; color: var(--ink);
  border-bottom: 2px solid transparent; }
.ev-leg i { font-style: normal; margin-inline-end: 0.35em; color: var(--cat, var(--ink-3)); }
.ev-leg[aria-pressed="true"] { border-bottom-color: var(--cat, var(--ink-3)); }
.ev-leg[aria-pressed="false"] { color: var(--ink-3); text-decoration: line-through;
  text-decoration-thickness: 1px; border-bottom-color: transparent; }
.ev-leg[aria-pressed="false"] i { color: var(--ink-3); }
.ev-n { margin-inline-start: 0.35em; font-size: var(--t-d-3); font-weight: 700;
  color: var(--ink-3); font-variant-numeric: lining-nums tabular-nums; }

.ev-leg--music,   .ev-row--music   { --cat: var(--lane-weather); }
.ev-leg--stage,   .ev-row--stage   { --cat: var(--lane-council); }
.ev-leg--market,  .ev-row--market  { --cat: var(--lane-dev); }
.ev-leg--sport,   .ev-row--sport   { --cat: var(--lane-transit); }
.ev-leg--family,  .ev-row--family  { --cat: var(--ok); }
.ev-leg--community,.ev-row--community { --cat: var(--lane-safety); }
.ev-leg--other,   .ev-row--other   { --cat: var(--ink-3); }
/* Thirteen kinds, eleven hues: campus shares stage's amber and outdoors shares
   the market green. The mark and the word carry the identity; the hue never
   did on its own, which is the whole reason a share is safe. */
.ev-leg--library, .ev-row--library { --cat: var(--link); }
.ev-leg--tech,    .ev-row--tech    { --cat: var(--accent); }
.ev-leg--festival,.ev-row--festival{ --cat: var(--danger); }
.ev-leg--art,     .ev-row--art     { --cat: var(--warn); }
.ev-leg--campus,  .ev-row--campus  { --cat: var(--lane-council); }
.ev-leg--outdoor, .ev-row--outdoor { --cat: var(--lane-dev); }

/* The fold. A day with sixty rows shows twelve and a disclosure; the rest are
   in the HTML, so scripts-off and print still get the whole day. */
.ev-fold { display: block; padding: 0.25rem 0 0; border-top: 1px solid var(--rule-soft); }
.ev-more > summary { cursor: pointer; list-style: none; padding: 0.3rem 0;
  font: 700 var(--t-d-3)/1.6 var(--sans); letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--link); }
.ev-more > summary::-webkit-details-marker { display: none; }
.ev-more > summary::before { content: "+ "; }
.ev-more[open] > summary::before { content: "\2013 "; }
.ev-list--more .ev-row:first-child { border-top: 1px solid var(--rule-soft); }
@media print { .ev-more > summary { display: none; }
  .ev-more:not([open]) > .ev-list--more { display: block; } }

.ev-day { display: flex; align-items: center; gap: 0.6rem; margin: 0.9rem 0 0.15rem;
  font: 700 var(--t-d-3)/1 var(--sans); letter-spacing: 0.15em; text-transform: uppercase;
  color: var(--ink-3); }
.ev-day::after { content: ""; flex: 1; height: 1px; background: var(--rule); }
.ev-list { margin: 0; padding: 0; list-style: none; }
.ev-row { display: grid; grid-template-columns: 4.6rem 1rem minmax(0, 1fr) auto;
  gap: 0.1rem 0.6rem; align-items: baseline; padding: 0.4rem 0;
  border-top: 1px solid var(--rule-soft); }
.ev-row:first-child { border-top: 0; }
.ev-t { font: 700 var(--t-d-1)/1.5 var(--sans); color: var(--ink);
  font-variant-numeric: lining-nums tabular-nums; }
.ev-t--all { font-weight: 500; color: var(--ink-3); letter-spacing: 0.04em; }
.ev-mark { color: var(--cat); font-size: var(--t-d-1); line-height: 1.5; }
.ev-what { font: 400 var(--t-p0)/1.35 var(--serif); color: var(--ink); text-decoration: none; }
.ev-what:hover { color: var(--link); text-decoration: underline;
  text-underline-offset: 0.15em; }
.ev-where { grid-column: 3; font: 400 var(--t-d-1)/1.4 var(--sans); color: var(--ink-3); }
.ev-cost { justify-self: end; font: 600 var(--t-d-1)/1.5 var(--sans); color: var(--ink-2);
  white-space: nowrap; }
.ev-note { margin: 0.8rem 0 0; max-inline-size: 74ch;
  font: 400 var(--t-d-1)/1.55 var(--sans); color: var(--ink-3); }
@media (max-width: 34rem) {
  .ev-row { grid-template-columns: 4.6rem 1rem minmax(0, 1fr); }
  .ev-cost { grid-column: 3; justify-self: start; }
}
@media print { .ev-legend { display: none; } }

/* The weather band below the news is a reference table, not a second telling
   of the sentence at the top of the page. Its previous form printed ECCC's
   prose AND a row of chips scraped from that same prose — four facts, each
   said twice, in two typefaces. */
.wx-table { border-collapse: collapse; margin: 0.1rem 0 0;
  font: 400 var(--t-d-1)/1.4 var(--sans);
  font-variant-numeric: lining-nums tabular-nums; }
.wx-table th { text-align: left; padding: 0.2rem 0.9rem 0.2rem 0; font-weight: 500;
  letter-spacing: 0.09em; text-transform: uppercase; font-size: var(--t-d-2);
  color: var(--ink-3); }
.wx-table td { padding: 0.2rem 0; color: var(--ink); font-weight: 600; }
.wx-warn { margin: 0 0 0.4rem; font: 600 var(--t-d0)/1.45 var(--sans); color: var(--danger); }

/* ── WHAT'S AHEAD ──────────────────────────────────────────────────────
   The civic diary. This used to render date, title and note and discard the
   time, the room, the agenda PDF, the bylaw number and whether it is streamed
   — all of which the pipeline was already supplying. Seven flat rows that
   looked like a schedule and answered none of a schedule's questions.
   The one a resident actually has is "can I still speak at this". */
.ahead-label { margin: 0 0 0.3rem; }
.ah-day { display: flex; align-items: center; gap: 0.55rem; margin: 0.7rem 0 0.1rem;
  font: 700 0.5875rem/1 var(--sans); letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-3); }
.ah-day::after { content: ""; flex: 1; height: 1px; background: var(--rule); }
.ah-list { margin: 0; padding: 0; list-style: none; }
.ah-row { display: grid; grid-template-columns: 4.4rem minmax(0, 1fr);
  gap: 0.1rem 0.6rem; padding: 0.4rem 0 0.45rem;
  border-top: 1px solid var(--rule-soft); }
.ah-row:first-child { border-top: 0; }
.ah-row--hearing { border-inline-start: 2px solid var(--ok); padding-inline-start: 0.5rem;
  grid-template-columns: 3.9rem minmax(0, 1fr); }
.ah-t { font: 700 var(--t-d-1)/1.5 var(--sans); color: var(--ink);
  font-variant-numeric: lining-nums tabular-nums; }
/* A hearing does not start at 12:00 AM. That is "date known, time not". */
.ah-t--tbd { font-weight: 500; font-size: var(--t-d-2); color: var(--ink-3);
  letter-spacing: 0.04em; }
.ah-what { font: 600 var(--t-d0)/1.35 var(--serif); color: var(--ink); text-decoration: none; }
.ah-what:hover { color: var(--link); text-decoration: underline; text-underline-offset: 0.15em; }
.ah-where { grid-column: 2; font: 400 var(--t-d-2)/1.4 var(--sans); color: var(--ink-3); }
.ah-tags { grid-column: 2; display: flex; flex-wrap: wrap; gap: 0.3rem; margin-top: 0.25rem; }
.ah-tag { padding: 0.05rem 0.34rem; font: 600 var(--t-d-3)/1.5 var(--sans);
  letter-spacing: 0.09em; text-transform: uppercase; border: 1px solid var(--rule);
  color: var(--ink-3); text-decoration: none; }
a.ah-tag:hover { color: var(--link); border-color: var(--link); }
.ah-tag--speak { color: var(--ok); border-color: var(--ok); font-weight: 700; }
.ah-tag--live { color: var(--danger); border-color: var(--danger); }
.ah-tag--quiet { border-color: transparent; padding-inline: 0; }


/* ── TIMELINE READOUT ──────────────────────────────────────────────────
   The lane key and its value are adjacent inline elements built by
   citytime.js, so with no layout between them they ran together: "SKY13°C",
   "Councilno council or committee meeting in this window". It read like the
   page had fallen over. The key is now a block label above its value. */
.ct-out .ct-r { display: block; margin-top: 0.5rem; }
.ct-out .ct-k { display: block; margin-bottom: 0.1rem;
  font: 600 var(--t-d-3)/1.4 var(--sans); letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--ink-3); }
.ct-out .ct-v { display: block; font: 400 var(--t-d0)/1.5 var(--sans); color: var(--ink-2); }
.ct-out .ct-hd { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem 0.8rem;
  padding-bottom: 0.35rem; border-bottom: 1px solid var(--rule); }
.ct-out .ct-time { font: 700 var(--t-d1)/1.4 var(--sans); color: var(--ink); }
.ct-out .ct-src { margin-inline-start: 0.4em; }

/* Readout entries. citytime.js emits the title, the tags, the sub-line and the
   source as bare inline siblings, so with no layout between them they render
   as one word: "Av-WBroute 10912:22–ongoing". The label fix earlier only
   reached one level; these are the level below it. Every part gets its own
   line or its own gap — nothing here relies on a space in the markup. */
.ct-e, .ct-v { display: block; }
.ct-e > b, .ct-v > b { display: block; margin-bottom: 0.15rem;
  font-weight: 700; color: var(--ink); }
.ct-e .ct-tags, .ct-v .ct-tags { display: flex; flex-wrap: wrap; gap: 0.3rem;
  margin: 0.15rem 0; }
.ct-e .ct-tag, .ct-v .ct-tag { padding: 0.04rem 0.32rem;
  font: 600 var(--t-d-3)/1.5 var(--sans); letter-spacing: 0.09em; text-transform: uppercase;
  border: 1px solid var(--rule); color: var(--ink-3); }
.ct-e .ct-sub, .ct-v .ct-sub { display: block; margin-top: 0.1rem;
  font: 400 var(--t-d-1)/1.45 var(--sans); color: var(--ink-3); }
.ct-e .ct-src, .ct-v .ct-src { display: inline-block; margin-top: 0.25rem;
  font: 500 var(--t-d-2)/1.4 var(--sans); color: var(--link); }
.ct-e { padding: 0.35rem 0; border-top: 1px solid var(--rule-soft); }
.ct-e:first-of-type { border-top: 0; }

/* A motion that is only a pointer to a report we do not hold. Saying so beats
   printing a crossword clue as though it were a summary. */
.dv-opaque { margin: 0.15rem 0 0.45rem; padding-inline-start: 0.7rem;
  border-inline-start: 2px solid var(--rule-strong);
  font: italic 400 var(--t-d-1)/1.5 var(--serif); color: var(--ink-3);
  max-inline-size: 62ch; }

/* "no council or committee meeting in this window" followed immediately by
   "→ next: ..." — the empty-lane sentence and the jump button rendered as one
   run of words. The button is the wayfinding half and belongs on its own line. */
.ct-none { display: block; font-style: italic; color: var(--ink-3); }
.ct-next { display: block; margin-top: 0.2rem; text-align: left; }

/* ── PAPER FURNITURE ───────────────────────────────────────────────────
   The reviewer: "It does not feel like a newspaper. There's no paper in it."
   He was right, and he was right about why — the layout was doing all the
   work of saying "somebody set this and took pride in it" and the surface was
   doing none. These are the small unnecessary things a real paper carries. */

/* A dropped initial on the lead. The single oldest signal in the trade that a
   person decided where a paper begins. */
@media (min-width: 44rem) {
  .fp-lead .item--lead .body::first-letter {
    /* initial-letter, NOT float.
       This was a floated ::first-letter, and the lead's body is column-count:2.
       A float inside a multi-column block is laid out against the wrong column
       box, so the dropped capital came out stranded mid-paragraph between the
       two columns with the sentence reading round it in the wrong order — the
       first thing on the page, set wrong. initial-letter is the property meant
       for this: the browser sinks the glyph into the following lines itself,
       with no float to misplace. */
    -webkit-initial-letter: 3 2;
    initial-letter: 3 2;
    font-family: var(--serif);
    font-weight: 600;
    padding-right: 0.08em;
    color: var(--ink);
    font-variation-settings: "opsz" 72;
  }
}

/* A mark between stories, so the eye has a place to stop that isn't a rule. */
.pack > section .item + .item::before {
  content: "❧";
  display: block;
  margin: 0 0 0.5rem;
  font-size: var(--t-d-1);
  line-height: 1;
  color: var(--rule-strong);
}

/* Edition furniture: the numbers a paper carries because a paper carries them. */

/* The section rules get a small terminal mark, the way a printed slug does. */
.pack > section > .sec::after,
.pack > section > .desk-h::after { background: var(--rule-strong); }

/* Permits as a record. The form's own wording stays one click away — it is the
   public record and not ours to discard — but it does not lead. */
.pm-table { margin-top: 0.2rem; }
.pm-table td { vertical-align: top; padding-block: 0.3rem; }
.pm-what { font-family: var(--serif); font-size: var(--t-d0); line-height: 1.35;
  color: var(--ink); }
.pm-where { font-size: var(--t-d-1); color: var(--ink-2); white-space: nowrap; }
.pm-addr { display: block; font-size: var(--t-d-2); color: var(--ink-3); }
.pm-val { text-align: right; font-weight: 700; white-space: nowrap;
  font-variant-numeric: lining-nums tabular-nums; }
.pm-form { margin-top: 0.2rem; }
.pm-form summary { list-style: none; cursor: pointer;
  font: 500 var(--t-d-3)/1.5 var(--sans); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-3); }
.pm-form summary::-webkit-details-marker { display: none; }
.pm-form summary::before { content: "+ "; }
.pm-form[open] summary::before { content: "− "; }
.pm-form summary:hover { color: var(--link); }
.pm-form p { margin: 0.2rem 0 0; font: 400 var(--t-d-1)/1.45 var(--sans); color: var(--ink-3); }

/* ── THE COUPON ────────────────────────────────────────────────────────
   The subscribe block was a rounded input, a bright button and two radio
   circles: every signup form on every SaaS site, sitting at the end of six
   thousand pixels of serif typography. A newspaper's subscription coupon is a
   ruled box set in the paper's own faces, and the button looks printed rather
   than clicked. It also ran in the left half and left the right half empty. */
.signup {
  margin: 2.5rem 0 0;
  padding: 1.1rem 1.25rem 1.25rem;
  border: 2px solid var(--ink);
  /* the dashed inner keyline of a coupon meant to be cut out */
  box-shadow: none;
  position: relative;
}
.signup::before {
  content: ""; position: absolute; inset: 5px;
  border: 1px dashed var(--rule-strong); pointer-events: none;
}
@media (min-width: 52rem) {
  .signup { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
            gap: 0 2.5rem; align-items: start; }
  .signup-h, .signup-lede { grid-column: 1; }
  .signup-form-wrap { grid-column: 2; grid-row: 1 / span 3; align-self: center; }
  .signup-fine { grid-column: 1 / -1; }
}
.signup-h { margin: 0 0 0.3rem; }
.signup-lede { margin: 0 0 0.8rem; font-family: var(--serif); font-size: var(--t-p0);
  line-height: 1.5; color: var(--ink-2); }
.signup-lede strong { color: var(--ink); font-weight: 600; }
.signup-row { display: flex; flex-wrap: wrap; gap: 0; max-width: 30rem;
  border: 1px solid var(--ink); }
.signup-row input {
  flex: 1 1 12rem; min-width: 0; border: 0; border-radius: 0;
  padding: 0.6rem 0.7rem; background: transparent; color: var(--ink);
  font-family: var(--serif); font-size: var(--t-p0);
}
.signup-row input::placeholder { color: var(--ink-3); font-style: italic; }
.signup-row button {
  flex: none; cursor: pointer; border-radius: 0;
  border: 0; border-inline-start: 1px solid var(--ink);
  padding: 0.6rem 1.1rem; background: var(--ink); color: var(--ground);
  font-family: var(--sans); font-weight: 700; font-size: var(--t-d-2);
  letter-spacing: 0.18em; text-transform: uppercase;
}
.signup-row button:hover { background: var(--ink-2); }
.signup-editions label { font-family: var(--serif); color: var(--ink-2); }
.signup-fine { margin-top: 0.9rem; font-size: var(--t-d-2); color: var(--ink-3);
  font-family: var(--sans); }

/* ── THE ADVERTISEMENT ─────────────────────────────────────────────────
   A boxed, ruled advertisement set in the paper's own faces — the way a paper
   sells a space — rather than a light grey rounded panel with a chevron link,
   which is the visual language of the web sitting in a page pretending to be
   print. */
.sponsor {
  border: 1px solid var(--ink); border-top-width: 3px;
  background: transparent; padding: 0.9rem 1rem 1rem;
}
.sponsor-kicker { margin: 0 0 0.5rem; text-align: center;
  font: 700 var(--t-d-3)/1 var(--sans); letter-spacing: 0.3em; text-transform: uppercase;
  color: var(--ink-3); }
.sponsor .name { font-family: var(--serif); font-weight: 700; font-size: var(--t-d-1);
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink); }
.sponsor-hed { font-family: var(--serif); font-weight: 600; font-size: 1.25rem;
  line-height: 1.2; color: var(--ink); margin: 0.1rem 0 0.3rem; }
.sponsor-text { font-family: var(--serif); font-size: var(--t-d0); line-height: 1.45;
  color: var(--ink-2); }
/* No border. Measured at 56px against the ground: the mark holds 13:1 on the
   shirt and hands and 17:1 at its brightest, with only 13% of its pixels below
   1.5:1 — the suit mass is low but the highlights carry it. It does not need a
   plate to be seen, and a plate here would be the first card in a stylesheet
   whose header says there are none. Desaturated, because everything else on
   this page is ink. */
.sponsor-logo { filter: grayscale(1) contrast(1.05); }
.sponsor-cta { display: inline-block; margin-top: 0.3rem; padding: 0.25rem 0.6rem;
  border: 1px solid var(--ink); color: var(--ink); text-decoration: none;
  font: 700 var(--t-d-3)/1.4 var(--sans); letter-spacing: 0.14em; text-transform: uppercase; }
.sponsor-cta:hover { background: var(--ink); color: var(--ground); }


/* Permits as sentences, not as a table. A three-column table squeezed into a
   newspaper column produced choppy two-word stubs; the reader wanted a line
   that says what is being built. */
.pm-list { margin: 0.2rem 0 0; padding: 0; list-style: none; }
.pm-row { padding: 0.4rem 0; border-top: 1px solid var(--rule-soft); }
.pm-row:first-child { border-top: 0; }
.pm-line { margin: 0; font-family: var(--serif); font-size: var(--t-d1);
  line-height: 1.4; color: var(--ink); }
.pm-in { color: var(--ink-2); }
.pm-val { margin-inline-start: 0.5em; font-family: var(--sans); font-weight: 700;
  font-size: var(--t-d-1); color: var(--ink-2); white-space: nowrap;
  font-variant-numeric: lining-nums tabular-nums; }
.pm-addr { margin: 0.05rem 0 0; font: 400 var(--t-d-2)/1.4 var(--sans); color: var(--ink-3); }
.data-h-r { white-space: nowrap; }

/* The record-block heading. This had NO rule at all — the .data styling was
   removed when the tinted card blocks went, leaving two bare spans inside a
   <p>, which is why "Permits issued" and "$3.0M across 3" rendered as one
   word. Three rounds of the same class of bug, and this one was in code
   written after the checker existed. The lesson is not "add spacing", it is
   "run the checker on your own new code". */
.data { margin: 0.9rem 0 0; }
.data-h {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 0.15rem 1rem;
  margin: 0 0 0.35rem; padding-bottom: 0.25rem;
  border-bottom: 1px solid var(--ink-3);
  font: 600 var(--t-d-3)/1.4 var(--sans); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-3);
}

/* The sponsor lockup: a small mark beside the wordmark, copy full width below.
   A 96px logo in a 330px column spent a third of the width on an image. */
.sponsor-lockup { display: flex; align-items: center; gap: 0.55rem; margin: 0 0 0.55rem; }
.sponsor-logo { width: 56px; height: 56px; object-fit: contain; flex: none; }
.sponsor-offer { margin: 0 0 0.7rem; font: 700 var(--t-d-2)/1.3 var(--sans);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3); }

/* A citywide pattern states a count, a sum and a span. What the sentence
   cannot answer is whether the reader's own neighbourhood is in it, so the
   names run beneath — the fixture carries them and throwing them away was
   the thing this paper is meant not to do. */
.pat-areas { margin: 0.35rem 0 0.55rem; font: 400 var(--t-d-1)/1.5 var(--sans);
  color: var(--ink-2); max-inline-size: 62ch; }
.pat-k { display: inline-block; min-width: 3.4rem;
  font: 600 var(--t-d-3)/1.5 var(--sans); letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--ink-3); }

/* ── THE PHONE ─────────────────────────────────────────────────────────
   Read at half six, standing at a counter, one hand on the cup. The desktop
   fix — news above everything — was never applied here, so the first screen
   was eight bands of apparatus with the headline at the very bottom edge:
   masthead, folio, dateline, weather, transit, next-at-city-hall, "the whole
   day", and four wrapped rows of filter buttons.

   Nobody buys a paper and sorts it before they open it. */
@media (max-width: 47.99rem) {

  /* The filters were the worst of it: eight buttons wrapping to four lines,
     directly between the reader and the front page. One scrollable line. */
  .facets {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    gap: 0.3rem 1rem;
    padding-block: 0.4rem;
  }
  .facets::-webkit-scrollbar { display: none; }
  .fx { flex: none; white-space: nowrap; }
  .fx-k, .fx-count { flex: none; }
  .fx-count { display: none; }          /* the count is on each word already */

  /* Weather, transit and the next council item are three facts. On a phone the
     first is the one anyone checks; the rest wait below the fold. */
  .conditions { gap: 0.2rem 0.8rem; padding-block: 0.45rem; }
  .conditions .cd-item:not(.cd-wx) { display: none; }
  .cd-more { margin-inline-start: 0; }
  .cd-wx .cd-q { display: none; }       /* humidex and UV are not a phone fact */

  /* The folio is furniture. It belongs on the page, not in front of the news. */
  .folio-line { display: none; }

  /* Tighten the masthead so the nameplate does not eat a third of the screen */
  .np-rules { margin: 0.55rem 0 0.4rem; }
  .dateline { gap: 0.15rem 0.5rem; padding-bottom: 0.4rem; }
  .dateline .how { display: none; }
}

/* Place names should not break across a line. A typesetter would have caught
   "in Clareview / Town Centre" and "elementary school in / Secord". */
.pm-in, .cm-where, .ev-where, .ah-where { white-space: nowrap; }
.pm-line { text-wrap: pretty; }

/* ── TAP TARGETS ───────────────────────────────────────────────────────
   Read one-handed, standing, with a cup in the other hand. The filter words
   were 22px tall and "the whole day" 17px — under the 24px WCAG 2.5.8 floor
   and well under anything hittable with a thumb.

   The padding is vertical only and the line-height is unchanged, so nothing
   moves: the words sit exactly where they did and the target grew around them.
   Applied on coarse pointers rather than by width, because the thing that
   makes a target hard to hit is a finger, not a narrow window. */
@media (pointer: coarse) {
  /* Named selectors kept missing controls that scripts build at runtime, so
     this is scoped by CONTAINER: anything clickable inside an instrument or a
     control bar gets a thumb-sized target, whoever created it. */
  .facets .fx, .ev-legend .ev-leg, .leg, .cd-more, .fx-reset, .skip,
  .citytime a, .citytime button, .citytime summary,
  .events a, .events button,
  .divisions a, .divisions summary,
  .locator button, .ahead a, .pm-form summary {
    padding-block: 0.5rem;
    display: inline-block;
  }
  /* links inside running prose are NOT targets to enlarge — padding there
     would break the line rhythm of the story itself */
  .body a, .dek a, .why a, .srcs a, .sources a, .colophon a, .pm-line a {
    padding-block: 0; display: inline;
  }
  .facets { padding-block: 0.1rem; }        /* the row grew; keep the band tight */
  .cm-btn { min-width: 40px; height: 40px; }
  .dv-dot { min-height: 18px; }            /* countable and touchable */
  .ah-tag, .ev-leg i { padding-block: 0.3rem; }
}

/* ── THE STRIP ON A PHONE ──────────────────────────────────────────────
   At 375px the time strip is two long bars running off both edges, a squiggle
   for the temperature and labels too small to read. It is coloured smudges.
   If it cannot be read at that width it should not be drawn there — the phone
   gets the words, the big screen keeps the picture. */
@media (max-width: 47.99rem) {
  /* The strip comes back on the phone, but NOT as a chart.
     It was killed for the right reason — at 375px it was two long bars running
     off both edges and labels too small to read, and it lost that argument as
     data. As atmosphere it earns its place: a quiet band of the day's weather
     and city texture at the foot of the page, with the numbers underneath in
     words. Nothing here is meant to be read off. The table below it is the
     record; this is the weather you feel through a window. */
  .ct-plot {
    display: block;
    height: 88px; opacity: .5;
    pointer-events: none;                 /* not a control any more */
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  }
  .ct-plot .ct-mklab, .ct-plot .ct-axis, .ct-plot .ct-tick,
  .ct-plot text { display: none; }        /* no values to squint at */
  .ct-zooms, .ct-tabtoggle { display: none; }
  .ct-table { display: block !important; }
  .ct-table[hidden] { display: block !important; }
  .citytime .ct-note::after {
    content: " The band above is the shape of the day, not a chart \2014 the hours are listed below.";
    color: var(--ink-3);
  }
}
@media (max-width: 47.99rem) and (prefers-reduced-motion: reduce) {
  .ct-plot { opacity: .38; }
}

/* ── THE COUNCIL DECISION RECORD ───────────────────────────────────────
   The page a reader reaches for when the front-page section is not enough.
   It carries every recorded division, so its job is to keep the contested
   ones legible while still holding the ones that were not. */
.vt-item { border-top: 1px solid var(--rule); padding-top: 1.1rem; }
.vt-meet { margin: 0 0 0.15rem; font: 700 1.375rem/1.2 var(--serif); color: var(--ink); }
.vt-date { font: 400 var(--t-d-1)/1 var(--sans); color: var(--ink-3);
  letter-spacing: 0.06em; margin-inline-start: 0.5rem; }
.vt-src { margin: 0 0 0.7rem; font: 400 var(--t-d-1)/1.5 var(--sans); }
.vt-src a { color: var(--link); }
.vt-lede { margin: 0 0 0.9rem; font: 400 var(--t-d1)/1.55 var(--sans); color: var(--ink-2); }
.vt-lede b { color: var(--ink); }
/* A transcript count is a weaker record than a roll, and says so where it sits
   rather than only in the standfirst, which a reader deep in the page has
   scrolled past. */
.vt-prov { border-inline-start: 2px solid var(--warn); padding-inline-start: 0.7rem;
  color: var(--ink-3); }
.vt-split .dv { margin-bottom: 1.15rem; }
.vt-rest { margin: 0.4rem 0 0.2rem; }
.vt-rest summary { cursor: pointer; font: 600 var(--t-d-1)/1.5 var(--sans);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-3); }
.vt-rest summary:hover, .vt-rest summary:focus-visible { color: var(--ink-2); }
.vt-list { margin: 0.6rem 0 0; padding: 0; list-style: none;
  max-inline-size: 68ch; }
.vt-list li { margin: 0 0 0.3rem; padding-inline-start: 3.6rem; text-indent: -3.6rem;
  font: 400 var(--t-d0)/1.5 var(--serif); color: var(--ink-2); }
/* Tabular figures so the tallies form a column the eye can run down. */
.vt-n { display: inline-block; min-width: 2.9rem; font-variant-numeric: tabular-nums;
  font: 700 var(--t-d-1)/1.5 var(--sans); color: var(--ink); }

/* AQHI once it is worth acting on. Amber, because it is the same class of
   information as a weather warning — something to change your afternoon over —
   and it must not read as another muted almanac figure. Set in the same warn
   colour the alert rail uses, and never as the only signal: the band is
   spelled out beside the number. */
.cd-aq { color: var(--warn); font-weight: 700; white-space: nowrap; }
.cd-aq[data-tip] { cursor: help; }
.cd-aq[data-tip]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Was this a fight? Once the front page ranks by subject instead of by margin,
   a unanimous vote and a one-vote cliffhanger sit next to each other, and the
   difference should not require the reader to subtract. Never colour alone —
   each state says what it is in words. */
.dv-mood { font: 600 var(--t-d-3)/1.5 var(--sans); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3);
  margin-inline-start: 0.55rem; white-space: nowrap; }
.dv-mood--clear { color: var(--ok); }
.dv-mood--close { color: var(--warn); }

/* ── HIERARCHY ON THE RECORD PAGE ──────────────────────────────────────
   A hundred-odd votes across four meetings, every one set at the same size and
   weight, gave the eye nowhere to go. The meeting is the organising unit, so
   it gets the weight; the sequence number gives the eye a rail to travel down.
   The number is position, never rank — the page ranks nothing. */
.vt-meet { font-size: 1.75rem; letter-spacing: -0.01em; }
.vt-item { border-top: 2px solid var(--rule-strong); padding-top: 1.4rem;
  margin-top: 2rem; }
.dv-seq { display: inline-block; min-width: 2.1rem;
  font: 600 var(--t-d-1)/1.5 var(--sans); font-variant-numeric: tabular-nums;
  color: var(--ink-3); vertical-align: 0.18em; }
.vt-split .dv-what { font-size: var(--t-d2); }
/* An index, so a reader landing mid-page can get to a meeting directly. */
.vt-jump { margin: 0.2rem 0 1.6rem; font: 400 var(--t-d-1)/1.7 var(--sans);
  color: var(--ink-3); }
.vt-jump a { color: var(--link); margin-inline-end: 0.9rem; white-space: nowrap; }

/* ── A CONTROL MUST STAY WITH THE THING IT CONTROLS ────────────────────
   The permit rows sit in the three-column well, and a row was free to break
   across a column boundary. On a 1512px screen the waterslide permit's line
   rendered at x=555, y=2361 and its "The filing" disclosure at x=993, y=1064 —
   a different column, thirteen hundred pixels up the page. A button with no
   thing above the fold, and a permit with no way to open its filing.

   These stay whole. They are small — a permit row is about ninety pixels — so
   unlike the 979px City Hall section that had to be allowed to split, keeping
   them together cannot strand a column. The rule is the general one: an
   element whose halves are meaningless apart must not be divided. */
.pack .pm-row,
.pack .dv,
.pack .ev-row,
.pack .cm-row,
.pack details { break-inside: avoid; }

/* ══ THE COSTUME ══════════════════════════════════════════════════════
   Everything below exists to make this feel like a paper somebody set,
   rather than a database that renders. None of it carries information;
   all of it is why you'd sit down with the thing. */

/* THE STOCK, over the whole sheet — a warm lamp at the top of the page and a
   vignette at the edges, so the ground reads as lit paper rather than as an
   unlit screen. */
.sheet {
  position: relative;
  /* THE TOOTH LIVES HERE AS WELL AS ON BODY.
     Canvas UI's particle scroll moves .sheet inside a <canvas layoutsubtree>
     and rasterises only that subtree; body's background is an ancestor and
     is never captured, so under the effect the paper went flat. The grain
     and fibre are painted on the sheet itself now, so whatever captures the
     sheet captures the paper. No background-attachment:fixed — inside a
     scroll-clipped subtree a fixed layer anchors to the wrong box. */
  background-image:
    var(--grain), var(--fibre),
    radial-gradient(120% 60% at 50% -8%, var(--lamp), transparent 62%),
    radial-gradient(100% 70% at 50% 108%, var(--vignette), transparent 60%);
  background-size: 200px 200px, 340px 340px, 100% 70vh, 100% 100%;
  background-repeat: repeat, repeat, no-repeat, no-repeat;
  background-position: 0 0, 0 0, 50% 0, 50% 100%;
  background-blend-mode: var(--tooth-blend), normal, normal;
}

/* ORNAMENT. A newspaper's masthead has engraving around it. Drawn in CSS —
   a rosette flanked by tapered rules — because there is no image source and
   an ornament is the one thing that can be honestly invented. */
.masthead .flag { position: relative; }
.masthead .flag::before,
.masthead .flag::after {
  content: "";
  position: absolute; top: 50%; width: clamp(28px, 9vw, 132px); height: 1px;
  background: linear-gradient(90deg, transparent, var(--rule-strong) 38%, var(--rule-strong));
  opacity: .85;
}
.masthead .flag::before { right: calc(50% + 12.5em); transform: scaleX(-1); }
.masthead .flag::after  { left:  calc(50% + 12.5em); }

/* The rules under the wordmark: thick-thin, the way a broadsheet sets them,
   with a diamond centred on the pair. */
.masthead .rules { position: relative; }
.masthead .rules::after {
  content: "";
  position: absolute; left: 50%; top: 50%;
  width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px;
  background: var(--ink-2); transform: rotate(45deg);
  box-shadow: -16px 0 0 -2px var(--ink-3), 16px 0 0 -2px var(--ink-3);
}

/* SECTION MARKS. Each desk gets a drawn dingbat instead of a bare label, so
   the eye has an anchor and the page has some furniture in it. */
.desk-h:not(.desk-h--gate):not(.sr-only)::before {
  content: "";
  display: inline-block; vertical-align: 0.18em;
  width: 6px; height: 6px; margin-inline-end: 0.55rem;
  background: var(--lane-council); transform: rotate(45deg);
  opacity: .9;
}
.kicker::after {
  content: ""; display: inline-block;
  width: 1.6rem; height: 1px; margin-inline-start: 0.5rem;
  vertical-align: 0.28em; background: var(--rule-strong);
}

/* VARIETY. Twelve of seventeen blocks were set at the same size, which is a
   machine's idea of a newspaper — real pages are irregular because an editor
   decided something deserved more room. The lead breathes; the block under it
   is set as a feature; the briefs stay tight. */
.pack .item--lead h3 { line-height: .96; letter-spacing: -.022em; }
.pack .item--lead .body {
  font-size: var(--t-d2); line-height: 1.62;
  /* A short body in two columns left one line — "U.S. tariffs." — alone in
     column two. The renderer cannot measure text; the browser can: with a
     three-line floor at each break, a body shorter than six lines stays in
     one column, and a long one still splits, just never onto a stub. */
  orphans: 3; widows: 3;
}
/* The opening phrase of the front lead, set in small caps. Was a
   ::first-line rule, which ended the small caps wherever the line
   happened to wrap — a different word at every width, and on a desk
   page it read as the font breaking mid-sentence. */
.pack .item--lead .body .lede-open { font-variant-caps: small-caps; letter-spacing: 0.02em; }

/* Every third brief in the well is set a step larger with a rule above it, so
   the column has rhythm instead of a metronome. */
/* NOT the lead. This first read `.pack section .item`, which caught the lead —
   dropping the 48px headline to 21px and flattening the one piece of hierarchy
   the whole page is built on. Variety in the briefs, never at the expense of
   the thing the eye is supposed to land on first.

   THE FIRST FIX GUARDED THE WRONG THING. `:not(.frontpkg)` excludes the front
   PACKAGE, which exists only on index.html — desk pages emit
   `.pack.pack--single > section > article.item.item--lead` and match it
   happily. So the bug was fixed on the one page someone looked at and left
   live on the other nine: measured on section/province, the lead rendered at
   21px while the two stories beneath it ran at 28px. Removing this one
   font-size in the live page took it back to 48px, which is the proof.
   The guard belongs on the ITEM, so it travels with the lead wherever the
   lead is rendered. */
.pack > section:not(.frontpkg) .item:not(.item--lead):nth-of-type(3n+1) h3 {
  font-size: 1.3125rem; line-height: 1.14; }
.pack > section:not(.frontpkg) .item:not(.item--lead):nth-of-type(3n+1) {
  border-top: 2px solid var(--rule-strong); padding-top: 0.85rem; }

/* A hairline between briefs, the way a column of shorts is separated. */
.pack > section:not(.frontpkg) .item + .item {
  border-top: 1px solid var(--rule); padding-top: 0.8rem; }

/* ALIVE. Not a widget — a single line that says when this was set, updating
   itself so the page is quietly current while looking stately. */
.setline { font: 400 var(--t-d-1)/1.5 var(--sans); color: var(--ink-3);
  letter-spacing: 0.04em; margin: 0.35rem 0 0; text-align: center; }
.setline b { color: var(--ink-2); font-weight: 600; }
.setline .pip {
  display: inline-block; width: 5px; height: 5px; border-radius: 50%;
  background: var(--ok); margin-inline-end: 0.45rem; vertical-align: 0.05em;
  animation: pip 4.5s ease-in-out infinite;
}
@keyframes pip { 0%,100% { opacity: .30; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .setline .pip { animation: none; opacity: .8; } }

/* The folio's own line carries the live time, rather than a second line
   repeating the words already set beside it. */
.folio-set .pip {
  display: inline-block; width: 5px; height: 5px; border-radius: 50%;
  background: var(--ok); margin-inline-end: 0.4rem; vertical-align: 0.02em;
  animation: pip 4.5s ease-in-out infinite;
}
.folio-set b { font-weight: 600; color: var(--ink-2); }
@keyframes pip { 0%,100% { opacity: .28; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .folio-set .pip { animation: none; opacity: .8; } }

/* The engraved flourish under the wordmark. Sized in ems so it tracks the
   nameplate at every width, and it inherits `color` so it takes the ink. */
.orn { display: block; margin: 0.55rem auto 0.35rem; width: min(320px, 46%);
  height: auto; color: var(--ink-3); }
@media (max-width: 47.99rem) { .orn { width: 62%; margin: 0.4rem auto 0.25rem; } }

/* With a real ornament in place the diamond on the rules is one flourish too
   many — the pair read as clutter rather than as furniture. */
.masthead .rules::after { display: none; }

/* ── OBJECTS THAT ARE NOT STORIES ──────────────────────────────────────
   The page had variety of size and none of KIND — seventeen blocks, each a
   headline over a paragraph over a link. A paper breaks rhythm by putting a
   different species of thing in the column, not by setting the same thing
   bigger. These are those things. */

/* A sentence from today, enlarged. Not written for the slot — taken from a
   story's own consequence line and attributed, so it is the paper quoting
   itself rather than decoration with words in it. */
.pull {
  break-inside: avoid; margin: 0 0 1.75rem;
  padding: 1.1rem 0 1.15rem;
  border-top: 3px solid var(--ink-3); border-bottom: 1px solid var(--rule);
}
.pull-q {
  margin: 0 0 0.55rem; font-family: var(--serif); font-weight: 500;
  font-size: var(--h-m); line-height: 1.24; letter-spacing: -.012em;
  color: var(--ink); text-wrap: pretty;
}
.pull-q::before { content: "\201C"; margin-inline-start: -.42em; color: var(--ink-3); }
.pull-q::after  { content: "\201D"; color: var(--ink-3); }
.pull-a {
  margin: 0; font: 600 var(--t-d-2)/1.5 var(--sans); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3);
}
.pull-a::before { content: "— "; }

/* An engraved cut of the day's sky. Line art, drawn from the forecast's own
   words, and absent entirely when the forecast says nothing we can draw —
   a generic sun on a day nobody promised sun is a small lie. */
.cut {
  break-inside: avoid; margin: 0 0 1.75rem; padding: 0.9rem 0 1rem;
  border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  display: flex; align-items: center; gap: 0.9rem;
}
.cut-art { width: 68px; height: 56px; flex: none; color: var(--ink-2); }
.cut-cap {
  font: 600 var(--t-d-2)/1.4 var(--sans); letter-spacing: 0.11em;
  text-transform: uppercase; color: var(--ink-3);
}
.cut-cap b { display: block; font-family: var(--serif); font-size: 1.75rem;
  font-weight: 600; letter-spacing: -.01em; color: var(--ink);
  text-transform: none; margin-top: 0.1rem; }

/* THE MAP AS A PLATE. It was a drawing on the page; it is now a plate ON the
   page — a ruled border with a caption bar, the way an engraving is mounted. */
.citymap { border: 1px solid var(--rule-strong); background:
  linear-gradient(180deg, var(--plate-wash), transparent 40%); }

/* The coverage-gaps list on the about page. Set as prose, not as a data list —
   it is the paper telling a reader what it cannot see, and it should read like
   a sentence somebody wrote rather than a status table. */
.gaps { margin: 0.4rem 0 1rem; padding-inline-start: 1.1rem; max-inline-size: 68ch; }
.gaps li { margin: 0 0 0.5rem; font-family: var(--serif); font-size: var(--t-p0);
  line-height: 1.55; color: var(--ink-2); }
.gaps li strong { color: var(--ink); }

/* Metro council sittings, under Edmonton's own diary. Set quieter than the
   Edmonton rows and labelled, because a reader seeing "Council meeting" on an
   Edmonton front page will reasonably assume it is Edmonton's. */
.ah-day--regional { margin-top: 0.9rem; padding-top: 0.55rem;
  border-top: 1px solid var(--rule); color: var(--ink-3); }
.ah-row--regional .ah-what { color: var(--ink-2); font-weight: 400; }
.ah-row--regional .ah-where { color: var(--ink-3); }

/* A desk that was quiet says so, rather than trailing off into dark. Set as
   prose in the paper's own voice — it is a report, not an error state, so it
   gets no rule, no box and no colour. */
.thin-desk { margin: 0 0 1.4rem; max-inline-size: 62ch;
  font: 400 var(--t-d1)/1.6 var(--serif); color: var(--ink-3); }
.thin-desk a { color: var(--link); }

/* ── WHAT THE CITY IS BUILDING ─────────────────────────────────────────
   The at-risk capital projects. Set as a list of statements rather than a
   table: the City's own explanation of an overrun is the substance, and a
   table would reduce it to a cell. */
.capital { margin: 2rem 0 0; }
.cap-lede { margin: 0.2rem 0 1rem; max-inline-size: 68ch;
  font: 400 var(--t-d1)/1.55 var(--sans); color: var(--ink-2); }
.cap-lede b { color: var(--ink); }
.cap-lede a { color: var(--link); }
.cap-list { margin: 0; padding: 0; list-style: none; }
.cap-row { break-inside: avoid; margin: 0 0 1.1rem; padding-top: 0.8rem;
  border-top: 1px solid var(--rule); max-inline-size: 68ch; }
.cap-name { font: 600 var(--t-d2)/1.3 var(--serif); color: var(--ink);
  text-decoration: none; }
.cap-name:hover, .cap-name:focus-visible { text-decoration: underline; }
.cap-lights { display: inline-flex; gap: 0.35rem; margin-inline-start: 0.5rem;
  vertical-align: 0.12em; flex-wrap: wrap; }
/* Never colour alone: each light names its own state in words. */
.cap-light { font: 600 var(--t-d-3)/1.5 var(--sans); letter-spacing: 0.09em;
  text-transform: uppercase; white-space: nowrap; color: var(--ink-3); }
.cap-light--red { color: var(--danger); }
.cap-light--yellow, .cap-light--amber { color: var(--warn); }
.cap-meta { margin: 0.25rem 0 0; font: 400 var(--t-d-1)/1.5 var(--sans);
  color: var(--ink-3); font-variant-numeric: tabular-nums; }
.cap-why { margin: 0.35rem 0 0; font: 400 var(--t-d1)/1.55 var(--serif);
  color: var(--ink-2); }

/* The diary saying what did not fit. Set as a quiet line under the rows —
   it is a fact about the list, not another entry in it. */
.ah-more { margin: 0.6rem 0 0; font: 400 var(--t-d-1)/1.5 var(--sans);
  color: var(--ink-3); }
.ah-more a { color: var(--link); }

/* The area filter, on the same bar as the section words. A select rather than
   more words: twenty-one places would be a paragraph, and "where" is a single
   choice where "what kind" is many. */
.fx-area { margin-inline-start: 0.7rem; }
.fx-area select {
  font: 600 var(--t-d-1)/1.5 var(--sans); letter-spacing: 0.04em;
  color: var(--ink-2); background: transparent;
  border: 0; border-bottom: 1px solid var(--rule-strong);
  padding: 0 0.1rem 0.1rem; cursor: pointer;
}
.fx-area select:hover, .fx-area select:focus-visible { color: var(--ink); }
.fx-area select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── ROADWORK ──────────────────────────────────────────────────────────
   What is being dug up and what it takes with it. Impacts are named only
   where the City stated one, so the row is a list of facts rather than a
   status table — a blank is silence, not a clearance. */
.roadwork { margin: 2rem 0 0; }
.rw-lede { margin: 0.2rem 0 1rem; max-inline-size: 68ch;
  font: 400 var(--t-d1)/1.55 var(--sans); color: var(--ink-2); }
.rw-list { margin: 0; padding: 0; list-style: none; }
.rw-row { break-inside: avoid; margin: 0 0 0.8rem; padding-top: 0.65rem;
  border-top: 1px solid var(--rule); max-inline-size: 68ch; }
.rw-where { font: 600 var(--t-p0)/1.3 var(--serif); color: var(--ink); text-decoration: none; }
.rw-where:hover, .rw-where:focus-visible { text-decoration: underline; }
.rw-takes { margin-inline-start: 0.55rem; white-space: nowrap;
  font: 600 var(--t-d-3)/1.5 var(--sans); letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--warn); }
.rw-meta { margin: 0.2rem 0 0; font: 400 var(--t-d-1)/1.5 var(--sans);
  color: var(--ink-3); font-variant-numeric: tabular-nums; }

/* THE EDITION CONTROL. Built by theme.js, so it exists only where it works —
   a toggle that needs localStorage must not ship as dead furniture for a
   reader with no scripts. Set as a line of small caps under the nameplate,
   not as a button with a box: the paper has no app bar. */

/* A label that did not fit inside its bar sits beside it, on the stock rather
   than on the tint — so it is set in the quieter ink, not the bar's. */
.ct-mklab--out { fill: var(--ink-2); font-weight: 400; font-style: italic; }

/* PARTICLE SCROLL mounting. These rules only ever apply once particlescroll.js
   has set data-ps="on", which happens only where the experimental
   html-in-canvas API exists. Without it the selectors match nothing and the
   document scrolls normally. */
:root[data-ps="on"], :root[data-ps="on"] body { height: 100%; overflow: hidden; }
:root[data-ps="on"] .ps-wrap { position: relative; height: 100vh; }
:root[data-ps="on"] .ps-source { position: absolute; inset: 0;
  width: 100%; height: 100%; }
:root[data-ps="on"] .ps-content { position: relative;
  width: 100%; height: 100%; overflow: auto; }
:root[data-ps="on"] .ps-output { position: absolute; inset: 0;
  width: 100%; height: 100%; pointer-events: none; }

/* THE TRACER. The head is a vertical rule across every lane; this is the dot
   that rides the temperature line itself, with the reading beside it — the
   gesture a reader already knows from a stock chart. Ink, not accent: it
   states a value, and the accent is chrome. */
.ct-trace { fill: var(--ground); stroke: var(--ink); stroke-width: 1.6; }
.ct-tracelab { font-family: var(--serif); font-size: 11.5px; font-weight: 600;
  fill: var(--ink); paint-order: stroke; stroke: var(--ground); stroke-width: 3px;
  stroke-linejoin: round; }

/* THE CITY HALL MODULE. Council business was in two places: what is coming sat
   in a band beside the weather, and how they voted sat somewhere else
   entirely, so a reader had to assemble "what is council doing" themselves.
   One module, in the order a reader wants it — what is about to be decided,
   then the record of what already was. */
.cityhall { margin-top: 1.75rem; border-top: 2px solid var(--ink); padding-top: 0.9rem; }
.ch-sub { margin: 0.45rem 0 1rem; max-width: 46em;
  font: 400 var(--t-p0)/1.5 var(--serif); color: var(--ink-2); font-style: italic; }
.cityhall > .ahead, .cityhall > .divisions { margin-top: 1.1rem; }

/* ROAD CLOSURES. Set as a list of instructions, not a chart: the useful thing
   is a sentence the City wrote about a specific road, and the line breaks in
   it are load-bearing. */
.closures { margin-top: 1.5rem; }
.cx-lede { margin: 0.3rem 0 0.9rem; max-width: 44em;
  font: 400 var(--t-d1)/1.5 var(--sans); color: var(--ink-2); }
.cx-list { list-style: none; margin: 0; padding: 0; }
.cx-row { padding: 0.6rem 0; border-top: 1px solid var(--rule); break-inside: avoid; }
.cx-where { font: 600 var(--t-d2)/1.25 var(--sans); color: var(--ink);
  text-decoration: none; border-bottom: 1px solid var(--rule-strong); }
.cx-where:hover { border-bottom-color: var(--ink); }
.cx-impact { margin-inline-start: 0.55em; padding: 1px 5px;
  font: 600 var(--t-d-2)/1.5 var(--sans); letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--ink-2); border: 1px solid var(--rule-strong); }
.cx-impact--full { color: var(--danger); border-color: var(--danger); }
.cx-meta { margin: 0.2rem 0 0; font: 400 var(--t-d-1)/1.45 var(--sans); color: var(--ink-3); }
.cx-detail { margin: 0.35rem 0 0; max-width: 44em;
  font: 400 var(--t-p0)/1.5 var(--serif); color: var(--ink); }
.cx-more { margin: 0.25rem 0 0; padding-inline-start: 1.1em; max-width: 44em;
  font: 400 var(--t-d1)/1.55 var(--sans); color: var(--ink-2); }
.cx-more li { margin: 0.08rem 0; }

/* THE ROLL AS MARKS. Thirteen marks a reader can count, with the councillor's
   name on each. Shape carries the vote as well as hue, so the three states
   survive having all colour removed. */
.dv-dots { display: flex; align-items: center; flex-wrap: wrap; gap: 5px;
  margin: 0.5rem 0 0.35rem; }
.dv-dots-sr { position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
.dv-dot { width: 11px; height: 11px; flex: 0 0 auto; cursor: help; }
.dv-dot--for { background: var(--ink); border-radius: 50%; }
.dv-dot--against { background: none; border: 1.5px solid var(--danger); }
.dv-dot--absent { background: none; border: 0; border-bottom: 1.5px solid var(--ink-3);
  height: 6px; }
.dv-dot:hover, .dv-dot:focus-visible { outline: 2px solid var(--accent);
  outline-offset: 2px; }
/* The majority rule. Seven of thirteen carries, so the eye answers "did this
   pass" by seeing whether the filled discs reach past this line. */
.dv-thresh { width: 1px; height: 15px; margin: 0 4px;
  background: var(--ink-3); flex: 0 0 auto; }
.dv-dots-n { margin-inline-start: 0.45em;
  font: 600 var(--t-d-2)/1 var(--sans); letter-spacing: 0.06em; color: var(--ink-3); }
.dv-what[data-tip] { cursor: help; }

/* MARKETS. A wire-service table: name left, figure right, direction last.
   Direction carries a GLYPH as well as a colour — a red number and a green
   number are the same number to a dichromat, and here the sign IS the message. */
.markets { margin-top: 1.25rem; }
.mk-when { margin-inline-start: auto; font: 500 var(--t-d-2)/1 var(--sans);
  letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-3); }
.mk-list { list-style: none; margin: 0.5rem 0 0; padding: 0; }
.mk-row { display: grid; grid-template-columns: 1fr auto auto; gap: 0.5rem 1rem;
  align-items: baseline; padding: 0.34rem 0; border-top: 1px solid var(--rule); }
.mk-name { font: 500 var(--t-d1)/1.3 var(--sans); color: var(--ink);
  text-decoration: none; border-bottom: 1px solid transparent; cursor: help; }
.mk-name:hover { border-bottom-color: var(--ink); }
.mk-price { font: 600 var(--t-d2)/1.2 var(--sans); font-variant-numeric: tabular-nums;
  color: var(--ink); text-align: right; }
.mk-unit { margin-inline-start: 0.35em; font: 400 var(--t-d-2)/1 var(--sans);
  color: var(--ink-3); text-transform: none; letter-spacing: 0; }
.mk-move { font: 600 var(--t-d-1)/1.2 var(--sans); font-variant-numeric: tabular-nums;
  text-align: right; white-space: nowrap; }
.mk-move--up { color: var(--ok); }
.mk-move--down { color: var(--danger); }
.mk-pct { margin-inline-start: 0.5em; color: var(--ink-3); }
.mk-flat { font: 400 var(--t-d-1)/1.2 var(--sans); color: var(--ink-3); }
.mk-gap, .mk-src { margin: 0.5rem 0 0; max-width: 42em;
  font: 400 var(--t-d-1)/1.5 var(--sans); color: var(--ink-3); }
.mk-src a { color: var(--link); }

/* THE TICKER. One line under the nameplate. Not a marquee — a moving strip of
   text is unreadable and unstoppable, and this is a newspaper. */
.ticker { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem 1.1rem;
  margin: 0.5rem 0 0; padding: 0.4rem 0; border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  font: 400 var(--t-d-1)/1.4 var(--sans); font-variant-numeric: tabular-nums; }
.tk-state { font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3); }
.ticker[data-open="1"] .tk-state { color: var(--ok); }
.tk-cell { color: var(--ink-2); }
.tk-cell b { font-weight: 600; color: var(--ink); }
.tk-up { color: var(--ok); }
.tk-down { color: var(--danger); }
.tk-flat { color: var(--ink-3); }

/* CATEGORY TOGGLES on the strip. Same idiom as the zoom controls — text that
   presses, not a pill. Pressed is the default state, because everything shows
   until the reader decides otherwise. */
.ct-cats { display: flex; flex-wrap: wrap; gap: 0.5rem 0.8rem;
  flex-basis: 100%; margin-top: 0.5rem; }
.ct-cat { appearance: none; background: none; border: 0; padding: 0.15rem 0;
  border-bottom: 1px solid var(--rule); cursor: pointer;
  font: 600 var(--t-d-2)/1.4 var(--sans); letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--ink-3); }
.ct-cat[aria-pressed="true"] { color: var(--ink); border-bottom-color: var(--ink); }
.ct-cat:hover { color: var(--ink); }
.ct-cat:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* MAP LAYER SWITCHES. Same idiom as the strip's category toggles. */
.cm-layers { display: flex; flex-wrap: wrap; gap: 0.5rem 0.8rem; margin: 0.55rem 0 0; }
.cm-layer { appearance: none; background: none; border: 0; padding: 0.15rem 0;
  border-bottom: 1px solid var(--rule); cursor: pointer;
  font: 600 var(--t-d-2)/1.4 var(--sans); letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--ink-3); }
.cm-layer[aria-pressed="true"] { color: var(--ink); border-bottom-color: var(--ink); }
.cm-layer:hover { color: var(--ink); }
.cm-layer:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.cm-mk--transit { color: var(--lane-transit); }

/* THE CORRECTION FORM. Set as a form on paper: labels above, rules under the
   fields, no boxes. It is the most important form on the site. */
.fixform { margin: 0.8rem 0 1rem; max-width: 38em; }
.fix-row { margin: 0 0 0.8rem; }
.fix-row label { display: block; margin-bottom: 0.2rem;
  font: 600 var(--t-d-2)/1.4 var(--sans); letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--ink-2); }
.fix-row label span { font-weight: 400; text-transform: none; letter-spacing: 0;
  color: var(--ink-3); }
.fix-row input, .fix-row textarea {
  width: 100%; box-sizing: border-box; background: var(--surface);
  border: 1px solid var(--rule-strong); border-radius: 0; padding: 0.45rem 0.55rem;
  font: 400 var(--t-p0)/1.45 var(--serif); color: var(--ink); }
.fix-row input:focus, .fix-row textarea:focus {
  outline: 2px solid var(--accent); outline-offset: 1px; }
.fix-actions { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.7rem; }
.fix-send { appearance: none; border: 1px solid var(--ink); background: var(--ink);
  color: var(--ground); padding: 0.4rem 0.9rem; cursor: pointer;
  font: 600 var(--t-d-1)/1.2 var(--sans); letter-spacing: 0.06em;
  text-transform: uppercase; }
.fix-send:hover { background: var(--link); border-color: var(--link); }
.fix-send[disabled] { opacity: .55; cursor: default; }
.fix-msg { font: 400 var(--t-d-1)/1.45 var(--sans); color: var(--ink-2); }
.fix-msg.is-err { color: var(--danger); }
.fix-msg.is-ok { color: var(--ok); }
.fix-msg a { color: var(--link); }

/* THE DISPLAY AD, set the way a paper sets one.
   It read as a panel of the paper's own furniture: left-aligned, one hairline,
   the advertiser's name smaller than the copy. A bought slot should look
   bought — centred, double-ruled, the name as the display element — so a
   reader can tell in a glance that this is somebody paying to talk to them and
   not the paper talking. That distinction is the whole reason the page can
   carry an ad and still argue for its own credibility. */
.sponsor {
  border: 3px double var(--ink);
  padding: 1.15rem 1.15rem 1.25rem;
  text-align: center;
}
.sponsor-kicker { margin: 0 0 0.7rem; padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--rule); letter-spacing: 0.34em; }
.sponsor-lockup { display: flex; flex-direction: column; align-items: center;
  gap: 0.35rem; margin-bottom: 0.55rem; }
.sponsor .name {
  font-family: var(--serif); font-weight: 700;
  font-size: clamp(1.25rem, 2.4vw, 1.6rem); line-height: 1.05;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink);
}
.sponsor-hed { font-style: italic; font-weight: 500;
  font-size: clamp(1rem, 1.7vw, 1.15rem); margin: 0.35rem auto 0.45rem;
  max-width: 26em; color: var(--ink); }
.sponsor-hed::before, .sponsor-hed::after { content: "\201C"; }
.sponsor-hed::after { content: "\201D"; }
.sponsor-text { margin: 0 auto 0.5rem; max-width: 30em; color: var(--ink-2); }
.sponsor-offer { margin: 0.55rem auto 0.7rem; padding-top: 0.55rem;
  border-top: 1px solid var(--rule); max-width: 26em;
  font: 600 var(--t-d-1)/1.4 var(--sans); letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-2); }
.sponsor-cta { margin-top: 0; padding: 0.45rem 1.2rem; border-width: 2px; }
.sponsor-logo { filter: grayscale(1) contrast(1.05); }
/* The disclosure runs between the kicker and the advertiser's name — above
   the pitch, not buried under it, because a reader who has already read the
   offer has read it without knowing who is making it. Quiet, but not faint:
   --ink-2, the same ink as the copy it qualifies. Fine print that has to be
   hunted for is not a disclosure. */
.sponsor-disc { margin: 0 auto 0.75rem; max-width: 32em;
  font: italic 400 var(--t-d-1)/1.5 var(--serif); color: var(--ink-2); }

/* THE MAP, on real tiles.
   Desaturated and warmed so it reads as an engraving on newsprint rather than
   as a web map dropped into a newspaper. The filter is on the tile pane only —
   markers and popups keep their colour, because those carry meaning. */
.cm-live { height: 480px; width: 100%; background: var(--surface);
  border: 1px solid var(--rule-strong); }
.cm-live .leaflet-tile-pane {
  filter: grayscale(1) contrast(.92) brightness(1.06) sepia(.16); }
:root[data-theme="dark"] .cm-live .leaflet-tile-pane {
  filter: grayscale(1) invert(.92) contrast(.85) brightness(1.05) hue-rotate(180deg); }
.cm-live .leaflet-container { font: 400 var(--t-d-1)/1.4 var(--sans); }
.cm-live .leaflet-popup-content { font: 400 var(--t-d0)/1.45 var(--sans); }
.cm-live .leaflet-popup-content a { color: var(--link); }
.cm-live .leaflet-control-attribution { font-size: 10px; }
.cm-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%;
  margin-inline-end: 0.35em; vertical-align: baseline; }
.cm-nojs { display: none; }

/* THE MAP GETS THE FULL MEASURE.
   It sat in a 1.25fr column beside a 1fr rail, so on a wide screen the map was
   two thirds of the width and the neighbourhood list took the rest — and below
   it the capital list ran at the same narrow measure with the right third of
   the page empty. A map is the one thing here that gets better with width.
   Full width now, with the list underneath in columns that fill the line. */
.cm-wrap { display: block; }
@media (min-width: 60rem) { .cm-wrap { grid-template-columns: none; } }
.cm-live { height: clamp(360px, 52vh, 620px); }
.cm-rail { margin-top: 1rem; }
.cm-rail .cm-list { columns: 2; column-gap: 2rem; }
@media (min-width: 60rem) { .cm-rail .cm-list { columns: 3; } }
.cm-rail .cm-list li { break-inside: avoid; }

/* The capital and closure lists were single-column at full page width, which
   left a third of the sheet blank down the whole section. */
@media (min-width: 60rem) {
  .cap-list, .cx-list { columns: 2; column-gap: 2.4rem; }
  .cap-row, .cx-row { break-inside: avoid; }
}

/* ══ THE DESK: map and listings, side by side ═══════════════════════════════
   The map was a full-width bar with the listings a screen further down, so
   "what is on" and "where is it" never appeared together — and a wide map is
   also what made the scroll trap so easy to fall into. It is a widget in a
   column now, with the listings beside it driving it. */
.mapdesk { display: grid; gap: 1.1rem 2rem; margin: 1.75rem 0 0;
  padding-top: 0.9rem; border-top: 2px solid var(--ink); }
@media (min-width: 62rem) {
  .mapdesk { grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); align-items: start; }
}
.mapdesk .locator, .mapdesk .events { margin-top: 0; border-top: 0; padding-top: 0; }
.mapdesk .cm-live { height: clamp(420px, 54vh, 600px); }
.mapdesk .cm-rail { margin-top: 0.7rem; }
.mapdesk .cm-rail .cm-list { columns: 1; }

/* The zoom hint, shown only when somebody wheels over the map. */
.cm-hint { position: absolute; inset: auto 0 0 0; z-index: 500; margin: 0;
  padding: 0.45rem; text-align: center; background: rgba(20,24,28,.78);
  color: #fff; font: 600 var(--t-d-1)/1.3 var(--sans);
  opacity: 0; transition: opacity .18s; pointer-events: none; }
.cm-hint.is-on { opacity: 1; }

/* A venue you can press. It is a control, so it looks like one — but a quiet
   one, because it sits at the end of every row in a dense list. */
.ev-where--map { appearance: none; background: none; border: 0; padding: 0;
  font: inherit; color: var(--ink-3); text-align: left; cursor: pointer;
  border-bottom: 1px dotted var(--rule-strong); }
.ev-where--map:hover { color: var(--link); border-bottom-color: var(--link); }
.ev-where--map:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The listings as a column: tighter leading, the day headings doing the work
   of the whitespace that used to separate them. */
.mapdesk .events { font-size: var(--t-d0); }
.mapdesk .ev-day { margin: 0.9rem 0 0.3rem; padding-bottom: 0.2rem;
  border-bottom: 1px solid var(--rule); font: 700 var(--t-d-2)/1.3 var(--sans);
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-2); }
.mapdesk .ev-list { margin: 0; padding: 0; list-style: none; }
.mapdesk .ev-row { display: grid;
  grid-template-columns: 4.6em auto 1fr; gap: 0.1rem 0.5rem;
  align-items: baseline; padding: 0.3rem 0; border-bottom: 1px solid var(--rule); }
.mapdesk .ev-what { grid-column: 2 / -1; font-weight: 600; }
.mapdesk .ev-where, .mapdesk .ev-where--map { grid-column: 2 / -1;
  font-size: var(--t-d-1); }
.mapdesk .ev-cost { grid-column: 2 / -1; font-size: var(--t-d-2); color: var(--ink-3); }

/* ══ ONE STYLE FOR THE RECORD ═══════════════════════════════════════════════
   Capital, closures, roadwork, City Hall and markets had each grown their own
   heading treatment, their own lede, their own rule weight — five sections
   that do the same job looking like five different papers. One rhythm now:
   a small-caps head on a hairline, an italic lede, rows on hairlines. */
.apparatus > section, .apparatus .cityhall { margin-top: 1.5rem; }
.apparatus .cap-lede, .apparatus .cx-lede, .apparatus .rw-lede,
.apparatus .ch-sub, .apparatus .mk-gap {
  margin: 0.35rem 0 0.7rem; max-width: 46em;
  font: italic 400 var(--t-p0)/1.5 var(--serif); color: var(--ink-2); }
.apparatus .cap-row, .apparatus .cx-row, .apparatus .rw-row {
  padding: 0.45rem 0; border-top: 1px solid var(--rule); }

/* The space after road closures was a full screen of nothing: three lists each
   set at one column against a 64em measure, each with a bottom margin sized
   for a full-width band. Two columns and a normal gap. */
@media (min-width: 62rem) {
  .rw-list { columns: 2; column-gap: 2.4rem; }
  .rw-row { break-inside: avoid; }
}
.closures, .roadwork, .capital, .markets { margin-bottom: 0; }
.apparatus .cx-list, .apparatus .rw-list, .apparatus .cap-list { margin-bottom: 0; }

/* The strip is the one thing that gets the full measure. */
.citytime { margin: 1.75rem 0 0; }

/* THE READOUT, IN COLUMNS.
   Under a full-width strip this stacked one lane per line down a 64em measure,
   so the panel ran a whole screen tall and was mostly margin. The lanes are
   independent blocks — exactly what columns are for. */
.ct-out { min-height: 0; padding: 0.7rem 0 0.4rem; }
@media (min-width: 48rem) { .ct-out { columns: 2; column-gap: 2.4rem; } }
@media (min-width: 74rem) { .ct-out { columns: 3; } }
.ct-out .ct-lane-h { break-after: avoid; margin-top: 0.6rem; }
.ct-out > * { break-inside: avoid; }
.ct-out .ct-when { break-after: avoid; }
.citytime .ct-note { margin-top: 0.4rem; }

/* THE BUSINESS DESK. Twenty-odd rows in seven groups: columns, so it reads as
   a market page rather than a single long ribbon down one side of the sheet. */
.mk-lede { margin: 0.35rem 0 0.8rem; max-width: 46em;
  font: italic 400 var(--t-p0)/1.5 var(--serif); color: var(--ink-2); }
.mk-cols { columns: 1; column-gap: 2.4rem; }
@media (min-width: 46rem) { .mk-cols { columns: 2; } }
@media (min-width: 74rem) { .mk-cols { columns: 3; } }
.mk-group { break-after: avoid; break-inside: avoid;
  margin: 0 0 0.2rem; padding-bottom: 0.18rem;
  border-bottom: 1px solid var(--rule-strong);
  font: 700 var(--t-d-2)/1.35 var(--sans); letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--ink); }
.mk-group:not(:first-child) { margin-top: 1rem; }
.mk-group-n { display: block; margin-top: 0.12rem;
  font: italic 400 var(--t-d-1)/1.35 var(--serif);
  letter-spacing: 0; text-transform: none; color: var(--ink-3); }
.mk-list { break-inside: avoid; margin: 0 0 0.2rem; }
.mk-row { grid-template-columns: 1fr auto auto; gap: 0.3rem 0.7rem; padding: 0.26rem 0; }
.mk-price { font-size: var(--t-d1); }

/* ══ THE ENERGY BAND ════════════════════════════════════════════════════════
   Alberta's own figures, set as figures rather than as table rows. These are
   the numbers this city is actually exposed to — what a barrel of Western
   Canadian Select fetches, what it sells below WTI, what Alberta gas gets in
   Canadian dollars a gigajoule, and how many rigs are turning — so they get
   the size and the space, and the equity board runs underneath as detail. */
.abx-band { display: grid; gap: 0; margin: 0.2rem 0 1.25rem; padding: 0;
  list-style: none; border-top: 2px solid var(--ink);
  border-bottom: 1px solid var(--rule-strong);
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
.abx { padding: 0.7rem 0.9rem 0.75rem; border-inline-start: 1px solid var(--rule); }
.abx:first-child { border-inline-start: 0; padding-inline-start: 0; }
.abx-k { display: block; margin-bottom: 0.12rem;
  font: 700 var(--t-d-2)/1.3 var(--sans); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-2);
  text-decoration: none; border-bottom: 1px solid transparent; }
a.abx-k:hover { color: var(--ink); border-bottom-color: var(--ink); }
.abx-v { margin: 0; font: 600 clamp(1.5rem, 2.6vw, 2rem)/1.05 var(--serif);
  font-variant-numeric: tabular-nums lining-nums; color: var(--ink); }
.abx-u { margin-inline-start: 0.3em; font: 400 var(--t-d-2)/1 var(--sans);
  letter-spacing: 0.04em; color: var(--ink-3); }
.abx-y { margin: 0.2rem 0 0; font: 600 var(--t-d-1)/1.3 var(--sans); color: var(--ink-2); }
.abx-y--up { color: var(--ok); }
.abx-y--down { color: var(--danger); }
.abx-d { margin: 0.1rem 0 0; font: 400 var(--t-d-2)/1.35 var(--sans); color: var(--ink-3); }

/* The differential leads and is set apart: it is the one figure here that is
   ours rather than the province's, so it should not be mistaken for a quote. */
.abx--diff { background: var(--surface); }
.abx--diff .abx-v { color: var(--danger); }
.abx--diff .abx-y { font-style: italic; font-weight: 400; color: var(--ink-2); }

/* Sparklines: the level is stated beside them, so these state only the shape. */
.spark { display: block; margin: 0.3rem 0 0; overflow: visible; }
.spark polyline { fill: none; stroke: var(--ink-2); stroke-width: 1.2;
  stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.spark-now { fill: var(--ink); }

/* The business desk sits directly under the map, as its own band rather than
   as one more section of the record: energy is not an appendix in Alberta. */
.business { margin-top: 1.75rem; padding-top: 0.9rem; border-top: 2px solid var(--ink); }
.business .markets { margin-top: 0; }
.business .instr-h { border-bottom: 0; padding-bottom: 0; }

/* The stock control is a control, not an edition slug. Out of the centred
   position under the nameplate — that spot MEANS "LATE EDITION" on a
   broadsheet — and over to the folio line where the paper keeps its own
   metadata, at the size of the rest of it. */
.masthead { position: relative; }
@media (max-width: 46rem) {
  /* Static, but pushed right rather than centred: centred under the nameplate
     is the edition-slug position on any width, and that is the one place this
     control must not sit. */
  .masthead .edition-pick { position: static; margin: 0.35rem 0 0 auto; }
}

/* Form controls do not inherit font-family, so the Alberta-mode checkbox and
   the area select were computing Arial — a third typeface in a banner that is
   meant to have two. */
input, select, textarea, button { font-family: inherit; }

/* ── THE FOLDED INDEX ──────────────────────────────────────────────────────
   One thin line where a control panel used to be. Closed it reads as a
   newspaper's index; open it is the filter bar it always was. */

/* The ticker is market furniture, so it takes the folio's face too — but the
   figures stay lining tabular, because a market line is read by comparing
   columns of digits and old-style numerals defeat that. */
.ticker { font: 400 var(--t-d-1)/1.45 var(--serif);
  font-variant-numeric: tabular-nums lining-nums; }
.tk-state { font-family: var(--sans); font-size: var(--t-d-3); }

/* ══ MOBILE: THE BANNER MUST NOT BE THE WHOLE FIRST SCREEN ══════════════════
   On a 375x812 phone the furniture ran to 816px — the news began one pixel
   below the fold, so the entire first screen was masthead and instruments.
   A broadsheet reader sees a headline immediately; so should this one. */
@media (max-width: 46rem) {
  /* The market line keeps the figures an Edmonton reader checks first — the
     composite and the barrel — and drops the rest to the Business desk. */
  .ticker { gap: 0.2rem 0.8rem; }
  .ticker .tk-cell:nth-of-type(n+4) { display: none; }
  .tk-more { margin-inline-start: 0; }

  .conditions { padding: 0.4rem 0; gap: 0.15rem 0.8rem; }

  /* The nameplate keeps its authority but not a third of the screen. */
  .masthead { padding-block: 0.6rem 0.4rem; }
  .orn { transform: scale(.8); margin-block: 0.1rem; }

  /* The index is one line, and its section list is the first thing to go —
     the chips inside carry the same names once it is opened. */
  .fx-index { display: none; }
  .fx-sum { gap: 0.2rem 0.6rem; }

  /* Instruments get a phone-sized height rather than a desktop one. */
  .mapdesk .cm-live { height: 300px; }
  .abx-band { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .abx { padding: 0.55rem 0.6rem; }
  .abx:nth-child(odd) { border-inline-start: 0; padding-inline-start: 0; }
  .mk-cols { columns: 1; }
}


/* THE MARKET LINE IS A GRID, NOT A WRAP.
   Six cells wrapped by width left one orphan on line two with "MARKETS"
   pushed to the far edge by an auto margin. A grid of three columns (two on
   a phone) holding exactly six cells is full on every row at every width —
   the renderer guarantees the six, the grid guarantees the rows. Symbols,
   not company names, so a cell is a cell and not a sentence; the name rides
   on the tooltip. */
.ticker { display: block; }
.tk-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.15rem 0.9rem; margin-top: 0.15rem; }
.tk-cell { white-space: nowrap; overflow: hidden; text-overflow: clip; min-width: 0; }
.tk-cell b { font-family: var(--sans); font-size: var(--t-d-2); letter-spacing: 0.06em; }
@media (max-width: 46rem) {
  .tk-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ticker .tk-cell:nth-of-type(n+4) { display: block; }
}


/* ══ THE BANNER ═══════════════════════════════════════════════════════════
   Four rows, one job each, on a shared three-column rhythm so they align:
   nameplate · folio with the controls · the ears · the desks.              */

/* ── row 2: the folio. Date and edition left, the paper's numbers centre,
   the two switches and the Customize control right. */
.folio { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center; gap: .4rem 1.25rem; margin: .45rem 0 0; padding: .45rem 0 .4rem;
  border-bottom: 1px solid var(--rule); }
.folio p { margin: 0; font: 500 var(--t-d-2)/1.5 var(--serif);
  font-variant-caps: all-small-caps; letter-spacing: .08em; color: var(--ink-3); }
.folio b { color: var(--ink-2); font-weight: 700; }
.folio-l { text-align: left; }
.folio-l time { color: var(--ink); font-weight: 700; }
.folio-c { text-align: center; white-space: nowrap; }
.folio .dot { display: inline-block; width: 3px; height: 3px; margin: 0 .45em;
  vertical-align: middle; border-radius: 50%; background: var(--rule-strong); }
.folio-r { display: flex; justify-content: flex-end; align-items: center; gap: .45rem; }

/* ── the switch. Both toggles and the Customize control share one shape so
   a reader learns it once: a bordered pill, 28px tall, the whole label the
   hit target, a knob that slides. Sans, because it is a control. */
.sw { position: relative; display: inline-flex; align-items: center; gap: .45rem;
  min-height: 28px; padding: 0 .7rem 0 .55rem; margin: 0;
  border: 1px solid var(--rule-strong); border-radius: 2px; background: var(--surface);
  font: 600 var(--t-d-2)/1 var(--sans); letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-2); cursor: pointer; user-select: none; white-space: nowrap; }
.sw:hover { color: var(--ink); border-color: var(--ink); }
.sw:focus-visible, .sw:has(:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.sw input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0;
  opacity: 0; cursor: pointer; }
.sw-knob { display: inline-block; width: 1.55em; height: .85em; flex: none;
  border: 1px solid currentColor; border-radius: 999px; position: relative; }
.sw-knob::after { content: ""; position: absolute; top: 1px; left: 1px;
  width: calc(.85em - 4px); height: calc(.85em - 4px); border-radius: 50%;
  background: currentColor; transition: transform .15s; }
.sw[aria-checked="true"] .sw-knob::after,
.sw:has(input:checked) .sw-knob::after { transform: translateX(.7em); }
.sw[aria-checked="true"], .sw:has(input:checked) { color: var(--ink); border-color: var(--ink); }
body.alberta-on .ab-toggle { color: var(--accent-ink, var(--ink)); }
.sw--menu { list-style: none; }
.sw--menu::-webkit-details-marker { display: none; }
.sw-glyph { font-size: 1.15em; line-height: 1; }

/* Customize: a panel that drops from the control, not a band in the paper. */
.facets { position: relative; margin: 0; }
.fx-bar { position: absolute; right: 0; top: calc(100% + .4rem); z-index: 40;
  min-width: min(38rem, calc(100vw - 2.5rem)); padding: .75rem .9rem .7rem;
  background: var(--ground); border: 1px solid var(--ink);
  box-shadow: 0 6px 18px -8px rgba(20,24,28,.35); }
.fx-head { margin: 0 0 .45rem; font: 700 var(--t-d-2)/1.3 var(--sans);
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink); }
.fx-chips { display: flex; flex-wrap: wrap; align-items: baseline; gap: .35rem 1rem; }
.fx-area { display: block; margin-top: .55rem; }
.fx-reset { margin-top: .5rem; }
.fx-note { margin: .6rem 0 0; font: italic 400 var(--t-d-2)/1.4 var(--serif); color: var(--ink-3); }

/* ── row 3: the ears. Weather left, the city centre, markets right; the
   weather and market ears are links to their modules, whole. */
/* the market ear carries six cells in three columns, so it gets the widest
   share; the city cell is two short lines and needs the least. */
.ears { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, .7fr) minmax(0, 1.5fr);
  gap: .2rem 1.5rem; align-items: start; padding: .5rem 0;
  border-top: 1px solid var(--ink); border-bottom: 1px solid var(--rule);
  font: 400 var(--t-d0)/1.5 var(--serif); color: var(--ink-2); }
.ear { display: block; min-width: 0; color: inherit; text-decoration: none; }
a.ear:hover b, a.ear:focus-visible b { text-decoration: underline; text-underline-offset: 2px; }
a.ear:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ear .cd-item { display: inline; }
.ear .cd-item b { color: var(--ink); font-weight: 700; }
.ear--city .cd-item { display: block; }
.ear--city .cd-k { font: 600 var(--t-d-2)/1.5 var(--sans); letter-spacing: .11em;
  text-transform: uppercase; color: var(--ink-3); margin-inline-end: .4em; }
.ear--city a { color: var(--link); }
.cd-alert { color: var(--danger) !important; }
.ear--mk .ticker { border: 0; margin: 0; padding: 0; }

/* ── row 4: the desks. The ten section names as links — the paper's index,
   where it has always been, and not a filter. */
.deskrow { margin: 0; border-bottom: 1px solid var(--rule); }
.deskrow .desks { margin: 0; border: 0; }
.deskrow .desks ul { padding: .4rem 0; gap: 0 1.1rem; }

/* phone: the folio stacks, the controls go full width, the ears stack, the
   market ear keeps two full rows of three. */
@media (max-width: 46rem) {
  /* The banner has a budget on a phone: the first headline must clear the
     fold. The folio drops Vol. and Price, the three switches are compacted so
     they sit on one line, the desks become one scrolling row rather than four
     wrapped ones, and the market ear runs two columns so no cell clips. */
  .masthead { padding-block: .4rem .2rem; }
  .masthead .rules { margin-block: .35rem; }
  .folio { grid-template-columns: 1fr; gap: .25rem; text-align: left; margin-top: .3rem;
    padding: .35rem 0 .3rem; }
  .folio-c { white-space: normal; text-align: left; }
  .folio-x { display: none; }
  .folio-r { justify-content: flex-start; flex-wrap: nowrap; gap: .35rem; }
  .sw { padding: 0 .5rem 0 .45rem; letter-spacing: .05em; gap: .35rem; }
  .sw--menu .sw-txt { position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); }                   /* ☰ alone; the label stays for readers */
  .fx-bar { position: static; min-width: 0; margin-top: .5rem; box-shadow: none; }
  .ears { grid-template-columns: 1fr; gap: .3rem; padding: .4rem 0; }
  .ear--city { display: none; }              /* one tap away via the weather ear */
  .tk-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .05rem .6rem; }
  .deskrow .desks ul { flex-wrap: nowrap; overflow-x: auto; gap: 0 .9rem;
    padding: .35rem 0; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
  .deskrow .desks ul::-webkit-scrollbar { display: none; }
  .deskrow .desks li { flex: none; }
  .deskrow .desks a { white-space: nowrap; }
}


/* ══ STEP 4 · the lead column ends where its content ends ═══════════════════ */
.frontpkg { align-items: start; }
.fp-lead .item--second { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--rule); }
.fp-lead .pull { margin: 1rem 0 0; }
/* a two-line body in two columns IS the hole; a short lead runs one measure */
.fp-lead--short .item--lead .body { column-count: 1; max-width: 40em; }

/* ══ STEP 5 · the business desk under the map ══════════════════════════════ */
@media (min-width: 62rem) {
  .mapdesk { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); align-items: start; }
}
.md-left { min-width: 0; }
.md-left .business { margin-top: 1.25rem; padding-top: .6rem; border-top: 2px solid var(--ink); }
.md-left .instr-h { border-bottom: 1px solid var(--rule-strong); padding-bottom: .3rem; }
.md-left .abx-band { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.md-left .abx { padding: .6rem .7rem; }
.md-left .abx:nth-child(odd) { border-inline-start: 0; padding-inline-start: 0; }
.md-left .mk-cols { columns: 2; column-gap: 2rem; }
.mk-fold { margin-top: .6rem; }
.mk-fold > summary { cursor: pointer; list-style: none; padding: .3rem 0;
  border-top: 1px solid var(--rule-strong); border-bottom: 1px solid var(--rule);
  font: 700 var(--t-d-2)/1.4 var(--sans); letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-2); }
.mk-fold > summary::-webkit-details-marker { display: none; }
.mk-fold > summary::after { content: " \25BE"; }
.mk-fold[open] > summary::after { content: " \25B4"; }
.mk-fold .mk-cols { columns: 2; margin-top: .4rem; }
@media (max-width: 62rem) {
  .md-left .mk-cols, .mk-fold .mk-cols { columns: 1; }
}


/* ══ STEP 6 · the desk pages ═══════════════════════════════════════════════ */
.week { margin-top: 1.75rem; border-top: 2px solid var(--ink); padding-top: .8rem; }
.wk-lede { margin: .3rem 0 .8rem; max-width: 46em;
  font: italic 400 var(--t-p0)/1.5 var(--serif); color: var(--ink-2); }
.wk-day { margin: .9rem 0 .25rem; font: 700 var(--t-d-2)/1.4 var(--sans);
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-2); }
.wk-list { list-style: none; margin: 0; padding: 0; }
@media (min-width: 56rem) { .wk-list { columns: 2; column-gap: 2.4rem; } }
.wk-row { break-inside: avoid; padding: .3rem 0; border-top: 1px solid var(--rule);
  font: 400 var(--t-d1)/1.4 var(--serif); }
.wk-row a { color: var(--ink); text-decoration: none;
  border-bottom: 1px solid var(--rule-strong); }
.wk-row a:hover { color: var(--link); border-bottom-color: var(--link); }
.desk-record { margin-top: 1.5rem; }
.desk-none { margin: 1.2rem 0 0; padding-top: .8rem; border-top: 2px solid var(--ink);
  font: italic 400 var(--t-p0)/1.5 var(--serif); color: var(--ink-3); }


/* ══ BATCH E · the map desk, finished ══════════════════════════════════════ */
.more-desks { margin-top: 1.25rem; padding-top: .6rem; border-top: 2px solid var(--ink); }
.md-lede { margin: .3rem 0 .5rem; font: italic 400 var(--t-d1)/1.45 var(--serif); color: var(--ink-3); }
.item--compact { padding: .55rem 0 .6rem; border-top: 1px solid var(--rule); }
.item--compact .kicker { margin: 0 0 .15rem; }
.item--compact .kicker a { color: inherit; text-decoration: none; }
.item--compact .kicker a:hover { color: var(--link); }
.item--compact h3 { margin: 0 0 .25rem; font-size: var(--h-s); line-height: 1.2; }
.item--compact h3 a { color: var(--ink); text-decoration: none; }
.item--compact h3 a:hover { color: var(--link); }
.item--compact .body { margin: 0 0 .3rem; font-size: var(--t-d1); line-height: 1.45; column-count: 1; }
.ev-all { margin: .7rem 0 0; padding-top: .5rem; border-top: 1px solid var(--rule-strong);
  font: 700 var(--t-d-2)/1.4 var(--sans); letter-spacing: .1em; text-transform: uppercase; }
.ev-all a { color: var(--link); text-decoration: none; border-bottom: 1px solid var(--link); }
/* the strip has no readout now; the table toggle is the accessible fallback */
.citytime .ct-table-toggle, .citytime .ct-tbl-btn { margin-top: .5rem; }


/* ══ THE EARS, ALIGNED ═════════════════════════════════════════════════════
   The city cell was the narrowest of the three and its second row —
   "Next at City Hall · Utility Committee · Mon 24 Aug" — broke inside the
   date. Each row is now one line that may not break; the cell gets the width
   it needs and the weather ear gives up what it does not use. */
/* the city cell is sized to its own longest row — it is two short lines and
   must never clip — and the two ears share whatever is left, markets wider
   because it holds three cells a row. */
/* Desktop only: this base rule was appended AFTER the phone's stacking rule and,
   being later at equal specificity, un-stacked the ears at 375px — the weather
   ear measured 11px wide with its figure over the markets label. Phone and
   tablet each keep their own rule below. */
@media (min-width: 64.01rem) {
  .ears { grid-template-columns: minmax(0, 1fr) fit-content(26rem) minmax(0, 1.35fr); }
}
.ear--city .cd-item { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* the weather ear wraps only between facts, never inside one */
.ear--wx .cd-q { white-space: normal; }
.ear--wx b, .ear--wx .cd-aq { white-space: nowrap; }
@media (max-width: 46rem) {
  .ear--city .cd-item { white-space: normal; }
}

/* Tablet: three cells across 768px clipped every city row and every market
   cell. Two rows instead — weather beside the city, the market ear taking the
   full width under them — until the desktop row has room for three. */
@media (min-width: 46.01rem) and (max-width: 64rem) {
  .ears { grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr); }
  .ear--mk { grid-column: 1 / -1; padding-top: .3rem; border-top: 1px solid var(--rule); }
  .ear--city .cd-item { white-space: normal; overflow: visible; text-overflow: clip; }
  .tk-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}


/* Two columns only when the renderer says there is enough body for them. */
.pack .item--lead .body--cols { column-count: 2; column-gap: 1.6rem; }
.cd-f { white-space: nowrap; }

/* The nameplate is the way home from every page. */
.wordmark a.home { color: inherit; text-decoration: none; display: block; }
.wordmark a.home:hover .lockup-city { color: var(--link); }

/* A running story: its instalments as dated links; a continuing story: its earlier ones. */
.thread-n { margin: .2rem 0 .4rem; font: 500 var(--t-d-2)/1.4 var(--sans); letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-3); }
.thread { list-style: none; margin: 0 0 .4rem; padding: 0; }
.thread li { padding: .3rem 0; border-top: 1px solid var(--rule); font: 400 var(--t-d1)/1.45 var(--serif); }
.thread time { display: inline-block; min-width: 4.2em; font: 500 var(--t-d-2)/1.4 var(--sans);
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); }
.thread a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--rule-strong); }
.thread a:hover { color: var(--link); border-bottom-color: var(--link); }
.earlier { margin: .45rem 0 0; font: 400 var(--t-d0)/1.5 var(--serif); color: var(--ink-2); }
.earlier b { font: 700 var(--t-d-2)/1.4 var(--sans); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-3); margin-inline-end: .3em; }
.earlier a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--rule-strong); }
.earlier a:hover { color: var(--link); border-bottom-color: var(--link); }
.earlier .stamp { margin-inline-start: .25em; }

/* The rundown instruments: a table on the desk, a source line under it. */
.fxi { margin-top: 1.5rem; }
.fxt { width: 100%; border-collapse: collapse; margin: .5rem 0 .3rem;
  font: 400 var(--t-d1)/1.4 var(--serif); }
.fxt th { text-align: left; font: 700 var(--t-d-2)/1.4 var(--sans); letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-3); border-bottom: 1px solid var(--rule-strong);
  padding: .25rem .6rem .25rem 0; }
.fxt td { padding: .3rem .6rem .3rem 0; border-bottom: 1px solid var(--rule); vertical-align: top; }
.fxt .fx-n { font-variant-numeric: tabular-nums; white-space: nowrap; }
.fxt .fx-prev { color: var(--ink-3); }
.fxt .fx-cat { font: 500 var(--t-d-2)/1.4 var(--sans); letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-3); }
.fxt .fx-na td { color: var(--ink-3); }
.fxt a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--rule-strong); }
.fx-sum { margin: .4rem 0 0; font: 400 var(--t-p0)/1.5 var(--serif); }
.fx-sum b { font-weight: 700; font-variant-numeric: tabular-nums; }
.fx-sum svg { vertical-align: middle; margin-inline-start: .4em; }
.fx-note { margin: .3rem 0 0; max-width: 46em; font: italic 400 var(--t-d0)/1.5 var(--serif); color: var(--ink-2); }
.fx-src { margin: .3rem 0 0; font: 500 var(--t-d-2)/1.4 var(--sans); letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-3); }
.fx-src a { color: var(--ink-2); }


/* A kept story from outside the city: one word-mark in its source line.
   New selector, no breakpoint interaction — it adds a list item style and
   overrides nothing the phone or tablet rules set. */
.sources .scope { font: 500 var(--t-d-2)/1.4 var(--sans); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-3); cursor: help; }
.sources .scope::before { content: "\25C6"; font-size: .6em; vertical-align: .2em; margin-inline-end: .4em; color: var(--accent, var(--ink-3)); }

/* The reportage hedge. Set like the INCOMPLETE box — a bordered aside in the
   paper's own voice, not a tinted card — so the two honesty surfaces read as
   the same kind of statement rather than one looking graver than the other. */
.alleg { margin: .5rem 0 .6rem; padding: .5rem .7rem;
  border-inline-start: 3px solid var(--rule-strong);
  font: 400 var(--t-d0)/1.5 var(--serif); color: var(--ink-2); }
.alleg-k { display: block; margin-bottom: .1rem;
  font: 700 var(--t-d-2)/1.4 var(--sans); letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-3); }


/* The sky as an engraved mark in the nameplate ear. Same paths as the weather
   cut, at text size: it replaces the words it was drawn from, so it sits on
   the baseline like a word rather than floating like an image. */
.wx-mark { width: 1.6em; height: 1.3em; vertical-align: -0.32em;
  color: var(--ink); flex: none; }
@media (max-width: 46rem) { .wx-mark { width: 1.45em; height: 1.2em; } }

/* "Time not published" is not "All day". Set quieter than a real time so a
   scanning reader sees an absence rather than a claim about the schedule. */
.ev-t--none { font-style: italic; color: var(--ink-3); }
