/* ============================================================
   Planet Journey — layout layer (core)
   Every rule is scoped under html.journey… except the few
   ungated additions at the end. The plain page (reduced motion,
   localStorage.layout = 'list', no JS) must stay as it is today.
   ============================================================ */

/* Tokens */
html.journey {
    --nav-h: 70px;
    --sky-band: clamp(88px, 12vh, 120px);
    --card-gap-b: 3vh; /* breathing room under the card */
    --sky-band-v: 96px;
    --card-w: min(1120px, calc(100vw - 64px));

    /* The logbook cards are glass, not paper: the sky shows through them and the
       stars stay visible behind the text. --glass-tint is the one dial — raise it
       for more paper, lower it for more space. --glass-solid is the same tint
       opaque, for the "more below" fade.

       No backdrop-filter: a blurred backdrop has to be recomputed on every frame
       in which anything on the page animates, and this page always has something
       moving, so it cost ~36 of the 60 frames per second at rest. The tint does
       the legibility work on its own. */
    --glass-tint: 0.34;
    --glass-bg: rgba(14, 14, 18, var(--glass-tint));
    --glass-solid: rgba(14, 14, 18, 0.9);
    --glass-edge: rgba(237, 230, 216, 0.14);
}

html.journey body.dark-mode {
    --glass-bg: rgba(18, 16, 13, calc(var(--glass-tint) + 0.06));
    --glass-solid: rgba(18, 16, 13, 0.9);
}

/* Sky — one fixed deep-space backdrop behind everything (JS-injected) */
html.journey body {
    background: #000;
}

/* Content palette inside the cards. The sky is space in both colour modes, so
   the content that floats in it is always light — styles.css reads these tokens
   everywhere (its body.dark-mode rules resolve them too), so redefining them
   here carries every component: headings, tiles, tags, carousel chips, links. */
html.journey .panel-card {
    --text-color: #EDE6D8;
    --text-muted: #B3A99C;
    --text-subtle: #8B8275;
    --bg-color: rgba(237, 230, 216, 0.05);
    --section-bg: rgba(237, 230, 216, 0.05);
    --card-bg: rgba(237, 230, 216, 0.05); /* inner tiles: a frosted pane on the glass */
    --border-color: rgba(237, 230, 216, 0.16);
    --border-soft: rgba(237, 230, 216, 0.08);
    --shadow-color: rgba(0, 0, 0, 0.3);
    --accent-primary: #9DB096;
    --accent-secondary: #D9A857;
    --accent-soft: rgba(157, 176, 150, 0.12);
    color: var(--text-color);
}

/* The four tiles that hard-code `background: white` in styles.css (the dark-mode
   rules already resolve var(--card-bg), so this covers the light mode) */
html.journey .panel-card .highlight-item,
html.journey .panel-card .stat-item,
html.journey .panel-card .coauthors-section,
html.journey .panel-card .contact-form {
    background: var(--card-bg);
    border: 1px solid var(--border-soft);
    box-shadow: none;
}

/* Form controls default to the UA's white; give them the same frosted pane */
html.journey .panel-card input,
html.journey .panel-card textarea {
    background: var(--section-bg);
    color: var(--text-color);
    border-color: var(--border-color);
}

html.journey .panel-card ::placeholder {
    color: var(--text-subtle);
    opacity: 1;
}


/* Carousel footers sit above the "more below" fade so the dots and the
   "1 / 9" counter stay crisp when the card still has content below */
html.journey .panel-card .publications-carousel-footer,
html.journey .panel-card .github-carousel-footer {
    position: relative;
    z-index: 1;
}

html.journey .journey-sky {
    position: fixed;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    pointer-events: none;
}

html.journey .journey-sky-base {
    position: absolute;
    inset: 0;
}

/* the star layers take their size from journey.js, so no bottom/right anchor */
html.journey .journey-sky-far,
html.journey .journey-sky-near {
    position: absolute;
    top: 0;
    left: 0;
}

