/* ── Phase 0.9.0 type system (see DESIGN.md "Typography") ──
   Three typographic registers, kept distinct so each one signals its
   own role:
     * --io-font-display — Syne Mono. The brand voice. Used for the
       wordmark and for headlines / sub-headlines (h1–h6).
     * --io-font-body — Josefin Sans. The reading voice. Used for body
       copy, lede paragraphs and lesson explanations.
     * --io-font-var — EB Garamond italic. The math voice. Used for
       single-letter variables (e, a, v, r, …) via the `.io-var` class
       and for the Kepler legend / HUD parameter glyphs that already
       reached for an italic-serif treatment. */
:root {
    --io-font-display: 'Syne Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    --io-font-body: 'Josefin Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    --io-font-var: 'EB Garamond', 'Georgia', 'Times New Roman', serif;
}

html, body {
    margin: 0;
    padding: 0;
    font-family: var(--io-font-body);
}

/* All headlines and sub-headlines render in Syne Mono — the same face
   as the wordmark, so headline weight unifies the brand voice across
   pages. Per-page rules can still override `font-size`, `font-weight`
   and `letter-spacing`; only the family is locked here. */
h1, h2, h3, h4, h5, h6 {
    font-family: var(--io-font-display);
}

/* Math-style variable glyph. Use as `<span class="io-var">e</span>` in
   prose, or apply to existing labels (legend symbols, HUD parameter
   values) so a single letter reads as "this is a variable" instead of
   a stray character. Italic is intentional — it matches the textbook
   convention for scalars. */
.io-var {
    font-family: var(--io-font-var);
    font-style: italic;
    font-feature-settings: "kern" 1, "liga" 0;
}

/* ── Scrollbars (viewport + any scrollable pane) ───────────────────
   Default OS scrollbars look out of place against the deep-space
   palette — a bright rectangle on the right, a chunky bar along the
   bottom. We style them to be thin, dim, and tinted with the site's
   cyan accent so they recede into the background and only surface
   subtly when the user actually needs to scroll. */
html {
    /* Firefox */
    scrollbar-width: thin;
    scrollbar-color: rgba(0, 180, 255, 0.22) transparent;
}
/* Chromium / WebKit */
::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}
::-webkit-scrollbar-track {
    background: transparent;
}
::-webkit-scrollbar-thumb {
    background: rgba(0, 180, 255, 0.18);
    border-radius: 4px;
    border: 2px solid transparent;
    background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
    background: rgba(0, 180, 255, 0.32);
    background-clip: padding-box;
}
::-webkit-scrollbar-corner {
    background: transparent;
}

a, .btn-link {
    color: #006bb7;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* ═══════════════════════════════════════════════════════════════
   Shared styles for Kepler lesson pages (.kl-*)
   See Web.Client Pages/Lessons/KeplerFirstLaw.razor and KeplerSecondLaw.razor
   ═══════════════════════════════════════════════════════════════ */

/* ── Top-left info panel ──
   The `.kl-info` container has two visual states, toggled via the
   `.kl-info-intro-mode` class:
     * Resting (default)    — narrow box at top-left, holds the title +
       Law of Ellipses quote + legend.
     * Intro mode           — wide box in the bottom-left, sitting to the
       left of the centred e/a/b/v/r controls panel. Holds only the
       current step's explanation text (content swaps per step).
   The morph between the two states plays as the intro's closing beat:
   when the guide finishes, `.kl-info-intro-mode` is removed and the CSS
   transitions below glide the panel from the bottom-left where the
   explanations lived, to the top-left corner where it lives at rest. */
.kl-info {
    /* `top: 38px` clears the discrete breadcrumb bar (Components/Shared/
       Breadcrumb.razor) which is fixed at top: 12px + 18px logo height.
       Before the breadcrumb existed, this panel sat at top: 14px and
       opened with a small `← Discover` link (`.kl-back`) above the
       title. The breadcrumb now owns that ancestor nav, so the link is
       gone and the panel slides down by ~24px to give the breadcrumb
       its own strip across the very top of the viewport. */
    position: absolute; top: 38px; left: 14px; z-index: 10;
    width: 288px; max-height: calc(100% - 124px); overflow-y: auto;
    padding: 14px 16px 16px;
    background: linear-gradient(160deg, rgba(10,14,28,0.88) 0%, rgba(3,3,8,0.88) 100%);
    border: 1px solid rgba(0,180,255,0.12);
    border-radius: 10px;
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    box-shadow: 0 4px 24px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.04);
    transform: translate(0, 0);
    transition:
        transform 1100ms cubic-bezier(0.65, 0.02, 0.25, 1),
        width    1100ms cubic-bezier(0.65, 0.02, 0.25, 1),
        padding  1100ms cubic-bezier(0.65, 0.02, 0.25, 1),
        border-color 700ms ease-out;
    will-change: transform, width;
}

/* While the panel is morphing between intro and resting positions, drop the
   most expensive composited effects (backdrop blur, layered + inset shadow)
   so the transform/width/padding transition stays in the GPU compositor.
   The blur and full shadow snap back when `.kl-info-morphing` is removed.
   `contain: layout paint` is scoped here (rather than the base rule) so the
   width+padding transition can't dirty the rest of the document each frame,
   while avoiding mobile browsers misclipping the panel when it is static. */
.kl-info.kl-info-morphing {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: 0 4px 12px rgba(0,0,0,0.4);
    overflow: hidden;
    contain: layout paint;
}

/* Intro-mode position/size. We keep `top:38/left:14` from the base rule
   and morph via `transform` + `width` so the transition back to resting
   is purely interpolated transform/width (no layout thrash, no
   top/left `auto` pitfalls).

   translate's `100%` refers to the element's own height, so the
   vertical expression lands the top edge at `(100vh - 200px) - height`,
   i.e. the panel's bottom sits ~200px above the viewport bottom — which
   clears the centred `.kl-bottom` (max ~180px tall + padding).
   Horizontally the box sits flush against `left: 20px` (translateX
   compensates for the base 14px) and its width clamps between 380px and
   720px, scaling with the viewport so it always leaves room for the
   centred controls panel. */
