/* ==========================================================================
   FLOATING SECTION NAVIGATOR — "route rail"  (src/styles/section-nav.css)
   --------------------------------------------------------------------------
   Markup from includes/section-nav.php, behaviour from src/js/section-nav.js.

   A fixed column of liquid-glass waypoint beads on a slim plate, halfway
   down the right edge, in the announcement bar's hiking-route vocabulary
   (.anno__trail — same 7:10 dash rhythm, same two-period loop trick). The
   only line drawn is the stretch you've already travelled: grey-white
   dashes drifting slowly in the direction of travel, growing stop to stop
   as you read (height = --sn-fill, script-set). Ahead of you there is no
   line — just the waypoints. The beads rank by BRIGHTNESS AND SIZE, not
   hue — clear glass ahead, lightly frosted once passed, and the active
   stop the brightest and largest — which is how the navbar marks its own
   current item. The rail carries no accent colour at all.

   THE RAIL IS BUILT FROM THE NAVBAR'S DROPDOWN MATERIAL (nav.css), because
   it is navigation too and should read as a piece of the same system: the
   plate is .dropdown's surface (overlay-black body, the shared 1.5px
   dropdown rim, panel radius, its drop shadow + inset white hairline, and
   the blur carried on a ::before layer). THE LABELS WEAR THAT SAME SURFACE
   — same background stack, rim, radius and shadows — so a chip reads as a
   piece of the plate that has slid out, not as a menu row; both brighten
   their rim to white-18 on hover and change nothing else. Reveal timing is
   the menu's own (180ms, cubic-bezier(.22,1,.36,1) — nav-dropdown-in). All
   of it flows from the theme tokens, so restyling the navbar's dropdowns
   restyles the rail with them; that is the point — don't hard-code past it.

   Labels sit to the LEFT of the rail as dropdown menu items;
   they show when the rail is hovered / keyboard-focused (staggered
   top-to-bottom via --sn-i, the stop's index, script-set) or when the
   script "peeks" one stop (.is-peek) as the reader crosses into its
   section.

   GEOMETRY IS DERIVED FROM THE DOT OUTWARD, and integer-pixel on purpose.
   --sn-gap is the space between a dot and the capsule's inner edge; the
   column's width and its vertical padding are both built from it, so the
   inset above the first dot and below the last equals the inset either
   side. Stops are dot-sized and the dot-to-dot pitch is made up by the
   flex gap (a taller link floats over each stop for the hit target — see
   .section-nav__link). A 10px dot and a 2px trail centre everything on
   whole pixels; odd sizes land on half-pixels and the dots visibly wobble
   off the line, so keep --sn-dot and the trail width even.

   Entrance (all keyed on .is-ready, which the script commits once, after a
   settle delay + double-rAF so the animation reliably plays): the rail
   slides in on the site's "robust" settle curve — cubic-bezier(.34,1.32,
   .48,1), the md-subnav dock's overshoot — while the waypoints cascade in
   down the rail; the script then peeks the active label once the rail has
   landed.

   State classes (all script-owned):
     .section-nav.is-ready     — script initialised; until then the rail is
                                 invisible AND inert (no-JS visitors never
                                 see dead chrome)
     .section-nav.is-off       — retired: page too short to need a map, or
                                 the footer has climbed into the rail's zone
     .section-nav.is-idle      — the page has been still for a beat: the
                                 plate dissolves and the beads dim, until a
                                 scroll (script) or a pointer/keyboard visit
                                 (CSS :not() guards) brings it back
     .section-nav__stop.is-active / .is-passed / .is-peek

   Palette is the site's glass recipe — low-alpha white fills, hairline
   borders, inset top highlight, deep soft shadow. z-index 480 clears all in-flow
   content (compare-table sticky cells top out at 5) while staying under the
   compare feature popup (900) and every navbar/overlay layer.

   Hidden below 900px — on small screens the rail would ride the very
   content it navigates — and in print.
   ========================================================================== */

