:root {
    --accent: #08457d;
    --link: #0f7c8b;
    --bg: #ffffff;
    --text: #111111;
    --muted: #6b7280;
    --border: #e5e7eb;
    --pad: 32px;
    --gap: 40px;
    --panel-w: 360px;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
    background: var(--bg);
    color: var(--text);
    line-height: 1.55;
}

a { color: var(--link); }
.muted { color: var(--muted); }
.accent { color: var(--accent); }
.road { color: #b31b1b; }   /* Cornell red, matching the ROADWork project page */

/* ────────── The deck: a vertical scroll-snap sequence of panels ─────────────
   The page reads as a deck — one full-viewport panel per idea — navigated with
   the arrow keys, the dot rail, or the glowing chevron cues (see js/deck.js). */
.deck {
    height: 100vh;
    overflow-y: scroll;
    scroll-snap-type: y mandatory;
    scroll-behavior: smooth;
    scrollbar-width: none;          /* Firefox: hide the deck scrollbar */
}
.deck::-webkit-scrollbar { width: 0; height: 0; }   /* WebKit: same */

.panel {
    position: relative;
    height: 100vh;
    overflow: hidden;
    scroll-snap-align: start;
    scroll-snap-stop: always;
}
/* Panels whose content can exceed the viewport scroll internally, then snap. */
.panel-split,
.panel-cite { overflow-y: auto; }

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

/* ────────── Panel 1: side info panel ────────── */
.info {
    position: absolute;
    top: var(--pad);
    bottom: var(--pad);
    left: calc(100% - var(--pad) - var(--panel-w));
    width: var(--panel-w);
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 10px;
    overflow-y: auto;
}
.info > h1 { margin-bottom: 2px; }
.info > .links { margin-top: 6px; margin-bottom: 6px; }

.info h1 {
    font-size: 1.7rem;
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: -0.02em;
    margin: 0;
    text-wrap: balance;
}
.info h1 .accent { color: var(--accent); }

.info .authors,
.info .affiliations,
.info .venue {
    color: var(--muted);
    font-size: 0.95rem;
    margin: 0;
}
.info .authors { color: var(--text); font-size: 1rem; }
.info .authors a { color: var(--text); text-decoration: none; }
.info .authors a:hover { text-decoration: underline; color: var(--link); }
.info .authors sup,
.info .affiliations sup { color: var(--muted); }
.info .venue { font-style: italic; }

.info .links { display: flex; flex-wrap: wrap; gap: 8px; }
.info .links a {
    display: inline-block;
    padding: 6px 14px;
    background: var(--text);
    color: white;
    text-decoration: none;
    border-radius: 999px;
    font-size: 0.85rem;
    transition: background 0.15s;
}
.info .links a:hover { background: var(--accent); }

.info .tldr {
    color: var(--muted);
    font-size: 1rem;
    margin: 0;
}

/* Glowing scroll cue under the tl;dr. */
.scroll-cue {
    align-self: center;
    margin-top: 14px;
    background: transparent;
    border: 0;
    color: var(--accent);
    cursor: pointer;
    padding: 6px;
    border-radius: 999px;
    display: inline-flex;
    animation: scroll-cue-bounce 1.8s ease-in-out infinite;
    filter: drop-shadow(0 0 6px rgba(8, 69, 125, 0.55))
            drop-shadow(0 0 14px rgba(15, 124, 139, 0.35));
    transition: color 0.15s, filter 0.15s, transform 0.15s;
}
.scroll-cue:hover {
    color: var(--link);
    filter: drop-shadow(0 0 8px rgba(15, 124, 139, 0.85))
            drop-shadow(0 0 18px rgba(8, 69, 125, 0.55));
}
@keyframes scroll-cue-bounce {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(6px); }
}

/* Every panel's cue (except the hero's) is pinned to the panel's bottom-center,
   rather than floating at the end of the text. */
.panel:not(.panel-hero) .scroll-cue {
    position: absolute;
    left: 50%;
    bottom: 14px;
    margin: 0;
    z-index: 20;
    animation-name: scroll-cue-bounce-centered;
}
@keyframes scroll-cue-bounce-centered {
    0%, 100% { transform: translateX(-50%) translateY(0); }
    50%      { transform: translateX(-50%) translateY(6px); }
}

/* ────────── Compact view: stage / carousel ────────── */
.stage-area {
    position: absolute;
    top: var(--pad);
    bottom: var(--pad);
    left: var(--pad);
    right: calc(var(--pad) + var(--panel-w) + var(--gap));
    display: flex;
    align-items: center;
    gap: 12px;
}
.stage {
    flex: 1;
    height: 100%;
    background: #111;
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
    position: relative;
}
.stage > *,
.stage canvas {
    width: 100% !important;
    height: 100% !important;
    border: 0 !important;
    display: block;
}

/* Minimalist nav: chevrons sitting beside the stage on the page background. */
.nav {
    flex: 0 0 32px;
    height: 64px;
    background: transparent;
    color: var(--muted);
    border: 0;
    font-size: 2rem;
    line-height: 1;
    cursor: pointer;
    opacity: 0.6;
    padding: 0;
    transition: opacity 0.15s, color 0.15s;
}
.nav:hover:not(:disabled) { opacity: 1; color: var(--text); }
.nav:disabled { opacity: 0.15; cursor: default; }

