/* 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: dark;

  /* ground + ink ------------------------------------------------ */
  --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 */

  /* semantic — separate from accent, cyan-teal ok (a green ok vs a
     red danger collapses to ΔE 10.55 under deuteranopia) ---------- */
  --ok:#12B4BD; --warn:#D9B44A; --danger:#F17F80;  /* warn was #FADC42, a highlighter */

  /* categorical lanes — two pale marks, three mid marks. That two-tier
     lightness structure is the design: under dichromacy the five hues
     collapse onto one blue–yellow line, so 4 of 10 pairs are held apart
     by lightness alone. Worst pair across the set: ΔE 20.65. --------- */
  --lane-weather:#AEE3FA; --lane-transit:#3D93EF; --lane-council:#D69B2B;
  --lane-safety:#DC6B92;   /* was #D75C86: 4.47 on --surface */  --lane-dev:#C4EFD2;

  /* legacy aliases — the existing component layer still speaks these */
  --paper:var(--ground); --paper-2:var(--surface);
  --ink-muted:var(--ink-3);
  --spruce:var(--link); --spruce-hover:#B6DEFA;
  --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;

  --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");
}


@media (prefers-color-scheme: light) {
  :root {
    color-scheme: light;
    --ground:#F4F1E9; --surface:#EAE6DA; --surface-2:#DED9C9;
    --rule:#C9C2B0; --rule-strong:#A69D88;
    --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, missed AA by .01 */
    --lane-weather:#2E6C93; --lane-transit:#1B4F87;
    --lane-council:#7A5411; --lane-safety:#8E2E4E; --lane-dev:#2F6B45;
    --spruce-hover:#0B3A59;
    --tooth:
      var(--grain), var(--fibre);
  }
}

/* FOCUS BASELINE — every focusable thing gets a ring, including controls that
   scripts create at runtime. Five buttons built by citytime.js (the pin, the
   "next" jumps, the table toggle) had none: a keyboard reader could tab into
   them and see nothing move. Component rules below may restyle this; none may
   remove it. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

*, *::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: soft-light, soft-light;
  color: var(--ink);
  font-variant-numeric: tabular-nums lining-nums;
  font: 400 1rem/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 0.75rem/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 0.625rem/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; }

.dateline {
  margin: 0;
  font: 500 0.75rem/1.45 var(--sans);
  color: var(--ink-muted);
}

.dateline time {
  font-weight: 700;
  color: var(--ink);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.dateline .dot {
  display: inline-block;
  width: 3px;
  height: 3px;
  margin: 0 0.55rem 0.15rem;
  background: var(--ink-muted);
  border-radius: 50%;
  vertical-align: middle;
}

.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 0.6875rem/1 var(--sans);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.folio select {
  flex: 1;
  min-width: 0;
  font: 600 0.875rem/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 0.75rem/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 0.8125rem/1.45 var(--sans);
  color: var(--ink);
}
.beta p { margin: 0; }
.beta-tag {
  flex: none;
  font: 700 0.625rem/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 0.625rem/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 0.75rem/1.2 var(--sans);
  letter-spacing: 0.04em;
  color: var(--ink-muted);
}

.wx-body {
  grid-area: body;
  margin: 0;
  font: 400 0.875rem/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 0.75rem/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 0.75rem/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;
}
.sec h2 {
  margin: 0;
  font: 700 0.6875rem/1 var(--sans);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
}
.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 1.125rem/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 1rem/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 0.9375rem/1.4 var(--serif);
  color: var(--ink);
}
.why-k {
  margin-right: 0.4em;
  font: 700 0.6875rem/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 0.75rem/1.35 var(--sans);
}
.sources li + li::before {
  content: "·";
  margin: 0 0.45em;
  color: var(--ink-muted);
  font-weight: 400;
}

.filed-mark {
  font: 600 0.6875rem/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: 2.25rem;
  line-height: 1.12;
  font-variation-settings: "opsz" 72;
  letter-spacing: -0.022em;
}
.item--lead .body {
  font-size: 1.0625rem;
  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 0.625rem/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: 1rem;
  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 0.875rem/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 0.6875rem/1.4 var(--sans);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--filed);
}
.docket-when {
  display: block;
  font-size: 0.75rem;
  color: var(--ink-muted);
}

/* Votes */
.votes {
  margin: 0 0 0.55rem;
  padding: 0;
  list-style: none;
}
.votes li {
  font: 400 0.875rem/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 0.6875rem/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 1.125rem/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 0.75rem/1.35 var(--sans);
  color: var(--ink-muted);
}