.section-nav {
    /* Geometry is derived from the dot outward, so the capsule's inset is
       the SAME on all four sides by construction: --sn-gap is the space
       between a dot and the capsule's inner edge, and the column's width
       and vertical padding are both built from it (the +2px is the two
       hairline borders, since the capsule is border-box). Keep --sn-dot
       and the 2px trail EVEN (see header). */
    /* THE size knob. Everything else follows: the plate's width and padding
       are built from it, so the bead grows and the panel grows around it
       with the inset unchanged. Two rules when changing it — keep it EVEN
       (see header), and keep --sn-pitch comfortably above it, since the
       pitch is what leaves the run of trail visible between one bead and
       the next. */
    --sn-dot: 10px;
    /* Wide enough that the dropdown panel radius below reads as a panel
       corner rather than closing into a pill — and no wider. This is the
       token that decides how heavy the rail feels: it sets the plate's
       width and its vertical inset at once, so a generous value reads as a
       chunky panel wrapped around small content. */
    --sn-gap: 10px;
    /* Dot centre to dot centre. Pitch is what makes a bead read as a marker
       rather than a blob: it is the clear space either side, not the dot's
       own diameter, that the eye judges the size against. */
    --sn-pitch: 36px;
    /* How much the active bead grows. Shared with the trail's mask below,
       which has to clear the LARGEST a bead ever gets — change it here, not
       in the two rules that read it. */
    --sn-active-scale: 1.3;
    /* The dropdown surface's own rim + radius (nav.css). */
    --sn-rim: var(--nav-dropdown-border-width, 1.5px);
    --sn-radius: var(--theme-radius-md, 1rem);
    /* One dash period: 6px ink, 8px gap — .anno__trail's 7:10 rhythm. */
    --sn-dash: 6px;
    --sn-period: 14px;
    /* Pointer/keyboard target per stop — taller than the dot, reaching the
       capsule's ends on the first and last. Capped at the pitch: past that
       neighbouring targets would overlap, and since the links are absolutely
       positioned the later stop would quietly win the shared strip. */
    --sn-hit: min(calc(var(--sn-dot) + var(--sn-gap) * 2), var(--sn-pitch));
    position: fixed;
    top: 50%;
    right: clamp(14px, 2.2vw, 34px);
    translate: 14px -50%;
    z-index: 480;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition:
        opacity 0.4s ease,
        translate 0.55s cubic-bezier(0.34, 1.32, 0.48, 1),
        visibility 0s 0.55s;
}

.section-nav.is-ready {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    translate: 0 -50%;
    transition:
        opacity 0.4s ease,
        translate 0.55s cubic-bezier(0.34, 1.32, 0.48, 1),
        visibility 0s 0s;
}

/* Page too short for a map right now, or the footer has arrived — retire. */
.section-nav.is-ready.is-off {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    translate: 10px -50%;
    transition:
        opacity 0.4s ease,
        translate 0.4s ease,
        visibility 0s 0.4s;
}

/* The plate is a navbar dropdown panel, built the same way .dropdown is in
   nav.css: a thin black overlay for the body, the crisp 1.5px rim every
   desktop dropdown shares (--nav-dropdown-border), the panel radius, and
   the same drop shadow paired with an inset white hairline. Like .dropdown
   it carries its blur on a ::before layer rather than on the box, so the
   stops can sit above it — .dropdown cannot use `overflow: hidden` here
   because the labels deliberately hang outside the plate. */
.section-nav__list {
    position: relative;
    isolation: isolate;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    /* Stops are dot-sized; the pitch is made up by the gap, so the padding
       above the first dot and below the last equals the side inset exactly. */
    gap: calc(var(--sn-pitch) - var(--sn-dot));
    margin: 0;
    padding: var(--sn-gap) 0;
    list-style: none;
    width: calc(var(--sn-dot) + var(--sn-gap) * 2 + var(--sn-rim) * 2);
    border-radius: var(--sn-radius);
    background: var(--theme-overlay-black-20, rgba(8, 8, 8, 0.2));
    border: var(--nav-dropdown-border, var(--sn-rim) solid var(--theme-border-strong, rgba(255, 255, 255, 0.16)));
    box-shadow:
        0 1.5rem 3rem var(--theme-shadow-black-42, rgba(0, 0, 0, 0.42)),
        0 0 0 1px var(--theme-surface-white-045, rgba(255, 255, 255, 0.045)) inset;
    /* Wake side of the idle fade (see the idle block below), plus the hover rim. */
    transition:
        background 0.28s ease,
        border-color 0.22s ease,
        box-shadow 0.28s ease;
}

