/* Double Pendulum - Minimalist Dark Theme */

:root {
    --bg: #0a0a0a;
    --surface: #111;
    --border: #1a1a1a;
    --text: #e0e0e0;
    --text-dim: #555;
    --accent: #888;
}

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

body {
    display: flex;
    height: 100vh;
    background: var(--bg);
    color: var(--text);
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
    overflow: hidden;
}

/* Sidebar — sized to fit WITHOUT scrolling. Live controls up top, reset/recompute pinned to the
   bottom (btn-column has margin-top:auto), initial conditions tucked in a disclosure between them. */
#sidebar {
    width: 280px;
    padding: 24px 22px 20px;
    background: var(--surface);
    border-right: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: 22px;
    overflow-y: auto;
}

/* A cluster of related live controls, grouped by spacing (no section header). */
.group {
    display: flex;
    flex-direction: column;
    gap: 18px;
}

#sidebar h1 {
    font-size: 18px;
    font-weight: 600;
    letter-spacing: -0.02em;
    margin: 0;
}

.sidebar-title {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Title + info icon on one line. */
.title-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* Small circled-i that links to Wikipedia (replaces the old text link). */
.info-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-dim);
    transition: color 0.12s;
    line-height: 0;
}

.info-icon:hover {
    color: var(--text);
}

.subtitle {
    font-size: 12px;
    color: var(--text-dim);
    font-weight: 400;
    margin: 0;
    line-height: 1.1;
}

/* Stacked credit lines: small gap between consecutive subtitles. */
.subtitle + .subtitle {
    margin-top: 2px;
}

/* Credit links in the subtitle: muted to match, brighten on hover. */
.subtitle-link {
    color: var(--text-dim);
    text-decoration: none;
    border-bottom: 1px solid color-mix(in srgb, var(--text-dim) 45%, transparent);
    transition: color 0.15s ease, border-color 0.15s ease;
}

.subtitle-link:hover {
    color: var(--text);
    border-bottom-color: currentColor;
}

/* Small muted note under a control (e.g. "applies on reset"). */
.control-hint {
    font-size: 11px;
    color: var(--text-dim);
    font-weight: 400;
    margin: -4px 0 0;
    line-height: 1.2;
}

/* Buttons. The tour's nav buttons (.tour-btn) share this base; only their padding and the extra
   opacity transition differ (see the .tour-btn override block below). */
.btn,
.tour-btn {
    padding: 8px;
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: transparent;
    color: var(--text);
    transition: background 0.1s, border-color 0.1s;
}

.btn:hover,
.tour-btn:hover {
    background: var(--border);
}

.btn:disabled,
.tour-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

/* Primary action (Pause): taller, filled, the clear focal control of the cluster. */
.btn-primary {
    padding: 11px;
    font-size: 13px;
    font-weight: 600;
    background: var(--border);
    border-color: var(--border);
}

.btn-primary:hover,
.tour-btn-primary:hover {
    background: #242424;
    border-color: #242424;
}

/* Compact secondary action (the reset/recompute row): smaller text, tighter padding. */
.btn-sm {
    padding: 7px 4px;
    font-size: 11px;
    color: var(--text-dim);
}

.btn-sm:hover {
    color: var(--text);
}

.btn-column {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: auto;
    padding-top: 8px;
}

.btn-column .btn {
    width: 100%;
}

/* The secondary actions sit side-by-side in one compact row. */
.btn-row {
    display: flex;
    gap: 6px;
}

.btn-row .btn {
    flex: 1;
    min-width: 0;
}

/* Render-space mode toggle (Angle | Momentum) — a segmented control. */
.mode-toggle {
    display: flex;
    border: 1px solid var(--border);
    border-radius: 4px;
    overflow: hidden;
}

.mode-btn {
    flex: 1;
    padding: 7px 8px;
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    border: none;
    background: transparent;
    color: var(--text-dim);
    transition: background 0.1s, color 0.1s;
}

.mode-btn:hover {
    background: var(--border);
    color: var(--text);
}

.mode-btn.active {
    background: var(--border);
    color: var(--text);
}

/* Divergence recolour toggle: a full-width pill below the Angle/Momentum switch. Orthogonal to the
   mode — when `.active` it lights up to show the Lyapunov colouring is on. */
.toggle-btn {
    width: 100%;
    margin-top: 6px;
    padding: 7px 8px;
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: transparent;
    color: var(--text-dim);
    transition: background 0.1s, color 0.1s, border-color 0.1s;
}

