/* SPDX-License-Identifier: Apache-2.0 */
/* Copyright (C) 2026 Zixiao Wang */

/* ============================================================================
   VOID — summoned interface.

   Negative space is the primary material. The resting survey is the frozen
   globe plus a single line of text; every control is stripped of its box and
   reduced to bare translucent ink, legible over the bright disc by a halo the
   type casts on itself — never a plate beneath it. The full readout, hud rows
   3-5, and the nav cluster are deferred: they bloom only while the summon
   gesture (hold Shift / hover / tap latch) is engaged and dissolve on release.

   Every rule is scoped under html[data-ui-theme='void']. The globe pipeline is
   frozen: nothing here targets #globeGL / #globeGL2 / #dotfield / #globe or
   changes #stage geometry, and the reserved variables --mono / --serif /
   --dot-ink / --grid-alpha-settle are never redefined. --signal is not
   reserved, so VOID overrides it in scope to swap the legacy warm amber for a
   single cold "live" accent on the shared chrome.
   ============================================================================ */

html[data-ui-theme='void'] {
    /* Surfaces — VOID has almost none; kept transparent on purpose. */
    --v-scrim: rgba(0, 0, 0, 0.3); /* faint wash behind the summoned readout only */
    --v-hairline: rgba(255, 255, 255, 0.08); /* the sole rule weight, summon-only  */

    /* Ink hierarchy — translucent white, four steps. */
    --v-ink: rgba(255, 255, 255, 0.92); /* critical: coords, lead line   */
    --v-ink-2: rgba(255, 255, 255, 0.62); /* values, secondary readouts  */
    --v-ink-3: rgba(255, 255, 255, 0.34); /* labels, tracked caps        */
    --v-ink-4: rgba(255, 255, 255, 0.18); /* subliminal: enter, footnote */

    /* One cold accent — live / connected micro-states ONLY. */
    --v-live: #8fb4c4; /* glacier */
    --v-live-soft: rgba(143, 180, 196, 0.5);

    /* Live longitude rails (shared rail-engine): numeral size + rail alpha.
       The centre index is exempt from the dimmed alpha — a live mark reads
       full strength — and carries its own RED ink (the one warm mark on the
       cold plate, pointing at the meridian under the crosshair). */
    --v-rail-fs: 7px;
    --v-rail-alpha: 0.8;
    --v-rail-index: #d5453a;

    /* Scoped override of the shared chrome accent (legacy warm #e8b04a → cold).
       app.js no longer reads --signal from CSS, so this only retints the DOM
       chrome (needle, sound-on, first gauge, ws-dot). */
    --signal: var(--v-live);
    --signal-hot: #b7d4de;

    /* Typeset UI + data faces. No serif anywhere (anti-convergence). */
    --v-ui: 'Inter', system-ui, -apple-system, sans-serif;
    --v-data: 'IBM Plex Mono', ui-monospace, 'SF Mono', monospace;

    /* Modular scale — deliberately tiny; nothing exceeds 14px. */
    --v-fs-micro: 7px;
    --v-fs-label: 8.5px;
    --v-fs-body: 10px;
    --v-fs-data: 11px;
    --v-fs-lead: 13px;
    --v-fs-title: 14px;

    /* Tracking. */
    --v-track-caps: 0.22em;
    --v-track-body: 0.06em;
    --v-track-data: 0.01em;

    /* Spacing — generous; space is the material. */
    --v-s1: 4px;
    --v-s2: 8px;
    --v-s3: 16px;
    --v-s4: 24px;
    --v-s5: 40px;
    --v-s6: 64px;
    --v-inset: clamp(16px, 4vw, 48px);

    /* Safe-area-aware insets. env() resolves to 0 on non-notched hardware, so
       desktop stays pixel-identical; on notched phones the corner-anchored
       chrome clears the notch / home indicator. */
    --v-inset-t: calc(var(--v-inset) + env(safe-area-inset-top));
    --v-inset-r: calc(var(--v-inset) + env(safe-area-inset-right));
    --v-inset-b: calc(var(--v-inset) + env(safe-area-inset-bottom));
    --v-inset-l: calc(var(--v-inset) + env(safe-area-inset-left));

    /* Legibility halo — cast by the type itself, never a box beneath it.
       Overlay-text shadow only; touches no canvas pixel. */
    --v-halo: 0 0 8px rgba(0, 0, 0, 0.55), 0 1px 2px rgba(0, 0, 0, 0.7);
}