/* The panel's glass, exactly as .dropdown::before mixes it. */
.section-nav__list::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    border-radius: inherit;
    background: var(--nav-surface-bg, var(--theme-surface-glass, rgba(10, 10, 10, 0.72)));
    backdrop-filter: var(--theme-blur-xl, blur(48px)) saturate(155%) brightness(0.76);
    -webkit-backdrop-filter: var(--theme-blur-xl, blur(48px)) saturate(155%) brightness(0.76);
    transition: opacity 0.28s ease;
}

.section-nav:hover .section-nav__list {
    border-color: var(--theme-surface-white-18, rgba(255, 255, 255, 0.18));
}

/* Stops ride above the blur layer — .dropdown > li does the same. */
.section-nav__stop {
    z-index: 1;
}

/* ---- idle ---------------------------------------------------------------
   Once the page has been still for a beat (section-nav.js adds .is-idle),
   the plate dissolves and the beads dim to a whisper: the rail stops being
   chrome parked over the content and becomes a faint position mark. Any
   scroll clears the class; a pointer or keyboard visit is caught by the
   :not() guards below, so the rail returns on the frame the pointer
   arrives rather than waiting on an event.

   Waking is quick and fading is slow, which is why each pair states its own
   transition: the one on the base rule runs when the idle rule stops
   applying (wake), the one inside the idle rule runs on the way in.

   The whole block is gated on `no-preference` rather than being undone in
   the reduced-motion section below. The quiet state is only worth having as
   a cross-fade — with transitions suppressed it would pop the plate in and
   out on every scroll, which is more distracting than simply leaving the
   rail as it is, so for those visitors it never engages at all.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
    .section-nav.is-idle:not(:hover):not(:focus-within) .section-nav__list {
        background: transparent;
        border-color: transparent;
        box-shadow: none;
        transition:
            background 0.7s ease,
            border-color 0.7s ease,
            box-shadow 0.7s ease;
    }

    .section-nav.is-idle:not(:hover):not(:focus-within) .section-nav__list::before {
        opacity: 0;
        transition: opacity 0.7s ease;
    }

    .section-nav.is-idle:not(:hover):not(:focus-within) .section-nav__dot {
        opacity: 0.45;
        transition: opacity 0.7s ease;
    }

    .section-nav.is-idle:not(:hover):not(:focus-within) .section-nav__list::after {
        opacity: 0.45;
        transition:
            height 0.5s cubic-bezier(0.22, 0.61, 0.36, 1),
            opacity 0.7s ease;
    }
}

/* The route exists only where you've been: no line runs ahead — just the
   waypoints — and the stretch already travelled draws itself in behind you
   as dashes drifting slowly in the direction of travel (two full periods
   per loop, so the loop point is invisible — same trick as @keyframes
   anno-trail). Height = --sn-fill, script-set; at the top of the page
   there is no trail at all.

   Kept deliberately faint, and unglowed: it is a breadcrumb between the
   beads, not a progress bar. If it competes with the waypoints for
   attention it has been turned up too far. */