.kl-info.kl-info-intro-mode {
    width: clamp(380px, calc(50vw - 260px), 720px);
    padding: 20px 24px 22px;
    transform: translate(6px, calc(100vh - 38px - 200px - 100%));
    border-color: rgba(0,200,255,0.22);
    overflow: visible;
    max-height: none;
}

/* The single caption text shown inside `.kl-info` during the intro.
   Larger than the resting panel's body copy so it reads well from the
   bottom area. Each new step gets a fresh `@key`, so Blazor replaces
   this element on advance and the fade-in animation plays again. */
.kl-info-intro-text {
    font-size: 16px;
    line-height: 1.6;
    color: rgba(225,235,255,0.96);
    margin: 0;
}
.kl-info-intro-text em { color: rgba(200,220,255,0.9); font-style: italic; }
.kl-info-intro-text strong { color: #fff; font-weight: 600; }
.kl-info-intro-fade {
    animation: kl-info-intro-fade 520ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
@keyframes kl-info-intro-fade {
    0%   { opacity: 0; transform: translateY(6px); filter: blur(3px); }
    60%  { opacity: 1; filter: blur(0); }
    100% { opacity: 1; transform: translateY(0); filter: blur(0); }
}

/* Flash-of-inspiration reveal for the lesson title — a proper bright-idea
   pop: blinding white flash, a scale punch, then a long luminous settle to
   a bright cyan-tinted resting glow. Applied on first post-intro render
   via the .kl-title-flash class. */
.kl-title-flash {
    animation: kl-title-flash 2200ms cubic-bezier(0.1, 0.7, 0.2, 1) both;
    will-change: transform, opacity;
}
/* Text-shadow blur radii > ~80px and `filter: brightness()` are CPU-paint
   heavy and were previously animating in lock-step with the panel morph,
   compounding jank. Brightness is dropped (the text-shadow stack already
   carries the bloom) and the largest blur radii are trimmed; the visible
   peak still reads as a flash. */
@keyframes kl-title-flash {
    0%   { color: #fff; opacity: 0; transform: scale(0.92); letter-spacing: 0.2px;
           text-shadow: 0 0 0 rgba(255,255,255,0); }
    8%   { color: #fff; opacity: 1; transform: scale(1.12); letter-spacing: 2.5px;
           text-shadow:
               0 0 12px rgba(255,255,255,1),
               0 0 36px rgba(255,255,255,0.95),
               0 0 80px rgba(160,215,255,0.85); }
    22%  { color: #fff; transform: scale(1.06); letter-spacing: 1.4px;
           text-shadow:
               0 0 8px  rgba(255,255,255,0.95),
               0 0 24px rgba(220,240,255,0.8),
               0 0 70px rgba(0,180,255,0.6); }
    55%  { color: #fff; transform: scale(1.01); letter-spacing: 0.5px;
           text-shadow:
               0 0 6px  rgba(255,255,255,0.7),
               0 0 18px rgba(180,220,255,0.55),
               0 0 40px rgba(0,200,255,0.4); }
    100% { color: #fff; transform: scale(1); letter-spacing: 0.2px;
           text-shadow:
               0 0 4px  rgba(255,255,255,0.55),
               0 0 10px rgba(180,220,255,0.4),
               0 0 20px rgba(0,200,255,0.25); }
}
.kl-info::-webkit-scrollbar { width: 4px; }
.kl-info::-webkit-scrollbar-thumb { background: rgba(0,180,255,0.15); border-radius: 2px; }

/* `.kl-back` was the per-page "← Discover" link in the top-left of every
   law's info panel. The discrete breadcrumb bar (Components/Shared/
   Breadcrumb.razor) now owns ancestor navigation site-wide, so the link
   was removed from each lesson's markup and the rule deleted with it.
   The kl-title sits at the top of the info panel directly. */

.kl-title {
    color: #fff; font-size: 18px; font-weight: 600;
    margin: 0; line-height: 1.2;
    letter-spacing: 0.2px;
}
.kl-subtitle {
    color: rgba(0,200,255,0.7); font-size: 11px; font-weight: 500;
    margin: 3px 0 14px;
    letter-spacing: 1.4px; text-transform: uppercase;
    font-family: var(--io-font-display);
}

.kl-quote-box {
    border-left: 2px solid rgba(0,180,255,0.35);
    padding: 8px 10px; margin: 0 0 16px;
    background: linear-gradient(90deg, rgba(0,180,255,0.06), rgba(0,180,255,0));
    border-radius: 0 4px 4px 0;
}
.kl-quote {
    font-size: 12px; font-style: italic;
    color: rgba(200,220,255,0.7);
    margin: 0; line-height: 1.6;
}

/* Legend — the per-law fact list. Each entry has a small italic
   cyan/gold variable glyph (matching .io-var) on the left and a
   name + description on the right. Items are separated by a soft
   horizontal cyan-fade hairline (echoing .kl-quote-box's gradient
   accent) instead of a flat one-pixel rule, so the divider reads
   as part of the same panel language rather than a hard boundary. */
.kl-legend { list-style: none; padding: 0; margin: 0; }
.kl-legend-item {
    display: grid;
    grid-template-columns: 22px 1fr;
    column-gap: 12px;
    align-items: baseline;
    padding: 10px 2px;
    /* Hairline divider as a top border-image gradient — fades in from
       the cyan accent at the centre and out to fully transparent at
       both ends. Replaces the old solid rgba(0,180,255,0.06) line. */
    border-top: 1px solid transparent;
    border-image: linear-gradient(90deg,
        rgba(0,180,255,0) 0%,
        rgba(0,180,255,0.18) 50%,
        rgba(0,180,255,0) 100%) 1;
}
.kl-legend-item:first-child { border-top: 0; padding-top: 4px; }
.kl-legend-sym {
    font-family: var(--io-font-var); font-style: italic;
    font-size: 17px; font-weight: 500;
    text-align: center; line-height: 1;
    color: rgba(0,200,255,0.92);
    text-shadow: 0 0 8px rgba(0,200,255,0.28);
}
.kl-legend-sym-gold {
    color: rgba(255,200,90,0.92);
    text-shadow: 0 0 8px rgba(255,200,90,0.28);
}
.kl-legend-name {
    display: block;
    color: rgba(225,238,255,0.95);
    font-family: var(--io-font-display);
    font-size: 11px; font-weight: 400;
    letter-spacing: 0.6px;
    margin-bottom: 3px;
}
.kl-legend-desc {
    display: block;
    color: rgba(180,210,255,0.6);
    font-size: 10.5px; line-height: 1.5;
}

/* Glowing highlight on the legend row when a tip references it */
.kl-legend-item.kl-legend-highlight {
    background: linear-gradient(90deg, rgba(0,200,255,0.08), rgba(0,200,255,0));
    border-radius: 4px;
}
.kl-legend-item.kl-legend-highlight .kl-legend-sym {
    animation: kl-glow-wave 1.8s ease-in-out infinite;
}
.kl-legend-item.kl-legend-highlight .kl-legend-name {
    color: #fff;
}
@keyframes kl-glow-wave {
    0%, 100% { text-shadow: 0 0 6px rgba(0,200,255,0.3); }
    50%      { text-shadow: 0 0 14px rgba(0,200,255,0.75), 0 0 24px rgba(0,200,255,0.35); color: #fff; }
}

/* ── Contextual captions ───────────────────────────────────────
   Captions float free over the sim — no card background — so the text
   itself points at what it's describing. Each anchored variant drops a
   thin line + node in the direction of the subject.
     .kl-caption-planet   → floats above the orbit ring, line drops onto the planet
     .kl-caption-sun      → sits just right of the Sun, line points left to it
     .kl-caption-sectors  → upper-right over the swept sectors
     .kl-caption-orbit    → generic overlay (legacy fallback)
     .kl-caption-slider   → above the slider panel, line drops to the panel
     .kl-caption-center   → centered near the top (final "your turn" moment)
   All captions share .kl-caption styling. */
.kl-caption {
    z-index: 13;
    max-width: 320px;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: 0;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    pointer-events: none;
}
.kl-caption p {
    margin: 0;
    font-size: 13.5px;
    line-height: 1.6;
    color: rgba(225,235,255,0.96);
    text-align: left;
    /* legibility replacement for the removed card background */
    text-shadow:
        0 0 1px rgba(0,0,0,0.95),
        0 1px 6px rgba(0,0,0,0.85),
        0 0 18px rgba(2,6,16,0.9);
}
.kl-caption-fade-in { animation: kl-caption-in 620ms cubic-bezier(0.2, 0.7, 0.2, 1) both; }
.kl-caption-fade-out { animation: kl-caption-out 400ms ease-in forwards; }
@keyframes kl-caption-in {
    0%   { opacity: 0; transform: translateY(8px) scale(0.97); filter: blur(3px); }
    60%  { opacity: 1; filter: blur(0); }
    100% { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
}
@keyframes kl-caption-out {
    0%   { opacity: 1; }
    100% { opacity: 0; transform: translateY(-4px); }
}

/* Orbit-anchored captions share absolute positioning over the sim. Each
   variant draws its own thin pointer line + small node so the subject of
   the caption is visually implicated without any surrounding box. */
.kl-caption-orbit,
.kl-caption-planet,
.kl-caption-sun,
.kl-caption-sectors {
    position: absolute;
}
/* Thin pointer line (::before) + node dot (::after) drawn in per-variant
   positions below. Colours: cyan for orbital/geometry subjects, gold for
   anything anchored to the Sun. */
.kl-caption-orbit::before,
.kl-caption-planet::before,
.kl-caption-sun::before,
.kl-caption-sectors::before,
.kl-caption-slider::before {
    content: '';
    position: absolute;
    background: linear-gradient(var(--kl-tail-dir, to bottom),
                rgba(0,200,255,0.85) 0%,
                rgba(0,200,255,0.15) 100%);
    box-shadow: 0 0 6px rgba(0,200,255,0.45);
    pointer-events: none;
}
.kl-caption-orbit::after,
.kl-caption-planet::after,
.kl-caption-sun::after,
.kl-caption-sectors::after,
.kl-caption-slider::after {
    content: '';
    position: absolute;
    width: 6px; height: 6px;
    border-radius: 50%;
    background: rgba(0,220,255,0.95);
    box-shadow: 0 0 8px rgba(0,200,255,0.8), 0 0 16px rgba(0,200,255,0.45);
    pointer-events: none;
}

/* Generic 'orbit' fallback — top-right of sim (legacy placement). */
.kl-caption-orbit { top: 64px; right: 20px; }
.kl-caption-orbit::before {
    top: -36px; right: 22px;
    width: 1.5px; height: 32px;
    background: linear-gradient(to top, rgba(0,200,255,0.85), rgba(0,200,255,0.1));
}
.kl-caption-orbit::after { top: -42px; right: 19px; }

/* 'planet' — first intro caption. World (0,0) = Sun = canvas centre
   (50%, 50%), and for Neptune's e≈0.009 the orbit is nearly circular and
   centred on the Sun, so placing the caption at 50%,50% sits it literally
   inside the orbit loop. No pointer — the text is describing the orbit it
   sits inside. */
.kl-caption-planet {
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    max-width: 440px;
    text-align: center;
}
.kl-caption-planet p { text-align: center; }
.kl-caption-planet::before,
.kl-caption-planet::after { display: none; }

/* 'sun' — text sits just right of centre; a soft quarter-arc (built from
   border-left + border-bottom + border-bottom-left-radius) curves down-left
   from the caption, and the glowing node dot lands right on the Sun at
   canvas centre. Avoids the stick-straight horizontal pointer. */
.kl-caption-sun {
    top: calc(50% - 10px);
    left: calc(50% + 96px);
    max-width: 300px;
}
.kl-caption-sun::before {
    background: transparent !important;
    box-shadow: none !important;
    top: -8px;
    left: -92px;
    width: 80px;
    height: 22px;
    border-left: 1.5px solid rgba(255,200,80,0.9);
    border-bottom: 1.5px solid rgba(255,200,80,0.9);
    border-bottom-left-radius: 60px;
    filter: drop-shadow(0 0 6px rgba(255,200,80,0.45));
}
.kl-caption-sun::after {
    top: 11px;
    left: -95px;
    background: rgba(255,220,140,0.98);
    box-shadow: 0 0 10px rgba(255,200,80,0.9), 0 0 22px rgba(255,180,40,0.55);
}

/* 'sectors' — upper-right over the coloured wedges; the line drops further
   down-left to actually overlap the wedge band. */
.kl-caption-sectors {
    top: 22%; right: clamp(20px, 10%, 140px);
    max-width: 300px;
}
.kl-caption-sectors::before {
    top: 100%; left: 28px;
    width: 1.5px;
    height: clamp(60px, 12vh, 130px);
    margin-top: 6px;
    background: linear-gradient(to bottom, rgba(0,200,255,0.85), rgba(0,200,255,0.15));
}
.kl-caption-sectors::after {
    top: 100%; left: 25px;
    margin-top: calc(clamp(60px, 12vh, 130px) + 3px);
}

/* Slider caption: lifted OUT of `.kl-bottom` (see Razor) so it doesn't
   widen the centred flex row. Anchored in the top-level absolute context,
   centred horizontally, floating above the panel in the empty sky. A short
   soft-J pointer drops from the bottom of the caption onto the panel;
   the `.kl-slider-pulse` glow on the active slider (already applied) draws
   attention to the specific slider being discussed. */
.kl-caption-slider {
    position: absolute;
    bottom: 190px;
    left: 50%;
    transform: translateX(-50%);
    max-width: 460px;
    text-align: center;
}
.kl-caption-slider p { text-align: center; }
.kl-caption-slider::before {
    background: transparent !important;
    box-shadow: none !important;
    top: calc(100% + 4px);
    left: calc(50% - 22px);
    width: 22px;
    height: 36px;
    border-right: 1.5px solid rgba(0,200,255,0.85);
    border-bottom: 1.5px solid rgba(0,200,255,0.85);
    border-bottom-right-radius: 28px;
    filter: drop-shadow(0 0 6px rgba(0,200,255,0.4));
}
.kl-caption-slider::after {
    top: calc(100% + 4px + 36px - 3px);
    left: calc(50% - 25px);
}

/* Neutral center caption for the final "your turn" moment — no pointer.
   Centred on the orbit (same anchor as `.kl-caption-planet`) so the
   intro's first and last beats bookend in the same spot. */
.kl-caption-center {
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    text-align: center;
    max-width: 440px;
}
.kl-caption-center p { text-align: center; }

/* "Skip intro" button pinned to the left edge, just beneath the wide
   intro-mode info panel. The intro panel's bottom edge sits at
   `100vh - 200px` (see `.kl-info-intro-mode`), so placing the button at
   `bottom: 170px` puts it ~30px below the panel and ~10px above the
   controls bar. Shown only while the intro is running. */
/* ── Previous / Next / Ready navigation row inside the intro panel ──
   Concept C "sunlit-edge" button language (see
   wwwroot/design/button-concepts/README.md). Firm capsule with a
   `border-radius: 4px` cabin-panel silhouette, glassy cyan fill, no rim
   line — the silhouette is implied by layered inner-highlight + outer
   drop-shadows. On hover, a warm radial whose centre sits *past* the
   right edge bleeds back in from the right (the "sunlit edge"). The
   button itself never moves. The same language ports to .kl-skip-global,
   .sb (control bar) and .pb (preset grid) further down — all chrome
   speaks one button vocabulary. */
.kl-nav-buttons {
    display: flex;
    gap: 10px;
    margin-top: 20px;
    align-items: center;
}

.kl-nav-btn {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    font-family: inherit;
    font-size: 11px;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    color: rgba(200,220,255,0.82);
    background: linear-gradient(180deg, rgba(120,210,255,0.07) 0%, rgba(80,180,240,0.025) 100%);
    border: 0;
    border-radius: 4px;
    padding: 8px 20px;
    cursor: pointer;
    box-shadow:
        inset 0 1px 0 rgba(210,235,255,0.05),
        inset 0 0 0 1px rgba(150,210,255,0.08),
        0 1px 2px rgba(0,0,0,0.55),
        0 2px 6px rgba(0,0,0,0.35);
    transition: color 160ms ease, box-shadow 220ms ease;
}
.kl-nav-btn::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    border-radius: inherit;
    background: radial-gradient(circle at 110% 50%,
        rgba(255,240,210,0.32) 0%,
        rgba(255,230,195,0.18) 18%,
        rgba(255,225,185,0.08) 38%,
        rgba(255,220,175,0)    62%);
    opacity: 0;
    transition: opacity 280ms ease;
}
.kl-nav-btn:hover,
.kl-nav-btn:focus-visible {
    color: #eaf6ff;
    box-shadow:
        inset 0 1px 0 rgba(255,255,255,0.09),
        inset 0 0 0 1px rgba(160,200,230,0.14),
        0 1px 2px rgba(0,0,0,0.55),
        0 2px 8px rgba(0,0,0,0.40);
    outline: none;
}
.kl-nav-btn:hover::before,
.kl-nav-btn:focus-visible::before { opacity: 1; }

/* Ready: the primary variant of the same Concept C button. Slightly
   warmer fill at rest so it reads as the next-step affordance even
   before hover; the sunlit-edge wash still arrives on hover. */
.kl-ready-btn {
    color: #fff;
    background: linear-gradient(180deg, rgba(140,225,255,0.16) 0%, rgba(100,200,250,0.06) 100%);
    box-shadow:
        inset 0 1px 0 rgba(220,245,255,0.10),
        inset 0 0 0 1px rgba(160,225,250,0.18),
        0 1px 2px rgba(0,0,0,0.55),
        0 2px 8px rgba(0,0,0,0.40);
}
.kl-ready-btn:hover,
.kl-ready-btn:focus-visible {
    color: #fff;
    box-shadow:
        inset 0 1px 0 rgba(255,255,255,0.14),
        inset 0 0 0 1px rgba(160,230,250,0.30),
        0 1px 2px rgba(0,0,0,0.55),
        0 2px 10px rgba(0,0,0,0.45);
}

/* Skip intro — same Concept C language as the nav row, just a touch
   smaller so it reads as a secondary, push-it-aside action. */
.kl-skip-global {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    margin-right: auto;
    font-family: inherit;
    font-size: 10.5px;
    letter-spacing: 0.6px;
    text-transform: uppercase;
    color: rgba(200,220,255,0.7);
    background: linear-gradient(180deg, rgba(120,210,255,0.06) 0%, rgba(80,180,240,0.02) 100%);
    border: 0;
    border-radius: 4px;
    padding: 6px 12px;
    cursor: pointer;
    box-shadow:
        inset 0 1px 0 rgba(210,235,255,0.04),
        inset 0 0 0 1px rgba(150,210,255,0.08),
        0 1px 2px rgba(0,0,0,0.55),
        0 2px 6px rgba(0,0,0,0.35);
    transition: color 160ms ease, box-shadow 220ms ease;
}
.kl-skip-global::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    border-radius: inherit;
    background: radial-gradient(circle at 110% 50%,
        rgba(255,240,210,0.32) 0%,
        rgba(255,230,195,0.18) 18%,
        rgba(255,225,185,0.08) 38%,
        rgba(255,220,175,0)    62%);
    opacity: 0;
    transition: opacity 280ms ease;
}
.kl-skip-global:hover,
.kl-skip-global:focus-visible {
    color: #eaf6ff;
    box-shadow:
        inset 0 1px 0 rgba(255,255,255,0.09),
        inset 0 0 0 1px rgba(160,200,230,0.14),
        0 1px 2px rgba(0,0,0,0.55),
        0 2px 8px rgba(0,0,0,0.40);
    outline: none;
}
.kl-skip-global:hover::before,
.kl-skip-global:focus-visible::before { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
    .kl-nav-btn::before,
    .kl-skip-global::before { transition: none; }
}

/* ── Astronaut-hand cursor that drives a slider automatically ──
   Positioned absolutely inside .kl-param (which must be position:relative).
   `left` is set by JS each animation frame to track the slider thumb.
   After flipping the SVG vertically so fingertips point DOWN, the index
   fingertip sits at x = 20/32 = 62.5% of the element's width (it was x=12
   before the flip), so we pull the hand back by that amount — not 50% —
   to align the fingertip itself with the thumb centre. */
.kl-hand {
    position: absolute;
    width: 30px; height: 34px;
    pointer-events: none;
    z-index: 5;
    transform: translate(-62.5%, 0);
    animation: kl-hand-press 1.6s ease-in-out infinite;
    filter: drop-shadow(0 0 6px rgba(0,200,255,0.45));
}
.kl-hand svg { width: 100%; height: 100%; display: block; }
@keyframes kl-hand-press {
    0%, 100% { transform: translate(-62.5%, 0); }
    50%      { transform: translate(-62.5%, 3px); }
}

/* .kl-param must be position:relative so the hand anchors correctly. */
.kl-param { position: relative; }

/* ── Bottom bar: HUD + controls ── */
.kl-bottom {
    position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%); z-index: 10;
    display: flex; align-items: flex-end; gap: 8px;
}
/* HUD is absolutely positioned to the LEFT of the centred play panel
   so its variable-width content never shifts the panel horizontally.
   Common sources of width drift this guards against:
     * decimal-width changes in any numeric readout (e.g. `t = 1.23`
       becomes `t = 12.3` and the string grows by a character),
     * the three-body ghost note swapping between `ghost off` (9 chars)
       and `ghost drift X.XXX` (17+ chars) when toggled,
     * Kepler First Law switching between planets — `a = 9.54 AU` vs
       `a = 19.19 AU` differ in width.
   With the HUD outside the flex flow, `.kl-bottom`'s width depends only
   on fixed-width children (the play panel, plus the 210 px chart panel
   on Third Law), so the centred bar stays put. */
.kl-hud {
    position: absolute;
    right: 100%;
    bottom: 0;
    margin-right: 8px;
    padding: 10px 12px;
    background: rgba(3,3,8,0.88);
    border: 1px solid rgba(0,180,255,0.1);
    border-radius: 6px;
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    font-family: monospace; font-size: 11px;
    color: rgba(150,190,230,0.75);
    white-space: nowrap;
}
.kl-hud:empty { display: none; }

.kl-panel {
    width: 420px; padding: 10px 14px;
    background: rgba(3,3,8,0.88);
    border: 1px solid rgba(0,180,255,0.1);
    border-radius: 6px;
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
}

/* Third Law only: with just two sliders (Planet 1, Planet 2) and three
   speed buttons, the panel can be much narrower than the shared default.
   Scoped to the Third Law bottom bar via `.kl-bottom-third` so First /
   Second Law layouts are untouched. */
.kl-bottom-third .kl-panel {
    width: 280px;
}
.kl-panel-controls {
    display: flex; align-items: center; gap: 5px;
    justify-content: center; flex-wrap: nowrap;
}
.kl-panel-divider { height: 1px; background: rgba(0,180,255,0.08); margin: 8px 0; }
.kl-sep { width: 1px; height: 18px; background: rgba(0,180,255,0.12); margin: 0 2px; }

.kl-param { margin-bottom: 8px; }
.kl-param:last-of-type { margin-bottom: 0; }
.kl-param-row { display: flex; justify-content: space-between; margin-bottom: 2px; }
.kl-param-row span { font-size: 11px; color: rgba(180,210,255,0.6); }
.kl-param-val { color: #00ccff !important; font-family: monospace; }

/* ── Third-Law side chart (sits to the left of the play panel in
     `.kl-bottom`). Mirrors the play panel's dark translucent card look
     so the two read as part of the same instrument strip. ── */
.kl-chart-panel {
    width: 210px; height: 160px;
    background: rgba(8,12,24,0.78);
    border: 1px solid rgba(0,180,255,0.25);
    border-radius: 6px;
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    display: block;
}

/* ── Top-right button row ── */
.kl-topright {
    position: absolute; top: 10px; right: 10px; z-index: 11;
    display: flex; gap: 4px;
}

/* ── Buttons (shared) — Concept C "sunlit-edge" language ──
   Same vocabulary as .kl-nav-btn / .kl-skip-global above, scaled
   down for the control-bar row (Play / Step / Reset / 1× / 2× / 5×
   etc.). Compact padding so a full row fits the .kl-panel width.
   `.sb.on` is the persistent-active variant — used by speed
   buttons, focus toggle, etc. — and brightens the cyan fill rather
   than warming the right edge, so "currently selected" reads as a
   different signal from the transient sunlit-edge hover. */
.sb {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    background: linear-gradient(180deg, rgba(140,215,255,0.06) 0%, rgba(100,190,250,0.025) 100%);
    border: 0;
    border-radius: 4px;
    padding: 5px 12px;
    color: rgba(200,220,255,0.75);
    font-size: 11px; cursor: pointer; font-family: inherit;
    box-shadow:
        inset 0 1px 0 rgba(220,240,255,0.06),
        inset 0 0 0 1px rgba(150,215,255,0.10),
        0 1px 2px rgba(0,0,0,0.55),
        0 2px 6px rgba(0,0,0,0.35);
    transition: color 160ms ease, box-shadow 220ms ease;
}
.sb::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    border-radius: inherit;
    background: radial-gradient(circle at 110% 50%,
        rgba(160,220,255,0.20) 0%,
        rgba(120,200,255,0.10) 18%,
        rgba(80,180,255,0.04) 38%,
        rgba(0,160,255,0)    62%);
    opacity: 0;
    transition: opacity 280ms ease;
}
.sb:hover,
.sb:focus-visible {
    color: #eaf6ff;
    box-shadow:
        inset 0 1px 0 rgba(210,240,255,0.12),
        inset 0 0 0 1px rgba(140,210,255,0.22),
        0 1px 2px rgba(0,0,0,0.55),
        0 2px 8px rgba(0,0,0,0.40);
    outline: none;
}
.sb:hover::before,
.sb:focus-visible::before { opacity: 1; }
.sb.on {
    color: #00ccff;
    background: linear-gradient(180deg, rgba(130,225,255,0.14) 0%, rgba(90,200,250,0.05) 100%);
    box-shadow:
        inset 0 1px 0 rgba(220,245,255,0.08),
        inset 0 0 0 1px rgba(160,230,250,0.18),
        0 1px 2px rgba(0,0,0,0.55),
        0 2px 8px rgba(0,0,0,0.40);
}
.sb.sp { padding: 5px 10px; text-align: center; white-space: nowrap; box-sizing: border-box; }
.sb:disabled { opacity: 0.25; cursor: not-allowed; }
.sb:disabled::before { opacity: 0; }

/* ── Preset grid ── */
.kl-presets {
    display: grid; grid-template-columns: repeat(5, 1fr); gap: 3px; margin-top: 8px;
}
.kl-presets-second { grid-template-columns: repeat(5, 1fr); }

/* ── Preset grid — same Concept C language, smaller still. The grid
   is dense (5 columns) so padding stays minimal and font-size shrinks
   one notch from .sb. `.pb-active` is the persistent selection (cyan
   fill) and `.pb-solar` is the warm-amber "Solar System" override that
   spans the full row; both keep their existing colour identity but
   inherit the layered shadow + sunlit-edge hover from the base. */
.pb {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    background: linear-gradient(180deg, rgba(120,210,255,0.04) 0%, rgba(80,180,240,0.015) 100%);
    border: 0;
    color: rgba(180,210,255,0.6); padding: 5px 0; border-radius: 3px;
    font-size: 10px; cursor: pointer; font-family: inherit;
    text-align: center; letter-spacing: 0.2px;
    box-shadow:
        inset 0 1px 0 rgba(210,235,255,0.03),
        inset 0 0 0 1px rgba(150,210,255,0.06),
        0 1px 2px rgba(0,0,0,0.45);
    transition: color 160ms ease, box-shadow 220ms ease;
}
.pb::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    border-radius: inherit;
    background: radial-gradient(circle at 110% 50%,
        rgba(255,240,210,0.28) 0%,
        rgba(255,230,195,0.14) 22%,
        rgba(255,220,175,0)    62%);
    opacity: 0;
    transition: opacity 240ms ease;
}
.pb:hover,
.pb:focus-visible {
    color: #fff;
    box-shadow:
        inset 0 1px 0 rgba(255,255,255,0.07),
        inset 0 0 0 1px rgba(160,200,230,0.13),
        0 1px 2px rgba(0,0,0,0.5);
    outline: none;
}
.pb:hover::before,
.pb:focus-visible::before { opacity: 1; }
.pb.pb-active {
    color: #00ccff;
    background: linear-gradient(180deg, rgba(130,225,255,0.12) 0%, rgba(90,200,250,0.04) 100%);
    box-shadow:
        inset 0 1px 0 rgba(220,245,255,0.06),
        inset 0 0 0 1px rgba(160,230,250,0.16),
        0 1px 2px rgba(0,0,0,0.5);
}
.pb.pb-solar {
    grid-column: span 5;
    color: rgba(255,200,80,0.65);
    background: linear-gradient(180deg, rgba(255,200,80,0.06) 0%, rgba(255,170,40,0.02) 100%);
    box-shadow:
        inset 0 1px 0 rgba(255,255,255,0.04),
        inset 0 0 0 1px rgba(255,200,90,0.14),
        0 1px 2px rgba(0,0,0,0.45);
    letter-spacing: 1px; text-transform: uppercase; font-size: 9px; padding: 6px 0;
}
.pb.pb-solar:hover,
.pb.pb-solar:focus-visible {
    color: rgba(255,220,100,0.95);
    box-shadow:
        inset 0 1px 0 rgba(255,255,255,0.07),
        inset 0 0 0 1px rgba(255,210,110,0.22),
        0 1px 2px rgba(0,0,0,0.5);
}