#sports .item h3 { font-size: 1.0625rem; }

/* ------------------------------------------------------------------ */
/* 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 0.625rem/1 var(--sans);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.sponsor .name {
  margin: 0 0 0.25rem;
  font: 600 0.9375rem/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 0.75rem/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: 0.8125rem; }

  .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: .5rem 0; list-style: none;
}
.desks a {
  font: 700 .6875rem/1.8 var(--sans); letter-spacing: .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: .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 .5rem; font: 700 .625rem/1 var(--sans);
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-muted);
}
.ahead-list { margin: 0; padding: 0; list-style: none; }
.ahead-list li {
  display: grid; grid-template-columns: 5.25rem 1fr; gap: .5rem;
  padding: .3rem 0; border-top: 1px solid var(--rule);
  font: 400 .8125rem/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 .6875rem/1.3 var(--sans); color: var(--ink-muted);
}

/* --- section (desk) pages --- */
.masthead--sub { padding-top: .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 .15rem; font: 700 .75rem/1.2 var(--sans);
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink);
}
.sponsor-hed {
  margin: 0 0 .35rem; font: 600 1.25rem/1.2 var(--serif);
  letter-spacing: -.01em; color: var(--ink);
}
.sponsor-text { margin: 0 0 .5rem; font: 400 .875rem/1.45 var(--sans); color: var(--ink-muted); }
.sponsor-cta {
  font: 700 .75rem/1 var(--sans); letter-spacing: .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 --- */
.ab-toggle {
  display: inline-flex; align-items: center; gap: .4rem; flex: none;
  font: 700 .6875rem/1 var(--sans); letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-muted); cursor: pointer;
  white-space: nowrap;
}
.ab-toggle input { accent-color: var(--spruce); width: 1rem; height: 1rem; }
.ab-toggle:hover { color: var(--ink); }
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: .75rem 0 0; padding: .55rem .8rem;
  background: var(--paper-2); border-left: 3px solid var(--spruce);
  font: 400 .8125rem/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 .75rem; font: 700 1.375rem/1.15 var(--serif);
  letter-spacing: -.01em; color: var(--ink);
}
.ab-gate-card p { margin: 0 0 .75rem; font: 400 .9375rem/1.55 var(--sans); color: var(--ink-2); }
.ab-gate-card strong { color: var(--ink); }
.ab-gate-fine { font-size: .8125rem !important; color: var(--ink-3) !important; }
.ab-gate-row { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1.1rem; }
.ab-gate-row button {
  font: 700 .8125rem/1 var(--sans); letter-spacing: .04em;
  padding: .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. */
body.alberta-on article.item:not([data-ab-b]):not(.item--stub) > h3::after {
  content: "kept plain"; display: inline-block; vertical-align: middle;
  margin-left: .5rem; padding: .1rem .35rem;
  font: 700 .625rem/1.3 var(--sans); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-3); border: 1px solid var(--rule);
}