/* Kill the whole-surface graphite grain + vignette — VOID trusts the void. */
html[data-ui-theme='void'] body::before,
html[data-ui-theme='void'] body::after {
    display: none;
}

/* ---------------------------------------------------------------------------
   State A — landing. The invitation, a subliminal wordmark, one copyright line.
   --------------------------------------------------------------------------- */

/* Enter chip destroyed: no fill, border, radius, blur, shadow, or float pulse.
   Full hit area retained; nearly subliminal type. */
html[data-ui-theme='void'] .enter {
    bottom: calc(var(--v-s5) + env(safe-area-inset-bottom));
    gap: var(--v-s1);
    padding: var(--v-s3) var(--v-s4);
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    color: var(--v-ink-4);
    font-family: var(--v-ui);
    letter-spacing: var(--v-track-caps);
    animation: chipIn var(--dur-reveal) var(--ease-out) 0.5s backwards;
    transition:
        opacity var(--dur-move) var(--ease-out),
        transform var(--dur-move) var(--ease-out),
        color var(--dur-quick) var(--ease-out),
        visibility 0s 0s;
}
/* Suppress the refusal-pulse box-shadow bloom; keep the entrance only. */
html[data-ui-theme='void'] .enter.is-pulse {
    animation: chipIn var(--dur-reveal) var(--ease-out) 0.5s backwards;
    border: 0;
}
html[data-ui-theme='void'] .enter:hover,
html[data-ui-theme='void'] .enter:focus-visible {
    background: none;
    border: 0;
    box-shadow: none;
    color: var(--v-ink-2);
}
html[data-ui-theme='void'] .enter:focus-visible {
    box-shadow: none;
    outline: 1px solid var(--v-hairline);
    outline-offset: var(--v-s2);
}
html[data-ui-theme='void'] .enter__mark {
    font-size: var(--v-fs-label);
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: var(--v-track-caps);
    color: inherit;
    text-shadow: var(--v-halo);
}
html[data-ui-theme='void'] .enter__sub {
    font-size: var(--v-fs-micro);
    font-weight: 300;
    letter-spacing: var(--v-track-caps);
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.12);
    text-shadow: var(--v-halo);
}

/* Landing whisper — the legacy rule still parks it at bottom: calc(12% + 74px)
   with no VOID override. Anchor it above the repositioned enter chip so the
   two stay stacked at every width (and clear the home indicator). */
html[data-ui-theme='void'] .whisper {
    bottom: calc(var(--v-s5) + 74px + env(safe-area-inset-bottom));
}

/* Wordmark: hide the hand-drawn SVG; a typeset micro-mark injected by theme.js
   replaces it. The plate keeps fading with the base .is-desk rule. */
html[data-ui-theme='void'] .plate {
    top: var(--v-inset-t);
    left: var(--v-inset-l);
    width: auto;
    animation: none;
}
html[data-ui-theme='void'] .plate__title {
    width: auto;
    color: var(--v-ink-4);
}
html[data-ui-theme='void'] .plate__signature {
    display: none;
}
html[data-ui-theme='void'] .v-wordmark {
    display: block;
    font-family: var(--v-ui);
    font-size: var(--v-fs-label);
    font-weight: 400;
    letter-spacing: var(--v-track-caps);
    text-transform: uppercase;
    color: var(--v-ink-4);
    text-shadow: var(--v-halo);
    white-space: nowrap;
}
/* Drop the relief legend — not part of the whisper. */
html[data-ui-theme='void'] #rampLabel {
    display: none;
}

/* Attribution re-anchored to the bottom-left corner; sources suppressed. */
html[data-ui-theme='void'] .plate__meta {
    top: auto;
    bottom: var(--v-inset-b);
    left: var(--v-inset-l);
    width: auto;
    animation: none;
}
html[data-ui-theme='void'] .plate__copyright {
    font-size: var(--v-fs-micro);
    line-height: 1.6;
    letter-spacing: var(--v-track-caps);
    color: var(--v-ink-4);
    text-shadow: var(--v-halo);
}
html[data-ui-theme='void'] .plate__sources {
    display: block;
    margin-top: var(--v-s1);
    font-size: var(--v-fs-micro);
    line-height: 1.6;
    /* The footnote's footnote: one step below --v-ink-4, so hierarchy comes
       from letterform posture, not size. */
    text-transform: uppercase;
    letter-spacing: var(--v-track-body);
    color: rgba(255, 255, 255, 0.13);
    text-shadow: var(--v-halo);
}
html[data-ui-theme='void'] .plate__credit {
    margin-top: 0;
    font-size: var(--v-fs-micro);
    line-height: 1.6;
    letter-spacing: var(--v-track-caps);
    color: var(--v-ink-4);
    text-shadow: var(--v-halo);
}

