/* Costomize Slider */
.slider-navigation-next {
    margin-top: -32px !important;
    color: white !important;
    background: #252525 !important;
}

.slider-navigation-previous {
    margin-top: -32px !important;
    color: white !important;
    background: #252525 !important;
}

.slider-page {
    color: white !important;
    background: #252525 !important;
}

* {
    font-family: 'Avenir', 'Avenir Next Cyr', 'Config', 'Microsoft YaHei';
}

a {
    color: #6567C9;
    text-decoration: none;
}
  
a.link:focus, a.link:hover {
    color: #ff9349;
    text-decoration: none;
}

body {
    position: relative;
    margin: 0px;
    padding: 0px;
}

p {
    position: relative;
    margin: 16px;
    color: black;
    font-size: 16px;
    font-weight: 500;
    text-align: justify;
}

p span {
    font-weight: 600;
}

video {
    width: 100%;
    height: auto;
}

.x-row {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: nowrap;
}

.x-column {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: nowrap;
    flex-direction: column;
}

.x-center-text {
    margin: 16px 32px;
    text-align: center;
}

.x-left-align {
    display: flex;
    align-items: center;
    justify-content: left;
    flex-wrap: nowrap;
}

.x-right-align {
    display: flex;
    align-items: center;
    justify-content: right;
    flex-wrap: nowrap;
}

.x-flex-spacer {
    flex: 1;
}

.x-labels {
    position: absolute;
    top: 8px;
    right: 6px;
    display: flex;
    align-items: center;
    justify-content: left;
    flex-direction: row-reverse;
}

.x-label {
    height: 20px;
    padding: 0px 6px;
    margin: 0px 2px;
    color: white;
    font-size: 12px;
    font-weight: 600;
    background: #6567C9;
    border-radius: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.x-button {
    height: 40px;
    padding: 0px 14px;
    background: #6567C9;
    color: white;
    border-radius: 50px;
    box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);
    font-size: 16px;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.2s ease;
}

.x-button.small {
    height: 32px;
    padding: 0px 12px;
    border-radius: 50px;
    font-size: 14px;
    font-weight: 600;
}

.x-button:hover {
    transform: scale(1.05);
    box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5);
}

.x-gradient-font {
    background: linear-gradient(107.54deg, #000000.39%, #525252 51.23%, #969696 100%);
    background-clip: text;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

.x-gradient-block {
    color: #3f3f3f;
    background: linear-gradient(107.54deg, #d9d9d9 .39%, #f0f0f0 51.23%, #ffffff 100%);
    border-radius: 16px;
}

.x-gradient-border {
    position: relative;
    padding: 1px;
    margin: 3px;
    border: 3px;
    background: white;
    background-clip: padding-box;
    border: solid border transparent;
    border-radius: 16px;
}

.x-gradient-border::before {
    content: '';
    position: absolute;
    top: 0; right: 0; bottom: 0; left: 0;
    z-index: -1;
    margin: -3px;
    border-radius: 16px;
    background: linear-gradient(107.54deg, #0078d4 .39%, #8661c5 51.23%, #ff9349 100%);
}

.x-section-title {
    margin: 64px 0px 16px 0px;
    padding-left: 16px;
    color: black;
    font-size: 32px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: left;
}

.x-section-title.small {
    margin: 8px 0px 4px 0px;
    padding-left: 4px;
    font-size: 20px;
    font-weight: 700;
}

.x-note {
    color: gray;
    font-size: 16px;
    font-weight: 500;
}

.x-card {
    position: relative;
    margin: 8px;
    padding: 16px;
    border-radius: 16px;
    box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.25);
    transition: all 0.2s ease;
}

.x-card.clickable {
    cursor: pointer;
}

.x-card.clickable:hover {
    box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5);
    transform: scale(1.02);
}

.x-card .caption {
    margin: 8px 0px;
    height: 80px;
    display: flex;
    align-items: start;
    justify-content: center;
}

.x-handwriting {
    width: 100%;
    font-family: 'Segoe Print';
    font-size: 12px;
    font-weight: 600;
    line-height: 1.5;
    color: black;
    text-align: justify;
}

.x-image-prompt {
    height: calc(100% - 2px);
    aspect-ratio: 1/1;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid black;
}

.x-image-prompt img {
    max-width: 100%;
    max-height: 100%;
}

#main {
    max-width: 1000px;
    margin: 0px auto;
    padding-top: 100px;
    padding-bottom: 200px;
}

#logo {
    width: 70%;
    min-width: 350px;
    margin: 0px auto;
    display: flex;
    align-items: start;
    justify-content: center;
    font-size: 32px;
    font-weight: 600;
    color: gray;
}

#title {
    margin-bottom: 32px;
    color: black;
    font-size: 54px;
    font-weight: 700;
    /* white-space: nowrap; */
    text-align: center;
}

#authors {
    width: 80%;
    margin: 8px auto;
    color: black;
    font-size: 16px;
    font-weight: 500;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    /* white-space: nowrap; */
}

#authors div {
    margin: 0px 16px;
}

#institution {
    margin: 8px 32px;
    color: black;
    font-size: 16px;
    font-weight: 500;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    /* white-space: nowrap; */
}

#institution div {
    margin: 0px 16px;
}

#conference {
    margin: 16px 32px;
    color: gray;
    font-size: 24px;
    font-weight: 500;
    display: flex;
    align-items: center;
    justify-content: center;
    white-space: nowrap;
}