.toggle-btn:hover {
    background: var(--border);
    color: var(--text);
}

.toggle-btn.active {
    background: var(--border);
    border-color: var(--border);
    color: var(--text);
}

/* Initial-condition slider groups that swap with the mode (ω in Angle, θ in Momentum). */
.mode-group {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.mode-group.hidden {
    display: none;
}

/* Disclosure (Initial conditions) — a collapsible group keyed off `.open` on the wrapper. The head
   is a full-width row (label + chevron); the panel uses the grid 0fr→1fr height animation. */
.disclosure-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 7px 0;
    background: none;
    border: none;
    color: var(--text-dim);
    font: inherit;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.01em;
    text-align: left;
    cursor: pointer;
    transition: color 0.1s;
}

.disclosure-head:hover {
    color: var(--text);
}

.disclosure.open .disclosure-head {
    color: var(--text);
}

.disclosure.open .disclosure-head .chevron {
    transform: rotate(90deg);
}

.disclosure-panel {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.25s ease;
}

.disclosure.open .disclosure-panel {
    grid-template-rows: 1fr;
}

.disclosure-inner {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-height: 0;
    overflow: hidden;
    /* A touch of top padding only once open, so the collapsed state is truly flush. */
    padding-top: 4px;
}

/* Controls */
.control {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.control-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.control label {
    font-size: 13px;
    font-weight: 500;
    color: var(--text);
    letter-spacing: -0.01em;
}

.control .value {
    font-size: 11px;
    color: var(--text-dim);
    font-family: monospace;
    font-variant-numeric: tabular-nums;
    font-weight: 400;
}

/* Range sliders */
input[type="range"] {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    height: 3px;
    background: #2a2a2a;
    border-radius: 2px;
    outline: none;
    cursor: pointer;
}

input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 14px;
    height: 14px;
    background: #888;
    border-radius: 50%;
    cursor: pointer;
}

input[type="range"]::-webkit-slider-thumb:hover {
    background: #aaa;
}

input[type="range"]::-moz-range-thumb {
    width: 14px;
    height: 14px;
    background: #888;
    border: none;
    border-radius: 50%;
    cursor: pointer;
}

input[type="range"]::-moz-range-thumb:hover {
    background: #aaa;
}

input[type="range"]::-moz-range-track {
    width: 100%;
    height: 3px;
    background: #2a2a2a;
    border-radius: 2px;
    border: none;
}

/* Base-colour picker — the swatch GROWS into the selector. The whole picker is a column; the
   `.open` class (toggled on #color-picker by main.js) drives the collapsed→expanded transition. */
#color-picker {
    gap: 12px;
}

/* Header row (label + hex + chevron) is the click target that expands/collapses. */
.color-toggle {
    display: flex;
    flex-direction: column;
    width: 100%;
    padding: 0;
    background: none;
    border: none;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.color-header-right {
    display: flex;
    align-items: center;
    gap: 8px;
}

.chevron {
    color: var(--text-dim);
    font-size: 11px;
    line-height: 1;
    transition: transform 0.2s ease;
}

#color-picker.open .chevron {
    transform: rotate(90deg);
}

/* The swatch IS the picker — one rectangle. Collapsed: a thin colour bar. Expanded: the same
   rectangle grows tall and reveals the full hue×value colour field. No separate preview field. */
.color-space {
    display: flex;
}

/* The WHOLE colour space in one rectangle: X = hue (full rainbow), Y = saturation (vivid→washed-out),
   value pinned at 100%. The base layer is a horizontal rainbow gradient; the `.sv-overlay-val`
   child fades transparent→white down Y (desaturating it). Collapsed it's a short bar showing the flat
   chosen colour (rainbow hidden); on `.open` it grows tall and reveals the rainbow + saturation fade. */
.sv-square {
    position: relative;
    flex: 1;
    height: 36px;                             /* collapsed: thin bar */
    border-radius: 4px;
    border: 1px solid var(--border);
    overflow: hidden;
    cursor: pointer;
    touch-action: none;
    transition: height 0.25s ease;
    /* Collapsed flat colour is set inline by JS (background-color); the rainbow lives on the
       ::before so the flat colour shows through when it's hidden. */
}

/* Full-spectrum hue rainbow across X — only shown when open. */
.sv-square::before {
    content: "";
    position: absolute;
    inset: 0;
    opacity: 0;
    transition: opacity 0.25s ease;
    background: linear-gradient(to right,
            #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
}

#color-picker.open .sv-square {
    height: 132px;
    cursor: crosshair;
}

