/* ============================================================
   DRIVING CANADA — SITE STYLES
   Layout grammar: flat poster-style solid-colour blocks, hard
   edges, generous margins. No gradients, no glows, no shadows,
   no noir atmospherics.

   Depends on tokens.css for all colour/type/space values.
   ============================================================ */

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

html {
    background: var(--shell);
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    background: var(--shell);
    color: var(--ink);
    font-family: var(--font-sans);
    font-size: var(--fs-body);
    font-weight: var(--fw-regular);
    line-height: 1.6;
    /* Inter's optical size axis: spacing tightens automatically
       above 32px, so display and body come from one file. */
    font-optical-sizing: auto;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

img {
    max-width: 100%;
    display: block;
}

h1, h2, h3, h4 {
    font-family: var(--font-sans);
    /* §3 type table: H1 and H2 are 600, not 700. */
    font-weight: var(--fw-semibold);
    letter-spacing: var(--ls-tight);
    line-height: 1.05;
    margin: 0;
}

p { margin: 0; }

a { color: inherit; }

/* Visible focus on every interactive element, on any background. */
a:focus-visible,
button:focus-visible,
video:focus-visible,
[tabindex]:focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-offset);
}

.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 100;
    background: var(--accent);
    color: var(--shell);
    padding: 0.75rem 1.25rem;
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-bold);
    text-decoration: none;
}
.skip-link:focus {
    left: var(--gutter);
    top: 0.5rem;
}

.sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ---- Shared shell ---------------------------------------- */

.wrap {
    width: 100%;
    max-width: 78rem;
    margin: 0 auto;
    padding-inline: var(--gutter);
    min-width: 0; /* let scroll containers shrink instead of stretching */
}

.label {
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-bold);
    line-height: 1.3;
}

.prose {
    max-width: var(--measure);
    font-size: var(--fs-read);
    line-height: 1.65;
    color: var(--ink-muted);
}
.prose > p + p { margin-top: 1.25rem; }

/* Inline highlighted words — solid accent block, hard edges. */
.hl {
    background: var(--accent);
    color: var(--shell);
    padding: 0.05em 0.28em;
    font-weight: var(--fw-medium);
}

.block-gap { margin-top: var(--block-gap); }

/* ---- Header -----------------------------------------------
   Per Haroon's mock: wordmark left, primary nav centre, controls
   right. The mock omits the language toggle, the view toggle and
   the partner marks; all three are contract terms. The view toggle
   stays in the header (DC-6), the partner marks move to the footer
   with the funder acknowledgement, and the language toggle returns
   with the French copy. Placement, not omission (spec §8). */

.site-header {
    display: flex;
    align-items: center;
    gap: clamp(0.75rem, 2vw, 1.5rem);
    padding: 1.25rem var(--gutter);
    border-bottom: 1px solid var(--rule);
    flex-wrap: wrap;
}

/* The lockup. Haroon's supplied mark: a route above stacked type,
   DRIVING in --ink and CANADA in --accent. Inline SVG, so it inherits
   the tokens rather than baking colours into an asset. */
.wordmark {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
    margin-right: auto;
}
/* Wider tracking on the lockup's type, per the mock. The supplied PNG sets
   DRIVING and CANADA considerably looser than the default, and at header
   size the difference is what makes it read as an identity rather than as
   a word. Scoped to the header's instance so the standalone lockup and the
   footer keep their own measured spacing. */
.wordmark .logo-word {
    letter-spacing: 0.26em;
}
.wordmark .logo {
    width: clamp(148px, 15vw, 190px);
}

.logo {
    display: block;
    width: clamp(132px, 13vw, 168px);
}
.logo-svg {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
}

/* Route: the same four parts as the route-line primitive in §2.
   The group carries what they share; each part overrides its own
   colour. Found unstyled by the audit — harmless, but a group element
   with no rule is a group element nobody can style later. */
.logo-route {
    fill: none;
    stroke-linecap: butt;
}
/* Colour, taken from the supplied PNG rather than assumed. The ring and
   the TRAVELLED segment are white; the DASHED remainder and the pin are
   lime. The previous version had this inverted. It reads correctly as
   well as matching: the lime is the distance still to travel, which is
   the right note for an exhibition about journeys in progress. */
.logo-origin  { fill: none; stroke: var(--ink); }
.logo-travelled { stroke: var(--ink); fill: none; }
.logo-remaining { stroke: var(--accent); fill: none; }
.logo-pin { fill: var(--accent); }

/* Type. SVG text does not inherit sizing from the cascade reliably at
   arbitrary viewBox scales, so the metrics are set explicitly here and
   only the colour comes from the tokens. */
/* Type. SVG text does not inherit sizing from the cascade reliably at
   arbitrary viewBox scales, so the metrics are set explicitly here and
   only the colour comes from the tokens.

   Measured from the PNG: both words run 962/1672 of the frame, which is
   a 168-unit lockup with the type spanning ~129 units — the same right
   edge the route above it terminates at. The supplied mark is a LIGHT
   weight, widely letterspaced, not the semibold this previously used.
   textLength on the elements themselves would be more exact but renders
   inconsistently across browsers, so the fit is by weight and tracking. */
.logo-word {
    font-family: var(--font-sans);
    font-size: 22px;
    font-weight: 300;
    letter-spacing: 0.2em;
}
.logo-word--ink { fill: var(--ink); }
.logo-word--accent { fill: var(--accent); }

/* The default, on the shell. Stated explicitly rather than left as
   "whatever .logo--light does not override", so the two variants are
   symmetrical and either can change without the other breaking. */
.logo--dark .logo-origin { stroke: var(--ink); }
.logo--dark .logo-travelled { stroke: var(--ink); }
.logo--dark .logo-remaining { stroke: var(--accent); }
.logo--dark .logo-pin { fill: var(--accent); }
.logo--dark .logo-word--ink { fill: var(--ink); }
.logo--dark .logo-word--accent { fill: var(--accent); }

/* Light-surface variant. The lime rule holds: --accent is 1.73:1 on
   --surface-light, so on a light ground CANADA takes --shell and the
   lime does not appear at all — including in the dashed segment and the
   pin, which would be just as unreadable there as the type. */
.logo--light .logo-origin { stroke: var(--shell); }
.logo--light .logo-travelled { stroke: var(--shell); }
.logo--light .logo-remaining { stroke: var(--rule-strong); }
.logo--light .logo-pin { fill: var(--shell); }
.logo--light .logo-word--ink { fill: var(--shell); }
.logo--light .logo-word--accent { fill: var(--shell); }

/* Hover and focus activate the dashed segment, per §2. The dashes are
   already lime at rest now that the mark matches the supplied PNG, so
   activation is the TRAVELLED segment reaching toward them rather than a
   colour change that would have nowhere to go. Value, not hue. */
.wordmark .logo-travelled,
.wordmark .logo-pin {
    transition: opacity var(--t-quick) var(--ease);
}
.wordmark:hover .logo-pin,
.wordmark:focus-visible .logo-pin {
    opacity: 0.75;
}

/* Primary nav, per the mock. */
.site-nav {
    display: flex;
    align-items: center;
    gap: clamp(1rem, 2.5vw, 2rem);
}
.site-nav-link {
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-medium);
    color: var(--ink);
    text-decoration: none;
    white-space: nowrap;
    /* 44px minimum touch target (spec §4), achieved with height rather
       than padding so the row stays visually tight. */
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    border-bottom: 2px solid transparent;
    transition: color var(--t-quick) var(--ease),
                border-color var(--t-quick) var(--ease);
}
.site-nav-link:hover,
.site-nav-link:focus-visible { color: var(--accent); border-bottom-color: var(--accent); }
/* Lime means live — and never colour alone: the current item also
   carries an underline. */