#abstract {
    position: relative;
    margin: 32px;
    padding: 16px 24px;
    color: #3f3f3f;
    font-size: 16px;
    font-weight: 500;
    text-align: justify;
    border-radius: 16px;
}

#abstract::before {
    content: "Abstract:";
    font-weight: 700;
    margin-right: 8px;
}


#links {
    margin: 16px 0px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    /* white-space: nowrap; */
}

#links div {
    margin: 4px 8px;
    width: 140px;
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: center;
}

#links a {
    width: 100px;
    height: 20px;
    padding: 8px 16px;
    color: white;
    font-size: 16px;
    font-weight: 500;
    background: linear-gradient(107.54deg, #0078d4 .39%, #8661c5 51.23%, #ff9349 100%) fixed;
    border-radius: 50px;
    box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.2s ease;
}

#links a:hover {
    width: 105px;
    height: 21px;
    font-size: 17px;
    box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.75);
}

#links a.disabled {
    background-color: rgb(192, 192, 192);
}

#links a.disabled:hover {
    background-color: rgb(192, 192, 192);
}

#links a::before {
    /* use !important to prevent issues with browser extensions that change fonts */
    font-family: 'icomoon' !important;
    font-style: normal;
    font-weight: normal;
    font-variant: normal;
    text-transform: none;
    line-height: 1;

    /* Better Font Rendering =========== */
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;

    margin-right: 8px;
}

#links #paper::before {
    content: "\e000";
    font-size: 18px;
}

#links #arxiv::before {
    content: "\e001";
    font-size: 20px;
}

#links #code::before {
    content: "\e002";
    font-size: 20px;
}

#links #poster::before {
    content: "\e004";
    font-size: 20px;
}

#links #video::before {
    content: "\e003";
    font-size: 20px;
}

#links #demo::before {
    content: "\e005";
    font-size: 20px;
}

#teaser {
    margin-top: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
}

/* #teaser::after {
    content: "";
    position: absolute;
    top: 0px;
    left: calc(50% - 50vw);
    width: 100vw;
    height: 100%;
    background: gray;
    opacity: 0.5;
    z-index: -1;
} */

#teaser .label div {
    height: 100%;
    width: 100%;
    font-size: 36px;
    font-weight: 700;
    line-height: 150%;
    text-align: center;
}

#teaser div {
    height: 100%;
}


/* .section::before {
    content: "";
    position: relative;
    left: -32px;
    width: 2px;
    height: 32px;
    background-color: rgb(32, 163, 196);
}

.section::after {
    content: "";
    position: relative;
    right: -32px;
    width: 2px;
    height: 32px;
    background-color: rgb(32, 163, 196);
} */
/* 
.figure {
    margin: 16px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

.figure div {
    margin-top: 4px;
    color: black;
    font-size: 16px;
    font-weight: 400;
} */

.bibtex {
    margin: 32px 96px;
    padding: 0px 24px;
    font-family: consolas, monospace;
    white-space: pre;
    text-wrap: wrap;
    font-size: 14px;
    font-weight: 600;
    display: flex;
    text-align: justify;
}

#bottombar {
    position: relative;
    bottom: 0px;
    height: 50px;
    width: 100%;
    padding: 0px 10%;
    background: linear-gradient(107.54deg, #000000.39%, #252525 51.23%, #525252 100%);
    display: flex;
    align-items: center;
    justify-content: space-between;
    user-select: none;
}

#bottombar div {
    color: rgba(255, 255, 255, 0.7);
    font-size: 12px;
    font-weight: 500;
}

#bottombar div span {
    font-weight: 700;
}

/* --------------------------------------------------------------------
 * Qualitative-comparison grid layout (HOI retarget)
 * -------------------------------------------------------------------- */

/* Slider-hint banner above the grid — REMOVED.
 * The qualitative-results section now uses a plain <p> matching the
 * Interactive 3D Animation hint, so the styled banner + svg icon
 * are gone. Selectors kept removed (not just unused) so they can't
 * accidentally re-engage if someone re-adds the markup later. */

/* Grid of comparison cells */
.compare-grid {
    display: flex;
    flex-direction: column;
    gap: 28px;
    width: 100%;
    max-width: 1400px;
    margin: 0 auto;
}

.compare-row {
    display: flex;
    flex-direction: row;
    align-items: stretch;
    justify-content: center;
    gap: 20px;
    width: 100%;
}

.compare-row-single {
    justify-content: center;
}

/* A comparison cell. Uses `flex: var(--ar)` so each cell's width is
 * proportional to its video aspect ratio, giving equal heights in a row. */
.compare-cell {
    flex: var(--ar, 1) 1 0;
    min-width: 0;
    display: flex !important;              /* override .comparison-panel { display:none } default */
    flex-direction: column;
    gap: 10px;
}

/* Single-panel row: cap the width so it doesn't span full page */
.compare-cell-solo {
    flex: 0 0 auto;
    width: min(900px, 90%);
}

/* Reset the nested triple-compare so it fills the cell and is aspect-driven */
.compare-cell .triple-compare {
    width: 100%;
    max-width: none;
    margin: 0;
    aspect-ratio: var(--ar, 16 / 9);
}

.compare-cell .triple-compare canvas {
    width: 100%;
    height: 100%;
    min-height: 0;
}

.compare-cell .triple-compare-labels {
    max-width: none;
    margin: 4px 0 0 0;
    font-size: 16px;
}

/* Larger caption text, sized to the cell */
.compare-cell .comparison-prompt-box {
    max-width: none;
    margin: 0;
    padding: 14px 18px 16px 20px;
    min-height: 0;
    height: auto;
    max-height: none;
    overflow: visible;
    cursor: default;
    font-size: 18px;
}