html.journey .journey-sky-base {
    background:
        radial-gradient(ellipse 70% 50% at 22% 32%, rgba(58, 62, 96, 0.16) 0%, rgba(0, 0, 0, 0) 70%),
        radial-gradient(ellipse 60% 45% at 82% 68%, rgba(86, 60, 74, 0.13) 0%, rgba(0, 0, 0, 0) 70%),
        radial-gradient(ellipse at 50% 60%, #050507 0%, #020203 70%, #000 100%);
}

html.journey body.dark-mode .journey-sky-base {
    background:
        radial-gradient(ellipse 70% 50% at 22% 32%, rgba(74, 66, 88, 0.16) 0%, rgba(0, 0, 0, 0) 70%),
        radial-gradient(ellipse 60% 45% at 82% 68%, rgba(96, 68, 66, 0.14) 0%, rgba(0, 0, 0, 0) 70%),
        radial-gradient(ellipse at 50% 60%, #0E0D0B 0%, #0A0908 60%, #070605 100%);
}

/* The two star layers are painted by journey.js into a canvas each (see
   paintSky): one field per layer, no tiling, sized by JS to the viewport plus
   its parallax range. They only move when you do — the old perpetual sideways
   drift is what made the repeat legible, and it kept the compositor awake. */
html.journey .journey-stars {
    display: block;
    width: 100%;
    height: 100%;
}

html.journey-h .journey-sky-far,
html.journey-h .journey-sky-near {
    translate: calc(var(--journey-p, 0) * var(--sky-shift, 0px)) 0;
}

html.journey-v .journey-sky-far,
html.journey-v .journey-sky-near {
    translate: 0 calc(var(--journey-p, 0) * var(--sky-shift, 0px));
}

/* ------------------------------------------------------------
   Dark forest: after ten still seconds the stars go out. Only the
   backdrop dims — every card keeps its own contrast, so whatever you
   stopped to read stays exactly as readable as it was.
   ------------------------------------------------------------ */
html.journey .journey-sky-far,
html.journey .journey-sky-near,
html.journey .jp-planet,
html.journey .jp-droplet,
html.journey .jr-pilot {
    transition: opacity 2.4s ease;
}

html.journey .journey-sky-base {
    transition: filter 2.4s ease;
}

html.journey-dark .journey-sky-far,
html.journey-dark .journey-sky-near {
    opacity: 0.08;
}

html.journey-dark .journey-sky-base {
    filter: brightness(0.5);
}

html.journey-dark .jp-planet,
html.journey-dark .jp-droplet {
    opacity: 0.3;
}

html.journey-dark .jr-pilot {
    opacity: 0.45;
}

html.journey-dark .journey-twinkle {
    animation: none;
    opacity: 0.12;
}

.journey-forest {
    display: none;
}

/* It lives in the empty band between the navbar and the card, the one strip
   that is never text — a line of it over whatever you stopped to read would
   be exactly the wrong thing. Phones have no such strip, so they just dim. */
html.journey-h .journey-forest {
    display: block;
    position: fixed;
    left: 50%;
    top: calc(var(--nav-h) + var(--sky-band) / 2);
    transform: translate(-50%, -50%);
    margin: 0;
    z-index: 950;
    color: rgba(226, 216, 200, 0.5);
    font-size: 0.72rem;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity 1.6s ease;
}

html.journey-dark .journey-forest {
    opacity: 1;
    transition: opacity 2.4s ease 1.2s;
}

/* A few stars breathe, at different rates, so the sky is alive without
   anything sliding. These are the only animations left in the backdrop. */
html.journey .journey-twinkle {
    position: absolute;
    width: 2px;
    height: 2px;
    border-radius: 50%;
    background: #fffdf7;
    box-shadow: 0 0 4px 1px rgba(255, 252, 240, 0.5);
    opacity: 0.3;
    animation: jsTwinkle 6s ease-in-out infinite;
}

@keyframes jsTwinkle {
    0%, 100% { opacity: 0.22; }
    50% { opacity: 0.95; }
}

/* Panels sit on the shared sky (both modes): the hero's gradient and the
   sections' own backgrounds go (body.dark-mode .hero/.research/… use
   !important), as do the hero star tiles and the in-hero rocket;
   Earth, Moon, motto and shooting stars stay */
html.journey .panel {
    background: transparent !important;
}

html.journey .hero::before,
html.journey .hero::after,
html.journey .hero .space-rocket {
    display: none;
}

/* Panels: neutralise section padding + the section + section border */
html.journey .panel + .panel {
    border-top: 0;
}

html.journey .panel-card > .container {
    padding-left: 0;
    padding-right: 0;
}

/* Footer sits inside the Beyond card */
html.journey .beyond .footer {
    background: transparent !important; /* overrides body.dark-mode .footer !important */
    color: var(--text-muted);
    padding: 0;
}

/* ------------------------------------------------------------
   Horizontal journey (desktop)
   ------------------------------------------------------------ */
html.journey-h,
html.journey-h body {
    height: 100%;
    overflow: hidden;
}

html.journey-h .journey-track {
    position: fixed;
    inset: 0;
    display: flex;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    overscroll-behavior: contain;
    scrollbar-width: none;
    z-index: 1;
}

html.journey-h .journey-track::-webkit-scrollbar {
    display: none;
}

html.journey-h .panel {
    flex: 0 0 100vw;
    width: 100vw;
    height: 100%;
    position: relative;
    scroll-snap-align: start;
    scroll-snap-stop: always;
    padding: 0;
}

html.journey-h .hero.panel {
    min-height: 0;
}

/* Keep the motto clear of the greeting on short viewports (768px tall) */
html.journey-h .space-motto {
    top: max(80px, calc(50% - 275px));
}

/* Narrower grids stack the hero text taller: lift and shrink the motto */
@media (max-width: 1299px) {
    html.journey-h .space-motto {
        top: 74px;
        font-size: 1.5rem;
    }
}

/* Short viewports: centre the hero below the navbar and tighten the column */
@media (max-height: 640px) {
    html.journey-h .hero.panel {
        padding-top: var(--nav-h);
    }

    html.journey-h .hero-title {
        font-size: 2.6rem;
    }

    html.journey-h .hero-subtitle {
        font-size: 1.25rem;
    }

    html.journey-h .hero-description {
        margin-bottom: 1rem;
    }

    html.journey-h .social-links {
        margin-top: 1.25rem;
    }
}

html.journey-h .panel-card {
    position: absolute;
    top: calc(var(--nav-h) + var(--sky-band));
    left: 0;
    right: 0;
    margin: 0 auto; /* centred by layout, not a transform: no half-pixel blur on odd widths */
    width: var(--card-w);
    max-height: calc(100% - var(--nav-h) - var(--sky-band) - var(--card-gap-b));
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    z-index: 2;
    background: var(--glass-bg);
    border: 1px solid var(--glass-edge);
    border-radius: 18px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3);
    padding: 2.5rem 2rem 0; /* bottom padding lives on .container so the sticky hint reaches the card's edge */
}

html.journey-h .panel-card > .container {
    padding-bottom: 2.5rem;
}

html.journey-h .panel-card .section-header {
    margin-bottom: 2.5rem;
}

/* The publications card is the tallest: a tighter header keeps its dots in view at 900px */
html.journey-h .publications .section-header {
    margin-bottom: 1.5rem;
}

/* ------------------------------------------------------------
   Research, compacted so all four areas fit one screen without
   the card scrolling: the sketch shrinks from a banner into a mark
   beside its heading, and the paragraph runs the card's full width.
   Only in the horizontal journey — the plain page and phones keep
   the roomy banner layout.
   ------------------------------------------------------------ */
html.journey-h .research .section-header {
    margin-bottom: 1.1rem;
}

/* the research card carries the most content: give it back its margins */
html.journey-h .research .panel-card {
    padding-top: 1.6rem;
}

html.journey-h .research .panel-card > .container {
    padding-bottom: 1.6rem;
}

html.journey-h .research .section-title {
    font-size: 2rem;
}

html.journey-h .research .section-subtitle {
    font-size: 0.95rem;
}

html.journey-h .research-grid {
    gap: 1rem;
}

html.journey-h .research-card {
    display: grid;
    grid-template-columns: 78px minmax(0, 1fr);
    align-items: center;
    column-gap: 0.85rem;
    padding: 0.8rem 1rem 0.85rem;
}

html.journey-h .research-card .rc-header {
    grid-column: 1;
    grid-row: 1;
    min-height: 0;
    padding: 0;
    border-bottom: 0;
}

html.journey-h .research-card .research-sketch {
    max-width: 78px;
}

html.journey-h .research-card h3 {
    grid-column: 2;
    grid-row: 1;
    padding: 0;
    font-size: 1.15rem;
}

html.journey-h .research-card p {
    grid-column: 1 / -1;
    grid-row: 2;
    padding: 0.6rem 0 0;
    font-size: 0.875rem;
    line-height: 1.45;
}

html.journey-h .research-card .research-tags {
    grid-column: 1 / -1;
    grid-row: 3;
    padding: 0.45rem 0 0;
    font-size: 0.78rem;
}

/* Short screens: a shallower sky band and no section subtitle buy the
   research grid the last ~60px it needs to stay on one screen */
@media (max-height: 820px) {
    html.journey-h {
        --sky-band: clamp(72px, 10vh, 100px);
        --card-gap-b: 2vh;
    }
}

@media (max-height: 760px) {
    html.journey-h .research .section-subtitle {
        display: none;
    }

    html.journey-h .research .section-title {
        font-size: 1.85rem;
    }
}

/* the cardiac card's ECG wash was sized for the banner */
html.journey-h .rc-header-cardiac .cardiac-ecg-bg {
    display: none;
}

/* "More below" hint (JS-injected last child, toggled via .has-more): the fade
   is solid in its lower half so the chevron always sits on paper, not on text */
html.journey-h .panel-more {
    position: sticky;
    bottom: 0;
    display: flex;
    align-items: flex-end;
    justify-content: flex-end; /* right, so it never lands on centred content like the carousel counter */
    height: 64px;
    margin: -64px -2rem 0;
    padding: 0 1.5rem 8px 0;
    background: linear-gradient(to bottom, transparent 0, var(--glass-solid) 75%);
    color: var(--text-muted);
    font-size: 0.8rem;
    opacity: 0;
    transition: opacity 0.25s ease;
    pointer-events: none;
}

html.journey-h .panel-card.has-more .panel-more {
    opacity: 1;
}

/* The last stop's card is short: centre it in the panel instead of pinning it to the band */
html.journey-h .beyond .panel-card {
    top: 0;
    bottom: 0;
    height: fit-content;
    margin: auto;
    text-align: center;
}

/* ------------------------------------------------------------
   Vertical journey (phones / short viewports)
   ------------------------------------------------------------ */
html.journey-v .journey-track {
    display: block;
}

html.journey-v .panel {
    position: relative;
}

html.journey-v .panel:not(.hero) {
    padding: var(--sky-band-v) 0 0;
}

html.journey-v .panel-card {
    position: relative;
    z-index: 2;
    margin: 0 16px 28px;
    padding: 1.75rem 1.25rem;
    max-height: none;
    overflow: visible;
    background: var(--glass-bg);
    border: 1px solid var(--glass-edge);
    border-radius: 16px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.3);
}