.site-nav-link.is-current { color: var(--accent); border-bottom-color: var(--accent); }

.site-header-controls {
    display: flex;
    align-items: center;
    gap: clamp(0.5rem, 1.5vw, 1rem);
    /* On a phone the controls do not fit beside the wordmark, and the
       view toggle does not fit beside the menu button, so the row breaks
       twice: wordmark / call to action + menu / view toggle. Each row
       keeps to the right edge, so the header reads as one block rather
       than as three ragged starts. The DOM order is the visual order in
       every case, so the focus order never crosses a row. */
    flex-wrap: wrap;
    justify-content: flex-end;
    margin-left: auto;
}

/* The mock's TAKE A RIDE call to action: a filled lime block with shell
   text, which is the one direction the lime rule permits. */
.btn-ride {
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-semibold);
    background: var(--accent);
    color: var(--shell);
    text-decoration: none;
    white-space: nowrap;
    padding: 0 1.1rem;
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    transition: background-color var(--t-quick) var(--ease),
                color var(--t-quick) var(--ease);
}
/* Inverting to an outline on hover keeps the contrast pair legal in
   both states: shell-on-lime and lime-on-shell are both 10.50:1. */
.btn-ride:hover,
.btn-ride:focus-visible { background: transparent; color: var(--accent); }

/* The HEADER's call to action is outlined, per the mock: lime border,
   lime text, transparent fill. The HERO's stays solid.

   Two weights of the same action, deliberately: the solid one in the hero
   is the primary invitation, and the header's is a persistent way back to
   it. If both were solid the header would compete with the page.

   Hover inverts to the solid treatment, so the two states are the two
   variants -- the interaction says "this is the same button". */
.btn-ride--outline {
    background: transparent;
    color: var(--accent);
}
.btn-ride--outline:hover,
.btn-ride--outline:focus-visible {
    background: var(--accent);
    color: var(--shell);
}

/* The compact header, used by the driver experience. That page has its
   own auto-hiding topbar, so the site header keeps only the wordmark
   and the two contract controls. */
.site-header--compact .site-nav { display: none; }

.menu-btn {
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 44px;
    height: 44px;
    padding: 0 8px;
    background: none;
    border: 0;
    cursor: pointer;
    flex: none;
}
.menu-btn span {
    display: block;
    height: 2px;
    background: var(--ink);
    width: 100%;
    transition: background-color var(--t-quick) var(--ease);
}
.menu-btn:hover span,
.menu-btn:focus-visible span { background: var(--accent); }

/* The nav collapses into the menu panel on small screens; the panel
   already lists everything the nav does. */
@media (max-width: 900px) {
    .site-nav { display: none; }
}
@media (min-width: 901px) {
    .menu-btn { display: none; }
}

/* ---- Immersive / Simplified view toggle (DC-6) -------------
   A DMC commitment: "a visible toggle sits in the site header,
   persistent across every page ... with plain labels (Immersive view,
   Simplified view, or similar)." Removed in September 2026 on a bet
   that had already been lost; restored. Do not remove it again (§6).

   The same shape as the language toggle it sits beside when French
   lands: a labelled group of two options, the current one filled with
   the accent and marked aria-current, the other a real link to this
   page at its /lv/ URL (or back). Lime means live, and never colour
   alone -- the fill AND aria-current carry the state.

   The caption is the word VIEW, so the reader sees VIEW: IMMERSIVE |
   SIMPLIFIED. The two-word labels do not fit a 390px column (351px in
   this style against a 350px column), and a media query may not hide a
   word, so the caption carries it at every width. */
.view-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    /* Never split the caption from its options when the header wraps. */
    flex: none;
}
.view-toggle-caption {
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-medium);
    /* --ink-dim is 5.87:1 on the shell: a caption, legible, quieter
       than the options it names. */
    color: var(--ink-dim);
    white-space: nowrap;
}
.view-toggle-options {
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--rule-strong);
    border-radius: var(--radius);
}
.view-toggle .view-link,
.view-toggle .view-current {
    font-family: var(--font-sans);
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-medium);
    background: none;
    border: 0;
    color: var(--ink);
    padding: 0.5rem 0.9rem;
    text-decoration: none;
    white-space: nowrap;
    /* 44px minimum touch target, per §4 and §8. */
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    transition: background-color var(--t-quick) var(--ease),
                color var(--t-quick) var(--ease);
}
.view-toggle .view-link { cursor: pointer; }
/* Both legal directions of the lime pair: shell-on-lime is 10.50:1. */
.view-toggle .view-link:hover,
.view-toggle .view-link:focus-visible { background: var(--accent); color: var(--shell); }
.view-toggle .view-current { background: var(--accent); color: var(--shell); }

/* ---- Menu panel ------------------------------------------- */

.menu-panel {
    position: fixed;
    inset: 0;
    z-index: 90;
    background: var(--shell);
    padding: 1.25rem var(--gutter) 3rem;
    overflow-y: auto;
}
.menu-panel[hidden] { display: none; }

.menu-panel-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding-bottom: 1.25rem;
    border-bottom: 1px solid var(--rule);
}

.menu-close {
    width: 44px;
    height: 44px;
    background: none;
    border: 1px solid var(--rule-strong);
    border-radius: var(--radius);
    color: var(--ink);
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
}
.menu-close:hover { background: var(--accent); color: var(--shell); border-color: var(--accent); }

/* Language toggle. Real links to the /fr/ routes now, not the decorative
   aria-pressed flipper it used to be. The current locale renders as a
   non-interactive span; a locale with no copy yet renders as a genuinely
   disabled button with an accessible explanation beside it, so the
   control never lands someone on English dressed as French. */
/* Wraps the toggle and its explanation note. Found unstyled by the
   markup-vs-CSS audit: without it the note joins the header's flex row
   instead of sitting beneath the control it describes. */
.lang-group {
    display: inline-flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
}

.lang-toggle {
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--rule-strong);
}
.lang-toggle .lang-link,
.lang-toggle .lang-current {
    font-family: var(--font-sans);
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-medium);
    background: none;
    border: 0;
    color: var(--ink);
    padding: 0.5rem 0.9rem;
    text-decoration: none;
    /* 44px minimum touch target. */
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    transition: background-color var(--t-quick) var(--ease),
                color var(--t-quick) var(--ease);
}
.lang-toggle .lang-link { cursor: pointer; }
.lang-toggle .lang-link:hover:not([disabled]) { background: var(--accent); color: var(--shell); }
.lang-toggle .lang-current { background: var(--accent); color: var(--shell); }

/* Disabled, not hidden: the bilingual commitment is a contract term, so a
   visible "coming soon" is honest where a vanished control would imply
   the exhibition is English-only. --ink-dim is 5.87:1, so it stays
   legible rather than relying on opacity. */
.lang-toggle .lang-link[disabled] {
    color: var(--ink-dim);
    cursor: not-allowed;
}

.lang-note {
    display: block;
    margin-top: 0.5rem;
    font-size: var(--fs-label);
    color: var(--ink-dim);
    text-transform: none;
    letter-spacing: normal;
}

.menu-nav {
    margin-top: 2.5rem;
    display: grid;
    gap: 2.5rem;
}
@media (min-width: 60rem) {
    .menu-nav { grid-template-columns: 1fr 1fr; gap: 3rem; }
}

.menu-link {
    display: block;
    font-size: clamp(1.75rem, 4vw, 2.75rem);
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-tight);
    text-transform: uppercase;
    text-decoration: none;
    padding: 0.35rem 0;
}
.menu-link:hover,
.menu-link.is-current { color: var(--accent); }

.menu-section-label {
    color: var(--ink-dim);
    margin-bottom: 1rem;
    display: block;
}