.compare-cell .prompt-text {
    font-size: 20px;
    font-weight: 700;
    white-space: normal;
    color: #222;
}

/* Responsive: stack rows vertically on narrow viewports */
@media (max-width: 900px) {
    .compare-row {
        flex-direction: column;
        align-items: center;
        gap: 24px;
    }
    .compare-cell {
        width: min(720px, 95%);
        flex: 0 0 auto;
    }
    .compare-cell .prompt-text {
        font-size: 18px;
    }
}

/* --------------------------------------------------------------------
 * Liquid-glass slider overlay (iOS 26 / WWDC 2025 "Liquid Glass" look)
 *
 * The vertical splitter and the round capsule handle sit ABOVE the
 * canvas as real DOM elements so they can use `backdrop-filter:
 * url(#…)` to genuinely refract the video underneath.
 *
 * IMPORTANT: this matches the look from the liquid-glass skill's
 * `examples/04-draggable-lens.html` (Technique C — per-pixel SDF
 * displacement). The displacement filter is built at runtime by
 * `js/liquid_glass.js`; that script encodes a rounded-rect SDF + a
 * smoothStep bezel into the R/G channels of an off-screen canvas and
 * pipes it into <feImage>/<feDisplacementMap>. The CSS below is
 * deliberately lean — only a faint translucent surface and the
 * highlight stack — so the *refraction* is the dominant visual cue.
 *
 *   - Chromium gets the full curved-glass bend.
 *   - Safari/Firefox fall back to plain blur+saturate (still polished
 *     frosted glass) via the @supports block at the bottom.
 * -------------------------------------------------------------------- */

.triple-compare {
    /* establish a stacking context so absolute children sit above canvas */
    isolation: isolate;
}

/* ---- The vertical bar ----
 *
 * Two visual states, transitioned smoothly between each other (iOS 26
 * button-press feel — soft cubic-bezier, ~320 ms):
 *
 *   REST  — a thin, light-grey, very-lightly-frosted rule. Just enough
 *           presence to mark the split position. No SDF refraction, no
 *           heavy shadow stack — the bar should feel calm.
 *
 *   ACTIVE (`.hover`, `.dragging`) — the example-04 liquid-glass pill:
 *           thicker, near-clear surface, full SDF rim refraction, the
 *           inset highlight pair + outer drop shadow + dark inset
 *           crescent on top.
 *
 * Both states transition the same set of properties — width,
 * border-radius, background, box-shadow, border, backdrop-filter — so
 * the morph reads as one continuous change instead of a jump-cut.
 *
 * Why animate `backdrop-filter`? Chromium *does* animate the filter
 * chain (including the url(...) reference's effective strength via the
 * surrounding contrast/brightness/saturate), so the SDF lens fades in
 * organically with the surface change. Safari/Firefox don't animate
 * url(...) at all, but they also don't render it — they only see the
 * blur portion of the chain, which transitions cleanly.
 *
 * The shared `transition` rule lives on the bare `.glass-splitter`
 * selector so it applies in both directions (rest → active and back). */

/* === Rest state ============================================== */
.glass-splitter {
    position: absolute;
    top: -4px;
    bottom: -4px;
    width: 4px;
    /* center the bar on the split position the JS sets via `left:` */
    transform: translateX(-50%);
    /* clicks pass through to the canvas which owns the drag logic */
    pointer-events: none;
    z-index: 5;
    border-radius: 2px;
    overflow: hidden; /* clip refraction to the pill silhouette when
                       * the bar morphs to its active state */

    /* Mid-grey rule — half-opaque so the video underneath still
     * peeks through, but dark enough to stay legible on bright
     * frames. The hairline highlights below give it a polished
     * bezel feel, not a flat painted strip. */
    background: rgba(110, 115, 125, 0.45);

    /* Light filtering on the peek-through — a touch of saturation
     * + a hint of darken so the rule keeps its silhouette even on
     * white-washed frames, without looking heavy. */
    -webkit-backdrop-filter: blur(2px) saturate(1.1) brightness(0.96);
    backdrop-filter: blur(2px) saturate(1.1) brightness(0.96);

    /* Hairline white top + soft dark bottom + gentle drop shadow:
     * reads as a thin floating bezel rather than a painted line. */
    box-shadow:
        0 1px 2px rgba(0, 0, 0, 0.22),
        inset 0  0.5px 0 rgba(255, 255, 255, 0.55),
        inset 0 -0.5px 0 rgba(0, 0, 0, 0.22);
    border: none;

    will-change: width, border-radius, background, box-shadow,
                 border-color, backdrop-filter, transform;

    /* Apple iOS 26 button-press cubic-bezier — soft, slightly springy,
     * no overshoot. Used for both rest → active and active → rest. */
    transition:
        width            320ms cubic-bezier(0.32, 0.72, 0, 1),
        top              320ms cubic-bezier(0.32, 0.72, 0, 1),
        bottom           320ms cubic-bezier(0.32, 0.72, 0, 1),
        border-radius    320ms cubic-bezier(0.32, 0.72, 0, 1),
        background       320ms cubic-bezier(0.32, 0.72, 0, 1),
        box-shadow       320ms cubic-bezier(0.32, 0.72, 0, 1),
        border-color     320ms cubic-bezier(0.32, 0.72, 0, 1),
        backdrop-filter  320ms cubic-bezier(0.32, 0.72, 0, 1),
        -webkit-backdrop-filter 320ms cubic-bezier(0.32, 0.72, 0, 1);
}