@media (prefers-reduced-motion: reduce) {
    .sb::before,
    .pb::before { transition: none; }
}

/* ── Sliders ── */
.sr {
    width: 100%; height: 3px;
    -webkit-appearance: none; appearance: none;
    background: rgba(0,180,255,0.1); border-radius: 2px; outline: none;
    transition: background 0.2s, box-shadow 0.2s;
}
.sr::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    width: 12px; height: 12px; border-radius: 50%;
    background: #00ccff; cursor: pointer; box-shadow: 0 0 6px rgba(0,200,255,0.3);
    transition: box-shadow 0.2s, transform 0.2s;
}
.sr::-moz-range-thumb {
    width: 12px; height: 12px; border-radius: 50%;
    background: #00ccff; cursor: pointer; border: none; box-shadow: 0 0 6px rgba(0,200,255,0.3);
    transition: box-shadow 0.2s, transform 0.2s;
}

/* ── Aggressive slider glow when a tip points at it ── */
.kl-slider-pulse .kl-param-row span { color: rgba(0,220,255,0.95); }
.kl-slider-pulse .kl-param-val { color: #7df3ff !important; }
.kl-slider-pulse .sr {
    background: rgba(0,200,255,0.35);
    animation: kl-slider-track-pulse 1.2s ease-in-out infinite;
}
.kl-slider-pulse .sr::-webkit-slider-thumb {
    animation: kl-slider-thumb-pulse 1.2s ease-in-out infinite;
    transform: scale(1.15);
}
.kl-slider-pulse .sr::-moz-range-thumb {
    animation: kl-slider-thumb-pulse 1.2s ease-in-out infinite;
    transform: scale(1.15);
}
@keyframes kl-slider-track-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(0,220,255,0), inset 0 0 4px rgba(0,220,255,0.4); }
    50%      { box-shadow: 0 0 12px 2px rgba(0,220,255,0.55), inset 0 0 6px rgba(0,220,255,0.6); }
}
@keyframes kl-slider-thumb-pulse {
    0%, 100% { box-shadow: 0 0 6px rgba(0,220,255,0.6), 0 0 0 0 rgba(0,220,255,0); }
    50%      { box-shadow: 0 0 14px rgba(0,220,255,0.95), 0 0 0 6px rgba(0,220,255,0.18); }
}