/* ---------------------------------------------------------------------------
   Live longitude rails — the shared graticule instrument (rail-engine.js)
   in VOID's voice: no frame, no corner marks, whisper ink at the screen's
   very edges. Ticks/labels are drawn on these theme-owned canvases; the
   glacier centre index marks the meridian under the crosshair.
   --------------------------------------------------------------------------- */
html[data-ui-theme='void'] .v-rails {
    position: fixed;
    inset: 0;
    z-index: 3;
    pointer-events: none;
}
html[data-ui-theme='void'] .v-rails__canvas {
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    width: 100%;
    height: 17px;
}
html[data-ui-theme='void'] .v-rails__canvas[data-edge='top'] {
    top: 0;
}
html[data-ui-theme='void'] .v-rails__canvas[data-edge='bottom'] {
    bottom: 0;
}

/* ---------------------------------------------------------------------------
   Sound toggle — a bare glyph whisper, far-left, vertically centered.
   Persistent (sound is core) but stripped to ink; retains the live breath.
   --------------------------------------------------------------------------- */
html[data-ui-theme='void'] .soundrail {
    left: var(--v-inset-l);
}
html[data-ui-theme='void'] .sound-toggle {
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    color: var(--v-ink-3);
    font-family: var(--v-ui);
    transition: color var(--dur-quick) var(--ease-out);
}
html[data-ui-theme='void'] .sound-toggle:hover,
html[data-ui-theme='void'] .sound-toggle:focus-visible {
    background: none;
    border: 0;
    box-shadow: none;
    color: var(--v-ink);
}
html[data-ui-theme='void'] .sound-toggle:focus-visible {
    outline: 1px solid var(--v-hairline);
    outline-offset: var(--v-s2);
}
html[data-ui-theme='void'] .sound-toggle__mark {
    margin: 0;
    font-size: 15px;
    text-shadow: var(--v-halo);
}
/* "sound" word hidden — the glyph alone carries it. */
html[data-ui-theme='void'] .sound-toggle__label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
html[data-ui-theme='void'] .sound-toggle[data-on='1'] {
    background: none;
    border: 0;
    color: var(--v-live);
}
html[data-ui-theme='void'] .sound-toggle[data-on='1']:hover,
html[data-ui-theme='void'] .sound-toggle[data-on='1']:focus-visible {
    border: 0;
    color: var(--signal-hot);
}

/* ---------------------------------------------------------------------------
   Cursor HUD — bare type, no panel; follows the survey cursor (base translate).
   Rest shows only coord + biome name; rows 3-5 are summon-only.
   #hud stays a direct child of #stage — never re-parented here.
   --------------------------------------------------------------------------- */
html[data-ui-theme='void'] .hud {
    min-width: 0;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    color: var(--v-ink);
    font-size: var(--v-fs-data);
    letter-spacing: var(--v-track-data);
    text-shadow: var(--v-halo);
}
html[data-ui-theme='void'] .hud__row {
    gap: var(--v-s1);
    line-height: 1.6;
}
html[data-ui-theme='void'] .hud__coord {
    font-family: var(--v-data);
    font-size: var(--v-fs-data);
    color: var(--v-ink);
    font-variant-numeric: tabular-nums;
}
html[data-ui-theme='void'] .hud__biome {
    font-family: var(--v-ui);
    font-size: var(--v-fs-body);
    letter-spacing: var(--v-track-body);
    color: var(--v-ink-2);
}
/* Swatch reduced to a 6px tick. */
html[data-ui-theme='void'] .hud__swatch {
    width: 6px;
    height: 6px;
    border-radius: 0;
    border: 0;
}
/* Rows 3-5 deferred; a soft dissolve on release. */
html[data-ui-theme='void'] .hud__data,
html[data-ui-theme='void'] .hud__human,
html[data-ui-theme='void'] .hud__climate {
    font-family: var(--v-data);
    font-size: var(--v-fs-body);
    color: var(--v-ink-3);
    opacity: 0;
    transition: opacity var(--dur-quick) var(--ease-out);
}
html[data-ui-theme='void'] .hud__human .hud__light {
    color: var(--v-live);
}
html[data-ui-theme='void'] .hud__climate .hud__arid {
    color: var(--v-ink-3);
}
/* The "est." tag loses its bordered chip — bare tracked caps. */
html[data-ui-theme='void'] .hud__climate .hud__proxy {
    font-size: var(--v-fs-micro);
    letter-spacing: var(--v-track-caps);
    color: var(--v-ink-4);
    border: 0;
    border-radius: 0;
    padding: 0;
    opacity: 1;
}