/* --- 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 .35rem; font: 700 .6875rem/1 var(--sans);
  letter-spacing: .14em; text-transform: uppercase; color: var(--alert);
}
.item--alert h3 { color: var(--ink); }
.alert-notice {
  margin: .6rem 0 .5rem; padding-top: .6rem;
  border-top: 1px solid var(--rule);
  font: 400 .75rem/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 .4rem; font: 600 1.5rem/1.15 var(--serif); color: var(--ink);
}
.signup-lede {
  margin: 0 0 1rem; max-width: 34rem;
  font: 400 .9375rem/1.5 var(--sans); color: var(--ink-muted);
}
.signup-lede strong { color: var(--ink); }
.signup-row { display: flex; flex-wrap: wrap; gap: .5rem; max-width: 30rem; }
.signup-row input {
  flex: 1 1 14rem; min-width: 0;
  font: 400 .9375rem/1 var(--sans); color: var(--ink);
  background: var(--paper); border: 1px solid var(--ink);
  border-radius: 0; padding: .6rem .7rem;
}
.signup-row button {
  flex: none; cursor: pointer;
  font: 700 .75rem/1 var(--sans); letter-spacing: .1em; text-transform: uppercase;
  color: var(--paper); background: var(--spruce);
  border: 1px solid var(--spruce); border-radius: 0; padding: .65rem 1.1rem;
}
.signup-row button:hover { background: var(--spruce-hover); border-color: var(--spruce-hover); }
.signup-editions { border: 0; margin: .9rem 0 0; padding: 0; max-width: 34rem; }
.signup-editions label {
  display: flex; gap: .5rem; align-items: baseline; margin-bottom: .4rem;
  font: 400 .875rem/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: .2rem; }
.signup-fine {
  margin: .9rem 0 0; max-width: 34rem;
  font: 400 .75rem/1.45 var(--sans); color: var(--ink-muted);
}
.signup-msg { margin: .6rem 0 0; font: 500 .8125rem/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: .9rem; border-top: 2px solid var(--ink); }
.ct-head { display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: .5rem 1rem; margin-bottom: .6rem; }
.ct-h { margin: 0; font: 700 .6875rem/1 var(--sans);
  letter-spacing: .18em; text-transform: uppercase; color: var(--ink); }
.ct-zooms { display: flex; gap: .9rem; }
.ct-z { appearance: none; background: none; border: 0; padding: .15rem 0; cursor: pointer;
  font: 500 .6875rem/1 var(--sans); letter-spacing: .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: .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(--lane-weather); opacity: .10; }
.ct-twi  { fill: var(--lane-transit); opacity: .16; }
.ct-grid { stroke: var(--rule); stroke-width: 1; }
.ct-templine { fill: none; stroke: var(--lane-weather); stroke-width: 2;
  stroke-linejoin: round; stroke-linecap: round; }
.ct-tempfill, .ct-range { fill: var(--lane-weather); opacity: .13; 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: .55; }
.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 --- */
.ct-axis { stroke: var(--ink-3); stroke-width: 1; }
.ct-sep  { stroke: var(--rule); stroke-width: 1; }
.ct-tick { stroke: var(--ink-3); stroke-width: 1; opacity: .55; }
.ct-tick--maj { opacity: 1; stroke-width: 1; }