.section-nav__list::after {
    content: "";
    position: absolute;
    /* Abspos in the list resolves against its PADDING box, so the first dot
       centre is simply the inset plus half a dot. */
    top: calc(var(--sn-gap) + var(--sn-dot) / 2);
    left: calc(50% - 1px);
    width: 2px;
    height: var(--sn-fill, 0px);
    background: repeating-linear-gradient(
        180deg,
        rgba(255, 255, 255, 0.17) 0,
        rgba(255, 255, 255, 0.17) var(--sn-dash),
        transparent var(--sn-dash),
        transparent var(--sn-period)
    );
    /* The beads are glass, so anything drawn under them shows through. The
       trail is therefore not drawn under them at all: this mask punches a
       hole at every bead and leaves ink only in the runs between. The trail
       starts at the first bead's CENTRE, so each period opens with that
       bead's lower half, carries the visible run, and closes with the next
       bead's upper half. The hole clears a bead at its largest (the active
       one is scaled), plus a hair for the rim and its antialiasing. */
    --sn-hole: calc(var(--sn-dot) * var(--sn-active-scale) / 2 + 1px);
    -webkit-mask-image: repeating-linear-gradient(
        180deg,
        transparent 0 var(--sn-hole),
        #000 var(--sn-hole) calc(var(--sn-pitch) - var(--sn-hole)),
        transparent calc(var(--sn-pitch) - var(--sn-hole)) var(--sn-pitch)
    );
    mask-image: repeating-linear-gradient(
        180deg,
        transparent 0 var(--sn-hole),
        #000 var(--sn-hole) calc(var(--sn-pitch) - var(--sn-hole)),
        transparent calc(var(--sn-pitch) - var(--sn-hole)) var(--sn-pitch)
    );
    transition:
        height 0.5s cubic-bezier(0.22, 0.61, 0.36, 1),
        opacity 0.28s ease;
    animation: sn-trail-drift 3.4s linear infinite;
}

@keyframes sn-trail-drift {
    to {
        background-position: 0 calc(var(--sn-period) * 2);
    }
}

.section-nav__stop {
    position: relative;
    height: var(--sn-dot);
}

/* The stop box is only as tall as its dot (that is what makes the capsule's
   inset uniform), so the link floats a taller target over it — centred on
   the dot, never changing the layout. */
.section-nav__link {
    position: absolute;
    z-index: 1; /* dots above the trail */
    left: 0;
    right: 0;
    top: calc(50% - var(--sn-hit) / 2);
    height: var(--sn-hit);
    display: grid;
    place-items: center;
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
}

/* The navbar's keyboard ring — a white hairline, not an accent colour. */
.section-nav__link:focus-visible {
    outline: 2px solid rgba(255, 255, 255, 0.4);
    outline-offset: 2px;
    border-radius: 100vmax;
}

/* ---- waypoint rings ------------------------------------------------------ */

/* Ahead of the reader: a glass bead. The site's liquid-glass recipe at
   waypoint scale — a top-lit white tint over a small backdrop blur, a
   hairline rim, and an inset highlight along the top of the sphere with a
   soft bounce along the bottom, which is what makes a 10px circle read as
   a lens rather than a flat dot. */
.section-nav__dot {
    box-sizing: border-box;
    width: var(--sn-dot);
    height: var(--sn-dot);
    border-radius: 50%;
    background-color: var(--theme-surface-white-08, rgba(255, 255, 255, 0.08));
    background-image: linear-gradient(
        180deg,
        rgba(255, 255, 255, 0.2) 0%,
        rgba(255, 255, 255, 0) 55%,
        rgba(255, 255, 255, 0.07) 100%
    );
    border: 1px solid var(--theme-surface-white-09, rgba(255, 255, 255, 0.09));
    box-shadow:
        inset 0 1px 0.5px rgba(255, 255, 255, 0.4),
        inset 0 -1px 1px rgba(255, 255, 255, 0.1),
        0 1px 3px rgba(0, 0, 0, 0.4);
    transition:
        background-color 0.22s ease,
        border-color 0.22s ease,
        scale 0.22s cubic-bezier(0.22, 1, 0.36, 1),
        box-shadow 0.22s ease,
        opacity 0.28s ease;
}

/* Entrance cascade, following the track draw down the rail. The keyframes
   move translate/opacity only — never scale — so an already-active dot
   rides in at its final size with no end-of-animation snap. */
.section-nav.is-ready .section-nav__dot {
    animation: sn-dot-in 0.5s cubic-bezier(0.34, 1.32, 0.48, 1) backwards;
    animation-delay: calc(var(--sn-i, 0) * 80ms + 0.12s);
}