/* The last band holds no planet: let the footer card follow more closely */
html.journey-v .beyond.panel {
    padding-top: 48px;
}

html.journey-v .beyond .panel-card {
    text-align: center;
}

/* Phones: the flight-path dots, the motto and the title share the top strip —
   drop the motto and the hero content so all three get their own line */
@media (max-width: 768px) {
    html.journey-v .hero .space-motto {
        top: 86px;
    }

    html.journey-v .hero-content {
        padding-top: 64px;
    }
}

/* ------------------------------------------------------------
   Ungated additions (harmless on the plain page)
   ------------------------------------------------------------ */
section[id] {
    scroll-margin-top: 70px;
}

/* The injected "more" hint only renders in the horizontal journey */
.panel-more {
    display: none;
}

/* The footer moved into #beyond; keep it looking as before */
.beyond {
    padding: 0;
    border-top: 0;
}

.beyond > .panel-card > .container {
    max-width: none;
    padding: 0;
}

@media (prefers-reduced-motion: reduce) {
    .hero::before,
    .hero::after,
    .space-rocket,
    .space-rocket .rocket-exhaust,
    .space-earth::before,
    .space-earth::after,
    .shooting-star,
    .scroll-indicator,
    .mini-rocket-exhaust {
        animation: none;
    }

    /* The orbits freeze mid-flight (their static base pose stacks the moon on the
       Earth and the three ring dots on one spot): keyframe 0 puts the moon at the
       bottom centre; the rings hold different angles */
    .space-moon,
    .profile-decoration,
    .profile-decoration-inner,
    .profile-decoration-third {
        animation-play-state: paused;
    }

    .profile-decoration {
        animation-delay: -5s;
    }

    .profile-decoration-inner {
        animation-delay: -10s;
    }

    .profile-decoration-third {
        animation-delay: -20s;
    }

    .fade-in {
        opacity: 1;
        transform: none;
        transition: none;
    }
}


/* ============================================================
   Planet Journey — rocket module (pilot rocket + Return to Earth)
   Every rule is scoped under html.journey…; the pilot element only
   exists while the journey is active, so the plain page is untouched.
   Prefix .jr-*, keyframes jr*.
   ============================================================ */

/* Pilot shell: fixed, body-level, driven by --jr-x/--jr-y/--jr-a (JS).
   Hidden whenever the journey classes are gone (live reduced-motion opt-out). */
.jr-pilot {
    display: none;
}

/* The pilot flies BEHIND the track, so it shows through the glass cards and can
   cross the whole screen without ever covering a word. Parked on the Earth it
   comes to the front, otherwise the hero's own planet would hide it. */
html.journey .jr-pilot.is-grounded {
    z-index: 900;
}

html.journey .jr-pilot {
    display: block;
    position: fixed;
    left: 0;
    top: 0;
    width: 60px;
    height: 120px;
    z-index: 0;
    pointer-events: none;
    transform: translate(var(--jr-x, 0px), var(--jr-y, 0px)) rotate(var(--jr-a, 0deg));
    will-change: transform;
    transition: opacity 0.35s ease;
}

/* Out of sight: parked with no room beside the Earth (short h viewports), or
   waiting in the hero before the first scroll (v) */
/* The pilot's call sign, shown when the pointer finds the ship */
.jr-callsign {
    display: none;
}

html.journey-h .jr-callsign {
    display: block;
    position: fixed;
    z-index: 901;
    transform: translateX(-50%);
    padding: 2px 7px;
    border: 1px solid rgba(237, 230, 216, 0.22);
    border-radius: 3px;
    background: rgba(14, 14, 18, 0.72);
    color: #E4DACA;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.2s ease;
}

html.journey-h .jr-callsign.is-shown {
    opacity: 1;
}

html.journey .jr-pilot.is-tucked {
    opacity: 0;
}

html.journey-v .jr-pilot {
    left: auto;
    right: 10px;
}

/* Idle bob lives on its own element and on the `translate` property, so it
   never fights the JS-driven `transform` above. Amplitude shrinks with the
   sky band so the fin tips stay inside the lane (0 at 88px, 3px from 98px). */
html.journey .jr-bob {
    --jr-bob-amp: clamp(0px, calc((var(--sky-band) - 92px) / 2), 3px);
    position: relative;
    width: 60px;
    height: 120px;
    animation: jrBob 3.2s ease-in-out infinite;
}

html.journey-v .jr-bob {
    --jr-bob-amp: 5px;
    scale: 0.6;
}

/* At the 88px minimum band (≤733px tall) the rotated fins would graze the
   navbar and the card edge: fly a touch smaller */
@media (max-height: 733px) {
    html.journey-h .jr-bob {
        scale: 0.9;
    }
}

@keyframes jrBob {
    0%, 100% { translate: 0 0; }
    50% { translate: 0 calc(-1 * var(--jr-bob-amp, 3px)); }
}

/* The styles.css .space-rocket drawing, pinned to 0,0 without its bounce */
html.journey .jr-pilot .space-rocket {
    position: absolute;
    top: 0;
    left: 0;
    right: auto;
    animation: none;
}

/* Exhaust length follows the scroll velocity; dims while docked */
html.journey .jr-pilot .rocket-exhaust {
    height: calc(44px + var(--jr-thrust, 0) * 100px);
    opacity: 1;
    animation: none;
    transition: height 0.15s ease-out, opacity 0.35s ease;
}

html.journey .jr-pilot.is-docked .rocket-exhaust {
    opacity: 0.45;
}

/* Parked beside the Earth: a faint idle flame, full burn from the first scrolled pixel */
html.journey .jr-pilot.is-grounded .rocket-exhaust {
    opacity: 0.3;
}

/* styles.css already shrinks the drawing on phones (45x90 at <=768px, 35x70
   at <=480px): follow its box and ease the extra scale so the pilot stays
   ~36x72 on screen; the exhaust keeps the same proportion to the body */
@media (max-width: 768px) {
    html.journey-v .jr-pilot,
    html.journey-v .jr-bob {
        width: 45px;
        height: 90px;
    }

    html.journey-v .jr-bob {
        scale: 0.8;
    }

    html.journey-v .jr-pilot .rocket-exhaust {
        height: calc(33px + var(--jr-thrust, 0) * 75px);
    }
}