/* ────────── Split-panel template (media + text, sides alternate) ────────────
   Reused by every evidence panel: a large visual on one side, a short text
   block on the other. `.flip` swaps the sides for rhythm down the deck. */
.panel-split {
    display: flex;
    align-items: center;
    gap: var(--gap);
    padding: var(--pad);
}
.panel-split.flip { flex-direction: row-reverse; }

.panel-media {
    flex: 1 1 56%;
    min-width: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-height: calc(100vh - 2 * var(--pad));
}
.panel-media img {
    width: 100%;
    min-height: 0;
    max-height: 76vh;
    object-fit: contain;
    object-position: center;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #fff;
}
/* NVS comparison already carries its own framing — drop the panel box. */
#nvs .panel-media img { border: 0; border-radius: 0; background: none; }
.media-cap {
    margin: 0;
    font-size: 0.82rem;
    color: var(--muted);
    line-height: 1.45;
    text-align: center;
}
.media-cap strong { color: var(--text); font-weight: 600; }

.panel-text {
    flex: 0 1 40%;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 14px;
}
.eyebrow {
    margin: 0;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-size: 0.74rem;
    font-weight: 700;
    color: var(--accent);
}
/* In the coverage stats column the eyebrow sits just above the big heading. */
.cs-head .eyebrow { margin-bottom: 8px; }
.panel-text h2 {
    margin: 0;
    font-size: clamp(1.5rem, 2.4vw, 2.2rem);
    font-weight: 700;
    line-height: 1.15;
    letter-spacing: -0.02em;
    text-wrap: balance;
}
.panel-text h2 .accent { color: var(--accent); }
.panel-text .lede {
    margin: 0;
    color: var(--text);
    font-size: 1.02rem;
    line-height: 1.62;
}
.panel-text .lede strong { font-weight: 600; }
.panel-text .lede em { font-style: italic; color: var(--accent); }
.panel-text .abstract-full { text-align: justify; font-size: 0.98rem; }
/* Match the abstract everywhere: justified, hyphenated body copy for clean blocks
   (chips, captions, and eyebrows keep their own alignment). */
.tldr,
.cs-sub,
.panel-text > p:not(.stat-chip):not(.media-cap):not(.eyebrow) {
    text-align: justify;
}
.stat-chip {
    align-self: center;
    margin: 2px 0 0;
    padding: 7px 14px;
    border: 1px solid var(--border);
    border-radius: 999px;
    font-size: 0.82rem;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}
.panel-text .scroll-cue { align-self: center; margin-top: 4px; }

/* Verification paradigms: an (a)/(b)/(c) list; the third (ours) is highlighted. */
.paradigms {
    list-style: none;
    counter-reset: pg;
    margin: 12px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.paradigms li {
    counter-increment: pg;
    position: relative;
    padding: 9px 13px 9px 40px;
    border: 1px solid var(--border);
    border-radius: 10px;
    font-size: 0.95rem;
    line-height: 1.5;
    color: var(--text);
}
.paradigms li::before {
    content: "(" counter(pg, lower-alpha) ")";
    position: absolute;
    left: 13px;
    top: 9px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--muted);
}
.paradigms .pg-tag { font-weight: 600; color: var(--accent); }
.paradigms .pg-tag em { font-style: italic; }
.paradigms .pg-ours {
    border-color: var(--accent);
    background: rgba(8, 69, 125, 0.05);
}
.paradigms .pg-ours::before { color: var(--accent); }