/* Canvas host — stretch to container */
.simulation-container, .simulation-canvas {
    width: 100% !important; height: 100% !important; background: transparent !important;
}

/* ══════════════════════════════════════════════════════════════════
   Responsive breakpoints for interactive lesson pages
   ══════════════════════════════════════════════════════════════════

   Breakpoint convention (see STYLE_GUIDE.md § Responsive layout):
     * 820 px — tablet / phone landscape threshold. Lesson pages stop
       trying to fit info panel + canvas + controls side-by-side and
       stack the overlay cards full-width instead.
     * 560 px — phone portrait threshold. Further typographic and
       padding compression.
     * (pointer: coarse) — touch-target sweep. Independent of viewport
       width because phones in landscape are still touch devices.

   The Kepler / Hohmann / Three-Body pages share the same `.kl-*`
   layout scaffolding, so one rule set covers all five at once. Pages
   with their own page-level styles (EscapeVelocity, NewtonsGravitation,
   Brachistochrone) already carry their own media queries.                */

@media (max-width: 820px) {
    /* Resting info panel: full-width banner across the top instead of
       a 288 px column on the left. Max-height clamped so the tour text
       never fills more than ~45 % of the viewport, leaving the canvas
       and the bottom controls reachable in one thumb swipe.
       `top: 58px` accounts for the ~6.4 px upward shift caused by
       `.lesson-page { margin: -1.5rem }` cancelling `.content`'s
       `padding-top: 1.1rem`, which makes the lesson-stage render
       ~6 px above the viewport top. 58 px places the panel top at
       ~51 px from the viewport, clearing both the fixed breadcrumb bar
       (bottom ≈ 30 px) and the Focus button (bottom ≈ 20 px) with
       comfortable room to spare on devices where font metrics differ. */
    .kl-info {
        left: 8px; right: 8px; top: 58px;
        width: auto;
        max-width: none;
        max-height: 45vh;
        padding: 10px 12px 12px;
        font-size: 13px;
    }

    /* Intro-mode: same full-width banner, pinned at the top rather than
       floating bottom-left next to a centred control panel (which no
       longer exists at this width). Dropping the fancy translate keeps
       the transition from intro → resting a no-op position-wise, which
       is fine — the fade-in of the resting content carries the beat. */
    .kl-info.kl-info-intro-mode {
        left: 8px; right: 8px; top: 58px;
        width: auto;
        max-height: 42vh;
        padding: 12px 14px 14px;
        transform: none;
    }

    .kl-info-intro-text { font-size: 14px; line-height: 1.55; }

    .kl-nav-buttons { margin-top: 12px; }

    /* Bottom bar: switch to a stacked column so HUD + panel + (optional)
       chart all take the full viewport width. HUD sits above the panel
       so the numeric readout isn't pushed offscreen by the wider panel
       on a narrow viewport. `order` reshuffles the flex children without
       needing to change the razor markup. */
    /* `right: 100px` — the `.detail-toggle` (position:fixed, bottom-right)
       is ~90px wide at fine-pointer sizes (10px monospace, 3px 6px padding).
       Leaving 100px on the right keeps the panel clear of the toggle. */
    .kl-bottom {
        left: 8px; right: 100px; bottom: 8px;
        transform: none;
        flex-direction: column;
        align-items: stretch;
        gap: 6px;
        max-height: 55vh;
        overflow-y: auto;
    }
    .kl-hud {
        /* Override desktop `position: absolute` — on mobile the HUD
           rejoins the column flow above the play panel. */
        position: static;
        margin-right: 0;
        order: -1;            /* above the panel */
        font-size: 10.5px;
        white-space: normal;  /* was `nowrap` for the desktop single-line HUD */
        line-height: 1.4;
    }
    .kl-panel { width: auto; }
    /* Third-Law override: chart panel stretches too, and sits between
       HUD and play panel (it's not interactive — putting it above the
       controls keeps controls within thumb reach). */
    .kl-bottom-third .kl-panel { width: auto; }
    .kl-chart-panel { width: 100%; height: 120px; order: 0; }

    /* Top-right focus / detail toggles stay where they are but shrink so
       they don't collide with the info banner. */
    .kl-topright { top: 6px; right: 6px; gap: 3px; }
    .kl-topright .sb { font-size: 10px; padding: 4px 8px; }

    /* Absolute-positioned captions are carefully tuned for the desktop
       coordinate system (Sun at canvas centre, sectors upper-right…).
       On a phone those anchors miss their targets and clutter the view.
       Suppress them; `.kl-info` already carries the step's caption text,
       which is the essential content. */
    .kl-caption-sectors,
    .kl-caption-sun,
    .kl-caption-slider { display: none; }

    /* Planet / centre captions are centred on the sim — keep them but
       shrink so they fit. */
    .kl-caption-planet,
    .kl-caption-center { max-width: 88vw; font-size: 13px; }
}