/* ---------------------------------------------------------------------------
   Nav cluster — chrome stripped to bare glyphs, bottom-right at the inset.
   Rest at --v-ink-4; lifts to --v-ink-2 on hover of the cluster or on summon.
   --------------------------------------------------------------------------- */
html[data-ui-theme='void'] .nav {
    right: var(--v-inset-r);
    bottom: var(--v-inset-b);
    gap: var(--v-s2);
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
}
html[data-ui-theme='void'] .nav__btn {
    width: auto;
    height: auto;
    padding: var(--v-s1);
    background: none;
    border: 0;
    border-radius: 0;
    color: var(--v-ink-4);
    font-family: var(--v-data);
    font-size: 13px;
    text-shadow: var(--v-halo);
    transition: color var(--dur-quick) var(--ease-out);
}
html[data-ui-theme='void'] .nav__btn:hover,
html[data-ui-theme='void'] .nav__btn:focus-visible {
    background: none;
    border: 0;
    color: var(--v-ink);
}
html[data-ui-theme='void'] .nav__btn:active {
    background: none;
    border: 0;
    transform: none;
}
html[data-ui-theme='void'] .nav__btn:focus-visible {
    box-shadow: none;
    outline: 1px solid var(--v-hairline);
    outline-offset: 2px;
}
html[data-ui-theme='void'] .nav__zoom {
    font-family: var(--v-data);
    font-size: var(--v-fs-micro);
    letter-spacing: var(--v-track-data);
    color: var(--v-ink-4);
    text-shadow: var(--v-halo);
    transition: color var(--dur-quick) var(--ease-out);
}
/* Compass reduced to a tiny mark; needle kept, retinted cold. */
html[data-ui-theme='void'] .nav__compass {
    width: auto;
    height: auto;
    margin-top: var(--v-s1);
    border-radius: 0;
}
html[data-ui-theme='void'] .nav__needle {
    border-left-width: 3px;
    border-right-width: 3px;
    border-bottom: 9px solid var(--v-ink-3);
    margin-top: 0;
}
html[data-ui-theme='void'] .nav__needle.is-home {
    border-bottom-color: var(--v-live);
}
html[data-ui-theme='void'] .nav__dist {
    font-size: var(--v-fs-micro);
    letter-spacing: var(--v-track-data);
    color: var(--v-ink-4);
    text-shadow: var(--v-halo);
    transition: color var(--dur-quick) var(--ease-out);
}
/* Cluster lift on hover (pure CSS) — mirrors the summon lift. */
html[data-ui-theme='void'] .nav:hover .nav__btn,
html[data-ui-theme='void'] .nav:hover .nav__zoom,
html[data-ui-theme='void'] .nav:hover .nav__dist {
    color: var(--v-ink-2);
}

/* ---------------------------------------------------------------------------
   Hint (graticule readout) — one faint tracked line, bottom-center.
   --------------------------------------------------------------------------- */
html[data-ui-theme='void'] .hint {
    bottom: calc(var(--v-s4) + env(safe-area-inset-bottom));
    font-family: var(--v-ui);
    font-size: var(--v-fs-body);
    font-weight: 300;
    letter-spacing: var(--v-track-caps);
    color: var(--v-ink-4);
    text-shadow: var(--v-halo);
}
/* Cap the base .is-in reveal opacity low — keep it a whisper. */
html[data-ui-theme='void'] .hint.is-in {
    opacity: 0.7;
}

/* ---------------------------------------------------------------------------
   Status line — single micro tracked line, above the attribution.
   Etch flip animation kept (opacity only).
   --------------------------------------------------------------------------- */