#color-picker.open .sv-square::before {
    opacity: 1;
}

/* Transparent (top, vivid) → white (bottom, washed-out): SATURATION axis. Overlaying white onto the
   full-saturation rainbow desaturates it toward the bottom. Sits above the rainbow; hidden collapsed. */
.sv-overlay-val {
    position: absolute;
    inset: 0;
    opacity: 0;
    transition: opacity 0.25s ease;
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0), #fff);
}

#color-picker.open .sv-overlay-val {
    opacity: 1;
}

/* Draggable marker (the "current colour circled"). Hidden while collapsed. */
.sv-marker {
    position: absolute;
    width: 14px;
    height: 14px;
    margin: -7px 0 0 -7px;
    border-radius: 50%;
    border: 2px solid #fff;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.55), inset 0 0 0 1px rgba(0, 0, 0, 0.35);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.25s ease;
}

#color-picker.open .sv-marker {
    opacity: 1;
}

/* The contrast + hue-spread rows live below the swatch and only appear when expanded. A single
   wrapper animates via the grid 0fr→1fr trick (same as the old panel). */
.color-extra {
    display: grid;
    grid-template-rows: 0fr;
    opacity: 0;
    transition: grid-template-rows 0.25s ease, opacity 0.2s ease;
}

#color-picker.open .color-extra {
    grid-template-rows: 1fr;
    opacity: 1;
}

.color-extra-inner {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-height: 0;
    overflow: hidden;
}

/* Canvas container — the full rectangular drawing area. */
#canvas-container {
    flex: 1;
    position: relative;
    overflow: hidden;
    cursor: grab;
    background: var(--bg);
}

/* Coordinate "Go to" search — floating top-right over the map. A units prefix (θ/ω), two compact
   number inputs, and a go button, in a single subtle pill. Sits above the display, below overlays. */
#goto {
    position: absolute;
    top: 16px;
    right: 16px;
    /* Above the inspect overlay (z-index 120) so you can search/jump to another pendulum while one
       is open — the overlay is click-through, but the field must sit visually on top of it too. */
    z-index: 130;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 5px 6px 5px 10px;
    background: rgba(17, 17, 17, 0.82);
    backdrop-filter: blur(8px);
    border: 1px solid var(--border);
    border-radius: 8px;
}

.goto-input {
    width: 58px;
    padding: 6px 8px;
    font-size: 12px;
    font-family: monospace;
    color: var(--text);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 5px;
    outline: none;
    transition: border-color 0.1s;
    /* Trim the native number spinners — noise at this size. */
    -moz-appearance: textfield;
    appearance: textfield;
}

.goto-input::-webkit-outer-spin-button,
.goto-input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.goto-input::placeholder {
    color: var(--text-dim);
}

.goto-input:focus {
    border-color: var(--accent);
}

.goto-go {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    cursor: pointer;
    color: var(--text);
    background: var(--border);
    border: 1px solid var(--border);
    border-radius: 5px;
    transition: background 0.1s, border-color 0.1s;
}

.goto-go svg {
    display: block;
}

.goto-go:hover {
    background: #242424;
    border-color: #242424;
}

/* Visible 2D display canvas — fills the whole container (no letterbox); its backing buffer is
   sized from Rust to the container × dpr. The host drawImages the WebGPU source onto it: square
   tiles to fill the rectangle when zoomed out, the display-aspect figure crop when zoomed in.
   `touch-action: none` lets the Rust pointer/wheel handlers own pinch/scroll. */
#display {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    touch-action: none;
}

/* Hidden WebGPU source canvas — rendered every frame but read only via drawImage, so it's kept
   out of the way (transparent, non-interactive). Its backing buffer is what matters, not its
   on-page footprint. */
#canvas {
    position: absolute;
    top: 0;
    left: 0;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

/* Click-to-inspect overlay — covers the whole canvas container with a dimmed backdrop and draws
   the clicked pixel's pendulum swinging full-viewport. Above the display + stats, below the
   loading/error overlays. Fades in/out; `.hidden` removes it from layout (and stops the JS loop). */
#inspect-overlay {
    position: absolute;
    inset: 0;
    z-index: 120;
    background: rgba(10, 10, 10, 0.82);
    opacity: 1;
    transition: opacity 0.18s ease;
    /* Interaction-invisible: clicks/drags/wheel pass through to the #display map underneath, so you
       can still pan/zoom the sim with the inspector open. Esc is the only dismiss. */
    pointer-events: none;
}