/* === Active state (cursor near the bar, or actively dragging) ====
 *
 * Direct port of the example 04 liquid-glass pill: thick rounded
 * capsule, SDF rim refraction, full shadow stack. */
.glass-splitter.hover,
.glass-splitter.dragging {
    top: -8px;
    bottom: -8px;
    width: 26px;
    border-radius: 999px;

    /* Near-clear surface — the SDF refraction is the dominant visual
     * cue, just like example 04. */
    background: rgba(255, 255, 255, 0.08);

    /* Same backdrop-filter chain as example 04. The url(#…) reference
     * is the SDF lens; Chromium animates it cleanly from the rest
     * state's bare blur+saturate chain.
     *
     * `var(--lg-filter)` is set by js/liquid_glass.js to a
     * `url(#liquid-glass-bar-N)` reference where N is the splitter's
     * sequential id. Each splitter has its OWN filter sized to its
     * actual height, because the qualitative-results grid mixes
     * cells with different aspect ratios and a shared filter clipped
     * the refraction on taller bars. The fallback `url(#liquid-glass-
     * bar)` keeps the original behaviour if the JS hasn't run yet
     * (e.g. before MutationObserver wires up a freshly-inserted
     * splitter). */
    -webkit-backdrop-filter:
        var(--lg-filter, url(#liquid-glass-bar)) blur(0.25px) contrast(1.2) brightness(1.05) saturate(1.1);
    backdrop-filter:
        var(--lg-filter, url(#liquid-glass-bar)) blur(0.25px) contrast(1.2) brightness(1.05) saturate(1.1);

    /* Mirror the example's shadow stack:
     *   inset top-left highlight + bottom-right rim   → curved bezel
     *   outer soft drop shadow                         → lifts off video
     *   inset dark crescent at the top (negative Y)    → underside
     *                                                    of the bezel,
     *                                                    "thick glass" cue */
    box-shadow:
        inset  1px  1px 0 rgba(255, 255, 255, 0.6),
        inset -1px -1px 0 rgba(255, 255, 255, 0.25),
        0 4px 10px rgba(0, 0, 0, 0.25),
        0 -10px 25px inset rgba(0, 0, 0, 0.15);
    border: 1px solid rgba(255, 255, 255, 0.35);
}

/* While actively dragging, push the active state a tiny bit further
 * for tactile feedback — slightly stronger surface and shadow. */
.glass-splitter.dragging {
    background: rgba(255, 255, 255, 0.12);
    box-shadow:
        inset  1px  1px 0 rgba(255, 255, 255, 0.85),
        inset -1px -1px 0 rgba(255, 255, 255, 0.40),
        0 8px 18px rgba(0, 0, 0, 0.32),
        0 -12px 28px inset rgba(0, 0, 0, 0.18);
}

/* ---- The capsule handle ----
 *
 * In the current "single thick pill" design we let the .glass-splitter
 * bar BE the entire liquid-glass element — so the previous inner pill
 * handle is hidden. Kept in the DOM (created by index.js) only so we
 * don't have to touch the drag logic. The :hover/.dragging styles on
 * the splitter cover the active-state visual feedback that used to
 * live on the handle. */
.glass-handle {
    display: none !important;
}

/* Safari/Firefox fallback: backdrop-filter:url() is Chromium-only.
 * Without the SVG warp the active state still gets a polished
 * frosted-glass pill via blur + saturate. The rest state already only
 * uses blur(2px), which works everywhere — no override needed. */
@supports not (backdrop-filter: url(#liquid-glass-bar)) {
    .glass-splitter.hover,
    .glass-splitter.dragging {
        backdrop-filter: blur(10px) saturate(1.7) brightness(1.04);
        -webkit-backdrop-filter: blur(10px) saturate(1.7) brightness(1.04);
        background: rgba(255, 255, 255, 0.16);
    }
}

/* --------------------------------------------------------------------
 * Liquid-glass scrub slider (used by the Interactive 3D Animation
 * panel's playback control row)
 *
 * The widget is a 4-element overlay:
 *   .glass-slider              — flex container, defines width
 *     .glass-slider-track      — frosted rounded-rect background bar
 *       .glass-slider-fill     — filled portion left of the thumb
 *     .glass-slider-thumb      — circular SDF lens (the bauble)
 *     <input type="range">     — laid over the whole thing, opacity:0,
 *                                gets input events so the browser does
 *                                keyboard/drag handling for free.
 *
 * The thumb's position and the fill width are driven by a CSS custom
 * property `--pos` (0..1) that the inline JS in index.html sets every
 * frame from the input value (see syncSlider() there).
 *
 * The thumb refraction uses #liquid-glass-thumb (built by
 * js/liquid_glass.js with a circle SDF — see thumbFragment there).
 * -------------------------------------------------------------------- */

.glass-slider {
    --pos: 0;
    --thumb-size: 28px;
    --track-h: 10px;
    /* Inset so the leftmost thumb position doesn't overlap the
     * preceding play/pause button when --pos is 0. The active-state
     * thumb scales to 30 × 30 px (1.12 transform brings it to ~34 px
     * visual width); without this inset its left half would push
     * ~17 px past the slider's left edge and overlap the button.
     *
     * The track, fill, thumb, and overlaid <input> all reference
     * this variable to keep their coordinate origin aligned — see
     * those rules below. */
    --inset-left: 18px;

    position: relative;
    flex: 1 1 auto;
    /* Made significantly longer than the previous bare <input>'s
     * 200 px minimum so the bar reads as a proper scrub track. */
    min-width: 360px;
    height: var(--thumb-size);
    display: flex;
    align-items: center;
    user-select: none;
}

.glass-slider-track {
    position: absolute;
    top: 50%;
    /* Inset on the left by --inset-left so the visible track starts
     * past the play/pause button. `right: 0` keeps the track's
     * right edge flush with the slider container — the inset only
     * shrinks the track from the left, which is what we want. */
    left: var(--inset-left); right: 0;
    height: var(--track-h);
    transform: translateY(-50%);
    border-radius: 999px;
    overflow: hidden;
    /* Frosted track — pale tint, hairline highlights. The thicker
     * bar gets a slightly stronger top/bottom edge to keep its
     * polished bezel feel. */
    background: rgba(0, 0, 0, 0.10);
    box-shadow:
        inset 0  1px 0 rgba(255, 255, 255, 0.65),
        inset 0 -1px 0 rgba(0, 0, 0, 0.20);
}

.glass-slider-fill {
    position: absolute;
    top: 0; bottom: 0;
    left: 0;
    /* Width follows the input value. */
    width: calc(var(--pos) * 100%);
    /* Flat iOS-blue fill (rgb(30, 110, 244)) — clean, constant color
     * matches the rest of the page's blue accents. */
    background: rgb(30, 110, 244);
    /* Fully rounded on all four corners so the fill's right edge
     * matches the track's pill silhouette under the thumb (was
     * `999px 0 0 999px` which left a square right edge). With the
     * track's border-radius: 999px clipping it, the visible result
     * is a perfectly rounded blue capsule that grows from the left. */
    border-radius: 999px;
}

.glass-slider-thumb {
    position: absolute;
    top: 50%;
    /* Center the bauble on the current pos. We translate by half its
     * own width so the centerline tracks `--pos` precisely.
     *
     * The thumb travels from the track's left edge (--inset-left)
     * to the slider's right edge (100%). Without the inset offset
     * the thumb's centerline at --pos: 0 would sit at the slider's
     * left edge, which lands on top of the play/pause button. */
    left: calc(var(--inset-left) + var(--pos) * (100% - var(--inset-left)));
    transform: translate(-50%, -50%);

    /* === Rest state =========================================== */
    /* Small grey-tinted pill — slightly wider than the 10 px track
     * so it's clearly visible as a draggable handle, but calm: no
     * SDF refraction, just a soft frost and a hairline highlight.
     * Same idea as the splitter's rest state. */
    width:  16px;
    height: 14px;
    border-radius: 999px;
    pointer-events: none; /* the input handles input */

    background: rgba(110, 115, 125, 0.55);

    -webkit-backdrop-filter: blur(2px) saturate(1.1) brightness(0.96);
    backdrop-filter:         blur(2px) saturate(1.1) brightness(0.96);

    box-shadow:
        0 1px 3px rgba(0, 0, 0, 0.22),
        inset 0  0.5px 0 rgba(255, 255, 255, 0.65),
        inset 0 -0.5px 0 rgba(0, 0, 0, 0.20);
    border: none;

    /* Same iOS-26 morph easing & duration as the splitter: width,
     * height, border-radius, surface, shadows and the filter chain
     * all transition together so the rest → active state reads as
     * one continuous change. */
    will-change: width, height, border-radius, background,
                 box-shadow, border-color, backdrop-filter, transform;
    transition:
        width            320ms cubic-bezier(0.32, 0.72, 0, 1),
        height           320ms cubic-bezier(0.32, 0.72, 0, 1),
        border-radius    320ms cubic-bezier(0.32, 0.72, 0, 1),
        background       320ms cubic-bezier(0.32, 0.72, 0, 1),
        box-shadow       320ms cubic-bezier(0.32, 0.72, 0, 1),
        border-color     320ms cubic-bezier(0.32, 0.72, 0, 1),
        backdrop-filter  320ms cubic-bezier(0.32, 0.72, 0, 1),
        -webkit-backdrop-filter 320ms cubic-bezier(0.32, 0.72, 0, 1),
        transform        200ms cubic-bezier(0.32, 0.72, 0, 1);
}

/* === Active state ===========================================
 *
 * Adapted from a Figma "Liquid Glass" pill spec (the horizontal
 * 309 × 187 px capsule reference the user supplied). That spec layers,
 * from outside in:
 *   - Rectangle 3   — 4 px black @ 40% rim, blurred 20 px  → soft dark halo
 *   - (focus ring)  — 2 px white @ 20% (hidden, used on focus only)
 *   - Borda outer   — 4 px white @ 30%   → bright crisp rim
 *   - Borda inner   — 8 px white @ 5%    → thick muted inner halo
 *   - Rectangle 12  — radial gradient (transparent core → white @ 5% rim)
 *                     over backdrop-blur(2 px)
 *
 * We keep the example-04 SDF refraction (`url(#liquid-glass-thumb)`)
 * because that's the "real Apple WWDC look" the user already signed
 * off on, and stack the Figma's rim/halo layers on top. The Figma
 * pixel sizes are scaled down from 187 px tall → 30 px thumb (~6×):
 *   8 px inner halo  ≈ 1.5 px → use 1.5 px inset white@5%
 *   4 px outer rim   ≈ 0.7 px → use 1 px inset white@30%
 *   20 px black blur ≈ 3.5 px → outer drop-shadow 0 3px 10px rgba(0,0,0,0.30)
 * The hidden focus ring becomes a real 2 px outer ring on
 * :focus-visible (see the .glass-slider:focus-visible rule below).
 *
 * Active state is triggered when:
 *   - the cursor hovers the slider widget, or
 *   - the input has focus (keyboard scrub), or
 *   - the user is actively dragging (.dragging is set by the inline
 *     JS on pointerdown — see index.html).
 * The .dragging variant is what keeps the active state pinned even
 * when the user's cursor strays outside the widget mid-drag. */
.glass-slider:hover         .glass-slider-thumb,
.glass-slider:focus-within  .glass-slider-thumb,
.glass-slider.dragging      .glass-slider-thumb {
    /* Bumped 28 → 30 so the stacked rim layers from the Figma spec
     * have a couple of pixels to read against the SDF core. */
    width:  30px;
    height: 30px;
    border-radius: 50%;

    /* Rectangle 12 — the soft radial rim highlight from the spec
     * (transparent core, faint white wash at the very edge). Layered
     * on top of the near-clear translucent base used previously so
     * the SDF lens still carries the refraction. */
    background:
        radial-gradient(58% 58% at 50% 50%,
            rgba(255, 255, 255, 0)    64.2%,
            rgba(255, 255, 255, 0.18) 100%),
        rgba(255, 255, 255, 0.10);

    -webkit-backdrop-filter:
        url(#liquid-glass-thumb) blur(0.25px) contrast(1.2) brightness(1.05) saturate(1.1);
    backdrop-filter:
        url(#liquid-glass-thumb) blur(0.25px) contrast(1.2) brightness(1.05) saturate(1.1);

    /* Layered borders + halos, in stacking order from the SDF surface
     * outward. box-shadow lets us stack rims without consuming the
     * single `border` slot (which we keep free for focus-visible).
     *
     *   inset  1.5px white@10%   → Borda inner (thick muted halo)
     *   inset    1px white@45%   → Borda outer (bright crisp rim)
     *   inset top-left highlight + bottom-right rim  → curved-glass bezel
     *                                                  (kept from prior
     *                                                  example-04 stack)
     *   outer 0 3px 10px black@30%   → Rectangle 3 dark halo, scaled
     *   outer 0 -8px 20px inset      → underside dark crescent (kept) */
    box-shadow:
        inset 0 0 0 1.5px rgba(255, 255, 255, 0.10),
        inset 0 0 0 1px   rgba(255, 255, 255, 0.45),
        inset  1px  1px 0 rgba(255, 255, 255, 0.70),
        inset -1px -1px 0 rgba(255, 255, 255, 0.30),
        0 3px 10px rgba(0, 0, 0, 0.30),
        0 4px 10px rgba(0, 0, 0, 0.18),
        0 -8px 20px inset rgba(0, 0, 0, 0.12);
    border: 1px solid transparent; /* reserved for :focus-visible */
}

/* Hidden-by-default 2 px outer ring from the Figma's third "Borda"
 * (the visibility:hidden one). Materialises on keyboard focus only —
 * matches the spec's intent without ever showing on hover/drag.
 *
 * The real focusable element is the overlaid <input type="range">.
 * The thumb is a *previous* sibling, so `~` won't reach it; we use
 * `:has()` (Chromium 105+, which the SDF refraction already requires)
 * to walk back up to the slider root and target the thumb. */
.glass-slider:has(input[type="range"]:focus-visible) .glass-slider-thumb {
    border-color: rgba(255, 255, 255, 0.20);
    box-shadow:
        0 0 0 2px rgba(255, 255, 255, 0.20),
        inset 0 0 0 1.5px rgba(255, 255, 255, 0.10),
        inset 0 0 0 1px   rgba(255, 255, 255, 0.45),
        inset  1px  1px 0 rgba(255, 255, 255, 0.70),
        inset -1px -1px 0 rgba(255, 255, 255, 0.30),
        0 3px 10px rgba(0, 0, 0, 0.30),
        0 4px 10px rgba(0, 0, 0, 0.18),
        0 -8px 20px inset rgba(0, 0, 0, 0.12);
}

/* During an active drag, push the active state a touch further for
 * tactile feedback — slightly stronger surface, brighter outer rim,
 * deeper dark halo, and a subtle scale-up. Mirrors the splitter's
 * .dragging tweak and re-stacks the Figma layers at higher intensity. */
.glass-slider.dragging .glass-slider-thumb {
    transform: translate(-50%, -50%) scale(1.08);
    background:
        radial-gradient(58% 58% at 50% 50%,
            rgba(255, 255, 255, 0)    64.2%,
            rgba(255, 255, 255, 0.24) 100%),
        rgba(255, 255, 255, 0.16);
    box-shadow:
        inset 0 0 0 1.5px rgba(255, 255, 255, 0.14),
        inset 0 0 0 1px   rgba(255, 255, 255, 0.55),
        inset  1px  1px 0 rgba(255, 255, 255, 0.95),
        inset -1px -1px 0 rgba(255, 255, 255, 0.45),
        0 4px 14px rgba(0, 0, 0, 0.36),
        0 8px 18px rgba(0, 0, 0, 0.24),
        0 -10px 24px inset rgba(0, 0, 0, 0.16);
}

/* The native input is invisible but receives all input events. We
 * make it cover the whole widget (including the thumb area) so
 * pointer-down anywhere on the track jumps the thumb there.
 *
 * Inset-left matches the track so clicking the visible track's
 * left edge maps to value=0 (browsers map click-x linearly to
 * [min, max] across the input's width). Without this inset the
 * input's coordinate origin would be ~18 px to the left of the
 * track and the play/pause button would steal clicks intended
 * for the slider's leftmost track positions. */
.glass-slider input[type="range"] {
    position: absolute;
    inset: 0;
    left: var(--inset-left);
    width: auto;
    right: 0;
    height: 100%;
    margin: 0;
    padding: 0;
    background: transparent;
    -webkit-appearance: none;
    appearance: none;
    cursor: pointer;
    opacity: 0;        /* invisible — the visual layers are above */
    z-index: 2;
}
.glass-slider input[type="range"]:focus { outline: none; }

/* Safari/Firefox fallback for the thumb: same graceful
 * degradation as the splitter — frosted disc instead of true
 * curved-glass refraction. */
@supports not (backdrop-filter: url(#liquid-glass-thumb)) {
    .glass-slider-thumb {
        backdrop-filter: blur(8px) saturate(1.5) brightness(1.04);
        -webkit-backdrop-filter: blur(8px) saturate(1.5) brightness(1.04);
        background: rgba(255, 255, 255, 0.20);
    }
}

/* --------------------------------------------------------------------
 * Liquid-glass icon button (.glass-icon-btn)
 *
 * Used for the play/pause control next to the scrub slider on the
 * Interactive 3D Animation demo. Same Figma-spec rim/halo stack as
 * the slider thumb, but tuned for an opaque page background:
 *   - The thumb sits over the video viewport, so it can be near-
 *     transparent and let the SDF lens carry the look.
 *   - This button sits over the page's light-grey panel (#f5f5f7),
 *     so a near-transparent surface would just look milky. We use a
 *     white-tinted base in both states (calm grey-glass at rest,
 *     bright frosted glass on hover/active) and let the icon glyph
 *     carry the contrast. The SDF refraction (`url(#liquid-glass-
 *     playpause)`) still bends whatever's behind — page texture,
 *     adjacent controls — so the curved-glass feel reads.
 *
 * The button has two icons stacked inside it (play + pause); CSS
 * shows the one matching [data-state] and hides the other. Toggling
 * is a single attribute write — no innerHTML, no flash. -------- */
.glass-icon-btn {
    /* === Geometry — must match ACTIVE_BTN_WIDTH/HEIGHT in
     *               js/liquid_glass.js so the SDF map is sampled
     *               at exactly the rendered size.
     *
     * `border-radius` morphs from 12 px (rest squircle) at rest to
     * 16 px (more-rounded squircle) when the user interacts — see
     * the active-state rule below. Both endpoints share the same
     * SDF map; the corner radius the SDF was generated at (~0.32 of
     * the half-extent ≈ 11.5 px) reads correctly across this range. */
    width:  36px;
    height: 36px;
    border-radius: 12px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    cursor: pointer;
    color: #1f2937;             /* icon glyph color — slate */
    /* Suppress the browser's default focus ring everywhere. We have
     * a bouncy + lit-up `:focus-visible` style that already serves
     * as the visible focus indicator for keyboard users, and the
     * default ring shows up as a dark "corner" rectangle around the
     * button after a mouse click — which is exactly the artifact
     * the design needs to avoid. */
    outline: none;
    -webkit-tap-highlight-color: transparent;
    /* Establish identity scale here so the bouncy hover transition
     * has something to spring back to. */
    transform: scale(1);

    /* === Rest state ============================================
     * Slightly grey-tinted frosted surface so the button is clearly
     * distinguishable from the page panel (#f5f5f7) it sits on,
     * without looking heavy. The neutral cool-grey tint
     * rgb(220, 220, 226) reads as "uncoloured glass" rather than
     * "white pill" — same visual weight as the slider's rest-state
     * pill. */
    background: rgba(220, 220, 226, 0.72);
    border: 1px solid rgba(0, 0, 0, 0.08);
    -webkit-backdrop-filter: blur(6px) saturate(1.05);
    backdrop-filter:         blur(6px) saturate(1.05);
    box-shadow:
        inset 0  0.5px 0 rgba(255, 255, 255, 0.70),
        inset 0 -0.5px 0 rgba(0, 0, 0, 0.08);

    /* === Springy bouncy transition ==============================
     * cubic-bezier(0.34, 1.56, 0.64, 1) — ease-out-back. Anything
     * that should "spring" rides this curve over 360 ms:
     *   - transform (the scale pop)
     *   - border-radius (the corner morph 12 px → 50%)
     * The colour/shadow chain stays on the calm 220 ms ease-out so
     * surface settles before the bounce finishes — that contrast is
     * what makes the bounce read as a *physical* spring instead of
     * the colours lagging behind. */
    will-change: background, box-shadow, border-color, backdrop-filter,
                 transform, color, border-radius;
    transition:
        background       220ms cubic-bezier(0.32, 0.72, 0, 1),
        box-shadow       220ms cubic-bezier(0.32, 0.72, 0, 1),
        border-color     220ms cubic-bezier(0.32, 0.72, 0, 1),
        backdrop-filter  220ms cubic-bezier(0.32, 0.72, 0, 1),
        -webkit-backdrop-filter 220ms cubic-bezier(0.32, 0.72, 0, 1),
        color            180ms cubic-bezier(0.32, 0.72, 0, 1),
        transform        360ms cubic-bezier(0.34, 1.56, 0.64, 1),
        border-radius    360ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.glass-icon-btn .icon {
    width: 18px;
    height: 18px;
    fill: currentColor;
    /* Clean white drop-shadow on the glyph — sharpens it against
     * the slightly busy frosted surface in the active state. We
     * deliberately keep this minimal: the lit-up feel should come
     * from the surface (rim wash + inset highlights), not from a
     * bloom on the icon. */
    filter: drop-shadow(0 1px 0 rgba(255, 255, 255, 0.7));
}

/* Show one icon at a time based on data-state. CSS handles the swap;
 * the JS just flips the attribute. */
.glass-icon-btn .icon-play  { display: none; }
.glass-icon-btn .icon-pause { display: none; }
.glass-icon-btn[data-state="paused"]  .icon-play  { display: block; }
.glass-icon-btn[data-state="playing"] .icon-pause { display: block; }

/* === Active state =============================================
 * Hover / focus / active drag — the surface itself carries the
 * lit-up effect. Three changes from the rest state:
 *   1. `border-radius: 16px` — corners spring to a more-rounded
 *      squircle using the same ease-out-back curve as the scale
 *      pop (rest is 12 px; bumping to 16 px on a 36 px button reads
 *      as "noticeably softer corners" without crossing over into
 *      circle territory).
 *   2. `transform: scale(1.12)` — bouncy size pop, overshoots and
 *      settles thanks to the spring easing.
 *   3. The surface goes from grey-frosted to bright-glass: the
 *      radial rim wash brightens, the inset highlight rims thicken
 *      and brighten, and a new inset top-edge highlight reads as
 *      "light hitting the dome from above". No outer warm bloom —
 *      just a hairline outer rim and a *gentle* drop-shadow so the
 *      brightness is on the button, not haloing around it. */
.glass-icon-btn:hover,
.glass-icon-btn:focus-visible,
.glass-icon-btn:active {
    color: #0b1220;
    transform: scale(1.12);
    border-radius: 16px;

    /* Bright frosted surface with a strong rim wash. The radial
     * gradient peaks at the rim, making the edges look "lit from
     * within"; the white-tinted base lifts the whole surface to
     * read as glass-with-light rather than grey-glass. */
    background:
        radial-gradient(58% 58% at 50% 50%,
            rgba(255, 255, 255, 0)    58%,
            rgba(255, 255, 255, 0.55) 100%),
        rgba(255, 255, 255, 0.78);
    -webkit-backdrop-filter:
        url(#liquid-glass-playpause) blur(0.25px) contrast(1.1) brightness(1.06) saturate(1.1);
    backdrop-filter:
        url(#liquid-glass-playpause) blur(0.25px) contrast(1.1) brightness(1.06) saturate(1.1);
    border-color: rgba(255, 255, 255, 0.75);
    box-shadow:
        /* Surface highlights — these carry the lit-up feel.
         *   Layered inset rims (Borda inner + outer from the Figma
         *   spec): a thicker translucent halo wrapped by a sharper
         *   bright rim, so the edge of the button reads bright. */
        inset 0 0 0 1.5px rgba(255, 255, 255, 0.28),
        inset 0 0 0 1px   rgba(255, 255, 255, 0.70),
        /* Curved-glass bezel: top-left highlight + bottom-right
         *   rim — same trick as example-04 / the slider thumb. */
        inset  1px  1px 0 rgba(255, 255, 255, 0.85),
        inset -1px -1px 0 rgba(255, 255, 255, 0.40),
        /* New: a 4 px tall inset highlight at the top of the button.
         *   On a rounded square this reads as a crescent of light
         *   along the top edge — like a polished glass tile lit from
         *   above. This is where most of the perceived "shine" comes
         *   from. */
        inset 0 4px 6px -3px rgba(255, 255, 255, 0.85),
        /* Hairline outer rim — keeps the silhouette crisp without
         *   looking like a halo. */
        0 0 0 1px rgba(255, 255, 255, 0.35),
        /* Gentle outer drop-shadow — just enough to lift the button
         *   off the page panel. No coloured bloom. */
        0 2px 6px rgba(0, 0, 0, 0.10);
    outline: none;
}

/* Pressed state — the bounce overshoots smaller (scale 1.04), then
 * the click releases back into the hover state's 1.12 spring. The
 * surface brightens one more notch so the button feels "turned on
 * harder" while pressed. */
.glass-icon-btn:active {
    transform: scale(1.04);
    background:
        radial-gradient(58% 58% at 50% 50%,
            rgba(255, 255, 255, 0)    56%,
            rgba(255, 255, 255, 0.65) 100%),
        rgba(255, 255, 255, 0.88);
    box-shadow:
        inset 0 0 0 1.5px rgba(255, 255, 255, 0.40),
        inset 0 0 0 1px   rgba(255, 255, 255, 0.85),
        inset  1px  1px 0 rgba(255, 255, 255, 0.95),
        inset -1px -1px 0 rgba(255, 255, 255, 0.55),
        inset 0 5px 7px -3px rgba(255, 255, 255, 0.95),
        0 0 0 1px rgba(255, 255, 255, 0.50),
        0 1px 4px rgba(0, 0, 0, 0.10);
}

/* Safari/Firefox fallback for the button: graceful frosted glass
 * without true curved-glass refraction, same as the splitter / thumb
 * fallbacks. */
@supports not (backdrop-filter: url(#liquid-glass-playpause)) {
    .glass-icon-btn:hover,
    .glass-icon-btn:focus-visible,
    .glass-icon-btn:active {
        backdrop-filter: blur(8px) saturate(1.5) brightness(1.04);
        -webkit-backdrop-filter: blur(8px) saturate(1.5) brightness(1.04);
    }
}