/* Axis toggle: pills that swap a figure (work-zone long-tail axes). */
.axis-toggle {
    margin-top: 8px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.axis-pills {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
}
.axis-pill {
    border: 1px solid var(--border);
    background: #fff;
    color: var(--muted);
    font: inherit;
    font-size: 0.74rem;
    font-weight: 600;
    padding: 5px 13px;
    border-radius: 999px;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.axis-pill:hover { border-color: var(--accent); color: var(--accent); }
.axis-pill.active {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}
.axis-view { margin: 0; display: flex; flex-direction: column; gap: 8px; }
.axis-view img {
    width: 100%;
    height: auto;
    max-height: 26vh;
    object-fit: contain;
    border: 0;
    border-radius: 8px;
    background: #fff;
}
.axis-view.zoomable img { cursor: zoom-in; transition: border-color 0.15s; }
.axis-view.zoomable:hover img { border-color: var(--accent); }

/* A row of small result thumbnails below a text column (e.g. the planning
   radar / data-scaling charts that summarize findings at a glance). */
.fig-row {
    display: flex;
    gap: 12px;
    margin-top: 4px;
}
.fig-row .mini-fig {
    flex: 1 1 0;
    min-width: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.fig-row .mini-fig img {
    width: 100%;
    height: auto;
    max-height: 20vh;
    object-fit: contain;
    border: 0;
    border-radius: 8px;
    background: #fff;
}
.fig-row .media-cap { font-size: 0.68rem; line-height: 1.35; }
.fig-row .mini-fig.zoomable img { cursor: zoom-in; transition: border-color 0.15s; }
.fig-row .mini-fig.zoomable:hover img { border-color: var(--accent); }

/* Compact numbers table (rad-lad style) that sits in a fig-row slot:
   horizontal rules only, uppercase header with an accent bottom rule,
   best value in accent. */
.num-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: 0.64rem;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
    font-variant-numeric: tabular-nums;
}
.num-table th,
.num-table td {
    padding: 3px 6px;
    text-align: center;
    border-bottom: 1px solid var(--border);
}
.num-table th:first-child,
.num-table td:first-child { text-align: left; }
.num-table thead th {
    background: #f3f4f6;
    color: var(--text);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    border-bottom: 1.5px solid var(--accent);
}
.num-table tbody tr:last-child td { border-bottom: 0; }
.num-table tbody tr:hover td { background: rgba(8, 69, 125, 0.05); }
.num-table .best { color: var(--accent); font-weight: 700; }

/* A small contained figure inside a text column (e.g. the GPS-vs-recovered
   trajectory under the anchoring copy). Short/wide image shown whole + caption. */
.inline-fig {
    margin: 2px 0 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.inline-fig img {
    width: 100%;
    height: auto;
    max-height: 26vh;
    object-fit: contain;
    border: 0;
    border-radius: 8px;
    background: #fff;
}
/* Hover-to-zoom: the thumbnail signals it's zoomable; the enlarged preview is
   rendered as a fixed overlay by js/deck.js so it escapes the panel's clipping. */
.inline-fig.zoomable img { cursor: zoom-in; transition: border-color 0.15s; }
.inline-fig.zoomable:hover img { border-color: var(--accent); }
.zoom-hint { font-style: italic; opacity: 0.8; }

.zoom-pop {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.55);
    opacity: 0;
    transition: opacity 0.15s ease;
    pointer-events: none;        /* let mouseleave on the thumbnail still fire */
}
.zoom-pop.show { opacity: 1; }
.zoom-pop img {
    max-width: 92vw;
    max-height: 88vh;
    padding: 8px;
    background: #fff;
    border-radius: 10px;
    box-shadow: 0 16px 50px rgba(0, 0, 0, 0.45);
}

/* A secondary figure inside a text column (e.g. multicam under the NVS copy).
   The image is tall (several stacked rows), so we show it at full column width
   inside a fixed-height scroll box rather than shrinking it to fit. */
.sub-media {
    margin: 2px 0 0;
    max-height: 34vh;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #fff;
}
.sub-media img {
    display: block;
    width: 100%;
    height: auto;
}
.sub-media.zoomable { transition: border-color 0.15s; }
.sub-media.zoomable:hover { border-color: var(--accent); }
.sub-media.zoomable img { cursor: zoom-in; }

/* Overview panel: smaller teaser, roomier abstract column. */
#overview .panel-media { flex: 0 1 48%; }
#overview .panel-media img { max-height: 60vh; }
#overview .panel-text { flex: 1 1 46%; }

/* ────────── Teaser, rebuilt as an animated 3/3/4 cell grid ──────────────────
   Rows reveal top→middle→bottom on a loop when .teaser-anim has .playing
   (added by deck.js on slide reveal), and the caption changes per row in step.
   The cell images carry their own labels/borders, so cells stay bare. */
.teaser-grid { display: flex; flex-direction: column; gap: 10px; }
.trow { display: flex; flex-direction: column; gap: 4px; }   /* cells, then this row's caption */
.trow-cells { display: flex; gap: 6px; align-items: stretch; }
.tcell { flex: 1; min-width: 0; aspect-ratio: 1.69; }        /* uniform cell height per row → bottoms align;
                                                                ratio ≥ widest trimmed image so cover never crops sideways */
.trow2 .tcell { aspect-ratio: 1.57; }
.trow3 .tcell { aspect-ratio: 1 / 1; }
.tcell img { width: 100%; height: 100%; object-fit: cover; display: block;
             border: 0; border-radius: 6px; background: none; }   /* no cell border/box */
.trow-cap { margin: 0; text-align: center; font-size: 0.74rem; color: var(--muted); }

/* Each row + its caption reveal together, top→middle→bottom, on a shared loop. */
/* Build from blank top→middle→bottom, hold ~30s, then loop. */
.teaser-anim.playing .trow1 { animation: t-row1 34s ease infinite; }
.teaser-anim.playing .trow2 { animation: t-row2 34s ease infinite; }
.teaser-anim.playing .trow3 { animation: t-row3 34s ease infinite; }
@keyframes t-row1 { 0% {opacity:0} 1.5% {opacity:1} 96% {opacity:1} 100% {opacity:0} }
@keyframes t-row2 { 0%,2% {opacity:0} 3.5% {opacity:1} 96% {opacity:1} 100% {opacity:0} }
@keyframes t-row3 { 0%,4% {opacity:0} 5.5% {opacity:1} 96% {opacity:1} 100% {opacity:0} }

@media (prefers-reduced-motion: reduce) {
    .teaser-anim.playing .trow1,
    .teaser-anim.playing .trow2,
    .teaser-anim.playing .trow3 { animation: none; }
}

/* ────────── Pipeline figure: inline SVG (photos + vector boxes/arrows) ──────
   Rebuilt from fig-pipeline.png so labels/arrows stay crisp and the three
   stages can build up on a loop. The wide diagram gets the larger half. */
#pipeline .panel-media { flex: 0 1 58%; }
#pipeline .panel-text { flex: 1 1 40%; }
.pipe-svg { width: 100%; height: auto; max-height: 82vh; display: block; }
/* Inherit the site's system-sans so the diagram matches the page, not a figure. */
.pipe-svg text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; }
.pipe-title    { font-size: 62px; font-weight: 700; fill: var(--text); letter-spacing: -1px; }
.pipe-box-lbl  { font-size: 33px; font-weight: 600; fill: #fff; }
.pipe-cap      { font-size: 31px; font-weight: 500; fill: var(--text); }
.pipe-cap-out  { font-size: 37px; font-weight: 600; fill: var(--text); }
.pipe-arr      { stroke: var(--muted); stroke-width: 5; fill: none; stroke-linecap: round; }
.pipe-arr-dash { stroke-dasharray: 10 8; }
.pipe-arrowhead { fill: var(--muted); }
/* Calm one-shot build-up: from a blank canvas, the stages fade in gently
   Geometry → Semantics → Simulation, hold ~30s, then loop.
   Replays whenever the slide scrolls back into view. */
.pipe-anim.playing .st-geom { animation: pipe-geom 34s ease infinite; }
.pipe-anim.playing .st-sem  { animation: pipe-sem 34s ease infinite; }
.pipe-anim.playing .st-sim  { animation: pipe-sim 34s ease infinite; }
@keyframes pipe-geom { 0% {opacity:0} 1.5% {opacity:1} 96% {opacity:1} 100% {opacity:0} }
@keyframes pipe-sem  { 0%,2% {opacity:0} 3.5% {opacity:1} 96% {opacity:1} 100% {opacity:0} }
@keyframes pipe-sim  { 0%,4% {opacity:0} 5.5% {opacity:1} 96% {opacity:1} 100% {opacity:0} }
/* …but keep the busy marching-ants flowing along the connectors throughout. */
.pipe-anim.playing .pipe-arr { stroke-dasharray: 14 11; animation: pipe-flow 0.7s linear infinite; }
@keyframes pipe-flow { to { stroke-dashoffset: -25; } }
@media (prefers-reduced-motion: reduce) {
    .pipe-anim.playing .st-geom,
    .pipe-anim.playing .st-sem,
    .pipe-anim.playing .st-sim { animation: none; opacity: 1; }
    .pipe-anim.playing .pipe-arr { animation: none; stroke-dasharray: none; }
}

/* ────────── Closed-loop planning: log replay (sbs) over a 2×2 planner grid ──
   Smaller, centered mosaic flanked by nav chevrons so more logs can cycle in. */
.cl-carousel { display: flex; align-items: center; justify-content: center; gap: 4px; }
.cl-nav { flex: 0 0 28px; height: 56px; }   /* inherits .nav look; smaller for this panel */

/* Pagination dots under a video carousel — one per log, the current one filled. */
.cl-dots { display: flex; justify-content: center; gap: 9px; margin-top: 4px; }
.cl-dot {
    width: 9px; height: 9px; padding: 0;
    border: 1px solid var(--muted);
    border-radius: 50%;
    background: transparent;
    cursor: pointer;
    opacity: 0.55;
    transition: opacity 0.15s, background-color 0.15s, border-color 0.15s, transform 0.15s;
}
.cl-dot:hover { opacity: 1; border-color: var(--text); }
.cl-dot.active {
    background: var(--accent);
    border-color: var(--accent);
    opacity: 1;
    transform: scale(1.1);
}
.cl-rollouts { display: flex; flex-direction: column; gap: 14px;
               width: 100%; max-width: 820px; padding: 2px 8px 16px 2px; position: relative; }
/* City chip sits on the top log-replay clip (cl-sbs), inset by the rollouts
   padding (2px) + the clip's border (2px) to stay flush in the corner. */
.cl-rollouts .vid-city { top: 4px; left: 4px; }

/* Closed-loop controls row + full-screen toggle: collapse the text column so
   the planner mosaic fills the whole panel. */
.cl-controls { display: flex; gap: 10px; justify-content: center; align-items: center; flex-wrap: wrap; }
#planning.cl-expanded .panel-text { display: none; }
#planning.cl-expanded .cl-panel { flex: 1 1 100%; max-width: 100%; }
#planning.cl-expanded .cl-rollouts { max-width: 1280px; }
.cl-sbs { width: 100%; max-height: 42vh; object-fit: contain; display: block;
          border: 2px solid #000; border-radius: 5px; background: #000;
          box-shadow: 6px 6px 13px rgba(0, 0, 0, 0.4); }
.cl-methods { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.cl-methods figure { margin: 0; }
.cl-methods video { width: 100%; max-height: 30vh; object-fit: contain; display: block;
                    border: 2px solid #000; border-radius: 5px; background: #000;
                    box-shadow: 6px 6px 13px rgba(0, 0, 0, 0.4); }
.cl-methods figcaption { font-size: 0.72rem; color: var(--muted); text-align: center; margin-top: 4px; }

/* ── Hero 4D-log viewer: same black-bordered, shadowed look as the closed-loop
   mosaic — ego + BEV on top, point cloud and simulation below, ‹ › carousel. */
.hero-4d { flex: 1; min-width: 0; height: 100%; margin: 0;
           display: flex; flex-direction: column; gap: 8px; justify-content: center; }
.hero-4d .cl-carousel { width: 100%; }
/* Size by width so each clip keeps its native aspect — no black letterbox/border. */
.h4d-rollouts { display: flex; flex-direction: column; gap: 12px;
                width: 100%; max-width: 792px; padding: 2px 12px 14px 2px; }
.h4d-main { margin: 0; position: relative; }
.h4d-main video,
.h4d-views video { width: 100%; height: auto; display: block;
                   border: 2px solid #000; border-radius: 5px;
                   box-shadow: 6px 6px 13px rgba(0, 0, 0, 0.4); }
.h4d-views { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; align-items: start; }
.h4d-views figure { margin: 0; }
.h4d-main figcaption,
.h4d-views figcaption { font-size: 0.72rem; color: var(--muted); text-align: center;
                        line-height: 1.3; margin-top: 5px; }

/* ── Reconstruction flythrough: single point-cloud orbit clip in the shared
   ‹ › + dots carousel, same black-bordered, shadowed look as the mosaics. */
.fly-panel { align-items: center; }
.fly-panel .cl-carousel { width: 100%; }
.fly-rollouts { width: 100%; max-width: 620px; padding: 2px 8px 6px 2px; position: relative; }
.fly-rollouts video { width: 100%; height: auto; max-height: 54vh; object-fit: contain;
                      display: block; border: 2px solid #000; border-radius: 5px; background: #000;
                      box-shadow: 6px 6px 13px rgba(0, 0, 0, 0.4); }

/* City-name overlay on a video, echoing the burned-in labels on the teaser
   mosaic stills: white bold text in a translucent dark rounded chip, top-left.
   Sits just inside the clip's 2px border. JS swaps the text per carousel slide. */
.vid-city {
    position: absolute;
    top: 2px;            /* inset by the clip's 2px border so the top/left border frames it */
    left: 2px;
    z-index: 2;
    padding: 4px 11px;
    background: rgba(15, 17, 20, 0.72);
    color: #fff;
    font-weight: 700;
    font-size: 0.95rem;
    line-height: 1.25;
    letter-spacing: 0.01em;
    /* Top & left ride the clip's border; the exposed bottom & right edges carry
       the same 2px black border as the clip, and the corners echo its radius. */
    border-right: 2px solid #000;
    border-bottom: 2px solid #000;
    border-radius: 3px 0 5px 0;   /* top-left nestles the corner; bottom-right is the exposed round */
    pointer-events: none;
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
}
/* The flythrough clip is inset 2px by .fly-rollouts padding, so its border sits
   2px further in — nudge the label to stay flush with it. */
.fly-rollouts .vid-city { top: 4px; left: 4px; }

/* Work-zones panel: text + rare-axes toggle get 45%, the failure mosaic 55%. */
#workzones .panel-media { flex: 0 1 55%; }
#workzones .panel-text { flex: 1 1 45%; }
#workzones .axis-view img { max-height: 30vh; }

/* ────────── Work-zone failure mosaic: exact masonry replica, revealed by year ──
   Cells are absolutely positioned at the box rectangles measured from the
   original collage, so the layout matches the source pixel-for-pixel. */
.wz-mosaic { position: relative; width: 100%; aspect-ratio: 2498 / 1309;
             max-height: 78vh; margin-inline: auto; background: #fff; }
.wz-cell { position: absolute; overflow: hidden; }
.wz-cell img { width: 100%; height: 100%; max-height: none; object-fit: cover;
               display: block; border: 0; border-radius: 0; background: none; }

/* Cumulative reveal: each year fades in on a shared 16s loop, 2022→2026, then resets. */
/* Calm one-shot reveal: from blank, cells fade in by year 2022 → 2026, hold ~30s, then loop. */
.wz-anim.playing .y2022 { animation: wz-2022 35s ease infinite; }
.wz-anim.playing .y2023 { animation: wz-2023 35s ease infinite; }
.wz-anim.playing .y2024 { animation: wz-2024 35s ease infinite; }
.wz-anim.playing .y2025 { animation: wz-2025 35s ease infinite; }
.wz-anim.playing .y2026 { animation: wz-2026 35s ease infinite; }
@keyframes wz-2022 { 0% {opacity:0} 1.5% {opacity:1} 96% {opacity:1} 100% {opacity:0} }
@keyframes wz-2023 { 0%,2% {opacity:0} 3.5% {opacity:1} 96% {opacity:1} 100% {opacity:0} }
@keyframes wz-2024 { 0%,4% {opacity:0} 5.5% {opacity:1} 96% {opacity:1} 100% {opacity:0} }
@keyframes wz-2025 { 0%,6% {opacity:0} 7.5% {opacity:1} 96% {opacity:1} 100% {opacity:0} }
@keyframes wz-2026 { 0%,8% {opacity:0} 9.5% {opacity:1} 96% {opacity:1} 100% {opacity:0} }
@media (prefers-reduced-motion: reduce) {
    .wz-anim.playing .wz-cell { animation: none; opacity: 1; }
}

/* ────────── Verification figure: three paradigms, redrawn as inline SVG ──────
   Node-and-arrow diagram drawn in the site accent (= the paper's darkcerulean).
   Paradigm (c) is ours — highlighted and softly glowing. */
.vp-fig { justify-content: center; }
.vp-svg {
    width: 100%;
    height: auto;
    max-height: 74vh;
    display: block;
}
/* Inherit the site's system-sans so the diagram doesn't read as a LaTeX figure. */
.vp-svg text {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
}
/* Nodes: white cards with the site's soft gray border + rounded corners. */
.vp-box   { fill: #ffffff; stroke: var(--border); stroke-width: 1; }
.vp-lbl   { fill: var(--text); font-size: 9.5px; font-weight: 500; }
/* Endpoints: accent-filled pills, like the link buttons. */
.vp-out   { fill: var(--accent); stroke: none; }
.vp-out-lbl { fill: #ffffff; font-weight: 600; }
/* Collection boundary: subtle dashed gray. */
.vp-bound { fill: none; stroke: var(--border); stroke-width: 1; stroke-dasharray: 4 3; }
/* Arrows: muted, a touch thicker, round caps. */
.vp-arr   { stroke: var(--muted); stroke-width: 1.2; fill: none; stroke-linecap: round; }
.vp-arrowhead        { fill: var(--muted); }
.vp-arrowhead-accent { fill: var(--accent); }
/* (a)/(b) titles read like the panel eyebrows: uppercase, muted. */
.vp-title { fill: var(--muted); font-weight: 700; font-size: 8.5px; letter-spacing: 0.4px; text-transform: uppercase; }
.vp-elbl  { fill: var(--muted); font-size: 7.5px; }
.vp-note  { fill: var(--muted); font-size: 8.5px; font-style: italic; }
.vp-badge { fill: #ffffff; font-weight: 700; font-size: 7.5px; letter-spacing: 0.5px; }
.vp-badge-pill { fill: var(--accent); }

/* Animation (toggled by .playing on .vp-fig): the diagram builds up a → b → c,
   then the caption, on a shared 10s loop, while the arrows march toward the
   Data quality pills, which pulse softly as the signal arrives. When paused,
   everything is shown statically. */
.vp-fig.playing .vp-arr {
    stroke-dasharray: 4 4;
    animation: vp-flow 0.6s linear infinite;
}
@keyframes vp-flow { to { stroke-dashoffset: -8; } }

.vp-fig.playing .vp-out { animation: vp-pulse 1.8s ease-in-out infinite; }
@keyframes vp-pulse {
    0%, 100% { filter: drop-shadow(0 0 0 rgba(8, 69, 125, 0)); }
    50%      { filter: drop-shadow(0 0 3px rgba(8, 69, 125, 0.65)); }
}

/* Staged reveal — each group fades in after the previous, caption last. */
/* Build a → b → c → caption from blank, hold ~30s, then loop. */
.vp-fig.playing .vp-g1  { animation: vp-stage-a   40s ease infinite; }
.vp-fig.playing .vp-g2  { animation: vp-stage-b   40s ease infinite; }
.vp-fig.playing .vp-g3  { animation: vp-stage-c   40s ease infinite; }
.vp-fig.playing .vp-cap { animation: vp-stage-cap 40s ease infinite; }
@keyframes vp-stage-a   { 0% {opacity:0} 2% {opacity:1} 96% {opacity:1} 100% {opacity:0} }
@keyframes vp-stage-b   { 0%,5% {opacity:0} 7% {opacity:1} 96% {opacity:1} 100% {opacity:0} }
@keyframes vp-stage-c   { 0%,10% {opacity:0} 12% {opacity:1} 96% {opacity:1} 100% {opacity:0} }
@keyframes vp-stage-cap {
    0%,14% {opacity:0; transform:translateY(6px)}
    16%,96% {opacity:1; transform:translateY(0)}
    100% {opacity:0; transform:translateY(6px)}
}

@media (prefers-reduced-motion: reduce) {
    .vp-fig.playing .vp-arr,
    .vp-fig.playing .vp-out,
    .vp-fig.playing .vp-g1,
    .vp-fig.playing .vp-g2,
    .vp-fig.playing .vp-g3,
    .vp-fig.playing .vp-cap { animation: none; }
    .vp-fig.playing .vp-arr { stroke-dasharray: none; }
}

/* Play/pause control under the diagram. */
.vp-anim-toggle {
    align-self: center;
    margin-top: 6px;
    border: 1px solid var(--border);
    background: #fff;
    color: var(--muted);
    font: inherit;
    font-size: 0.72rem;
    padding: 4px 12px;
    border-radius: 999px;
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s;
}
.vp-anim-toggle:hover { border-color: var(--accent); color: var(--accent); }

/* (c) — ours: accent-bordered cards in a glowing accent container. */
.vp-ours .vp-title { fill: var(--accent); }
.vp-ours .vp-box   { stroke: var(--accent); }
.vp-ours-bg {
    fill: rgba(8, 69, 125, 0.05);
    stroke: var(--accent);
    stroke-width: 1.2;
    filter: drop-shadow(0 0 6px rgba(8, 69, 125, 0.45))
            drop-shadow(0 0 16px rgba(15, 124, 139, 0.3));
}

/* ────────── Glowing dot rail (one dot per panel, fixed bottom-right) ───────── */
.deck-rail {
    position: fixed;
    right: 22px;
    bottom: 20px;
    display: flex;
    flex-direction: row;
    gap: 12px;
    z-index: 900;
}
.deck-dot {
    position: relative;
    width: 11px;
    height: 11px;
    padding: 0;
    border-radius: 50%;
    border: 1.5px solid var(--muted);
    background: transparent;
    cursor: pointer;
    transition: background 0.2s, border-color 0.2s, box-shadow 0.2s, transform 0.2s;
}
.deck-dot:hover { border-color: var(--link); transform: scale(1.2); }
.deck-dot.active {
    background: var(--accent);
    border-color: var(--accent);
    box-shadow: 0 0 7px rgba(8, 69, 125, 0.65),
                0 0 16px rgba(15, 124, 139, 0.4);
}
.deck-dot-label {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    white-space: nowrap;
    font-size: 0.72rem;
    color: var(--text);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 3px 8px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.deck-dot:hover .deck-dot-label { opacity: 1; }

/* ────────── Citation panel ────────── */
.panel-cite {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--pad);
}
.cite-inner {
    max-width: 780px;
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 18px;
    text-align: center;
}
.cite-inner h2 {
    margin: 0;
    font-size: clamp(1.8rem, 3vw, 2.6rem);
    font-weight: 700;
    letter-spacing: -0.02em;
}
.cite-inner h2 .accent { color: var(--accent); }
.cite-links { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.cite-links a {
    display: inline-block;
    padding: 6px 16px;
    background: var(--text);
    color: #fff;
    text-decoration: none;
    border-radius: 999px;
    font-size: 0.85rem;
    transition: background 0.15s;
}
.cite-links a:hover { background: var(--accent); }
.bibtex {
    width: 100%;
    margin: 0;
    text-align: left;
    background: #0f1115;
    color: #e6e6e6;
    border-radius: 10px;
    padding: 20px 22px;
    font-size: 0.82rem;
    line-height: 1.5;
    overflow-x: auto;
}
.bibtex code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.cite-foot { max-width: 560px; font-size: 0.78rem; color: var(--muted); }
.cite-foot p { margin: 0 0 8px; line-height: 1.55; }
.cite-foot a { color: var(--link); }
.cite-foot .footer-credit { margin-top: 14px; }

/* ────────── Coverage counterpoint (ROADWork4D map + stats) ──────────────────
   Light stats sub-panel on the left (40%, same palette as the hero above), dark
   city-scale map on the right (60%). */
.coverage {
    display: flex;
    width: 100%;
    height: 100vh;
    min-height: 600px;
    background: var(--bg);
    color: var(--text);
}
.coverage-map {
    flex: 0 0 60%;
    order: 1;                 /* stats on the left, map on the right */
    display: flex;
    padding: var(--pad);      /* inset the card from the panel edges, like panel 1's stage */
}
/* The dark map card — mirrors panel 1's .stage (bordered, rounded). Its chrome
   uses the Dash2Sim blue (brightened so it reads on the near-black basemap) in
   place of the page's teal. */
.cov-stage {
    --muted: #9aa3b2;
    --link: #3a86d8;
    position: relative;
    flex: 1;
    overflow: hidden;
    background: #000;
    border: 1px solid var(--border);
    border-radius: 8px;
}
#coverage-map { position: absolute; inset: 0; }
#coverage-map canvas { display: block; }

/* Map overlay: dynamic per-city readout (top), city navigator (bottom). */
.cov-overlay {
    position: absolute;
    top: 0; left: 0; right: 0;
    padding: 22px 28px;
    pointer-events: none;
    z-index: 10;
    background: linear-gradient(180deg, rgba(0, 0, 0, 0.55), transparent 80%);
}
#cov-current-city {
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--link);
    min-height: 20px;
}
#cov-city-stats {
    margin-top: 6px;
    font-size: 0.85rem;
    color: var(--muted);
    letter-spacing: 0.02em;
    min-height: 18px;
}
#cov-city-stats .stat + .stat::before {
    content: "·";
    margin: 0 6px;
    opacity: 0.6;
}

#cov-pillbar {
    position: absolute;
    left: 0; right: 0; bottom: 22px;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    padding: 0 16px;
    z-index: 10;
    pointer-events: none;
}
.cov-pill, #cov-play {
    pointer-events: auto;
    background: transparent;
    border: 1px solid rgba(154, 163, 178, 0.45);
    color: var(--muted);
    font-size: 11.5px;
    letter-spacing: 0.02em;
    padding: 6px 10px;
    border-radius: 999px;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s, color 0.12s, box-shadow 0.16s;
    user-select: none;
}
.cov-pill:hover, #cov-play:hover { color: var(--link); border-color: var(--link); }
#cov-play { margin-left: 10px; padding: 6px 12px; }
.cov-pill.active {
    background: var(--link);
    border-color: var(--link);
    color: #fff;
    box-shadow: 0 0 16px rgba(58, 134, 216, 0.5);
}

.cov-attrib {
    position: absolute;
    bottom: 4px; right: 8px;
    font-size: 10px;
    color: var(--muted);
    opacity: 0.7;
    z-index: 10;
}
.cov-attrib a { color: var(--muted); text-decoration: none; }
.cov-attrib a:hover { color: var(--link); }
.cov-error { position: absolute; top: 60px; left: 28px; color: #f87171; z-index: 10; }

/* Verified-only switch: a pill that slides red → green left-to-right. */
.cov-toggle {
    position: absolute;
    top: 22px; right: 28px;
    z-index: 11;
    pointer-events: auto;
    display: flex;
    align-items: center;
    gap: 9px;
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid rgba(154, 163, 178, 0.35);
    border-radius: 999px;
    padding: 5px 8px 5px 12px;
    cursor: pointer;
    font: inherit;
    font-size: 12px;
    letter-spacing: 0.02em;
    color: var(--muted);
    transition: color 0.15s, border-color 0.15s;
}
.cov-toggle:hover { border-color: var(--link); }
.cov-toggle.on { color: #fff; }
.cov-toggle-track {
    position: relative;
    flex: none;
    width: 38px; height: 20px;
    border-radius: 999px;
    background: #c8503f;                 /* off — red */
    transition: background 0.2s ease;
}
.cov-toggle-knob {
    position: absolute;
    top: 2px; left: 2px;
    width: 16px; height: 16px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
    transition: transform 0.2s ease;
}
.cov-toggle.on .cov-toggle-track {
    background: #2fb37f;                 /* on — green */
    box-shadow: 0 0 12px rgba(47, 179, 127, 0.5);
}
.cov-toggle.on .cov-toggle-knob { transform: translateX(18px); }
.coverage-stats {
    flex: 0 0 40%;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 14px;
    padding: 28px 36px;
    overflow-y: auto;
    text-align: center;
}
.cs-head h2 {
    font-size: clamp(1.5rem, 2.2vw, 2.1rem);
    font-weight: 700;
    letter-spacing: -0.02em;
    margin: 0;
    color: var(--text);
}
.cs-head h2 .accent { color: var(--accent); }
.cs-tagline {
    margin: 5px 0 0;
    font-size: 1rem;
    font-weight: 500;
    color: var(--text);
    letter-spacing: -0.01em;
}
.cs-sub {
    margin: 8px 0 0;
    color: var(--muted);
    font-size: 0.92rem;
    line-height: 1.5;
}
.cs-sub strong { color: var(--text); font-weight: 600; }
.cs-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 13px 18px;
    margin: 0;
}
.cs-item { margin: 0; }
.cs-item dt {
    font-size: 0.85rem;
    color: var(--muted);
    margin-bottom: 6px;
}
.cs-item dd {
    margin: 0;
    font-size: clamp(1.2rem, 1.7vw, 1.5rem);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.cs-item dd .unit {
    font-size: 0.5em;
    font-weight: 500;
    color: var(--accent);
    margin-left: 3px;
}
.cs-foot {
    margin: 0;
    padding-top: 14px;
    border-top: 1px solid var(--border);
    font-size: 0.82rem;
    color: var(--muted);
    line-height: 1.5;
    text-align: center;
}
.cs-foot strong { color: var(--text); }
.cs-foot em { font-style: normal; }
/* 2D-perception pointer: its own centered line under the verified-subset line. */
.cs-2d {
    margin: 8px 0 0;
    font-size: 0.85rem;
    color: var(--muted);
    line-height: 1.55;
    text-align: center;
}
/* ROADWork link: "ROAD" Cornell red (via .road), "Work" black — not link blue. */
.rw-link, .rw-link:hover { color: var(--text); text-decoration: underline; }

/* ────────── "Coming soon" toast ──────────
   Same pill language as the .links buttons (accent fill, white text, fully
   rounded), with the accent-tinted glow used by the scroll cue. */
.toast {
    position: fixed;
    left: 50%;
    bottom: var(--pad);
    transform: translate(-50%, 12px);
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: var(--accent);
    color: #fff;
    padding: 9px 16px;
    border-radius: 999px;
    font-size: 0.85rem;
    font-weight: 500;
    letter-spacing: 0.01em;
    box-shadow: 0 6px 20px rgba(8, 69, 125, 0.35),
                0 0 0 1px rgba(255, 255, 255, 0.06) inset;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease, transform 0.2s ease;
    z-index: 1000;
}
.toast.show {
    opacity: 1;
    transform: translate(-50%, 0);
}
.toast svg { flex: none; opacity: 0.85; }

/* ────────── Responsive ────────── */
@media (max-width: 900px) {
    /* Relax the snap deck into a normal scrolling column on small screens. */
    .deck {
        height: auto;
        overflow-y: visible;
        scroll-snap-type: none;
    }
    .panel { height: auto; min-height: 100vh; scroll-snap-align: none; }

    /* Split panels stack: media on top, text below. */
    .panel-split { flex-direction: column; justify-content: center; gap: 20px; }
    .panel-split.flip { flex-direction: column; }
    .panel-media, .panel-text { flex: none; width: 100%; }
    .panel-media img { max-height: 56vh; }

    /* Slim the dot rail: glowing dots only, labels suppressed. */
    .deck-rail { right: 10px; gap: 11px; }
    .deck-dot-label { display: none; }

    .panel-hero {
        height: auto;
        overflow: visible;
    }
    .stage-area {
        position: relative;
        top: auto; bottom: auto; left: auto; right: auto;
        height: 60vh;
        margin: 16px;
    }
    .info {
        position: relative;
        top: auto; bottom: auto; left: auto;
        width: auto;
        padding: 16px;
    }
    .info h1 { font-size: 1.5rem; }

    .coverage {
        flex-direction: column;
        height: auto;
        min-height: 0;
    }
    .coverage-map {
        flex: none;
        height: 60vh;
        padding: 16px;
    }
    .coverage-stats { flex: none; padding: 32px 20px; }
}