.menu-stories {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 0.35rem;
}
.menu-stories a {
    display: block;
    text-decoration: none;
    font-size: var(--fs-read);
    padding: 0.4rem 0;
    color: var(--ink-muted);
}
.menu-stories a:hover { color: var(--accent); }

/* ---- Intro film band --------------------------------------
   First element below the header in the mock: full width,
   bordered, with a centred circular play control. It sits above
   the hero rather than after it. */

.film-band {
    padding: 0 var(--gutter);
    margin-top: clamp(1.5rem, 3vw, 2.5rem);
}

.film {
    position: relative;
    background: var(--shell);
    /* Lime border, per the mock. The band is the page's one piece of
       film and the border is what frames it as such. */
    border: 1px solid var(--accent);
    aspect-ratio: 16 / 9;
    width: 100%;
}
.film video {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: cover;
    background: #000;
}

/* Designed poster frame — wordmark over black, shown until a
   film file exists. Sits behind the <video>, which is
   transparent until it has a source with data. */
/* THE ROUND-6 POSTER WAS NEVER BROKEN. It has been loading correctly all
   along and being covered up.

   This overlay sits `inset: 0` over the <video>, and it had
   `background: #000` — an opaque black sheet on top of the 7KB
   windshield-at-night artwork the video's own `poster` attribute was
   already painting. The band rendered as pure black because this div was
   painting it black, not because the poster failed.

   The fix is to stop painting: the overlay now carries the poster image
   itself, so the artwork and the play control are the same layer instead
   of fighting for it. A scrim keeps the control legible over the image
   without hiding it — flat, no gradient. */
.film-poster {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    background-color: var(--shell);
    /* The image URL is set INLINE by the template, not here.
       A stylesheet cannot be templated, so a url() written into this file
       would point at an unfingerprinted path — and images are served
       `immutable`, so that URL would cache-pin for a year and any edit to
       the artwork would be invisible to every returning visitor. That is
       exactly the bug that hid a corrected poster for six rounds.
       Writing a fingerprinted path in here instead would be circular:
       changing the image changes this file, which changes this file's own
       hash. So the declaration lives on the element. */
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    text-align: center;
    padding: 1.5rem;
}
/* No scrim and no text backing. Both existed to keep the control legible
   over the poster artwork -- the accent measured 8.25:1 against its
   lightest plane, and the lime road centre line ran directly behind the
   runtime text. With the band back to plain shell there is nothing to
   separate from: the accent is 10.50:1 on --shell, and a backing box
   would be a box on black. */
.film-poster > * { position: relative; }
.film-poster-word {
    font-size: clamp(1.75rem, 5vw, 3.5rem);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: var(--ls-tight);
    line-height: 1;
}
.film-poster-word .wm-hollow {
    color: transparent;
    -webkit-text-stroke: 1.5px var(--ink);
}
.film-poster-note {
    color: var(--ink-dim);
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
}

/* The centred circular play control from the mock. A real button, so
   it is focusable and keyboard-operable; it hands off to the <video>
   element's own controls once playback starts. */
.film-play {
    width: clamp(56px, 8vw, 72px);
    height: clamp(56px, 8vw, 72px);
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
    /* Lime, per the mock. It was --ink, which read as a system control
       rather than as the page's invitation. */
    color: var(--accent);
    transition: color var(--t-quick) var(--ease);
}
.film-play svg { width: 100%; height: 100%; display: block; }
.film-play-ring {
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
}
.film-play-tri { fill: currentColor; }
.film-play:hover,
.film-play:focus-visible { color: var(--ink); }

/* "WATCH THE INTRODUCTION" in lime, per the mock. 10.50:1 on the shell,
   and the scrim behind it keeps that ratio over the artwork. */
.film-play-label {
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-medium);
    color: var(--accent);
}
.film-play-runtime {
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--ink-dim);
}

.film-cc {
    position: absolute;
    right: 0.75rem;
    bottom: 0.75rem;
    z-index: 2;
    font-family: var(--font-sans);
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    background: var(--shell);
    color: var(--ink);
    border: 1px solid var(--rule-strong);
    border-radius: var(--radius);
    /* 44x44 minimum, per §8. It was 49x31. The padding does the work so
       the label keeps its size and only the target grows. */
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 0.7rem;
    cursor: pointer;
}
.film-cc[aria-pressed="true"],
.film-cc:hover { background: var(--accent); color: var(--shell); border-color: var(--accent); }

/* ---- Hero -------------------------------------------------
   Two columns per the mock: heading, watch-introduction link,
   intro copy and TAKE A RIDE on the left; the GTA map on the
   right. Copy is short by request, and nothing here is
   fixed-height, so another paragraph drops into .hero-intro
   without any block resizing. */

.hero { padding-top: clamp(2.5rem, 5vw, 4rem); }

.hero-grid {
    display: grid;
    gap: clamp(2rem, 5vw, 4rem);
    /* stretch, not center: the map column matches the text column's
       height rather than floating in the middle of it. */
    align-items: stretch;
}
/* Collapses at the same breakpoint as the nav, so the header and the
   hero change shape together rather than at two different widths. */
@media (min-width: 901px) {
    .hero-grid {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    }
}

.hero-col-text {
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
}
.hero-col-map {
    min-width: 0;
    display: flex;
}

/* Two lines, one heading element. The break is presentational — both
   spans are block-level inside a single <h1> — so the accessible name
   is the continuous title and the heading outline stays h1 → h2 → h3. */
.hero-title {
    font-size: var(--fs-h1);
    font-weight: var(--fw-semibold);
    line-height: 1.02;
    letter-spacing: var(--ls-tight);
    margin: 0 0 1.5rem;
}
.hero-title-line { display: block; }
.hero-title-accent { color: var(--accent); }

/* Watch-introduction affordance, anchored to the film above. */
/* Lime in the mock, not grey. It is the hero's one link out to the film
   and it was reading as body copy. 10.50:1 on the shell. */
.watch-intro {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    min-height: 44px;
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-medium);
    color: var(--accent);
    text-decoration: none;
    transition: color var(--t-quick) var(--ease);
}
/* Already lime at rest, so hover moves to --ink rather than nowhere. */
.watch-intro:hover,
.watch-intro:focus-visible { color: var(--ink); }
.watch-intro-icon svg {
    width: 20px;
    height: 20px;
    display: block;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
}
.watch-intro-icon path { fill: currentColor; stroke: none; }

.hero-intro { margin-top: 1.5rem; }
.hero-intro > * + * { margin-top: 1.1rem; }

/* The take-a-ride line sits a step above body copy without becoming a
   heading — it is the mock's one piece of emphasis in the hero. */
.hero-line {
    font-size: var(--fs-read);
    color: var(--ink-muted);
}

/* The hero's TAKE A RIDE. In addition to the header's, not instead of
   it — the mock has both. Solid accent with shell text, which is the
   one direction the lime rule permits. */
.btn-ride--hero {
    margin-top: clamp(1.75rem, 3vw, 2.5rem);
    gap: 0.75rem;
    padding: 0 1.4rem;
    min-height: 52px;
    font-size: var(--fs-body);
}
.btn-ride-arrow {
    width: 20px;
    height: 20px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    transition: transform var(--t-quick) var(--ease);
}
.btn-ride--hero:hover .btn-ride-arrow,
.btn-ride--hero:focus-visible .btn-ride-arrow { transform: translateX(4px); }

/* The GTA map's column, reserved at the proportions the designed SVG
   will occupy so it drops into a finished layout next round rather
   than triggering a hero rebuild. */
.map-placeholder {
    /* Anchors the right side at roughly the text column's height, as the
       mock does. The aspect ratio alone left it about half as tall and the
       hero read thin. The real map replaces this and inherits the height,
       so the layout does not move when it lands. */
    min-height: 100%;
    width: 100%;
    aspect-ratio: 4 / 3;
    border: 1px solid var(--rule-strong);
    background: var(--shell);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.25rem;
    padding: 1.5rem;
    text-align: center;
}
.map-placeholder-note {
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--ink-dim);
}