html[data-ui-theme='void'] .scalebar {
    left: var(--v-inset-l);
    /* Clear the two-line attribution stack below (~28px) plus one --v-s4
       (24px) of deliberate space — the status line is an instrument
       readout, not part of the colophon. */
    bottom: calc(var(--v-inset-b) + 52px);
    animation: none;
}
html[data-ui-theme='void'] .scalebar__label {
    font-family: var(--v-ui);
    font-size: var(--v-fs-label);
    font-weight: 400;
    letter-spacing: var(--v-track-caps);
    color: var(--v-ink-3);
    text-shadow: var(--v-halo);
}

/* ---------------------------------------------------------------------------
   Info-panel — the whole survey readout. Rest collapses to ONE lead line;
   everything else is summon-only. No border / fill / blur / shadow in any
   state. A faint radial scrim fades in behind the summoned block only.
   The base .is-in / .is-desk rules drive the panel's own opacity; VOID only
   restyles the box away and gates the deferred blocks by out-specificity.
   --------------------------------------------------------------------------- */
html[data-ui-theme='void'] .info-panel {
    top: var(--v-inset-t);
    right: var(--v-inset-r);
    width: 260px;
    /* dvh tracks the visible viewport under a mobile browser's collapsing
       toolbar, so the summoned readout never clips behind it. auto-scroll
       lets the bloom scroll internally once it outgrows the cap on phones. */
    max-height: calc(100dvh - 2 * var(--v-inset) - 96px);
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    color: var(--v-ink);
}
/* Right-anchored ambient wash — soft radial, summon-only; no hard box. */
html[data-ui-theme='void'] .info-panel::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: radial-gradient(130% 90% at 100% 30%, var(--v-scrim), transparent 72%);
    opacity: 0;
    transition: opacity var(--dur-move) var(--ease-out);
}

/* Header: eyebrow label over the lead line, ws-dot beside it. Always shown. */
html[data-ui-theme='void'] .info-panel__header {
    gap: var(--v-s3);
    padding-bottom: 0;
    border-bottom: 0;
}
html[data-ui-theme='void'] .info-panel__eyebrow {
    margin-bottom: var(--v-s1);
    font-family: var(--v-ui);
    font-size: var(--v-fs-label);
    font-weight: 400;
    letter-spacing: var(--v-track-caps);
    color: var(--v-ink-3);
    text-shadow: var(--v-halo);
}
/* The lead line — brightest text in the survey; VOID always shows it. */
html[data-ui-theme='void'] .info-panel__title {
    font-family: var(--v-ui);
    font-weight: 400;
    font-size: var(--v-fs-lead);
    line-height: 1.25;
    letter-spacing: var(--v-track-body);
    color: var(--v-ink);
    text-shadow: var(--v-halo);
    cursor: pointer;
}
html[data-ui-theme='void'] .info-panel__link {
    margin-top: 2px;
    font-family: var(--v-ui);
    font-size: var(--v-fs-micro);
    letter-spacing: var(--v-track-caps);
    color: var(--v-ink-3);
    text-shadow: var(--v-halo);
}
/* ws text hidden by default; the dot alone reads "live". */
html[data-ui-theme='void'] .ws-text {
    display: none;
}
html[data-ui-theme='void'] .ws-status {
    width: 5px;
    height: 5px;
    background: var(--v-ink-4);
    border: 0;
}
html[data-ui-theme='void'] .ws-status.connected {
    background: var(--v-live);
}
html[data-ui-theme='void'] .ws-status.disconnected {
    background: transparent;
    border: 1px solid var(--v-ink-3);
}

/* Deferred blocks — hidden at rest, revealed on summon. */
html[data-ui-theme='void'] .info-panel__metrics,
html[data-ui-theme='void'] .info-panel__gauges,
html[data-ui-theme='void'] .info-panel__section,
html[data-ui-theme='void'] .info-panel__ticker {
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-move) var(--ease-out);
}

/* Metrics — bare label / value pairs, no grid rules. */
html[data-ui-theme='void'] .info-panel__metrics {
    margin-top: var(--v-s4);
    padding-bottom: 0;
    border-bottom: 0;
    row-gap: var(--v-s3);
}
/* Plain `.metric` at (0,2,1) already out-specifies the legacy
   `.metric:nth-child()` border rules at (0,2,0) — no !important needed. */