@media (max-width: 480px) {
    html.journey-v .jr-pilot,
    html.journey-v .jr-bob {
        width: 35px;
        height: 70px;
    }

    html.journey-v .jr-bob {
        scale: 1;
    }

    html.journey-v .jr-pilot .rocket-exhaust {
        height: calc(26px + var(--jr-thrust, 0) * 58px);
    }
}

/* Lift-off puffs — ported from space.css @keyframes fumes as a one-shot
   transform/opacity burst behind the tail; shown only while .is-launching */
html.journey .jr-fumes {
    display: none;
    position: absolute;
    left: 0;
    top: 100%;
    width: 60px;
    height: 0;
    margin: 0;
    padding: 0;
    list-style: none;
}

html.journey .jr-pilot.is-launching .jr-fumes {
    display: block;
}

html.journey .jr-fumes li {
    position: absolute;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: rgba(245, 245, 245, 0.85);
    opacity: 0;
    transform: scale(0.3);
    animation: jrFumes 0.9s ease-out both;
}

html.journey .jr-fumes li:nth-child(1) { left: 18px; top: 8px; --jr-dx: 0px; --jr-dy: 60px; }
html.journey .jr-fumes li:nth-child(2) { width: 18px; height: 18px; left: 6px; top: 20px; --jr-dx: -10px; --jr-dy: 48px; animation-delay: 0.06s; }
html.journey .jr-fumes li:nth-child(3) { width: 18px; height: 18px; left: 36px; top: 20px; --jr-dx: 10px; --jr-dy: 48px; animation-delay: 0.12s; }
html.journey .jr-fumes li:nth-child(4) { width: 14px; height: 14px; left: 12px; top: 44px; --jr-dx: -8px; --jr-dy: 40px; animation-delay: 0.2s; }
html.journey .jr-fumes li:nth-child(5) { width: 14px; height: 14px; left: 34px; top: 44px; --jr-dx: 8px; --jr-dy: 40px; animation-delay: 0.26s; }

@keyframes jrFumes {
    0% { transform: translate(0, 0) scale(0.3); opacity: 0; }
    12% { opacity: 0.9; }
    100% { transform: translate(var(--jr-dx, 0px), var(--jr-dy, 40px)) scale(1.4); opacity: 0; }
}

/* ------------------------------------------------------------
   No back-to-top in the horizontal journey: the navbar's Home link
   already flies you back, and a second rocket competed with the pilot.
   The vertical journey keeps the button as it is on the plain page.
   ------------------------------------------------------------ */
html.journey-h #back-to-top {
    display: none;
}


/* ============================================================
   Planet Journey — planets module (.jp-*, keyframes jp*)
   Five planets peeking over the logbook cards, the Beyond decor,
   the Galilean dots on the research cards and the ring-particle
   carousel dots. Drawn in the .space-earth idiom: flat disc, inset
   terminator, box-shadow stamps, soft stamped glow. Everything is
   scoped under html.journey…; the plain page never sees it.
   ============================================================ */

/* Injected decor stays hidden outside the journey (live opt-out) */
.jp-planet,
.jp-beyond,
.jp-moon-dot {
    display: none;
}

/* ------------------------------------------------------------
   Planet stage: covers its panel and clips everything the planet
   paints (glow, rings, moons) to it, z-index 1 under the card (z 2).
   The .jp-body box is the disc; moons and rings hang around it.
   ------------------------------------------------------------ */
html.journey .jp-planet {
    --jp-glow: 0.35;
    display: block;
    position: absolute;
    inset: 0;
    z-index: 1;
    overflow: hidden;
    pointer-events: none;
}

html.journey .jp-body {
    position: absolute;
    width: var(--jp-size);
    height: var(--jp-size);
}

html.journey body.dark-mode .jp-planet {
    --jp-glow: 0.25;
}

html.journey .jp-disc,
html.journey .jp-moon,
html.journey .jp-ring,
html.journey .jp-orbit {
    position: absolute;
    display: block;
    border-radius: 50%;
}

html.journey .jp-disc {
    inset: 0;
    z-index: 2;
    overflow: hidden;
    background-color: var(--jp-c);
}

/* Surface stamps drift like the Earth's landmasses; the stamp row
   repeats every --jp-period so the loop is seamless */
html.journey .jp-disc::before {
    content: "";
    position: absolute;
    display: block;
    animation: jpDrift var(--jp-spin) linear infinite;
}

@keyframes jpDrift {
    from { transform: translateX(0); }
    to   { transform: translateX(calc(-1 * var(--jp-period))); }
}

/* Horizontal journey: every planet hangs in a different part of its panel —
   one high on the right, the next low on the left, the giant half off the
   bottom — so no two stops look alike. The cards are glass, so a planet
   behind one still shows through; --jp-x/--jp-y are its offsets from the
   panel's top-left, and --jp-reach keeps moons and rings out of the edges. */
html.journey-h .jp-body {
    top: var(--jp-y, calc(var(--nav-h) + var(--sky-band) - 0.45 * var(--jp-size)));
    left: var(--jp-x, auto);
    right: var(--jp-r, max(var(--jp-reach), calc((100vw - var(--card-w)) / 2 - 0.35 * var(--jp-size))));
}

/* Mars: high on the right, just clear of the navbar */
html.journey-h .jp-mars .jp-body {
    --jp-y: calc(var(--nav-h) + 14px);
    --jp-r: max(var(--jp-reach), calc((100vw - var(--card-w)) / 2 - 0.6 * var(--jp-size)));
}

/* Jupiter: the giant rises from the bottom right, mostly below the fold */
html.journey-h .jp-jupiter .jp-body {
    --jp-y: calc(100% - 0.62 * var(--jp-size));
    --jp-r: max(var(--jp-reach), calc((100vw - var(--card-w)) / 2 - 0.5 * var(--jp-size)));
}

/* Saturn: low on the left, ringed edge crossing the card's corner */
html.journey-h .jp-saturn .jp-body {
    --jp-y: calc(100% - 0.85 * var(--jp-size));
    --jp-x: max(var(--jp-reach), calc((100vw - var(--card-w)) / 2 - 0.45 * var(--jp-size)));
    --jp-r: auto;
}

/* Uranus: high on the left, tucked into the sky band */
html.journey-h .jp-uranus .jp-body {
    --jp-y: calc(var(--nav-h) + 6px);
    --jp-x: max(var(--jp-reach), calc((100vw - var(--card-w)) / 2 - 0.55 * var(--jp-size)));
    --jp-r: auto;
}

/* Neptune: the far one, small and high, almost off the right edge */
html.journey-h .jp-neptune .jp-body {
    --jp-y: calc(var(--nav-h) + 30px);
    --jp-r: calc((100vw - var(--card-w)) / 2 - 0.75 * var(--jp-size));
}

/* Vertical journey: ~72px at top 12px / right 20px of the 96px band;
   the stage starts in the 28px gap above the panel so the glow is
   not cut at the panel's top edge */
html.journey-v .jp-planet {
    top: -28px;
}

html.journey-v .jp-body {
    top: 40px;
    right: max(34px, calc(var(--jp-reach) * var(--jp-k) + 8px)); /* 34px keeps the stamped glow inside the panel */
    transform: scale(var(--jp-k));
    transform-origin: 100% 0;
}