/* Retained: the film section still uses this pairing. */
.hero-body {
    margin-top: 2.5rem;
    display: grid;
    gap: 2rem;
}
@media (min-width: 60rem) {
    .hero-body {
        grid-template-columns: minmax(0, 1fr) 20rem;
        gap: 4rem;
        align-items: start;
    }
}

.film-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1.25rem 2rem;
    margin-top: 1.5rem;
}

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    font-family: var(--font-sans);
    font-size: var(--fs-body);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    text-decoration: none;
    border-radius: var(--radius);
    padding: 0.85rem 1.4rem;
    cursor: pointer;
    text-align: center;
}
/* Outlined button — equal visual weight to the player controls. */
.btn-outline {
    background: none;
    color: var(--ink);
    border: 2px solid var(--ink);
    width: 100%;
}
.btn-outline:hover { background: var(--accent); color: var(--shell); border-color: var(--accent); }

/* Solid accent fill. Black text on accent = 11.94:1. */
.btn-solid {
    background: var(--accent);
    color: var(--shell);
    border: 2px solid var(--accent);
}
.btn-solid:hover { background: var(--ink); border-color: var(--ink); }

.film-credit {
    font-size: var(--fs-meta);
    color: var(--ink-muted);
    line-height: 1.45;
}
.film-credit strong {
    display: block;
    color: var(--ink);
    font-weight: var(--fw-bold);
}

.transcript-panel {
    margin-top: 1.5rem;
    border: 1px solid var(--rule);
    padding: 1.5rem;
    max-width: var(--measure);
    color: var(--ink-muted);
    font-size: var(--fs-body);
}
.transcript-panel[hidden] { display: none; }

/* ---- Story tab strip -------------------------------------- */

.tab-strip {
    margin-top: 2.5rem;
    display: flex;
    gap: 0.5rem;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    padding-bottom: 0.5rem;
}
.tab {
    flex: none;
    font-size: var(--fs-body);
    font-weight: var(--fw-medium);
    text-decoration: none;
    color: var(--ink);
    background: none;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    padding: 0.85rem 1.15rem;
    white-space: nowrap;
}
.tab:hover { border-color: var(--accent); color: var(--accent); }
.tab.is-active {
    background: var(--accent);
    color: var(--shell);
    border-color: var(--accent);
    font-weight: var(--fw-bold);
}

/* ---- Full-bleed band -------------------------------------- */

.band {
    margin-top: var(--block-gap);
    background: var(--accent);
    min-height: clamp(14rem, 30vw, 26rem);
    display: flex;
    align-items: flex-end;
    padding: clamp(1.5rem, 4vw, 3.5rem);
}
.band-word {
    font-size: clamp(2.25rem, 9vw, 7rem);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: var(--ls-tight);
    line-height: 0.92;
    color: var(--shell);
}
/* Outlined half of the wordmark, solid half — UT's band treatment. */
.band-word .wm-hollow {
    display: block;
    color: transparent;
    -webkit-text-stroke: 2px var(--shell);
}

/* ---- Section headings ------------------------------------- */

.section { margin-top: var(--block-gap); }

.section-title {
    font-size: var(--fs-h2);
    margin-bottom: 1.75rem;
}

/* ---- Driver card grid ------------------------------------- */

.grid-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 2rem;
}
.grid-count { color: var(--ink-dim); }

/* ---- Driver rows ------------------------------------------
   Row layout per Haroon's mock and spec §7: portrait, name,
   credential line, star rating, pull-quote, chevron.

   Rest is --shell with a --rule hairline; hover and focus fill
   --accent with --shell text at --t-quick. That is the Untold
   Toronto poster move used as interaction language.

   The whole row is ONE link. No nested interactive elements —
   nothing inside is separately focusable, so the row is a single
   tab stop with a single accessible name. */

/* ============================================================
   DRIVER ROWS — rebuilt to Haroon's mock, September 2026

   Three columns with dividers, inside individually bordered boxes.
   Previously everything stacked in one column under the portrait.

   The whole row stays ONE LINK with no nested interactive elements,
   and the accessible name is unchanged: it carries the name, the
   credential, the rating and the media state, so everything inside
   is decorative or aria-hidden and nothing is announced twice.
   ============================================================ */

.driver-rows {
    display: flex;
    flex-direction: column;
    /* A GAP between rows rather than a shared hairline, per the mock:
       each row is its own box, so the list reads as a set of cards
       rather than as one table of people. */
    gap: 0.75rem;
}

.driver-row {
    display: grid;
    /* identity | divider | rating | divider | quote | chevron */
    grid-template-columns: minmax(0, 1.05fr) 1px auto 1px minmax(0, 1.2fr) auto;
    align-items: center;
    gap: clamp(1rem, 2.5vw, 2rem);
    padding: clamp(1.1rem, 2vw, 1.5rem) clamp(1rem, 2vw, 1.5rem);
    /* Its own box. --radius-row is one of the two documented exceptions
       to --radius: 0 (spec §3): at this size a hard corner reads as a
       table cell rather than as a card. */
    border: 1px solid var(--rule);
    border-radius: var(--radius-row);
    background: var(--shell);
    color: var(--ink);
    text-decoration: none;
    transition: background-color var(--t-quick) var(--ease),
                color var(--t-quick) var(--ease),
                border-color var(--t-quick) var(--ease);
}

/* Hover fills. Focus gets the same fill, so keyboard and pointer reach
   the same state — §4 requires every pointer interaction to be
   reachable by keyboard. */
.driver-row:hover,
.driver-row:focus-visible {
    background: var(--accent);
    color: var(--shell);
    border-color: var(--accent);
}

/* Everything inside inherits, so one fill flips the whole row. */
.driver-row:hover .driver-row-meta,
.driver-row:focus-visible .driver-row-meta,
.driver-row:hover .driver-row-quote,
.driver-row:focus-visible .driver-row-quote,
.driver-row:hover .driver-row-rating-figure,
.driver-row:focus-visible .driver-row-rating-figure,
.driver-row:hover .driver-row-rating-label,
.driver-row:focus-visible .driver-row-rating-label { color: var(--shell); }

/* The lime marks invert on the fill: a lime star or quote glyph on a
   lime ground would disappear entirely. */
.driver-row:hover .driver-row-star-mark,
.driver-row:focus-visible .driver-row-star-mark,
.driver-row:hover .driver-row-quote-glyph,
.driver-row:focus-visible .driver-row-quote-glyph { color: var(--shell); fill: var(--shell); }

/* The dividers have to survive the fill too. */
.driver-row:hover .driver-row-divider,
.driver-row:focus-visible .driver-row-divider { background: var(--shell); opacity: 0.45; }

/* On hover the row fills --accent, so the portrait ring takes --shell to
   stay legible against it — per §7. --rule would vanish into the lime. */
.driver-row:hover .driver-row-portrait,
.driver-row:focus-visible .driver-row-portrait { border-color: var(--shell); }

.driver-row:hover .driver-row-chevron,
.driver-row:focus-visible .driver-row-chevron { transform: translateX(4px); }

/* ---- Column 1: identity ------------------------------------- */
.driver-row-identity {
    display: flex;
    align-items: center;
    gap: clamp(0.9rem, 2vw, 1.4rem);
    min-width: 0;
}
.driver-row picture { display: block; flex: none; }

/* Portrait. CIRCULAR, per §7 Portraits — the one deliberate exception to
   --radius: 0 alongside the row corners.

   THE HAIRLINE RING IS REQUIRED and is not decoration. The five
   photographs were shot against different backgrounds — Kulbir, Mohd and
   Rammah against near-black, Yalgar and Mehakjot against a lighter grey.
   Without a ring three portraits dissolve into the shell and two read as
   visible discs, and in a vertical row of five that inconsistency is more
   conspicuous than any difference in crop. */