html[data-ui-theme='void'] .metric {
    padding: 0;
    border: 0;
}
html[data-ui-theme='void'] .metric:nth-child(even) {
    padding-left: var(--v-s3);
}
html[data-ui-theme='void'] .metric:nth-child(n + 3) {
    margin-top: var(--v-s3);
}
html[data-ui-theme='void'] .metric__label {
    margin-bottom: var(--v-s1);
    font-family: var(--v-ui);
    font-size: var(--v-fs-label);
    letter-spacing: var(--v-track-caps);
    color: var(--v-ink-3);
    text-shadow: var(--v-halo);
}
html[data-ui-theme='void'] .metric__value {
    min-height: 0;
    font-family: var(--v-data);
    font-size: var(--v-fs-data);
    line-height: 1.2;
    color: var(--v-ink);
    text-shadow: var(--v-halo);
}

/* Gauges — 1px hairline track, 1px fill line driven by --pct. */
html[data-ui-theme='void'] .info-panel__gauges {
    gap: var(--v-s3);
    padding: var(--v-s4) 0 0;
    border-bottom: 0;
}
html[data-ui-theme='void'] .gauge__label {
    font-family: var(--v-ui);
    font-size: var(--v-fs-label);
    letter-spacing: var(--v-track-caps);
    color: var(--v-ink-3);
    text-shadow: var(--v-halo);
}
html[data-ui-theme='void'] .gauge__track {
    height: 1px;
    background: var(--v-hairline);
}
html[data-ui-theme='void'] .gauge__fill {
    height: 1px;
    background: var(--v-ink-2);
}
html[data-ui-theme='void'] .gauge:first-child .gauge__fill {
    background: var(--v-live);
}
html[data-ui-theme='void'] .gauge__value {
    font-family: var(--v-data);
    font-size: var(--v-fs-micro);
    color: var(--v-ink-2);
    text-shadow: var(--v-halo);
}

/* Land mix — ranked mono list, ruled by the single hairline. */
html[data-ui-theme='void'] .info-panel__section {
    margin-top: var(--v-s4);
}
html[data-ui-theme='void'] .info-panel__section-head {
    font-family: var(--v-ui);
    font-size: var(--v-fs-label);
    letter-spacing: var(--v-track-caps);
    color: var(--v-ink-3);
    text-shadow: var(--v-halo);
}
html[data-ui-theme='void'] .landcover-item {
    min-height: 0;
    padding: var(--v-s2) 0 var(--v-s1);
    border-bottom: 1px solid var(--v-hairline);
}
html[data-ui-theme='void'] .landcover-item::after {
    background: rgba(255, 255, 255, 0.12);
    opacity: 1;
}
html[data-ui-theme='void'] .landcover-item:first-child::after {
    background: var(--v-live-soft);
    opacity: 1;
}
html[data-ui-theme='void'] .landcover-rank {
    font-family: var(--v-data);
    font-size: var(--v-fs-micro);
    color: var(--v-ink-4);
    text-shadow: var(--v-halo);
}
html[data-ui-theme='void'] .landcover-name {
    font-family: var(--v-data);
    font-size: var(--v-fs-body);
    color: var(--v-ink-2);
    text-shadow: var(--v-halo);
}
html[data-ui-theme='void'] .landcover-percent {
    font-family: var(--v-data);
    font-size: var(--v-fs-body);
    color: var(--v-ink);
    text-shadow: var(--v-halo);
}
html[data-ui-theme='void'] .info-panel__footnote {
    margin-top: var(--v-s3);
    font-family: var(--v-data);
    font-size: var(--v-fs-micro);
    color: var(--v-ink-4);
    text-shadow: var(--v-halo);
}

/* Ticker — both loop groups kept, faint at the base. */
html[data-ui-theme='void'] .info-panel__ticker {
    margin-top: var(--v-s4);
    padding-top: var(--v-s2);
    border-top: 1px solid var(--v-hairline);
}
html[data-ui-theme='void'] .ticker__item,
html[data-ui-theme='void'] .ticker__sep {
    font-family: var(--v-ui);
    font-size: var(--v-fs-micro);
    letter-spacing: var(--v-track-body);
    color: var(--v-ink-4);
    text-shadow: var(--v-halo);
}

/* Stale feed — drop the whole readout a step fainter. */
html[data-ui-theme='void'] .info-panel.stale .info-panel__eyebrow,
html[data-ui-theme='void'] .info-panel.stale .info-panel__title,
html[data-ui-theme='void'] .info-panel.stale .metric__value,
html[data-ui-theme='void'] .info-panel.stale .landcover-percent {
    color: var(--v-ink-4);
}