/* --- marks: severity carries the hue, shape carries the redundancy --- */
.ct-bar { stroke: none; }
.ct-mk--info   .ct-bar, .ct-mk--info   .ct-dot { fill: var(--lane-transit); }
.ct-mk--warn   .ct-bar, .ct-mk--warn   .ct-dot { fill: var(--warn); }
.ct-mk--danger .ct-bar, .ct-mk--danger .ct-dot { fill: var(--danger); }
.ct-mk--act    .ct-bar, .ct-mk--act    .ct-dot { fill: 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-count { fill: var(--lane-transit); opacity: .5; }
.ct-aq { fill: var(--ink-3); opacity: .5; }

/* --- lettering --- */
.ct-lanelab, .ct-bandlab, .ct-rulelab, .ct-gridlab, .ct-empty, .ct-nowlab {
  font-family: var(--sans); font-size: 9px; font-weight: 600;
  letter-spacing: .09em; text-transform: uppercase; fill: var(--ink-3); }
.ct-bandlab { font-size: 8.5px; letter-spacing: .18em; fill: var(--ink-2); }
.ct-gridlab { font-size: 8px; letter-spacing: .04em; text-transform: none; }
.ct-empty { font-size: 9px; font-style: italic; text-transform: none;
  letter-spacing: .02em; opacity: .85; }
.ct-nowlab { fill: var(--ink-2); font-size: 8px; }

/* 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: 1; stroke-dasharray: 2 3; }
.ct-alertwash { fill: var(--danger); opacity: .1; }

/* --- readout: the document half. Every fact here is a link. --- */
.ct-out { min-height: 6rem; padding: .7rem 0 .2rem;
  font: 400 .875rem/1.5 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: .5em; font-weight: 500; }
.ct-out .ct-lane-h { display: block; margin-top: .45rem;
  font: 600 .625rem/1.4 var(--sans); letter-spacing: .13em;
  text-transform: uppercase; color: var(--ink-3); }
.ct-note { margin: .2rem 0 0; font: 400 .6875rem/1.45 var(--sans); color: var(--ink-3); }
.ct-tbtn { appearance: none; background: none; border: 0; padding: .4rem 0; cursor: pointer;
  font: 500 .6875rem/1 var(--sans); letter-spacing: .08em; text-transform: uppercase;
  color: var(--link); text-decoration: underline; text-underline-offset: .2em; }
.ct-table table { width: 100%; border-collapse: collapse; font: 400 .75rem/1.35 var(--sans); }
.ct-table th, .ct-table td { text-align: left; padding: .3rem .6rem .3rem 0;
  border-bottom: 1px solid var(--ink-3); }   /* traced rule: --ink-3, not --rule */
.ct-table th { font-weight: 600; letter-spacing: .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. clip() in citytime.js budgets 5.6px per character, so this
   size is load-bearing: leave it unstyled and the browser inherits 16px serif,
   the estimate under-counts by ~40%, and every label overruns its bar. */
.ct-mklab { font-family: var(--sans); font-size: 9px; font-weight: 600;
  letter-spacing: .01em; fill: var(--ground); }
.ct-mk--info .ct-mklab { fill: var(--ground); }
.ct-chev { font-family: var(--sans); font-size: 11px; font-weight: 700; fill: var(--ground); }
.ct-more { font-family: var(--sans); font-size: 8.5px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; 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: .9rem; border-top: 2px solid var(--ink); }
.dv-lede { margin: 0 0 .9rem; font: 400 1rem/1.5 var(--serif); color: var(--ink-2); }
.dv-lede b { color: var(--ink); }
.dv { padding: .75rem 0 .85rem; border-top: 1px solid var(--rule); }
.dv:first-of-type { border-top: 0; }
.dv-what { margin: 0 0 .3rem; font: 600 1.0625rem/1.3 var(--serif); color: var(--ink); }
.dv-tally { margin: 0 0 .45rem; font: 500 .8125rem/1.3 var(--sans); color: var(--ink-3); }
.dv-tally b { color: var(--ink); font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; font-size: .6875rem; }
.dv-n { color: var(--ink); font-weight: 700; margin-inline-start: .4em;
  font-variant-numeric: lining-nums tabular-nums; }
.dv-margin { margin-inline-start: .5em; padding: .05rem .3rem;
  font: 700 .625rem/1.4 var(--sans); letter-spacing: .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 .45rem; padding: 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 .5625rem/1 var(--sans); letter-spacing: .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: .35rem 0 0; font: italic 400 .875rem/1.45 var(--serif);
  color: var(--ink-3); }
.dv-note { margin: .9rem 0 0; font: 400 .75rem/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. */
.facets { display: flex; flex-wrap: wrap; align-items: baseline; gap: .35rem 1.1rem;
  margin: 0; padding: .55rem 0; border-bottom: 1px solid var(--rule); }
.fx-k { font: 500 .6875rem/1.5 var(--sans); letter-spacing: .13em;
  text-transform: uppercase; color: var(--ink-3); }
.fx { appearance: none; background: none; border: 0; padding: .1rem 0; cursor: pointer;
  font: 600 .75rem/1.4 var(--sans); letter-spacing: .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: .35em; font-size: .625rem; 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 .6875rem/1.5 var(--sans);
  letter-spacing: .06em; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.fx-reset { appearance: none; background: none; border: 0; padding: .1rem 0; cursor: pointer;
  font: 600 .6875rem/1.5 var(--sans); letter-spacing: .08em; text-transform: uppercase;
  color: var(--link); text-decoration: underline; text-underline-offset: .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: .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: .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 .4rem;
  font: 700 .8125rem/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: .625rem; letter-spacing: .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: .4rem .55rem; font: 500 .75rem/1.35 var(--sans);
  color: var(--ground); background: var(--ink); }

.cm-rail { align-self: start; }
.cm-list { margin: .35rem 0 0; padding: 0; list-style: none; }
.cm-list li { padding: .35rem 0; border-top: 1px solid var(--rule);
  font: 400 .8125rem/1.35 var(--sans); }
.cm-list li:first-child { border-top: 0; }
.cm-where { display: block; font: 600 .625rem/1.5 var(--sans); letter-spacing: .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: .3rem 1.2rem; margin: .7rem 0 0;
  padding: 0; list-style: none; font: 500 .6875rem/1.6 var(--sans);
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); }
.cm-legend li { display: flex; align-items: center; gap: .4rem; }
.cm-sw { width: 15px; height: 15px; flex: none; }
.cm-sw { stroke: var(--ink-3); fill: var(--ink-3); }
.cm-cap { margin: .5rem 0 0; font: 400 .75rem/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: .45rem .6rem;
  font: 400 .8125rem/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: .3rem; color: var(--ink-3);
  font-size: .6875rem; 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: .6rem; overflow-x: auto; }
.ct-table caption { text-align: left; padding-bottom: .35rem;
  font: 500 .6875rem/1.4 var(--sans); letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-3); }
.ct-table td a { color: var(--link); }
.ct-tabtoggle { appearance: none; background: none; border: 0; padding: .4rem 0;
  cursor: pointer; font: 500 .6875rem/1 var(--sans); letter-spacing: .08em;
  text-transform: uppercase; color: var(--link);
  text-decoration: underline; text-underline-offset: .2em; }
.ct-tabtoggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.ct-empty-note { margin: .4rem 0 0; font: italic 400 .8125rem/1.4 var(--sans);
  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: .4rem 0 .7rem; max-inline-size: 68ch;
  font: 400 .8125rem/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: .1rem .8rem; align-items: baseline;
  padding: .45rem 0; border-top: 1px solid var(--rule); }
.dg-row:first-child { border-top: 0; }
.dg-when { font: 600 .6875rem/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 1rem/1.35 var(--serif); color: var(--ink);
  text-decoration: none; }
.dg-head:hover { color: var(--link); text-decoration: underline;
  text-underline-offset: .15em; }
.dg-src { justify-self: end; white-space: nowrap;
  font: 500 .6875rem/1.6 var(--sans); letter-spacing: .04em; color: var(--ink-3); }
.dg-more { margin-inline-start: .35em; padding: 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: .1rem .25rem;
  cursor: pointer; font: 600 .6875rem/1.4 var(--sans); letter-spacing: .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: .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. */
.conditions { display: flex; flex-wrap: wrap; align-items: baseline;
  gap: .3rem 1.5rem; padding: .55rem 0;
  border-top: 1px solid var(--ink); border-bottom: 1px solid var(--rule);
  font: 400 .875rem/1.5 var(--sans); color: var(--ink-2); }
/* 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-item { white-space: nowrap; }
.cd-wx { white-space: normal; min-width: 0; flex: 1 1 18rem; }
.conditions { min-width: 0; }
.cd-item, .cd-q { overflow-wrap: anywhere; }
.cd-item b { color: var(--ink); font-weight: 700;
  font-variant-numeric: lining-nums tabular-nums; }
.cd-k { font-weight: 600; font-size: .6875rem; letter-spacing: .11em;
  text-transform: uppercase; color: var(--ink-3); margin-inline-end: .35em; }
.cd-alert { color: var(--danger) !important; }
.conditions a { color: var(--link); }
.cd-more { margin-inline-start: auto; font-weight: 600; font-size: .6875rem;
  letter-spacing: .08em; text-transform: uppercase; white-space: nowrap; }

/* ── 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 .5rem; max-inline-size: 62ch;
  font: italic 400 1rem/1.5 var(--serif); color: var(--ink-3); }
.colophon .desks { margin: 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: 1.5rem;
  line-height: 1.16;
  letter-spacing: -0.018em;
  font-variation-settings: "opsz" 40;
  margin-bottom: 0.5rem;
}
.item--second .body { font-size: 1.0625rem; 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: .1rem 0 .35rem; max-inline-size: 62ch;
  font: 400 .875rem/1.5 var(--sans); color: var(--ink-2); }
.dv-roll-k { padding-right: .75rem; padding-top: .18rem;
  font: 600 .625rem/1.5 var(--sans); letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3); }
.dv-mover { margin: 0; font: 400 .75rem/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: .625rem; gap: .25rem .8rem; }
.locator--locator .cm-list li { padding: .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 .3rem; font: 700 .625rem/1.3 var(--sans);
  letter-spacing: .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: .5rem 0 .6rem; padding: .5rem .7rem;
  border-inline-start: 3px solid var(--warn); background: var(--surface);
  font: 400 .875rem/1.5 var(--sans); color: var(--ink-2); }
.defect-k { display: block; margin-bottom: .1rem;
  font: 700 .625rem/1.4 var(--sans); letter-spacing: .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: .9rem 1.1rem;
  border-top: 2px solid var(--ink); border-bottom: 1px solid var(--rule);
  background: var(--surface); }
.quiet-h { margin: 0 0 .25rem; font: 600 1.25rem/1.25 var(--serif); color: var(--ink); }
.quiet-b { margin: 0; max-inline-size: 66ch;
  font: 400 .9375rem/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: .9rem; border-top: 2px solid var(--ink); }
.ev-legend { display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem 1rem;
  margin: 0 0 .6rem; padding-bottom: .5rem; border-bottom: 1px solid var(--rule); }
.ev-leg { appearance: none; background: none; border: 0; padding: .1rem 0; cursor: pointer;
  font: 600 .75rem/1.4 var(--sans); letter-spacing: .04em; color: var(--ink);
  border-bottom: 2px solid transparent; }
.ev-leg i { font-style: normal; margin-inline-end: .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: .35em; font-size: .625rem; 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); }

.ev-day { display: flex; align-items: center; gap: .6rem; margin: .9rem 0 .15rem;
  font: 700 .625rem/1 var(--sans); letter-spacing: .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: .1rem .6rem; align-items: baseline; padding: .4rem 0;
  border-top: 1px solid var(--rule-soft); }
.ev-row:first-child { border-top: 0; }
.ev-t { font: 700 .75rem/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: .04em; }
.ev-mark { color: var(--cat); font-size: .75rem; line-height: 1.5; }
.ev-what { font: 400 1rem/1.35 var(--serif); color: var(--ink); text-decoration: none; }
.ev-what:hover { color: var(--link); text-decoration: underline;
  text-underline-offset: .15em; }
.ev-where { grid-column: 3; font: 400 .75rem/1.4 var(--sans); color: var(--ink-3); }
.ev-cost { justify-self: end; font: 600 .75rem/1.5 var(--sans); color: var(--ink-2);
  white-space: nowrap; }
.ev-note { margin: .8rem 0 0; max-inline-size: 74ch;
  font: 400 .75rem/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: .1rem 0 0;
  font: 400 .8125rem/1.4 var(--sans);
  font-variant-numeric: lining-nums tabular-nums; }
.wx-table th { text-align: left; padding: .2rem .9rem .2rem 0; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase; font-size: .6875rem;
  color: var(--ink-3); }
.wx-table td { padding: .2rem 0; color: var(--ink); font-weight: 600; }
.wx-warn { margin: 0 0 .4rem; font: 600 .875rem/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 .3rem; font: 700 .625rem/1 var(--sans);
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink); }
.ah-day { display: flex; align-items: center; gap: .55rem; margin: .7rem 0 .1rem;
  font: 700 .5875rem/1 var(--sans); letter-spacing: .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: .1rem .6rem; padding: .4rem 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: .5rem;
  grid-template-columns: 3.9rem minmax(0, 1fr); }
.ah-t { font: 700 .75rem/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: .6875rem; color: var(--ink-3);
  letter-spacing: .04em; }
.ah-what { font: 600 .875rem/1.35 var(--serif); color: var(--ink); text-decoration: none; }
.ah-what:hover { color: var(--link); text-decoration: underline; text-underline-offset: .15em; }
.ah-where { grid-column: 2; font: 400 .6875rem/1.4 var(--sans); color: var(--ink-3); }
.ah-tags { grid-column: 2; display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .25rem; }
.ah-tag { padding: .05rem .34rem; font: 600 .5625rem/1.5 var(--sans);
  letter-spacing: .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: .5rem; }
.ct-out .ct-k { display: block; margin-bottom: .1rem;
  font: 600 .625rem/1.4 var(--sans); letter-spacing: .13em;
  text-transform: uppercase; color: var(--ink-3); }
.ct-out .ct-v { display: block; font: 400 .875rem/1.5 var(--sans); color: var(--ink-2); }
.ct-out .ct-hd { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem .8rem;
  padding-bottom: .35rem; border-bottom: 1px solid var(--rule); }
.ct-out .ct-time { font: 700 .9375rem/1.4 var(--sans); color: var(--ink); }
.ct-out .ct-src { margin-inline-start: .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: .15rem;
  font-weight: 700; color: var(--ink); }
.ct-e .ct-tags, .ct-v .ct-tags { display: flex; flex-wrap: wrap; gap: .3rem;
  margin: .15rem 0; }
.ct-e .ct-tag, .ct-v .ct-tag { padding: .04rem .32rem;
  font: 600 .5625rem/1.5 var(--sans); letter-spacing: .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: .1rem;
  font: 400 .75rem/1.45 var(--sans); color: var(--ink-3); }
.ct-e .ct-src, .ct-v .ct-src { display: inline-block; margin-top: .25rem;
  font: 500 .6875rem/1.4 var(--sans); color: var(--link); }
.ct-e { padding: .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: .15rem 0 .45rem; padding-inline-start: .7rem;
  border-inline-start: 2px solid var(--rule-strong);
  font: italic 400 .8125rem/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: .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 {
    float: left;
    font-family: var(--serif);
    font-weight: 600;
    font-size: 3.6em;
    line-height: .82;
    padding: .04em .1em 0 0;
    margin-top: .04em;
    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 .5rem;
  font-size: .75rem;
  line-height: 1;
  color: var(--rule-strong);
}

/* Edition furniture: the numbers a paper carries because a paper carries them. */
.folio-line {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: .3rem 1rem;
  margin: 0; padding: .35rem 0 0;
  font: 500 .625rem/1.5 var(--sans); letter-spacing: .13em;
  text-transform: uppercase; color: var(--ink-3);
}
.folio-line b { color: var(--ink-2); font-weight: 700; }

/* 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: .2rem; }
.pm-table td { vertical-align: top; padding-block: .3rem; }
.pm-what { font-family: var(--serif); font-size: .875rem; line-height: 1.35;
  color: var(--ink); }
.pm-where { font-size: .75rem; color: var(--ink-2); white-space: nowrap; }
.pm-addr { display: block; font-size: .6875rem; 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: .2rem; }
.pm-form summary { list-style: none; cursor: pointer;
  font: 500 .625rem/1.5 var(--sans); letter-spacing: .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: .2rem 0 0; font: 400 .75rem/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 .3rem; font-family: var(--serif); font-weight: 600;
  font-size: 1.5rem; line-height: 1.15; color: var(--ink); }
.signup-lede { margin: 0 0 .8rem; font-family: var(--serif); font-size: 1rem;
  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: .6rem .7rem; background: transparent; color: var(--ink);
  font-family: var(--serif); font-size: 1rem;
}
.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: .6rem 1.1rem; background: var(--ink); color: var(--ground);
  font-family: var(--sans); font-weight: 700; font-size: .6875rem;
  letter-spacing: .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: .9rem; font-size: .6875rem; 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: .9rem 1rem 1rem;
}
.sponsor-kicker { margin: 0 0 .5rem; text-align: center;
  font: 700 .5625rem/1 var(--sans); letter-spacing: .3em; text-transform: uppercase;
  color: var(--ink-3); }
.sponsor .name { font-family: var(--serif); font-weight: 700; font-size: .75rem;
  letter-spacing: .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: .1rem 0 .3rem; }
.sponsor-text { font-family: var(--serif); font-size: .875rem; 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: .3rem; padding: .25rem .6rem;
  border: 1px solid var(--ink); color: var(--ink); text-decoration: none;
  font: 700 .625rem/1.4 var(--sans); letter-spacing: .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: .2rem 0 0; padding: 0; list-style: none; }
.pm-row { padding: .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: .9375rem;
  line-height: 1.4; color: var(--ink); }
.pm-in { color: var(--ink-2); }
.pm-val { margin-inline-start: .5em; font-family: var(--sans); font-weight: 700;
  font-size: .8125rem; color: var(--ink-2); white-space: nowrap;
  font-variant-numeric: lining-nums tabular-nums; }
.pm-addr { margin: .05rem 0 0; font: 400 .6875rem/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: .9rem 0 0; }
.data-h {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: .15rem 1rem;
  margin: 0 0 .35rem; padding-bottom: .25rem;
  border-bottom: 1px solid var(--ink-3);
  font: 600 .625rem/1.4 var(--sans); letter-spacing: .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: .55rem; margin: 0 0 .55rem; }
.sponsor-logo { width: 56px; height: 56px; object-fit: contain; flex: none; }
.sponsor-offer { margin: 0 0 .7rem; font: 700 .6875rem/1.3 var(--sans);
  letter-spacing: .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: .35rem 0 .55rem; font: 400 .8125rem/1.5 var(--sans);
  color: var(--ink-2); max-inline-size: 62ch; }
.pat-k { display: inline-block; min-width: 3.4rem;
  font: 600 .625rem/1.5 var(--sans); letter-spacing: .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: .3rem 1rem;
    padding-block: .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: .2rem .8rem; padding-block: .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: .55rem 0 .4rem; }
  .dateline { gap: .15rem .5rem; padding-bottom: .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: .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: .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: .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 .15rem; font: 700 1.375rem/1.2 var(--serif); color: var(--ink); }
.vt-date { font: 400 .8125rem/1 var(--sans); color: var(--ink-3);
  letter-spacing: .06em; margin-inline-start: .5rem; }
.vt-src { margin: 0 0 .7rem; font: 400 .8125rem/1.5 var(--sans); }
.vt-src a { color: var(--link); }
.vt-lede { margin: 0 0 .9rem; font: 400 .9375rem/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: .7rem;
  color: var(--ink-3); }
.vt-split .dv { margin-bottom: 1.15rem; }
.vt-rest { margin: .4rem 0 .2rem; }
.vt-rest summary { cursor: pointer; font: 600 .8125rem/1.5 var(--sans);
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); }
.vt-rest summary:hover, .vt-rest summary:focus-visible { color: var(--ink-2); }
.vt-list { margin: .6rem 0 0; padding: 0; list-style: none;
  max-inline-size: 68ch; }
.vt-list li { margin: 0 0 .3rem; padding-inline-start: 3.6rem; text-indent: -3.6rem;
  font: 400 .875rem/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 .8125rem/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 .625rem/1.5 var(--sans); letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-3);
  margin-inline-start: .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 .8125rem/1.5 var(--sans); font-variant-numeric: tabular-nums;
  color: var(--ink-3); vertical-align: .18em; }
.vt-split .dv-what { font-size: 1.0625rem; }
/* An index, so a reader landing mid-page can get to a meeting directly. */
.vt-jump { margin: .2rem 0 1.6rem; font: 400 .8125rem/1.7 var(--sans);
  color: var(--ink-3); }
.vt-jump a { color: var(--link); margin-inline-end: .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;
  background-image:
    radial-gradient(120% 60% at 50% -8%, rgba(255,226,168,.075), transparent 62%),
    radial-gradient(100% 70% at 50% 108%, rgba(0,0,0,.28), transparent 60%);
  background-attachment: fixed, fixed;
}

/* 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. */
.sec h2::before, .lab-a::before {
  content: "";
  display: inline-block; vertical-align: .18em;
  width: 6px; height: 6px; margin-inline-end: .55rem;
  background: var(--lane-council); transform: rotate(45deg);
  opacity: .9;
}
.kicker::after {
  content: ""; display: inline-block;
  width: 1.6rem; height: 1px; margin-inline-start: .5rem;
  vertical-align: .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: 1.0625rem; line-height: 1.62; column-count: 2; column-gap: 1.6rem;
}
.pack .item--lead .body::first-line { font-variant-caps: small-caps; letter-spacing: .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 front package. 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. */
.pack > section:not(.frontpkg) .item:nth-of-type(3n+1) h3 {
  font-size: 1.3125rem; line-height: 1.14; }
.pack > section:not(.frontpkg) .item:nth-of-type(3n+1) {
  border-top: 2px solid var(--rule-strong); padding-top: .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: .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 .75rem/1.5 var(--sans); color: var(--ink-3);
  letter-spacing: .04em; margin: .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: .45rem; vertical-align: .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: .4rem; vertical-align: .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: .55rem auto .35rem; width: min(320px, 46%);
  height: auto; color: var(--ink-3); }
@media (max-width: 47.99rem) { .orn { width: 62%; margin: .4rem auto .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 .55rem; font-family: var(--serif); font-weight: 500;
  font-size: 1.5rem; 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 .6875rem/1.5 var(--sans); letter-spacing: .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: .9rem 0 1rem;
  border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  display: flex; align-items: center; gap: .9rem;
}
.cut-art { width: 68px; height: 56px; flex: none; color: var(--ink-2); }
.cut-cap {
  font: 600 .6875rem/1.4 var(--sans); letter-spacing: .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: .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, rgba(255,226,168,.028), 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: .4rem 0 1rem; padding-inline-start: 1.1rem; max-inline-size: 68ch; }
.gaps li { margin: 0 0 .5rem; font-family: var(--serif); font-size: 1rem;
  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: .9rem; padding-top: .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 .9375rem/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: .2rem 0 1rem; max-inline-size: 68ch;
  font: 400 .9375rem/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: .8rem;
  border-top: 1px solid var(--rule); max-inline-size: 68ch; }
.cap-name { font: 600 1.0625rem/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: .35rem; margin-inline-start: .5rem;
  vertical-align: .12em; flex-wrap: wrap; }
/* Never colour alone: each light names its own state in words. */
.cap-light { font: 600 .625rem/1.5 var(--sans); letter-spacing: .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: .25rem 0 0; font: 400 .8125rem/1.5 var(--sans);
  color: var(--ink-3); font-variant-numeric: tabular-nums; }
.cap-why { margin: .35rem 0 0; font: 400 .9375rem/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: .6rem 0 0; font: 400 .8125rem/1.5 var(--sans);
  color: var(--ink-3); }
.ah-more a { color: var(--link); }