.driver-row-portrait {
    width: clamp(56px, 7vw, 76px);
    aspect-ratio: 1 / 1;
    height: auto;
    border-radius: 50%;
    border: 1px solid var(--rule);
    object-fit: cover;
    object-position: center center;
    transition: border-color var(--t-quick) var(--ease);
}
.driver-row-portrait--empty {
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 0.5rem;
    color: var(--ink-dim);
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    background: none;
}

.driver-row-naming { min-width: 0; }

.driver-row-name {
    margin: 0;
    font-size: clamp(1.05rem, 1.6vw, 1.3rem);
    font-weight: var(--fw-semibold);
    line-height: 1.2;
}

/* The approved credential line, wrapping to two lines as the mock has
   it rather than being truncated. */
.driver-row-meta {
    margin: 0.35rem 0 0;
    font-size: var(--fs-label);
    line-height: 1.4;
    color: var(--ink-muted);
    transition: color var(--t-quick) var(--ease);
}

/* Transcript-only marker. Never colour alone: it is a labelled tag, and
   it stays where it is for the four drivers without recordings. */
.driver-row-tag {
    display: inline-block;
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-medium);
    color: var(--ink-dim);
    border: 1px solid var(--rule-strong);
    padding: 0.15rem 0.45rem;
    margin-top: 0.5rem;
}
.driver-row:hover .driver-row-tag,
.driver-row:focus-visible .driver-row-tag {
    color: var(--shell);
    border-color: var(--shell);
}

/* ---- The vertical dividers ---------------------------------- */
.driver-row-divider {
    width: 1px;
    align-self: stretch;
    background: var(--rule);
    transition: background-color var(--t-quick) var(--ease);
}

/* ---- Column 2: rating --------------------------------------- */
.driver-row-rating {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    margin: 0;
    text-align: center;
}
.driver-row-star-mark {
    width: 20px;
    height: 20px;
    fill: var(--accent);
    color: var(--accent);
    transition: fill var(--t-quick) var(--ease);
}
.driver-row-rating-figure {
    font-size: clamp(1.1rem, 1.7vw, 1.4rem);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
    transition: color var(--t-quick) var(--ease);
}
.driver-row-rating-label {
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--ink-dim);
    transition: color var(--t-quick) var(--ease);
}

/* ---- Column 3: the pull quote ------------------------------- */
.driver-row-saying {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    min-width: 0;
}
/* A large lime opening glyph, per the mock. Presentational: the real
   quote text carries no quotation marks, so it reads cleanly aloud. */
.driver-row-quote-glyph {
    font-size: 2.6rem;
    line-height: 0.8;
    color: var(--accent);
    flex: none;
    transition: color var(--t-quick) var(--ease);
}
.driver-row-quote {
    margin: 0;
    font-style: italic;
    font-size: var(--fs-body);
    line-height: 1.45;
    transition: color var(--t-quick) var(--ease);
}

.driver-row-chevron {
    flex: none;
    width: 24px;
    height: 24px;
    stroke: currentColor;
    stroke-width: 2;
    fill: none;
    transition: transform var(--t-quick) var(--ease);
}

/* ---- Narrower: STACK, never drop ------------------------------
   This block previously hid the quote below 68rem (1088px), which meant
   every phone, every tablet and any desktop window under 1088px showed
   the rows with no quote at all. The quotes are the exhibition's
   argument — the most important content on the page — and they were
   absent for most visitors. The comment here used to call them "the
   least load-bearing", which was simply wrong.

   Nothing is dropped now. Below the breakpoint the row becomes two
   blocks: identity and rating on the first line, the quote full width
   beneath it. The dividers go, because they divide nothing once
   stacked.

   THE BREAKPOINT, measured rather than inherited. 68rem was picked to
   fit three columns, not because three columns stopped working there.
   Measured across widths, the quote column holds a comfortable measure
   down to about 820px and the credential line only wraps to a third
   line below 940px. 60rem (960px) is where the three-column layout
   genuinely starts to squeeze — and it is already the site's main
   two-to-one-column breakpoint, so the rows now change shape with
   everything else rather than at a number of their own. */
@media (max-width: 60rem) {
    .driver-row {
        /* identity | rating | chevron, with the quote spanning beneath. */
        grid-template-columns: minmax(0, 1fr) auto auto;
        row-gap: 1rem;
    }
    /* The dividers separated columns that no longer sit side by side. */
    .driver-row-divider { display: none; }

    /* The quote takes the full width of the row, under everything else.
       It is CONTENT, so it stacks — it is never hidden. */
    .driver-row-saying {
        grid-column: 1 / -1;
        padding-top: 0.9rem;
        border-top: 1px solid var(--rule);
    }
    .driver-row:hover .driver-row-saying,
    .driver-row:focus-visible .driver-row-saying {
        border-top-color: var(--shell);
    }
}

@media (max-width: 40rem) {
    .driver-row {
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 0.9rem;
    }
    /* The rating moves under the identity rather than squeezing it. */
    .driver-row-rating {
        grid-column: 1;
        flex-direction: row;
        align-items: baseline;
        gap: 0.4rem;
    }
    .driver-row-chevron { grid-row: 1; grid-column: 2; align-self: center; }
    /* The quote still spans the row, and is still present. */
    .driver-row-saying { grid-column: 1 / -1; }
}
}

/* ---- About: credits grid ---------------------------------- */

.credits-grid {
    display: grid;
    gap: 1.5rem;
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
}
.credit-tile {
    border: 1px solid var(--rule-strong);
    padding: 1.5rem 1.25rem;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    min-height: 13rem;
}
.credit-logo {
    height: 3.5rem;
    display: flex;
    align-items: center;
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    font-size: 1rem;
    line-height: 1.15;
    letter-spacing: 0.01em;
    color: var(--accent);
}
.credit-role {
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-bold);
    color: var(--ink-dim);
}
.credit-names {
    font-size: var(--fs-body);
    color: var(--ink-muted);
    line-height: 1.5;
}

/* ---- Resources: stat blocks ------------------------------- */

.stat-grid {
    display: grid;
    gap: 1.5rem;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}
/* Flat accent-on-black poster block. */
.stat-block {
    background: var(--accent);
    color: var(--shell);
    padding: 1.75rem 1.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
/* Figures vary in length ("37%" vs "66 years"), so they must wrap and
   shrink rather than push the block wider than its grid track. */
.stat-figure {
    font-size: clamp(1.75rem, 3.4vw, 3rem);
    font-weight: var(--fw-bold);
    line-height: 1;
    letter-spacing: var(--ls-tight);
    /* Wrap between words ("66 years") but never inside a figure —
       break-word would split "$20,000-$30,000" mid-number. */
    overflow-wrap: normal;
    word-break: keep-all;
    max-width: 100%;
}
.stat-desc {
    font-size: var(--fs-body);
    line-height: 1.4;
}
/* Source text sits on the light accent block, so it uses the
   deep variant: #0E7C6B on #2DE1C2 is insufficient, so black at
   reduced weight is used instead — still 11.94:1. */
.stat-source {
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-bold);
    margin-top: auto;
    padding-top: 0.75rem;
}

/* Inverted stat block on a light surface. The lime rule: lime never
   appears on a light surface, so --ink-on-shell inverts to --shell
   on --surface-light and the figure carries no accent at all. The
   old --accent-deep variant is deleted along with its token. */
.stat-block--light {
    background: var(--surface-light);
    color: var(--shell);
}

/* Wide data escapes into its own scroll container so the page body
   never scrolls horizontally. */