/* ---------------------------------------------------------------------------
   Summon — the core VOID behavior. theme.js sets data-void-summon='1' on
   <html> while the gesture is held (or latched). Reveal is visibility only:
   opacity + pointer-events, never [hidden] / .is-in / .is-desk / hud opacity.
   --------------------------------------------------------------------------- */
html[data-ui-theme='void'][data-void-summon='1'] .hud__data,
html[data-ui-theme='void'][data-void-summon='1'] .hud__human,
html[data-ui-theme='void'][data-void-summon='1'] .hud__climate {
    opacity: 1;
}
html[data-ui-theme='void'][data-void-summon='1'] .info-panel__metrics,
html[data-ui-theme='void'][data-void-summon='1'] .info-panel__gauges,
html[data-ui-theme='void'][data-void-summon='1'] .info-panel__section,
html[data-ui-theme='void'][data-void-summon='1'] .info-panel__ticker {
    opacity: 1;
    pointer-events: auto;
}
html[data-ui-theme='void'][data-void-summon='1'] .info-panel::before {
    opacity: 1;
}
html[data-ui-theme='void'][data-void-summon='1'] .nav__btn,
html[data-ui-theme='void'][data-void-summon='1'] .nav__zoom,
html[data-ui-theme='void'][data-void-summon='1'] .nav__dist {
    color: var(--v-ink-2);
}

/* --------------------------------------------------------------------------
   Phone (≤640) — de-collision only; VOID's ink/scale stay intact. Every rule
   scoped so it out-specifies the VOID desktop base above.
   -------------------------------------------------------------------------- */
@media (max-width: 640px) {
    /* The longitude rails eat the full top/bottom 17px of the viewport. At the
       desktop 16px inset the top-left wordmark jams right under the top rail and
       the side gutters feel pinched. Push the top inset clear of the top rail
       band and widen the side gutters a little. The BOTTOM inset is left at the
       base value on purpose: the centred enter chip and the bottom-left colophon
       share that band in State A, and lifting it would drive them together — the
       bottom-right nav is lifted on its own below instead. */
    html[data-ui-theme='void'] {
        --v-inset-t: calc(16px + 17px + env(safe-area-inset-top));
        --v-inset-l: calc(20px + env(safe-area-inset-left));
        --v-inset-r: calc(20px + env(safe-area-inset-right));
    }
    /* Bottom-right nav (State B): its 5-item column bottoms out on the bottom
       longitude rail at the base inset, so the distance label collides with the
       rail numerals. Lift the whole cluster clear of the rail band. */
    html[data-ui-theme='void'] .nav {
        bottom: calc(var(--v-inset-b) + 20px);
    }
    /* Centre the wordmark across the top of the phone viewport; other
       left-anchored chrome stays put. */
    html[data-ui-theme='void'] .plate {
        left: 0;
        right: 0;
        top: calc(var(--v-inset-t) + 4px);
        text-align: center;
    }
    /* Readout drops below the top-left wordmark, right-anchored, narrowed,
       height-capped so the summoned bloom never reaches the bottom chrome. */
    html[data-ui-theme='void'] .info-panel {
        top: calc(var(--v-inset-t) + 22px);
        right: var(--v-inset-r);
        left: auto;
        width: min(248px, calc(100vw - var(--v-inset-l) - var(--v-inset-r)));
        max-height: 56dvh;
    }
    /* On phones the detail bloom never opens, yet the retracted blocks still
       occupy layout — leaving a tall invisible box in the top-right that
       swallowed globe drags (the stage only starts a drag when the pointer
       lands on the canvas/stage, not on chrome). Make the panel pass-through so
       touches reach the globe beneath it. The base `.stage.is-desk
       .info-panel.is-in` rule (0,4,0) turns pointer-events back on in the
       survey, so this override must out-specify it. pointer-events inherits, so
       the lead line below re-asserts `auto` to keep its touch summon latch. */
    html[data-ui-theme='void'] .stage.is-desk .info-panel.is-in {
        pointer-events: none;
    }
    html[data-ui-theme='void'] .stage.is-desk .info-panel.is-in .info-panel__title {
        pointer-events: auto;
    }
    /* One panel scroll region, not two — kill the inner list scroll. */
    html[data-ui-theme='void'] .landcover-list {
        max-height: none;
    }
    /* Keep the gauge triplet readable in the narrow column. */
    html[data-ui-theme='void'] .gauge {
        grid-template-columns: 60px minmax(0, 1fr) 36px;
    }
    /* Phone bottom-left colophon is pared to two lines only: the copyright and
       the data-source list. The acknowledgment credit and the status/grid line
       are dropped here — they crowd the small viewport's bottom corners without
       earning their place. The status readout (scalebar) is instrument chrome,
       so it goes in BOTH states; the credit lives inside the landing-only meta
       block. Sources, previously dropped, come back now that the status line no
       longer competes for the row. */
    /* Lift the whole colophon a little off the bottom rail so the source list
       clears the rail numerals. */
    html[data-ui-theme='void'] .plate__meta {
        bottom: calc(var(--v-inset-b) + 14px);
        /* Centre the two-line colophon across the phone viewport instead of
           pinning it to the left inset. The meta box is a grid, so centre the
           track (justify-content) and centre each line's text within it. */
        left: 0;
        right: 0;
        justify-content: center;
        justify-items: center;
        text-align: center;
    }
    html[data-ui-theme='void'] .plate__copyright,
    html[data-ui-theme='void'] .plate__sources {
        text-align: center;
    }
    /* Raise the copyright line further on its own — the colophon is bottom
       anchored, so widening the gap above the (pinned) source list pushes only
       the copyright upward while the sources stay put. */
    html[data-ui-theme='void'] .plate__copyright {
        margin-bottom: 4px;
    }
    html[data-ui-theme='void'] .plate__sources {
        display: block;
        /* Pull the source list up snug under the copyright line (drop the
           default 4px gap and a touch more) so the two read as one tight
           colophon block. */
        margin-top: -3px;
    }
    html[data-ui-theme='void'] .plate__credit,
    html[data-ui-theme='void'] .scalebar {
        display: none;
    }
    /* The enter chip (click · scroll · ↵ / hold the world still) is dropped on
       phones — a tap on the globe already crosses the enter threshold
       (input-bindings.js), so the hint box only clutters the small viewport. */
    html[data-ui-theme='void'] .enter {
        display: none;
    }
    /* Centered graticule hint has no room between the two bottom corners. */
    html[data-ui-theme='void'] .hint {
        display: none;
    }
    /* Phone keeps the readout to its lead line only: the summoned detail
       bloom (metrics / gauges / landcover section / ticker, and the scrim
       behind them) stays retracted no matter which summon path is engaged.
       hud rows and the nav ink lift still respond to the summon. Same
       specificity as the reveal rules above — this block is later, so it
       wins; leaving the phone width restores the bloom live. */
    html[data-ui-theme='void'][data-void-summon='1'] .info-panel__metrics,
    html[data-ui-theme='void'][data-void-summon='1'] .info-panel__gauges,
    html[data-ui-theme='void'][data-void-summon='1'] .info-panel__section,
    html[data-ui-theme='void'][data-void-summon='1'] .info-panel__ticker {
        opacity: 0;
        pointer-events: none;
    }
    html[data-ui-theme='void'][data-void-summon='1'] .info-panel::before {
        opacity: 0;
    }
}