/* Touch phones: the detail-level toggle becomes ~110 px wide at coarse
   pointer (min-height: 44 px, padding: 10px 12px, font-size: 11px), so
   the 100 px gap set above isn't wide enough. Widen to 124 px. */
@media (max-width: 820px) and (pointer: coarse) {
    /* `right: 124px` — the toggle is ~110px wide at coarse-pointer sizes
       (11px monospace, 10px 12px padding); 124px adds a comfortable margin. */
    .kl-bottom { right: 124px; }
}

@media (max-width: 560px) {
    /* Phone-portrait compression: let the control buttons wrap onto a
       second row instead of overflowing, and tighten typography in the
       info banner so the tour text fits in ~30 % of the viewport,
       leaving the simulation canvas the majority of the screen. */
    .kl-panel-controls { flex-wrap: wrap; row-gap: 6px; justify-content: flex-start; }
    .kl-title { font-size: 16px; }
    .kl-info { max-height: 30vh; font-size: 12px; }
    .kl-info-intro-text { font-size: 13px; line-height: 1.5; }
    .kl-panel { padding: 8px 10px; }

    /* Legend descriptions add ~160 px of height on narrow screens but
       aren't needed once the guided intro has explained each symbol.
       Hide the description text; the symbol + name row is enough for
       quick reference. Tighten item padding to match. */
    .kl-legend-desc { display: none; }
    .kl-legend-item { padding: 5px 2px; }

    /* Preset grid: planet shortcuts don't translate to a 360-px screen
       (buttons are unreadably small, labels truncate). The sliders give
       the same full range of exploration, so hide the grid entirely. */
    .kl-presets { display: none; }
}