/* Tablets have the room for bigger planets in a taller band */
@media (min-width: 600px) {
    html.journey-v {
        --sky-band-v: 136px;
    }

    html.journey-v .jp-body {
        transform: scale(calc(var(--jp-k) * 1.4));
    }
}

/* ------------------------------------------------------------
   Moons: the .space-moon orbit pattern on an ellipse (--jp-rx/--jp-ry,
   tilted by --jp-tilt); z-index flips behind the disc on the far half
   ------------------------------------------------------------ */
html.journey .jp-moon {
    left: 50%;
    top: 50%;
    width: var(--jp-d);
    height: var(--jp-d);
    margin: calc(var(--jp-d) / -2) 0 0 calc(var(--jp-d) / -2);
    background: currentColor;
    box-shadow: inset calc(var(--jp-d) / -3) 0 rgba(0, 0, 0, 0.3);
    animation: jpOrbit var(--jp-t) linear infinite;
    animation-delay: var(--jp-delay, 0s);
    animation-direction: var(--jp-dir, normal);
    transition: box-shadow 0.25s ease;
}

html.journey .jp-moon.is-lit {
    box-shadow:
        inset calc(var(--jp-d) / -3) 0 rgba(0, 0, 0, 0.3),
        0 0 0 4px rgba(255, 255, 255, 0.22),
        0 0 18px 4px currentColor;
}

@keyframes jpOrbit {
    0% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * 1), calc(var(--jp-ry) * 0)) scale(0.930);
        opacity: 0.90;
        z-index: 3;
    }
    6.25% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * 0.9239), calc(var(--jp-ry) * 0.3827)) scale(0.987);
        opacity: 0.94;
        z-index: 3;
    }
    12.5% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * 0.7071), calc(var(--jp-ry) * 0.7071)) scale(1.036);
        opacity: 0.97;
        z-index: 3;
    }
    18.75% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * 0.3827), calc(var(--jp-ry) * 0.9239)) scale(1.069);
        opacity: 0.99;
        z-index: 3;
    }
    25% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * 0), calc(var(--jp-ry) * 1)) scale(1.080);
        opacity: 1.00;
        z-index: 3;
    }
    31.25% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * -0.3827), calc(var(--jp-ry) * 0.9239)) scale(1.069);
        opacity: 0.99;
        z-index: 3;
    }
    37.5% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * -0.7071), calc(var(--jp-ry) * 0.7071)) scale(1.036);
        opacity: 0.97;
        z-index: 3;
    }
    43.75% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * -0.9239), calc(var(--jp-ry) * 0.3827)) scale(0.987);
        opacity: 0.94;
        z-index: 3;
    }
    50% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * -1), calc(var(--jp-ry) * 0)) scale(0.930);
        opacity: 0.90;
        z-index: 1;
    }
    56.25% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * -0.9239), calc(var(--jp-ry) * -0.3827)) scale(0.873);
        opacity: 0.86;
        z-index: 1;
    }
    62.5% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * -0.7071), calc(var(--jp-ry) * -0.7071)) scale(0.824);
        opacity: 0.83;
        z-index: 1;
    }
    68.75% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * -0.3827), calc(var(--jp-ry) * -0.9239)) scale(0.791);
        opacity: 0.81;
        z-index: 1;
    }
    75% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * 0), calc(var(--jp-ry) * -1)) scale(0.780);
        opacity: 0.80;
        z-index: 1;
    }
    81.25% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * 0.3827), calc(var(--jp-ry) * -0.9239)) scale(0.791);
        opacity: 0.81;
        z-index: 1;
    }
    87.5% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * 0.7071), calc(var(--jp-ry) * -0.7071)) scale(0.824);
        opacity: 0.83;
        z-index: 1;
    }
    93.75% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * 0.9239), calc(var(--jp-ry) * -0.3827)) scale(0.873);
        opacity: 0.86;
        z-index: 1;
    }
    100% {
        transform: rotate(var(--jp-tilt, 0deg)) translate(calc(var(--jp-rx) * 1), calc(var(--jp-ry) * 0)) scale(0.930);
        opacity: 0.90;
        z-index: 3;
    }
}

/* Rings (Saturn, Uranus): the same ellipse twice, clipped into a
   back half under the disc and a front half over it */
html.journey .jp-ring {
    left: 50%;
    top: 50%;
    z-index: 1;
    transform: rotate(var(--jp-tilt, 0deg));
}

html.journey .jp-ring::before {
    content: "";
    position: absolute;
    display: block;
    border-radius: 50%;
}

html.journey .jp-ring-front {
    z-index: 3;
}

html.journey .jp-orbit {
    left: 50%;
    top: 50%;
    z-index: 1;
    border: 1px dashed rgba(255, 255, 255, 0.3);
    transform: rotate(var(--jp-tilt, 0deg));
}

/* ------------------------------------------------------------
   Mars — rust disc, ochre lowland stamps, polar cap, Phobos + Deimos
   ------------------------------------------------------------ */
html.journey .jp-mars {
    --jp-size: 150px;
    --jp-reach: 50px;
    --jp-k: 0.48;
    --jp-c: #B9553A;
}

html.journey .jp-mars .jp-disc {
    --jp-period: 300px;
    --jp-spin: 48s;
    box-shadow:
        inset -52px 0 rgba(0, 0, 0, 0.3),
        0 0 0 18px rgba(185, 85, 58, 0.1),
        0 0 0 36px rgba(185, 85, 58, 0.08),
        0 0 0 54px rgba(185, 85, 58, 0.06),
        0 0 75px rgba(185, 85, 58, var(--jp-glow));
}

html.journey .jp-mars .jp-disc::before {
    top: 32px;
    left: 0;
    width: 44px;
    height: 14px;
    border-radius: 20px;
    background: #CB7A50;
    box-shadow:
        40px 58px 0 -2px #A34A33,
        72px 22px 0 4px #CB7A50,
        112px 92px 0 -3px #9E4530,
        150px 44px 0 2px #D08A5C,
        190px 78px 0 -4px #A34A33,
        232px 14px 0 1px #CB7A50,
        262px 60px 0 -1px #9E4530,
        300px 0 0 0 #CB7A50,
        340px 58px 0 -2px #A34A33,
        372px 22px 0 4px #CB7A50,
        412px 92px 0 -3px #9E4530,
        450px 44px 0 2px #D08A5C,
        490px 78px 0 -4px #A34A33,
        532px 14px 0 1px #CB7A50,
        562px 60px 0 -1px #9E4530;
}

/* Polar cap */
html.journey .jp-mars .jp-disc::after {
    content: "";
    position: absolute;
    top: 6px;
    left: 50%;
    width: 46px;
    height: 14px;
    margin-left: -23px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.85);
}

html.journey .jp-moon-phobos {
    color: #9C8F84;
    --jp-d: 8px;
    --jp-rx: 92px;
    --jp-ry: 30px;
    --jp-tilt: -12deg;
    --jp-t: 11s;
}

html.journey .jp-moon-deimos {
    color: #B5AAA0;
    --jp-d: 5px;
    --jp-rx: 118px;
    --jp-ry: 40px;
    --jp-tilt: -12deg;
    --jp-t: 24s;
    --jp-delay: -9s;
}