.table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    /* Without an explicit max-width the min-width table below stretches
       this container, which pushes the whole page into horizontal scroll. */
    max-width: 100%;
}
.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fs-body);
    min-width: 34rem;
}
.data-table th,
.data-table td {
    text-align: left;
    padding: 1rem 1.25rem 1rem 0;
    border-bottom: 1px solid var(--rule);
    white-space: nowrap;
}
.data-table thead th {
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--accent);
    border-bottom-color: var(--rule-strong);
}
.data-table tbody th {
    font-weight: var(--fw-bold);
    color: var(--ink);
}
.data-table tbody td { color: var(--ink-muted); }

.table-rows { border-top: 1px solid var(--rule); }
.row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.5rem;
    justify-content: space-between;
    align-items: baseline;
    padding: 1.25rem 0;
    border-bottom: 1px solid var(--rule);
    text-decoration: none;
}
/* A background fill rather than a padding or border change, so the row
   never changes width and cannot push the page into horizontal scroll.
   (The comment here previously described an inset box-shadow, which this
   rule does not use and the design would not permit.) */
a.row:hover { background: var(--accent); color: var(--shell); }
.row-title { font-size: var(--fs-read); font-weight: var(--fw-medium); }
.row-type {
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-bold);
    color: var(--ink-dim);
}
a.row:hover .row-type { color: var(--shell); }

.era-list { display: grid; gap: 0; border-top: 1px solid var(--rule); }
.era {
    padding: 1.5rem 0;
    border-bottom: 1px solid var(--rule);
    display: grid;
    gap: 0.5rem;
}
@media (min-width: 48rem) {
    .era { grid-template-columns: 16rem minmax(0, 1fr); gap: 2rem; }
}
.era-period {
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-bold);
    color: var(--accent);
}
.era-desc { color: var(--ink-muted); font-size: var(--fs-body); line-height: 1.6; }

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

/* The exhibition title set inline in body copy. Italic 400, per the type
   table in Haroon's "Webpage Text.docx" ("Exhibition title, Italic 400").
   A real <em>, so the emphasis is structural rather than a styling guess,
   and the weight is held at 400 because <em> inherits the paragraph's
   weight and the table specifies regular. */
.exhibition-title {
    font-style: italic;
    font-weight: 400;
}

/* ---- The footer circuit band -------------------------------
   A network of thin lime lines spanning the page and bleeding off
   BOTH edges: horizontal runs joined by 45-degree chamfers, small
   nodes at junctions, one hollow ring, isolated dots, and dashed
   stretches.

   Generated by tools/footer-band.js — the chamfers are derived
   (dx = dy) rather than typed, and the layout is seeded so it cannot
   change silently between deploys. Re-run that script to change it.

   THE CROP AXIS IS THE STRUCTURAL POINT. preserveAspectRatio="slice"
   scales to cover and crops whichever axis has the surplus. Cropping
   the SIDES is the intent, because the band bleeds. At the original
   1200x96 (aspect 12.5) inside a band of aspect 22.7 the surplus was
   VERTICAL: it cut 34px off the top and bottom and sliced nodes in
   half at both edges, which is most of why it read as unfinished.

   The viewBox is now 3200x96 (aspect 33.3), which exceeds the band's
   aspect at the widest supported viewport (2560px / 84px = 30.5). So
   the crop is horizontal BY CONSTRUCTION at every width, not by luck,
   and the test asserts that relationship rather than the numbers.

   IT DOES NOT ANIMATE. §2 reserves scroll-driven motion for the route
   line, and this is decoration with nothing to say.

   It sits BELOW the footer content in the document, not behind it —
   the funder acknowledgement and partner marks are contract terms and
   must not lose contrast to an ornament. It is also set back in value
   for the same reason: it is the last thing on the page, not a
   headline.

   No glow. The one permitted glow is the map's (spec §10). */
.footer-band {
    width: 100%;
    line-height: 0;
    margin-top: clamp(2.5rem, 5vw, 4rem);
    /* The band recedes. At full strength it competed with the funder
       acknowledgement directly above it, which is a contract term and
       has to win that comparison. */
    opacity: 0.5;
}
.footer-band svg {
    display: block;
    width: 100%;
    height: clamp(52px, 6vw, 84px);
}

.footer-band-run,
.footer-band-dash {
    fill: none;
    stroke: var(--accent);
    /* A true hairline. `vector-effect: non-scaling-stroke` means this is
       in SCREEN pixels regardless of the viewBox scale, so 0.75 renders
       as 0.75px at every width — the mock's weight rather than the 1.25
       that read as a drawn line. */
    stroke-width: 0.75;
    stroke-linecap: butt;
    stroke-linejoin: miter;
    vector-effect: non-scaling-stroke;
}
.footer-band-dash {
    stroke-dasharray: 4 5;
}

/* Nodes: small punctuation ON the lines, not features. These are filled
   circles, so unlike the strokes their radius DOES scale with the
   viewBox — at 0.77-0.88x they render at 4 to 7px across, against the
   10 to 21px blobs the old frame produced. */
.footer-band-node { fill: var(--accent); }

/* The one hollow ring — the primitive's origin marker, borrowed. */
.footer-band-ring {
    fill: none;
    stroke: var(--accent);
    stroke-width: 0.9;
    vector-effect: non-scaling-stroke;
}

/* Isolated dots, sitting on no route. Dimmer again, so they read as
   background incident rather than as junctions that lost their line. */
.footer-band-dot {
    fill: var(--accent);
    opacity: 0.55;
}

/* Simplified View: pure decoration with nothing to say, so it goes. */
.simplified .footer-band { display: none; }

@media (max-width: 40rem) {
    /* The network is wide by nature; at phone width it compresses into
       noise, and the footer's links are what matter there. */
    .footer-band svg { height: 40px; }
    .footer-band { opacity: 0.4; }
}

.site-footer {
    margin-top: var(--block-gap);
    border-top: 1px solid var(--rule);
    padding: 2.5rem var(--gutter) 3rem;
}
/* The gap is set for 30px links; with 44px targets the rows would drift
   apart, so the vertical gap comes down by the difference. */
.footer-nav {
    display: flex;
    flex-wrap: wrap;
    /* Row gap is smaller than the column gap: the 44px targets already
       carry their own vertical space, so the old uniform 1.5rem would
       push wrapped rows apart. */
    gap: 0.25rem 1.5rem;
    margin-bottom: 1.25rem;
}
/* 44x44 minimum, per §8. These were 30px tall — six links on seven
   pages, all failing a contract term, and the highest-priority finding
   from the phone-width audit.

   The padding is what makes the target, and the negative margin keeps
   the row's visual rhythm unchanged: the links look as they did and the
   hit area is now the size the guideline requires. inline-flex so the
   height applies to an inline element at all. */
.footer-nav a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 0.5rem;
    margin: 0 -0.5rem;
    font-size: var(--fs-body);
    text-decoration: underline;
    text-underline-offset: 4px;
}
.footer-nav a:hover { color: var(--accent); }
.footer-copy { color: var(--ink-dim); font-size: var(--fs-body); }
.footer-partners {
    margin-top: 2rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--rule);
    display: flex;
    flex-wrap: wrap;
    gap: 1.25rem 2rem;
}
.footer-partners span {
    font-size: var(--fs-label);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    font-weight: var(--fw-bold);
    color: var(--ink-dim);
}

/* ---- Lite view --------------------------------------------
   The promoted Simplified View. Strips decorative fills back to
   plain type on the shell, and shows the transcript inline rather
   than behind a control. Driven by the server-rendered .is-lite
   class on <body>, which comes from the /lv/ URL prefix or
   ?view=lite — not from a client-side preference. */