/* --------------------------------------------------------------------------
   Coarse pointer — touch hit targets only. No visual change to glyph size;
   the ink, tracking, and scale stay exactly as the fine-pointer base. Hover
   summon is unreliable on touch, so the tap-latch on the lead line is the
   documented path — enlarge its hit area too.
   -------------------------------------------------------------------------- */
@media (pointer: coarse) {
    html[data-ui-theme='void'] .sound-toggle,
    html[data-ui-theme='void'] .nav__btn {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 44px;
        min-height: 44px;
    }
    /* Compass keeps its own box; just guarantee a tappable band under it. */
    html[data-ui-theme='void'] .nav__compass {
        min-height: 44px;
    }
    /* Tap-latch target — comfortable band without moving the text baseline. */
    html[data-ui-theme='void'] .info-panel__title {
        padding-block: var(--v-s2);
    }
}

/* ---------------------------------------------------------------------------
   Reduced motion — summon transitions become instant, the ticker halts.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    html[data-ui-theme='void'] .hud__data,
    html[data-ui-theme='void'] .hud__human,
    html[data-ui-theme='void'] .hud__climate,
    html[data-ui-theme='void'] .info-panel__metrics,
    html[data-ui-theme='void'] .info-panel__gauges,
    html[data-ui-theme='void'] .info-panel__section,
    html[data-ui-theme='void'] .info-panel__ticker,
    html[data-ui-theme='void'] .info-panel::before {
        transition: none;
    }
    html[data-ui-theme='void'] .ticker__track {
        animation: none;
    }
}