@keyframes sn-dot-in {
    from {
        opacity: 0;
        translate: 16px 0;
    }
}

.section-nav__link:hover .section-nav__dot {
    background-color: var(--theme-surface-white-18, rgba(255, 255, 255, 0.18));
    border-color: var(--theme-surface-white-18, rgba(255, 255, 255, 0.18));
    scale: 1.15;
}

/* Behind the reader: the same bead, lightly frosted — visibly "made", but
   quieter than the stop you are on. History should not outrank position,
   which is what a bright fill here would do. */
.section-nav__stop.is-passed .section-nav__dot {
    background-color: var(--theme-surface-white-12, rgba(255, 255, 255, 0.12));
    border-color: var(--theme-surface-white-18, rgba(255, 255, 255, 0.18));
}

/* You are here — marked the way the navbar marks its current item, which
   uses no accent colour at all: the pill's fill and rim come up to their
   lit state (--nav-item-bg-hover / --nav-item-border) over the press
   shadow pair, and the mark reads as current by being the brightest and
   largest bead rather than by turning a different hue. A red core was
   tried here and read as a stray dot — the navbar has no such language,
   and on this site red means "act", which a position marker must not
   claim. Keep this monochrome. */
.section-nav__stop.is-active .section-nav__dot {
    /* Brighter FILL than a passed bead (white-12), not just a brighter rim —
       lit from within is what reads as current; a bright ring over a dark
       centre reads as an empty outline. */
    background-color: var(--theme-surface-white-18, rgba(255, 255, 255, 0.18));
    background-image: linear-gradient(
        180deg,
        rgba(255, 255, 255, 0.62) 0%,
        rgba(255, 255, 255, 0.28) 55%,
        rgba(255, 255, 255, 0.4) 100%
    );
    border-color: rgba(255, 255, 255, 0.78);
    scale: var(--sn-active-scale);
    box-shadow:
        var(--theme-shadow-white-inset, inset 0 1px 0 rgba(255, 255, 255, 0.04)),
        var(--theme-shadow-press, 0 0.75rem 1.5rem rgba(0, 0, 0, 0.16));
}

/* ---- glass label chips -------------------------------------------------- */

/* Each label is a dropdown menu item (.dropdown a): the same white tint,
   hairline border, item radius and type colour. One difference is forced —
   a menu item sits ON its panel, but these hang off the rail over live page
   content, so the item tint is composited over the panel's own glass base
   (background-image over background-color) instead of standing alone, which
   at --theme-surface-white-08 would be unreadable. */
.section-nav__label {
    position: absolute;
    top: 50%;
    right: calc(100% + 10px); /* clear of the panel's edge */
    translate: 8px -50%;
    white-space: nowrap;
    padding: 0.5rem 0.85rem;
    border-radius: var(--sn-radius);
    font-size: 0.78rem;
    font-weight: 500;
    letter-spacing: 0.01em;
    line-height: 1.35;
    color: var(--c4, rgba(220, 222, 230, 0.92));
    /* The plate's exact surface, stacked in the same order it is on the
       panel — blur, then the glass base, then the overlay-black body on top.
       The panel carries those first two on a ::before layer because its
       stops have to sit above them; a chip has only its own text, so the two
       fold into one background here (colour under image) and the result
       composites identically. */
    background-color: var(--nav-surface-bg, var(--theme-surface-glass, rgba(10, 10, 10, 0.72)));
    background-image: linear-gradient(
        var(--theme-overlay-black-20, rgba(8, 8, 8, 0.2)),
        var(--theme-overlay-black-20, rgba(8, 8, 8, 0.2))
    );
    border: var(--nav-dropdown-border, var(--sn-rim) solid var(--theme-border-strong, rgba(255, 255, 255, 0.16)));
    box-shadow:
        0 1.5rem 3rem var(--theme-shadow-black-42, rgba(0, 0, 0, 0.42)),
        0 0 0 1px var(--theme-surface-white-045, rgba(255, 255, 255, 0.045)) inset;
    backdrop-filter: var(--theme-blur-xl, blur(48px)) saturate(155%) brightness(0.76);
    -webkit-backdrop-filter: var(--theme-blur-xl, blur(48px)) saturate(155%) brightness(0.76);
    opacity: 0;
    visibility: hidden;
    pointer-events: none; /* invisible labels must never intercept the page */
    /* The dropdown's own open timing (nav-dropdown-in, 180ms + its easing),
       so revealing the rail feels like opening a menu. */
    transition:
        opacity 180ms cubic-bezier(0.22, 1, 0.36, 1),
        translate 180ms cubic-bezier(0.22, 1, 0.36, 1),
        background-color 0.22s ease,
        border-color 0.22s ease,
        box-shadow 0.22s ease,
        visibility 0s 180ms;
}