body.is-lite .band,
body.is-lite .film-poster { background: var(--shell); }
body.is-lite .card,
body.is-lite .stat-block {
    background: var(--shell);
    color: var(--ink);
    border: 1px solid var(--rule-strong);
}
body.is-lite .card-rule { background: var(--accent); }
body.is-lite .card-btn span,
body.is-lite .card-portrait { border-color: var(--rule-strong); }
body.is-lite .card-tag { background: var(--accent); color: var(--shell); }
body.is-lite .band-word { color: var(--ink); }
body.is-lite .band-word .wm-hollow { -webkit-text-stroke-color: var(--ink); }
body.is-lite .stat-figure { color: var(--accent); }
body.is-lite .hl { background: none; color: var(--accent); padding: 0; }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* ---- The ride, under the shared partials -------------------
   The driver experience now includes site-head/site-header/
   site-footer (spec §11, first task of step 2), so site.css loads
   on that page ahead of the legacy styles.css. These rules keep
   the immersive surface behaving as it did while the shared
   chrome sits above it.

   All of this goes when the player itself is rebuilt. It exists so
   that bringing the page under the partials is not also a redesign
   of the page. */

body.is-ride {
    background: #0D0D0D;
    /* The legacy player is typeset in Source Sans 3; leaving it on
       Inter would restyle a page this step is not rebuilding. */
    font-family: 'Source Sans 3', 'Helvetica Neue', Arial, sans-serif;
}

/* The ride fills the viewport below the header rather than sitting
   in a document flow. */
body.is-ride .site-header {
    position: relative;
    z-index: 30;
    background: var(--shell);
    /* The player's own topbar sits at z-index 20 and auto-hides; the
       shared header stays put above it. */
}

/* The player's overlays are full-bleed and must clear the header. */
body.is-ride .boarding-overlay,
body.is-ride .completion-modal {
    z-index: 60;
}

/* ---- Accessibility statement ------------------------------- */

.a11y-list {
    list-style: none;
    padding: 0;
    margin: 0;
}
.a11y-list li {
    position: relative;
    padding-left: 1.75rem;
    max-width: var(--measure);
}
.a11y-list li + li { margin-top: 1rem; }
/* A lime marker, but the list semantics carry the meaning — the colour
   is not doing the work on its own. */
.a11y-list li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.62em;
    width: 0.75rem;
    height: 2px;
    background: var(--accent);
}

.a11y-updated {
    margin-top: 2.5rem;
    font-size: var(--fs-meta);
    color: var(--ink-dim);
}

/* ---- Section heading (mock treatment) ----------------------
   Icon, heading, then a rule to the right edge terminating in a
   filled dot. The dot reuses the route line's origin geometry,
   filled rather than open, so it reads as the same family. */

.section-head {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-bottom: 2rem;
}

.section-head-icon {
    flex: none;
    display: block;
    width: 28px;
    height: 28px;
    color: var(--accent);
}
.section-head-icon svg {
    width: 100%;
    height: 100%;
    display: block;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.75;
    stroke-linecap: round;
}

.section-head-title {
    flex: none;
    margin: 0;
    font-size: var(--fs-h2);
    font-weight: var(--fw-semibold);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    line-height: 1.1;
}

/* The rule takes the remaining width, so the dot always lands on the
   right edge whatever the heading's length or the viewport. */
.section-head-rule {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    gap: 0;
    min-width: 2rem;
}
.section-head-rule-line {
    flex: 1 1 auto;
    height: 8px;
    display: block;
}
.section-head-rule-line line {
    stroke: var(--rule-strong);
    stroke-width: 1;
}
.section-head-rule-dot {
    flex: none;
    width: 12px;
    height: 12px;
    display: block;
}
.section-head-rule-dot circle { fill: var(--accent); }

@media (max-width: 560px) {
    /* The rule is the first thing to go when there is no room for it. */
    .section-head-rule { display: none; }
    .section-head-title { font-size: var(--fs-h3); }
}

/* ============================================================
   GTA MAP — spec §7

   Real Toronto geography plus ONE DECORATIVE LINE. No driver pins, no
   per-driver routes, no highlighting — Haroon, September 2026: "no need
   to drop any pins", and of the line, "It is decorative".

   The whole figure is aria-hidden and has no focusable elements. It is
   the setting, not the index; the driver rows below carry everything.

   Geometry generated by tools/geo/build-map.js from StatCan boundary
   files and the Ontario Road Network. Flat, hard-edged, no gradients,
   no glows, no shadows, no blur — the same poster grammar as the rest
   of the site.
   ============================================================ */

.gta-map {
    margin: 0;
    /* The mock's map BLEEDS off the right edge of the page and runs taller
       than the text column. Ours was a bordered box floating in a column,
       which is why the hero still read thin.

       The bleed: push past the hero's right gutter to the viewport edge.
       100vw minus the element's own left offset would need JS to measure,
       so instead the column is allowed to overflow its grid cell by the
       page gutter, which is the same distance by construction. */
    width: calc(100% + var(--bleed-right, 0px));
    margin-right: calc(var(--bleed-right, 0px) * -1);
    background: transparent;
    overflow: hidden;
    display: flex;
    /* A COLUMN, not a row: the map and its attribution stack.
       The credit is an HTML element rather than SVG text (see below), so
       the figure is no longer just a frame around one child. */
    flex-direction: column;
}

/* The bleed distance, only once the hero is actually two columns. Below
   that the map is full-width already and has nothing to bleed into. */
@media (min-width: 60rem) {
    .hero-col-map { --bleed-right: clamp(1.5rem, 5vw, 5rem); }
}

/* The map runs TALLER than the text column, per the mock.

   THE LETTERBOX BUG, THIRD OCCURRENCE. `min-height: 100%` was here to make
   the map fill a stretched grid row — and min-height BEATS aspect-ratio, so
   the box became 592x632 (1.06) while the frame is 1000x900 (1.11), and
   `meet` letterboxed 49px top and bottom. The CSS said one ratio and the
   rendered box was another.

   The fix is to stop forcing the box and let the declared aspect-ratio
   govern: the figure sizes itself from its width, which is what
   aspect-ratio is for. The column no longer stretches it, so the container
   and the frame agree by construction rather than by coincidence.

   The relationship is asserted in test-redesign.js against the RENDERED
   box, not against the stylesheet, because all three occurrences looked
   correct in the CSS. */
@media (min-width: 60rem) {
    .hero-col-map {
        /* start, not stretch: the row must not dictate the map's height. */
        align-self: start;
    }
}

.gta-map-svg {
    display: block;
    width: 100%;
    /* THE ASPECT RATIO LIVES HERE, not on the figure.
       The frame is 1000x900 (1.11), close to the column's own proportions.
       preserveAspectRatio="meet" never crops — which is what stops a repeat
       of the round-9 bug where "slice" removed a driver from the frame —
       but it letterboxes when the frame and its container disagree. At the
       old 1.61 that was ~90px of dead band top and bottom; matching the
       frame to the column removes the letterboxing without giving up the
       guarantee.

       It moved off the figure in round 20, when the attribution became an
       HTML sibling. A ratio on the figure would have been shared between
       the map and the credit, which is a letterbox by another route: the
       svg would get the figure's height MINUS the credit, and no longer
       match its own viewBox. The element with the viewBox carries the
       ratio. No min-height, which would reintroduce the same conflict at
       narrow widths where the ratio yields a shorter box than the minimum. */
    aspect-ratio: 1000 / 900;
}

/* ---- Lake Ontario -------------------------------------------
   The shape is DERIVED — the frame minus the shoreline-clipped
   municipal boundaries — so this fill colours real geography rather
   than a drawn curve. One step off the shell: enough that the coast
   reads as a coast, not so much that the water competes. */
.gta-map-water {
    fill: var(--water);
    stroke: none;
}

/* ---- Streets ------------------------------------------------
   The inversion from the previous round: behind five lime routes,
   22,711 arterials were grey haze competing with the thing that
   carried the meaning. Behind ONE thin decorative line they are the
   street texture that makes this read as Toronto rather than as an
   abstract diagram. Same data, opposite answer, because what sits on
   top of them changed. */