/* ------------------------------------------------------------
   Jupiter — banded giant, Great Red Spot, dashed Galilean orbit
   ------------------------------------------------------------ */
html.journey .jp-jupiter {
    --jp-size: 320px;
    --jp-reach: 92px;
    --jp-k: 0.3;
    --jp-c: #E8D9BF;
}

html.journey .jp-jupiter .jp-disc {
    --jp-period: 480px;
    --jp-spin: 80s;
    background-image: linear-gradient(180deg,
        #E8D9BF 0 12%, #C89158 12% 20%, #E8D9BF 20% 33%, #A66A3A 33% 44%,
        #E8D9BF 44% 56%, #C89158 56% 66%, #E8D9BF 66% 78%, #A66A3A 78% 86%, #E8D9BF 86%);
    box-shadow:
        inset -110px 0 rgba(0, 0, 0, 0.28),
        0 0 0 32px rgba(200, 145, 88, 0.1),
        0 0 0 64px rgba(200, 145, 88, 0.08),
        0 0 0 96px rgba(200, 145, 88, 0.06),
        0 0 120px rgba(200, 145, 88, var(--jp-glow));
}

/* Band swirls */
html.journey .jp-jupiter .jp-disc::before {
    top: 16px;
    left: 0;
    width: 70px;
    height: 10px;
    border-radius: 10px;
    background: #DCC6A3;
    box-shadow:
        90px 30px 0 -1px #D4A46E,
        170px 66px 0 1px #DCC6A3,
        60px 106px 0 -2px #B87F4A,
        230px 124px 0 0 #B87F4A,
        130px 152px 0 2px #DCC6A3,
        300px 178px 0 -1px #D4A46E,
        40px 242px 0 1px #DCC6A3,
        260px 258px 0 -1px #B87F4A,
        150px 290px 0 -2px #DCC6A3,
        480px 0 0 0 #DCC6A3,
        570px 30px 0 -1px #D4A46E,
        650px 66px 0 1px #DCC6A3,
        540px 106px 0 -2px #B87F4A,
        710px 124px 0 0 #B87F4A,
        610px 152px 0 2px #DCC6A3,
        780px 178px 0 -1px #D4A46E,
        520px 242px 0 1px #DCC6A3,
        740px 258px 0 -1px #B87F4A,
        630px 290px 0 -2px #DCC6A3;
}

/* Great Red Spot — in the upper-right slice that shows above the card */
html.journey .jp-jupiter .jp-disc::after {
    content: "";
    position: absolute;
    top: 46px;
    left: 190px;
    width: 58px;
    height: 26px;
    border-radius: 50%;
    background: #C75A3D;
    border: 3px solid #D98866;
}

html.journey .jp-jupiter .jp-orbit {
    width: 472px;
    height: 168px;
    margin: -84px 0 0 -236px;
    --jp-tilt: -16deg;
}

html.journey .jp-jupiter .jp-moon {
    --jp-rx: 236px;
    --jp-ry: 84px;
    --jp-tilt: -16deg;
}

html.journey .jp-moon-io       { color: #D9A441; --jp-d: 10px; --jp-t: 16s; }
html.journey .jp-moon-europa   { color: #E8E4DA; --jp-d: 9px;  --jp-t: 26s; --jp-delay: -7s; }
html.journey .jp-moon-ganymede { color: #9C8D7C; --jp-d: 12px; --jp-t: 40s; --jp-delay: -21s; }
html.journey .jp-moon-callisto { color: #5E534A; --jp-d: 11px; --jp-t: 60s; --jp-delay: -36s; }

/* ------------------------------------------------------------
   Saturn — pale disc inside a tilted ring drawn as two halves
   ------------------------------------------------------------ */
html.journey .jp-saturn {
    --jp-size: 170px;
    --jp-reach: 92px;
    --jp-k: 0.42;
    --jp-c: #E3CFA0;
}

html.journey .jp-saturn .jp-disc {
    --jp-period: 340px;
    --jp-spin: 60s;
    background-image: linear-gradient(180deg,
        #E3CFA0 0 22%, #D9C08A 22% 30%, #E3CFA0 30% 52%, #DEC896 52% 64%, #E3CFA0 64% 80%, #D9C08A 80% 86%, #E3CFA0 86%);
    box-shadow:
        inset -60px 0 rgba(0, 0, 0, 0.28),
        0 0 0 20px rgba(227, 207, 160, 0.1),
        0 0 0 40px rgba(227, 207, 160, 0.08),
        0 0 0 60px rgba(227, 207, 160, 0.06),
        0 0 85px rgba(227, 207, 160, var(--jp-glow));
}

html.journey .jp-saturn .jp-disc::before {
    top: 40px;
    left: 0;
    width: 50px;
    height: 7px;
    border-radius: 8px;
    background: #ECDCB4;
    box-shadow:
        90px 34px 0 0 #D3B87E,
        170px 74px 0 -1px #ECDCB4,
        60px 108px 0 0 #D3B87E,
        230px 20px 0 -1px #ECDCB4,
        340px 0 0 0 #ECDCB4,
        430px 34px 0 0 #D3B87E,
        510px 74px 0 -1px #ECDCB4,
        400px 108px 0 0 #D3B87E,
        570px 20px 0 -1px #ECDCB4;
}

html.journey .jp-saturn .jp-ring {
    width: 340px;
    height: 92px;
    margin: -46px 0 0 -170px;
    --jp-tilt: -14deg;
    box-shadow: inset 0 0 0 7px rgba(232, 214, 180, 0.55);
}

html.journey .jp-saturn .jp-ring::before {
    inset: 11px;
    box-shadow: inset 0 0 0 16px rgba(232, 214, 180, 0.45);
}

html.journey .jp-saturn .jp-ring-back  { clip-path: inset(0 0 50% 0); }
html.journey .jp-saturn .jp-ring-front { clip-path: inset(50% 0 0 0); }

/* ------------------------------------------------------------
   Uranus — mint disc, thin near-vertical ring
   ------------------------------------------------------------ */
html.journey .jp-uranus {
    --jp-size: 140px;
    --jp-reach: 8px;
    --jp-k: 0.51;
    --jp-c: #A7CFCB;
}

html.journey .jp-uranus .jp-disc {
    --jp-period: 280px;
    --jp-spin: 70s;
    background-image: linear-gradient(180deg, #A7CFCB 0 30%, #B6DAD6 30% 44%, #A7CFCB 44% 70%, #9CC6C1 70% 78%, #A7CFCB 78%);
    box-shadow:
        inset -48px 0 rgba(0, 0, 0, 0.26),
        0 0 0 17px rgba(167, 207, 203, 0.1),
        0 0 0 34px rgba(167, 207, 203, 0.08),
        0 0 0 51px rgba(167, 207, 203, 0.06),
        0 0 70px rgba(167, 207, 203, var(--jp-glow));
}

html.journey .jp-uranus .jp-disc::before {
    top: 50px;
    left: 0;
    width: 60px;
    height: 6px;
    border-radius: 6px;
    background: #BCDFDB;
    box-shadow:
        100px 40px 0 -1px #BCDFDB,
        180px -10px 0 0 #BCDFDB,
        280px 0 0 0 #BCDFDB,
        380px 40px 0 -1px #BCDFDB,
        460px -10px 0 0 #BCDFDB;
}

html.journey .jp-uranus .jp-ring {
    width: 30px;
    height: 210px;
    margin: -105px 0 0 -15px;
    --jp-tilt: 12deg;
    box-shadow: inset 0 0 0 2px rgba(214, 236, 234, 0.5);
}

html.journey .jp-uranus .jp-ring::before {
    inset: 4px;
    box-shadow: inset 0 0 0 1px rgba(214, 236, 234, 0.35);
}

html.journey .jp-uranus .jp-ring-back  { clip-path: inset(0 50% 0 0); }
html.journey .jp-uranus .jp-ring-front { clip-path: inset(0 0 0 50%); }

/* ------------------------------------------------------------
   Neptune — deep blue disc, dark spot + wind streak, Triton retrograde
   ------------------------------------------------------------ */
html.journey .jp-neptune {
    --jp-size: 150px;
    --jp-reach: 12px;
    --jp-k: 0.48;
    --jp-c: #3F63A8;
}

html.journey .jp-neptune .jp-disc {
    --jp-period: 300px;
    --jp-spin: 44s;
    box-shadow:
        inset -52px 0 rgba(0, 0, 0, 0.32),
        0 0 0 18px rgba(63, 99, 168, 0.1),
        0 0 0 36px rgba(63, 99, 168, 0.08),
        0 0 0 54px rgba(63, 99, 168, 0.06),
        0 0 75px rgba(63, 99, 168, var(--jp-glow));
}

html.journey .jp-neptune .jp-disc::before {
    top: 24px;
    left: 0;
    width: 56px;
    height: 8px;
    border-radius: 8px;
    background: #365896;
    box-shadow:
        70px 44px 0 1px #365896,
        150px 88px 0 -1px #4A70B8,
        40px 112px 0 0 #365896,
        220px 20px 0 -1px #4A70B8,
        300px 0 0 0 #365896,
        370px 44px 0 1px #365896,
        450px 88px 0 -1px #4A70B8,
        340px 112px 0 0 #365896,
        520px 20px 0 -1px #4A70B8;
}

/* Great Dark Spot with a thin pale wind streak above it (weather, not gloss) */
html.journey .jp-neptune .jp-disc::after {
    content: "";
    position: absolute;
    top: 64px;
    left: 42px;
    width: 30px;
    height: 14px;
    border-radius: 50%;
    background: #223B72;
    box-shadow: 8px -16px 0 -5px rgba(214, 228, 255, 0.3);
}

html.journey .jp-moon-triton {
    color: #D8CFC4;
    --jp-d: 7px;
    --jp-rx: 84px;
    --jp-ry: 40px;
    --jp-tilt: 28deg;
    --jp-t: 26s;
    --jp-dir: reverse;
}

/* ------------------------------------------------------------
   James Webb in the lower-left of Research, behind the glass.
   ------------------------------------------------------------ */
html.journey .jp-webb {
    position: absolute;
    z-index: 1;
    pointer-events: none;
    filter: drop-shadow(0 0 20px rgba(226, 184, 92, 0.16));
}

html.journey-h .jp-webb {
    left: 2%;
    bottom: 2%;
    transform: rotate(-11deg);
}

html.journey-h .jp-webb svg {
    width: 188px;
    height: auto;
}

html.journey-v .jp-webb {
    left: 8px;
    top: 8px;
    transform: rotate(-11deg);
}

html.journey-v .jp-webb svg {
    width: 92px;
    height: auto;
}

/* ------------------------------------------------------------
   The droplet, hanging off the left of About. Perfectly still: that
   is the whole character of the thing.
   ------------------------------------------------------------ */
html.journey .jp-droplet {
    position: absolute;
    z-index: 1;
    pointer-events: none;
    filter: drop-shadow(0 0 16px rgba(150, 180, 214, 0.22));
}

html.journey-h .jp-droplet {
    left: 2.5%;
    top: 44%;
    transform: rotate(-9deg);
}

html.journey-h .jp-droplet svg {
    width: 128px;
    height: auto;
}

html.journey-v .jp-droplet {
    left: 10px;
    top: 26px;
    transform: rotate(-12deg);
}

html.journey-v .jp-droplet svg {
    width: 76px;
    height: auto;
}

/* ------------------------------------------------------------
   Pluto and the Kuiper belt — the last stop. Small on purpose: at
   29 AU the headline planet is a dwarf, and the belt it belongs to
   is drawn as the near half of a ring of ice and rock.
   ------------------------------------------------------------ */
html.journey .jp-pluto {
    --jp-size: 96px;
    --jp-reach: 10px;
    --jp-k: 0.5;
    --jp-c: #C3A98C;
}

html.journey-h .jp-pluto .jp-body {
    --jp-y: 63%;
    --jp-r: 8%;
}

html.journey .jp-pluto .jp-disc {
    --jp-period: 180px;
    --jp-spin: 70s;
    box-shadow:
        inset -30px 0 rgba(0, 0, 0, 0.34),
        0 0 0 12px rgba(195, 169, 140, 0.07),
        0 0 0 24px rgba(195, 169, 140, 0.05),
        0 0 46px rgba(195, 169, 140, var(--jp-glow));
}

/* mottled ice and rock, darker than the surrounding tan */
html.journey .jp-pluto .jp-disc::before {
    top: 14px;
    left: 0;
    width: 26px;
    height: 9px;
    border-radius: 9px;
    background: #A2846A;
    box-shadow:
        44px 34px 0 -1px #A2846A,
        86px 12px 0 -2px #B49075,
        120px 52px 0 -1px #9C7C62,
        156px 26px 0 -2px #A2846A,
        180px 14px 0 0 #A2846A,
        224px 34px 0 -1px #A2846A,
        266px 12px 0 -2px #9C7C62,
        300px 52px 0 -1px #A2846A;
}

/* Tombaugh Regio: the nitrogen-ice heart, softened so it reads as terrain */
html.journey .jp-heart {
    position: absolute;
    left: 28px;
    top: 48px;
    width: 19px;
    height: 19px;
    background: #F1E6D4;
    opacity: 0.7;
    filter: blur(0.7px);
    transform: rotate(-45deg);
    z-index: 3;
}

html.journey .jp-heart::before,
html.journey .jp-heart::after {
    content: "";
    position: absolute;
    width: 19px;
    height: 19px;
    border-radius: 50%;
    background: #F1E6D4;
}

html.journey .jp-heart::before {
    top: -9.5px;
    left: 0;
}

html.journey .jp-heart::after {
    top: 0;
    left: 9.5px;
}

/* Charon is half Pluto's size, so it swings wide and slowly */
html.journey .jp-moon-charon {
    color: #B5AEA6;
    --jp-d: 12px;
    --jp-rx: 74px;
    --jp-ry: 30px;
    --jp-tilt: -22deg;
    --jp-t: 40s;
}

html.journey .jp-kuiper {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
}

html.journey .jp-rock {
    position: absolute;
    display: block;
}

/* The belt answers to the pointer simply being there: a band along the arc
   that brings the ice up and turns the last stop over to Belter Creole. No
   click, no cursor change — you find it by sweeping across the belt. */
html.journey .jp-belt-hit {
    position: absolute;
    left: 0;
    right: 0;
    top: 56%;
    bottom: 1%;
    z-index: 1;
    pointer-events: auto;
}

html.journey .jp-kuiper {
    transition: filter 0.35s ease;
}

html.journey .jp-belt-hit:hover ~ .jp-kuiper {
    filter: brightness(1.3);
}

html.journey .jp-kuiper.is-belter {
    filter: brightness(1.75) saturate(1.15);
}

html.journey .jp-belt-hit:hover ~ .jp-kuiper.is-belter {
    filter: brightness(2) saturate(1.15);
}

/* One rock is the OPA's split circle. It turns once every two minutes, so you
   only notice it if you were already looking. */
html.journey .jp-opa {
    position: absolute;
    left: 26%;
    top: 84%;
    width: 17px;
    height: 17px;
    margin: -8.5px 0 0 -8.5px;
    opacity: 0.55;
    color: #D6C3A4;
    animation: jpOpaTurn 120s linear infinite;
}

html.journey .jp-opa::before,
html.journey .jp-opa::after {
    content: "";
    position: absolute;
    left: 0;
    width: 17px;
    height: 8.5px;
    border: 1.4px solid currentColor;
}

html.journey .jp-opa::before {
    top: 0;
    border-bottom: 0;
    border-radius: 17px 17px 0 0;
    transform: translate(2px, -1px);
}

html.journey .jp-opa::after {
    bottom: 0;
    border-top: 0;
    border-radius: 0 0 17px 17px;
    transform: translate(-2px, 1px);
}

@keyframes jpOpaTurn {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

/* on phones the arc lives in the band above the card, so the hit band follows */
html.journey-v .jp-belt-hit {
    top: 0;
    bottom: auto;
    height: 190px;
}

/* on phones the panel is tall and narrow: pull the arc into the sky band */
html.journey-v .jp-kuiper {
    top: 0;
    height: 190px;
}

/* ------------------------------------------------------------
   Pause everything unless the panel is near the viewport
   ------------------------------------------------------------ */
html.journey .jp-planet,
html.journey .jp-planet *,
html.journey .jp-planet *::before {
    animation-play-state: paused;
}

html.journey .panel.is-near .jp-planet,
html.journey .panel.is-near .jp-planet *,
html.journey .panel.is-near .jp-planet *::before {
    animation-play-state: running;
}

/* ------------------------------------------------------------
   Beyond — the pale blue dot, its caption and the last motto
   ------------------------------------------------------------ */
html.journey .jp-beyond {
    display: block;
    padding: 2rem 1rem 1rem;
    text-align: center;
    pointer-events: none;
}

html.journey .jp-pale-dot {
    display: block;
    width: 6px;
    height: 6px;
    margin: 0 auto 1.4rem;
    border-radius: 50%;
    background: #A9CCF3;
    box-shadow:
        0 0 0 4px rgba(120, 170, 240, 0.16),
        0 0 0 9px rgba(120, 170, 240, 0.07),
        0 0 14px 2px rgba(150, 190, 245, 0.7);
}

html.journey .jp-caption {
    margin: 0 0 1rem;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.28em;
    color: var(--text-muted);
}

html.journey .jp-motto {
    display: inline-block;
    margin: 0 0 1.5rem;
    font-size: 2rem;
    font-weight: 700;
    letter-spacing: 3px;
    color: var(--text-color);
    text-shadow: 0 0 20px rgba(42, 40, 36, 0.12);
    transform: rotate(-5deg);
}

html.journey body.dark-mode .jp-motto {
    text-shadow: 0 0 20px rgba(255, 255, 255, 0.35);
}

html.journey-v .jp-motto {
    font-size: 1.5rem;
    letter-spacing: 1px;
}

/* ------------------------------------------------------------
   Metaphor details in the cards
   ------------------------------------------------------------ */
/* One Galilean dot per research card (lights its orbit moon on hover) —
   small and quiet, so it reads as a coloured mark, not a status badge */
html.journey .research-card {
    position: relative;
}

html.journey .jp-moon-dot {
    display: block;
    position: absolute;
    top: 14px;
    right: 14px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--jp-c);
    box-shadow: 0 0 0 1px rgba(128, 120, 110, 0.25);
    opacity: 0.5;
    pointer-events: none;
    transition: opacity 0.2s ease, box-shadow 0.2s ease;
}

html.journey .research-card:hover .jp-moon-dot {
    opacity: 1;
    box-shadow: 0 0 0 3px rgba(128, 120, 110, 0.18), 0 0 10px var(--jp-c);
}

html.journey .jp-moon-dot-io       { --jp-c: #D9A441; }
html.journey .jp-moon-dot-europa   { --jp-c: #E8E4DA; }
html.journey .jp-moon-dot-ganymede { --jp-c: #9C8D7C; }
html.journey .jp-moon-dot-callisto { --jp-c: #5E534A; }

/* Carousel dots become ring particles (Saturn, Uranus); lifted above
   the card's sticky "more" fade so the active one stays readable */
html.journey .publications-carousel-dot,
html.journey .github-carousel-dot {
    position: relative;
    z-index: 1;
    width: 14px;
    height: 5px;
    border-radius: 3px;
}


/* ============================================================
   Planet Journey — nav module: flight path along the navbar's
   bottom edge, arrival live region, focus targets and the
   list-view toggle. Every rule is gated under html.journey
   except the few that only touch elements journey-nav.js
   injects itself (so the plain page is untouched).
   ============================================================ */

/* Arrival announcements (visually hidden) */
.jn-live {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
}

/* Headings that receive programmatic focus on arrival: no ring (Arrow/Home/End,
   wheel and nav-click arrivals are not Tab-driven focus; the live region and the
   card itself show where the trip landed). Tab-driven focus keeps the browser ring
   on the element that was tabbed to. */
html.journey [data-jn-focus]:focus {
    outline: none;
}

/* List / journey view toggle (only in the DOM when injected) */
.jn-list-toggle {
    display: none;
}

html.journey .jn-list-toggle.jn-to-list:not([hidden]),
html:not(.journey) .jn-list-toggle.jn-to-journey:not([hidden]) {
    display: flex;
}

/* The extra button must not wrap the logo onto two lines (desktop navbar at 769-900px) */
html.journey .nav-logo h2 {
    white-space: nowrap;
}

@media (min-width: 769px) and (max-width: 900px) {
    html.journey .nav-menu {
        gap: 1.25rem;
    }

    html.journey .nav-controls {
        gap: 0.5rem;
    }
}

/* Phones: styles.css centres .nav-controls over the logo; with the injected
   toggle beside the dark-mode button it would cover the name, so dock the pair
   beside the hamburger. .jn-has-toggle is set by journey-nav.js whenever it
   injects a toggle (journey or list view), so the plain page without one is untouched */
@media (max-width: 768px) {
    .nav-controls.jn-has-toggle {
        left: auto;
        right: 61px;
        transform: none;
        gap: 0.75rem;
    }
}