/* `.hidden` is toggled by main.js; keep it removed from layout AND non-interactive. The fade-out
   is done by clearing opacity first (a frame before adding `.hidden`) for a smooth dismiss. */
#inspect-overlay.hidden {
    display: none;
}

#inspect-overlay.fading {
    opacity: 0;
}

/* The pendulum is drawn here; backing buffer is sized to the container × dpr from main.js. */
#inspect-canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
}

/* Honest caveat: the CPU overlay and the GPU pixel diverge over time (chaos + different float
   precision / timestep). Sits at the top, subdued and width-capped so it doesn't fight the
   pendulum. */
#inspect-warning {
    position: absolute;
    top: 16px;
    left: 50%;
    transform: translateX(-50%);
    max-width: min(560px, 88%);
    text-align: center;
    font-size: 11px;
    line-height: 1.5;
    color: var(--text-dim);
    pointer-events: none;
    user-select: none;
}

#inspect-warning em {
    font-style: italic;
    color: var(--text);
}

/* Initial-condition readout for the inspected pendulum — a compact row of θ/ω chips under the
   warning, centred at the top of the overlay. */
#inspect-coords {
    position: absolute;
    top: 92px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 16px;
    font-size: 12px;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
    pointer-events: none;
    user-select: none;
    white-space: nowrap;
}

#inspect-coords .ic-pair {
    font-family: monospace;
}

#inspect-coords .ic-pair b {
    color: var(--text);
    font-weight: 600;
    margin-left: 2px;
}

#inspect-hint {
    position: absolute;
    bottom: 16px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 12px;
    color: var(--text-dim);
    letter-spacing: 0.04em;
    pointer-events: none;
    user-select: none;
}

#inspect-hint kbd {
    font-family: inherit;
    color: var(--text);
    background: var(--border);
    border-radius: 3px;
    padding: 1px 5px;
}

/* Projection toggle inside the inspect overlay, just above the scrubber. Reuses `.toggle-btn`'s
   look but is absolutely positioned (auto width, centred) and opts back into pointer events since
   the overlay is otherwise click-through. */
#phase-toggle {
    position: absolute;
    bottom: 64px;
    left: 50%;
    transform: translateX(-50%);
    width: auto;
    margin-top: 0;
    pointer-events: auto;
    /* Solid chip (not see-through) over the dark overlay, using the sidebar's panel background. */
    background: var(--surface);
}

/* When selected, mimic the render-space buttons (greyish). Needs ID specificity to override the
   `#phase-toggle` surface background above. */
#phase-toggle.active {
    background: var(--border);
}

/* Trail history scrubber: a rounded progress-bar style track just above the Esc hint. The overlay
   is click-through (pointer-events: none), so the bar opts BACK IN so its handles are draggable
   while clicks elsewhere still pass to the map. */
#trail-scrubber {
    position: absolute;
    bottom: 44px;
    left: 50%;
    transform: translateX(-50%);
    width: min(520px, 80%);
    height: 10px;
    border-radius: 999px;                 /* full pill */
    background: var(--border);            /* unfilled track — greyish, matches the render-space UI */
    pointer-events: auto;
    user-select: none;
    touch-action: none;
}

/* The selected sub-range — the "filled" portion of the progress bar (a pill inside the track).
   Positioned by main.js via left/right %. */
#trail-sel {
    position: absolute;
    top: 0;
    bottom: 0;
    background: var(--text);
    border-radius: 999px;
    pointer-events: none;
}

/* No visible handle widgets — you drag the FILL's edges directly. Each handle is an invisible,
   slightly-widened hit zone centred on its edge so the thin bar is still easy to grab. */
.trail-handle {
    position: absolute;
    top: 50%;
    width: 16px;
    height: 16px;
    margin-left: -8px;
    transform: translateY(-50%);
    background: transparent;
    cursor: ew-resize;
}

/* Loading overlay */
#loading-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: var(--bg);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    z-index: 100;
}

#loading-overlay.hidden {
    display: none;
}

#loading-text {
    font-size: 14px;
    color: var(--text-dim);
    margin-bottom: 16px;
}

#loading-bar {
    width: 200px;
    height: 4px;
    background: var(--border);
    border-radius: 2px;
    overflow: hidden;
}