.gta-map-arterials path {
    fill: none;
    /* Lime-TINTED rather than neutral grey, per the mock, so the map reads
       as one palette instead of a grey map with lime on it. Nowhere near
       the accent in saturation — this is the accent's hue at a fraction of
       its intensity, well below the route in weight. */
    stroke: var(--map-street);
    stroke-width: 0.6;
    stroke-linecap: round;
    vector-effect: non-scaling-stroke;
}

/* The named highways sit one step above the arterials, so the frame
   still has a road hierarchy rather than one flat mat of streets. */
.gta-map-highways path {
    fill: none;
    /* One step up from the arterials in the same tinted family, so the road
       hierarchy survives while the whole network stays one palette. */
    stroke: var(--map-road);
    stroke-width: 1.1;
    stroke-linecap: round;
    stroke-linejoin: round;
    opacity: 0.7;
    vector-effect: non-scaling-stroke;
}

/* Municipal outlines, only when the pipeline draws them. Currently off
   — they were the loudest thing in the frame and the mock has no
   equivalent — but the rule stays so flipping drawBoundaries back on
   does not need a stylesheet change too. */
.gta-map-boundaries path {
    fill: none;
    stroke: var(--rule);
    stroke-width: 0.75;
    opacity: 0.7;
    vector-effect: non-scaling-stroke;
}

/* ---- Highway shields ----------------------------------------
   Boxed route numbers, derived from ROUTE_NUMBER on the geometry.
   Outlined on the shell rather than filled, so a shield never becomes
   a bright rectangle competing with the one lime line. */
.gta-map-shield rect {
    fill: var(--shell);
    stroke: var(--rule-strong);
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
}
.gta-map-shield text {
    fill: var(--ink-muted);
    font-family: var(--font-sans);
    font-size: 12px;
    font-weight: var(--fw-semibold);
    text-anchor: middle;
    letter-spacing: 0.02em;
}

/* ---- Place labels -------------------------------------------
   A real hierarchy, per the mock: TORONTO large, districts small.
   Ten labels at one size overprinted and read as clutter; five at two
   sizes reads as a map. */

/* The group carries what the labels share. A group element with no rule
   is a group nobody can style later -- the same finding the round 7
   audit turned up, caught again here by the test it produced. */
.gta-map-places {
    pointer-events: none;
}

.gta-map-place-label {
    font-family: var(--font-sans);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
}

/* The city. The one large label in the frame. */
.gta-map-place-label--city {
    fill: var(--ink);
    font-size: 34px;
    font-weight: var(--fw-semibold);
    letter-spacing: 0.18em;
}

/* The districts, deliberately quiet. */
.gta-map-place-label--district {
    fill: var(--ink-dim);
    font-size: 13px;
    font-weight: 400;
}

/* The lake, set apart from the land labels: italic, centred, lower
   case retained, sitting in the water rather than beside a dot. */
.gta-map-place-label--water {
    fill: var(--ink-dim);
    font-size: 15px;
    font-style: italic;
    text-anchor: middle;
    text-transform: none;
    letter-spacing: 0.08em;
    opacity: 0.7;
}

/* ---- The decorative line ------------------------------------
   THE ONLY LIME IN THE FRAME, and it means nothing: its endpoints
   were chosen for composition. It is not a journey, not a driver and
   not a place, and nothing here labels it as one.

   Thin, because it is the single accent on a quiet ground and does
   not need weight to be seen. pathLength="100" normalises the dash
   arithmetic so the draw needs no getTotalLength() call. */
/* ---- The decorative line's endpoint markers -----------------
   A ring-with-dot where the line starts, a filled pin where it ends,
   per the mock. The same vocabulary the route-line primitive uses:
   an origin reads as a ring, a destination as a teardrop.

   These are NOT driver pins. Haroon's "no need to drop any pins" was
   about the five driver pins, which are gone. These two mark the ends
   of a decorative line, link to nothing, and name nobody. */
.gta-map-endpoint--origin circle {
    fill: none;
    stroke: var(--accent);
    stroke-width: 2.5;
    vector-effect: non-scaling-stroke;
}
.gta-map-endpoint--origin .gta-map-endpoint-dot {
    fill: var(--accent);
    stroke: none;
}
.gta-map-endpoint--pin {
    fill: var(--accent);
    stroke: none;
}

/* The glow group. The blur itself is an SVG filter in the map's own
   markup, not a CSS shadow — see §10. The site's flat-design rules are
   unchanged and still asserted against this stylesheet; this group is
   only where the filtered elements live. */
.gta-map-decor {
    /* nothing to declare: the filter is applied in the markup, and this
       rule exists so the group is styleable rather than anonymous. */
    pointer-events: none;
}

.gta-map-line {
    fill: none;
    stroke: var(--accent);
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation: gta-line-draw var(--t-slow) var(--ease) forwards;
}
@keyframes gta-line-draw {
    to { stroke-dashoffset: 0; }
}

/* ---- Attribution --------------------------------------------
   Required by both open licences. It stays visible even though the
   figure is decorative: the obligation is to the data's publishers,
   and it does not lapse because the image is ornamental. */
.gta-map-credit {
    /* AN HTML ELEMENT, SIZED IN PX — and that is the whole point.

       It was SVG text for rounds 7-19 and could not be sized there. SVG
       text scales with the frame, so a size in viewBox units means
       choosing one viewport to be correct at:

           36 units  ->  12.1px at 390   24.3px at 768   21.3px at 1440

       Round 19 picked the 390px figure, which is the legibility floor, and
       shipped 21-24px to every desktop — larger than the TORONTO label at
       20px, three lines of it across 426px, dominating the lower third of
       a decorative map. A value measured at one viewport and applied at all
       of them is the bug, not the number; see §10a.

       Every OTHER label on this map is cartography and SHOULD scale with
       the frame. This one is a licence credit with a legibility floor,
       which is a different requirement and now has a different mechanism.

       Still inside the <figure>, directly beneath the map, so it credits
       the geography above it rather than floating in the page. */
    display: flex;
    flex-direction: column;
    margin: 0.5rem 0 0;
    font-family: var(--font-sans);
    font-size: 0.75rem;   /* 12px, at every width */
    line-height: 1.45;
    letter-spacing: 0.02em;
    color: var(--ink-dim);
    /* No opacity. On SVG text it was compensating for a small rendering;
       at a fixed 12px on --ink-dim (5.87:1) the contrast is the contrast,
       and dimming it would only spend that margin again. */
}

/* ---- Reduced motion -----------------------------------------
   §7: drawn, not animated. Not hidden, not faded — simply already
   there, carrying exactly the same (absence of) information. */
@media (prefers-reduced-motion: reduce) {
    .gta-map-line {
        animation: none;
        stroke-dashoffset: 0;
    }
}

/* The glow is a static treatment, not motion, so it stays in Reduced —
   removing it would change the design rather than the animation. */

/* Simplified View: the map is decoration on a page whose content is
   the driver rows, so it stays but stops moving. */
.simplified .gta-map-line {
    animation: none;
    stroke-dashoffset: 0;
    opacity: 0.5;
}

@media (max-width: 60rem) {
    /* The district labels and shields are the first things to go
       illegible as the frame shrinks. TORONTO and the geography still
       carry it. */
    .gta-map-place-label--district,
    .gta-map-shields { display: none; }
    /* NO min-height here. It was written when the FIGURE carried the
       aspect ratio; now the svg does, and a minimum that disagrees with
       the ratio is a letterbox — the fourth occurrence of which was
       round 17. The ratio already gives a taller box than 14rem at every
       width the query covers, so the rule was inert as well as wrong. */
}