/* ── Touch-target sweep (independent of viewport width) ───────────
   WCAG / Material / HIG all recommend ≥44×44 CSS px for primary touch
   targets. The default `<input type="range">` thumb is ~12 px at rest
   (our `.sr` class sizes it to exactly that to match the thin-track
   aesthetic), which is ungrabbable with a fingertip. We keep the thin
   track on desktop but upgrade thumb + button sizes on any coarse
   pointer (phones, tablets, kiosk touchscreens).                    */
@media (pointer: coarse) {
    /* Slider thumb ~2× bigger in every direction → the tap target goes
       from 12² = 144 px² to 24² = 576 px² without widening the track. */
    .sr::-webkit-slider-thumb {
        width: 24px; height: 24px;
        box-shadow: 0 0 10px rgba(0,200,255,0.45);
    }
    .sr::-moz-range-thumb {
        width: 24px; height: 24px;
        box-shadow: 0 0 10px rgba(0,200,255,0.45);
    }
    /* Thicker track so the thumb doesn't look stranded; still reads
       as a thin line next to the thumb. */
    .sr { height: 5px; }

    /* Extra vertical padding around each slider row so the flick/drag
       hit area spans the recommended 44 px without changing visual
       weight. `.kl-param` is the label+track wrapper. */
    .kl-param { padding: 8px 0; margin-bottom: 4px; }

    /* Play / Step / Reset / speed buttons — 44 px min height, generous
       horizontal padding. The text size stays small so buttons still
       read as "chrome" rather than CTAs. */
    .sb {
        min-height: 44px;
        padding: 8px 14px;
        font-size: 12px;
    }
    /* Preset pills (smaller grid) — keep their look but enforce a
       usable minimum tap height. */
    .pb {
        min-height: 44px;
        padding: 10px 4px;
        font-size: 11px;
    }
    /* Guided-tour nav pills (Previous / Next / Ready). */
    .kl-nav-btn {
        min-height: 44px;
        padding: 10px 22px;
        font-size: 12px;
    }
    /* Detail-level toggle (pinned bottom-right by MainLayout) — same
       treatment so it's reachable on phones. */
    .detail-toggle {
        min-height: 44px;
        padding: 10px 12px;
        font-size: 11px;
    }

    /* The astronaut-hand cursor exists to point at the slider thumb
       during the guided tour — it's a pure hover-era hint and makes no
       sense when the user's literal finger is the cursor. Hide it. */
    .kl-hand { display: none !important; }
}

/* Devices without hover (effectively a superset of touch) also don't
   benefit from the hand pointer or from .sb:hover-only discovery cues.
   Kept separate from (pointer: coarse) because a trackpad device may
   be (pointer: fine) + (hover: none). */
@media (hover: none) {
    .kl-hand { display: none !important; }
}