#loading-progress {
    height: 100%;
    width: 0%;
    background: var(--accent);
    transition: width 0.1s;
}

/* Stats Overlay */
#stats-overlay {
    position: fixed;
    bottom: 0;
    right: 0;
    padding: 20px 24px;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
    font-size: 11px;
    font-weight: 400;
    z-index: 50;
    display: flex;
    flex-direction: column;
    gap: 4px;
    pointer-events: none;
}

#stats-overlay .stat {
    display: flex;
    justify-content: space-between;
    gap: 24px;
}

#stats-overlay .label {
    color: rgba(255, 255, 255, 0.5);
    font-weight: 400;
}

#stats-overlay .stat span:last-child {
    color: rgba(255, 255, 255, 0.95);
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 500;
}

/* Error Overlay */
#error-overlay {
    position: absolute;
    inset: 0;
    background: var(--bg);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    z-index: 200;
    padding: 24px;
    text-align: center;
}

#error-overlay.hidden {
    display: none;
}

#error-icon {
    font-size: 48px;
    margin-bottom: 16px;
}

#error-title {
    font-size: 20px;
    font-weight: 600;
    color: #ff6b6b;
    margin-bottom: 12px;
}

#error-message {
    font-size: 13px;
    color: var(--text-dim);
    margin-bottom: 24px;
    font-family: monospace;
    background: rgba(255, 255, 255, 0.05);
    padding: 8px 16px;
    border-radius: 4px;
}

#error-help {
    text-align: left;
    font-size: 12px;
    color: var(--text-dim);
    max-width: 360px;
}

#error-help p {
    margin: 12px 0 6px;
    color: var(--text);
}

#error-help ul {
    margin: 0;
    padding-left: 20px;
}

#error-help li {
    margin: 4px 0;
}


/* ── Mobile / tablet block ───────────────────────────────────────────────────────────────────
   Phones and tablets aren't supported yet (the WebGPU compute path misbehaves on mobile GPUs).
   An inline <head> script tags the document with `html.is-mobile` (UA-based) before first paint.
   On those devices the notice is PERMANENT — it never fades, because there's nothing working to
   reveal underneath — and the (broken) display is hidden so no garbled frame shows.

   Desktop/laptop is the opposite case: the sim runs, so the same notice is shown only briefly as
   an intro and then fades away to reveal it. The two cases share the markup; only the animation
   (or lack of it) differs. */

/* Hidden by default; shown by the mobile or desktop branch below. */
#mobile-block {
    display: none;
}

/* ── Mobile: permanent block, sim suppressed ─────────────────────────────────────────────── */
html.is-mobile #loading-overlay {
    display: none;
}

html.is-mobile #display {
    visibility: hidden;
}

html.is-mobile #mobile-block {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 1000;
    align-items: center;
    justify-content: center;
    padding: 32px;
    text-align: center;
    background: var(--bg);
    /* No animation — stays up for good. */
}

/* ── Desktop / laptop: splash stays up WHILE the sim loads, then fades when ready ─────────────
   The splash sits on top (z-index 1000) covering the real #loading-overlay, so the GPU init +
   seed run behind it. While loading it gently PULSES (so it never looks frozen). JS adds
   `.is-fading` once Rust marks the sim ready (see the head script): the pulse stops and it
   FAST-fades out, then drops from the layout so it can't intercept anything. */
html:not(.is-mobile) #mobile-block {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 1000;
    align-items: center;
    justify-content: center;
    padding: 32px;
    text-align: center;
    background: var(--bg);
    opacity: 1;
    /* The BACKDROP stays solid (covers the loading); only the inner content pulses below. On
       ready, `.is-fading` fast-fades the whole block out. */
    transition: opacity 0.5s ease-out;
}

html:not(.is-mobile) #mobile-block.is-fading {
    /* Fast fade, then visibility:hidden once it's clear (delay matches the 0.5s opacity fade). */
    opacity: 0;
    pointer-events: none;
    animation: mobile-block-remove 0s linear 0.5s forwards;
}

/* The content (title + tagline) gently breathes while loading so the splash never looks frozen.
   The backdrop is untouched, so nothing shows through. Frozen the moment we start fading out. */
html:not(.is-mobile) #mobile-block .mobile-block-inner {
    animation: splash-pulse 2s ease-in-out infinite;
}

html:not(.is-mobile) #mobile-block.is-fading .mobile-block-inner {
    animation: none;
}