.section-nav:hover .section-nav__label,
.section-nav:focus-within .section-nav__label,
.section-nav__stop.is-peek .section-nav__label {
    opacity: 1;
    visibility: visible;
    translate: 0 -50%;
    transition:
        opacity 180ms cubic-bezier(0.22, 1, 0.36, 1),
        translate 180ms cubic-bezier(0.22, 1, 0.36, 1),
        background-color 0.22s ease,
        border-color 0.22s ease,
        box-shadow 0.22s ease,
        visibility 0s 0s;
}

/* Chips revealed by pointing/tabbing at the rail are clickable — each sits
   inside its stop's <a>, so hovering one also lights that stop's dot and
   clicking one jumps to the section. A lone peeked chip stays visual-only. */
.section-nav:hover .section-nav__label,
.section-nav:focus-within .section-nav__label {
    pointer-events: auto;
}

/* Hover bridge. Without it the open gap between the capsule and the chips
   is dead ground: the pointer leaves .section-nav on its way to a label,
   :hover drops, and the chip goes unclickable before it can be reached.
   Each chip carries an invisible pad spanning that gap, inset vertically
   past its neighbours so the pads form one continuous column — a pointer
   moving off the rail toward ANY chip stays inside the rail the whole way.
   It inherits the chip's pointer-events, so it is inert (and never eats a
   page click) whenever the rail is not being pointed at. */
.section-nav__label::after {
    content: "";
    position: absolute;
    top: calc((var(--sn-pitch) - 100%) / -2);
    bottom: calc((var(--sn-pitch) - 100%) / -2);
    left: 100%;
    width: 16px;
}

/* Sweeping the rail unfurls the chips top-to-bottom. */
.section-nav:hover .section-nav__label {
    transition-delay: calc(var(--sn-i, 0) * 26ms);
}

/* Hover answers the way the plate does — the rim brightens to the same
   white-18 and nothing else about the surface changes — plus a nudge toward
   the reader (the plate can't move, an edge-anchored chip can; its translate
   already carries the -50% centring, so the slide rides the x). */
.section-nav__link:hover .section-nav__label {
    color: #fff;
    translate: -3px -50%;
    border-color: var(--theme-surface-white-18, rgba(255, 255, 255, 0.18));
}

.section-nav__stop.is-active .section-nav__label {
    color: #fff;
    border-color: var(--theme-surface-white-18, rgba(255, 255, 255, 0.18));
}

/* ---- context ------------------------------------------------------------ */

@media (max-width: 899px) {
    .section-nav {
        display: none;
    }
}

@media print {
    .section-nav {
        display: none;
    }
}

/* Every loop and settle stops; the rail still appears, spies and retires,
   just without the choreography (mirrors the announcement bar's block). */
@media (prefers-reduced-motion: reduce) {
    .section-nav,
    .section-nav.is-ready,
    .section-nav.is-ready.is-off,
    .section-nav__list,
    .section-nav__list::after,
    .section-nav.is-ready .section-nav__dot,
    .section-nav__dot,
    .section-nav__label,
    .section-nav__link:hover .section-nav__label {
        transition: none;
        animation: none;
    }
}