@keyframes splash-pulse {
    0%,
    100% {
        opacity: 1;
    }

    50% {
        opacity: 0.55;
    }
}

@keyframes mobile-block-remove {
    to {
        visibility: hidden;
    }
}

.mobile-block-inner {
    max-width: 420px;
}

.mobile-block-title {
    font-size: 22px;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 14px;
}

.mobile-block-message {
    font-size: 15px;
    line-height: 1.6;
    color: var(--text-dim);
}

/* Explainer link sits a line below the message text. */
.mobile-block-message .subtitle-link {
    display: inline-block;
    margin-top: 14px;
}

/* Stacked credits at the foot of the block, centered. Mobile only: the desktop splash is
   transient (fades on load) and the sidebar already shows credits, so they'd only flash there. */
.mobile-block-credits {
    display: none;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    margin-top: 28px;
    font-size: 12px;
    color: var(--text-dim);
}

html.is-mobile .mobile-block-credits {
    display: flex;
}

/* Show the device-appropriate message: the "not supported" copy on mobile, the brief
   "loading…" line on desktop. */
.mobile-block-message--mobile,
.mobile-block-message--desktop {
    display: none;
}

html.is-mobile .mobile-block-message--mobile {
    display: block;
}

html:not(.is-mobile) .mobile-block-message--desktop {
    display: block;
}

/* ── Guided tour ───────────────────────────────────────────────────────────────────────────── */
/* The replay "?" button reuses .info-icon for the muted→bright hover; strip the default button
   chrome so it matches the adjacent Wikipedia link exactly. */
.tour-launch {
    border: none;
    background: none;
    padding: 0;
    cursor: pointer;
}

/* Root: a full-screen catcher above all sim UI (stats 50, inspect 120, goto 130, error 200) but
   below the loading / mobile splash (1000). The dim is painted by #tour-spotlight's box-shadow, so
   the root itself is transparent — it only blocks pointer events from reaching the controls beneath
   and fades in/out (mirrors the inspector's .hidden / .fading convention). */
#tour {
    position: fixed;
    inset: 0;
    z-index: 300;
    opacity: 1;
    transition: opacity 0.18s ease;
}

#tour.hidden {
    display: none;
}

#tour.fading {
    opacity: 0;
}

/* Spotlight cutout: a positioned box whose huge box-shadow spread dims everything AROUND it, with a
   light ring on the target. Position + size set by JS from getBoundingClientRect(). */
#tour-spotlight {
    position: fixed;
    top: 0;
    left: 0;
    width: 0;
    height: 0;
    border-radius: 8px;
    box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.62), 0 0 0 2px rgba(224, 224, 224, 0.85);
    pointer-events: none;
    transition: top 0.25s ease, left 0.25s ease, width 0.25s ease, height 0.25s ease;
}

/* Coachmark card — matches the dark theme (surface + border + text tokens). Positioned by JS. */
#tour-card {
    position: fixed;
    width: 280px;
    max-width: calc(100vw - 28px);
    padding: 16px 18px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.55);
    color: var(--text);
    outline: none;
    transition: top 0.25s ease, left 0.25s ease;
}

.tour-progress {
    font-size: 11px;
    color: var(--text-dim);
    margin-bottom: 6px;
    font-variant-numeric: tabular-nums;
}

.tour-title {
    font-size: 14px;
    font-weight: 600;
    letter-spacing: -0.01em;
    margin: 0 0 6px;
}

.tour-body {
    font-size: 12px;
    line-height: 1.45;
    color: var(--text);
    margin: 0 0 14px;
}

.tour-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.tour-nav-right {
    display: flex;
    gap: 8px;
}

/* Tour nav buttons: the .btn base (grouped above) plus tighter padding and an opacity transition
   for the disabled fade. Hover / :disabled / primary-hover are shared with .btn via grouped
   selectors above — only the true overrides live here. */
.tour-btn {
    padding: 6px 12px;
    transition: background 0.1s, border-color 0.1s, opacity 0.1s;
}

.tour-btn-primary {
    background: var(--border);
}

.tour-btn-ghost {
    border-color: transparent;
    color: var(--text-dim);
}

.tour-btn-ghost:hover {
    color: var(--text);
    background: transparent;
}

/* Respect reduced-motion: drop the position/shadow slide animations. */
@media (prefers-reduced-motion: reduce) {

    #tour,
    #tour-spotlight,
    #tour-card {
        transition: none;
    }
}