        /* ═══════════════════════════════════════════════════════════════
           DIGITAL LIBRARY - Where scholarly tradition meets computation
           ═══════════════════════════════════════════════════════════════ */

        :root {
            /* Core palette - warm parchment on deep ink */
            --ink: #0a0a0c;
            --ink-card: #1a1a1f;

            /* Parchment spectrum */
            --parchment: #f4f1e8;
            --parchment-dim: #c9c4b5;
            --parchment-muted: #8a857a;

            /* Accent - slate blue */
            --copper: #4f6cc4;
            --copper-bright: #6a84d1;
            --copper-glow: rgba(var(--rgb-copper), 0.3);

            /* Secondary - soft periwinkle */
            --gold: #8099d4;

            /* Semantic */
            --positive: #7d9c6d;
            --negative: #b86a6a;
            --info: #6a8fb8;

            /* ── Fixed RGB triplets (DO NOT redefine in index-light.css) ──────
               Back hard-coded rgba()/rgb() literals via rgba(var(--rgb-x), a).
               Unlike the role tokens above (--parchment, --ink, --copper …)
               which light mode INVERTS, these must resolve to the SAME triplet
               in both themes: light repaints these surfaces selector-by-
               selector with its own periwinkle literals, so flipping a triplet
               here would double-invert patched surfaces and wrongly flip
               unpatched ones. */
            --rgb-parchment: 244, 241, 232;  /* = #f4f1e8 (--parchment) */
            --rgb-ink:        10, 10, 12;     /* = #0a0a0c (--ink) */
            --rgb-ink-card:   31, 31, 37;     /* #1f1f25 — lightened card tone; see --card-fill */
            --rgb-copper:     79, 108, 196;   /* = #4f6cc4 (--copper) */
            --rgb-negative:  184, 106, 106;   /* = #b86a6a (--negative) */
            --rgb-black:       0, 0, 0;

            /* Card surface (dark) — frosted-glass fill + hairline shared by the
               top-level cards (.search-card, .results-card, .rubric-card).
               The fill derives from the lightened --rgb-ink-card
               "card tone" (31,31,37 = #1f1f25) at 0.74, with a firmer edge, so
               the cards set off from the background video. That same tone is
               shared (at 0.4 α) by .nav-pill and the .sf-details search-process
               panel — deliberately lighter than the opaque --ink-card #1a1a1f
               that still backs tooltips / menus / popovers. Dark-only:
               index-light.css repaints every card, the pill and the panel with
               its own light-glass recipe and never reads these. */
            --card-fill:   rgba(var(--rgb-ink-card), 0.74);
            --card-border: 1px solid rgba(var(--rgb-parchment), 0.12);

            /* Hairline divider between result rows and below the control strip
               (dark) — one token so both stay locked, at the same alpha tier as
               --card-border. Dark-only: index-light.css repaints both
               border-bottoms with its own ink-dark divider. */
            --row-divider: 1px solid rgba(var(--rgb-parchment), 0.12);

            /* Typography */
            --font-display: 'Spectral', Georgia, serif;
            --font-mono: 'IBM Plex Mono', 'Menlo', monospace;
            --font-sans: 'TeX Gyre Heros', system-ui, sans-serif;

            /* Spacing scale */
            --sp-1: 0.25rem;
            --sp-2: 0.5rem;
            --sp-3: 0.75rem;
            --sp-4: 1rem;
            --sp-5: 1.25rem;
            --sp-6: 1.5rem;
            --sp-8: 2rem;
            --sp-10: 2.5rem;
            --sp-12: 3rem;
            --sp-16: 4rem;

            /* Radius */
            --radius-sm: 4px;
            --radius-md: 8px;
            --radius-lg: 12px;

            /* Motion */
            --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
            --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
            --dur-fast: 0.1s;     /* slider thumbs, active snaps, scroll-fade */
            --dur-micro: 0.15s;   /* default: hovers, focus, menus, chips, toolbar */
            --dur-expand: 0.25s;  /* expand/collapse panels (accordions) */

            /* Hero bg anchor. Bg image is 1920x1252; precomputed ratios let us
               size a wrapper that reproduces object-fit: cover sizing math, so
               the brand title can be anchored to a fraction of the IMAGE
               (--brand-anchor-y) rather than to viewport pixels. Without this,
               browser-chrome differences in 100vh make title and dark row drift
               independently. Tune --brand-anchor-y by eye (0 = top edge of
               image, 1 = bottom edge). */
            --bg-height-per-width: 65.2083vw;   /* 1252/1920 */
            --bg-width-per-height: 153.3546vh;  /* 1920/1252 */
            --brand-anchor-y: 0.25;
        }

        /* ═══════════════════════════════════════════════════════════════
           RESET & BASE
           ═══════════════════════════════════════════════════════════════ */

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

        html {
            font-size: 16px;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
        }

        body {
            font-family: var(--font-mono);
            font-size: 0.875rem;
            font-weight: 400;
            line-height: 1.6;
            color: var(--parchment);
            background: var(--ink);
            min-height: 100vh;
            overflow-x: hidden;
        }

        /* ═══════════════════════════════════════════════════════════════
           VIDEO BACKGROUND
           ═══════════════════════════════════════════════════════════════ */

        /* Wrapper sized to match what object-fit: cover would produce on a
           1920x1252 image. Everything inside fills it; content anchored at a
           % of this wrapper (see .hero) stays glued to the same pixel of the
           image as the viewport changes. */
        .bg-anchor {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width:  max(100vw, var(--bg-width-per-height));
            height: max(100vh, var(--bg-height-per-width));
            z-index: -2;
            pointer-events: none;
        }

        .bg-anchor > video,
        .bg-anchor > img {
            position: absolute;
            inset: 0;
            width: 100%;
            height: 100%;
            object-fit: cover;
        }

        /* Layering within the wrapper's local stacking context: video on top
           of its matching poster. Scene switching (book ↔ matrix) is handled
           by display toggles in JS — see the video controller below. */
        .bg-anchor > video { z-index: 2; }
        .bg-anchor > img   { z-index: 1; }

        /* Flat dim layer — uniformly tones down the loud book background so the
           white book glyphs stop competing with the search portal. Sits above
           the video (z-index -2) and below the vignette/scroll-fade overlays
           (same -1, but earlier in the DOM). Dark-only: index-light.css
           neutralises it (the light backdrop is already calm). α is the knob. */
        .bg-dim {
            position: fixed;
            inset: 0;
            z-index: -1;
            pointer-events: none;
            background: rgba(var(--rgb-ink), 0.6);
        }

        /* While the matrix search animation is on screen, drop the dim so the
           animation plays at full strength. No transition — the book↔matrix
           scene switch is a hard cut, and the dim snaps with it. Restored when
           results freeze the book backdrop. Toggled by intro-video.js. */
        body.search-bg-active .bg-dim { opacity: 0; }

        /* Vignette overlay - fades in after animation */
        .vignette {
            position: fixed;
            inset: 0;
            z-index: -1;
            pointer-events: none;
            opacity: 0;
            transition: opacity 3s ease-out;
            background:
                radial-gradient(
                    ellipse 120% 80% at 50% 50%,
                    transparent 0%,
                    transparent 30%,
                    rgba(var(--rgb-ink), 0.5) 60%,
                    rgba(var(--rgb-ink), 0.9) 100%
                );
        }

        .vignette.visible {
            opacity: 1;
        }

        /* Scroll-based fade overlay - darkens background as user scrolls to results */
        .scroll-fade {
            position: fixed;
            inset: 0;
            z-index: -1;
            pointer-events: none;
            background: rgb(var(--rgb-ink));
            opacity: 0;
            transition: opacity var(--dur-fast) var(--ease-out);
        }


        /* ═══════════════════════════════════════════════════════════════
           NAVIGATION PILL
           ═══════════════════════════════════════════════════════════════ */

        .nav-pill {
            position: fixed;
            top: 1.5rem;
            right: 2rem;
            z-index: 100;
            display: flex;
            align-items: center;
            gap: 0.25rem;
            padding: 0.5rem 0.5rem;
            background: rgba(var(--rgb-ink-card), 0.4);
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            border: 1px solid rgba(var(--rgb-parchment), 0.18);
            border-radius: 100px;
        }

        .nav-pill a {
            font-family: var(--font-mono);
            font-size: 0.8125rem;
            font-weight: 500;
            letter-spacing: 0.03em;
            text-transform: uppercase;
            color: var(--parchment-muted);
            text-decoration: none;
            padding: 0.5rem 1rem;
            border-radius: 100px;
            transition: all var(--dur-micro) var(--ease-out);
        }

        .nav-pill a:hover {
            color: var(--parchment);
            background: rgba(var(--rgb-parchment), 0.08);
        }

        .nav-pill a.nav-active {
            color: var(--copper);
        }

        /* Nav pill intro animation - blur to sharp */
        .nav-pill.intro-hidden {
            opacity: 0;
            filter: blur(8px);
            transform: none;
        }

        .nav-pill.intro-visible {
            animation: nav-focus 1.2s var(--ease-out) forwards;
        }

        @keyframes nav-focus {
            from {
                opacity: 0;
                filter: blur(8px);
            }
            to {
                opacity: 1;
                filter: blur(0);
            }
        }

        /* ═══════════════════════════════════════════════════════════════
           CORNER RIBBON
           ═══════════════════════════════════════════════════════════════ */

        .corner-ribbon {
            position: fixed;
            top: 0;
            left: 0;
            width: 150px;
            height: 150px;
            overflow: hidden;
            z-index: 101;
            pointer-events: none;
        }

        .corner-ribbon span {
            position: absolute;
            display: block;
            width: 225px;
            padding: 8px 0;
            background: rgba(var(--rgb-copper), 0.85);
            color: var(--parchment);
            font-family: var(--font-mono);
            font-size: 0.75rem;
            font-weight: 500;
            letter-spacing: 0.05em;
            text-transform: uppercase;
            text-align: center;
            left: -55px;
            top: 35px;
            transform: rotate(-45deg);
            box-shadow: 0 2px 8px rgba(var(--rgb-black), 0.3);
            pointer-events: auto;
        }

        /* ═══════════════════════════════════════════════════════════════
           PLAY OVERLAY (for browsers blocking autoplay)
           ═══════════════════════════════════════════════════════════════ */

        #play-overlay {
            position: fixed;
            inset: 0;
            background: rgba(var(--rgb-ink), 0.95);
            display: none;
            align-items: center;
            justify-content: center;
            z-index: 1000;
            cursor: pointer;
        }

        #play-overlay .play-content {
            text-align: center;
        }

        #play-overlay .play-icon {
            width: 80px;
            height: 80px;
            border: 2px solid var(--parchment-dim);
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            margin: 0 auto var(--sp-6);
            transition: all 0.3s var(--ease-out);
        }

        #play-overlay:hover .play-icon {
            border-color: var(--copper);
            background: var(--copper-glow);
            transform: scale(1.05);
        }

        #play-overlay .play-icon svg {
            width: 32px;
            height: 32px;
            fill: var(--parchment);
            margin-left: 4px;
        }

        #play-overlay .play-text {
            font-family: var(--font-display);
            font-size: 1.25rem;
            font-weight: 500;
            color: var(--parchment-dim);
            letter-spacing: 0.05em;
        }

        /* ═══════════════════════════════════════════════════════════════
           LAYOUT
           ═══════════════════════════════════════════════════════════════ */

        .page {
            position: relative;
            min-height: 100vh;
            display: flex;
            flex-direction: column;
        }

        .main-content {
            flex: 1;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: flex-start;
            padding: var(--sp-8);
            max-width: 972px;
            margin: 0 auto;
            width: 100%;
        }

        /* Hero = brand title + search card. Padding-top anchors the brand to
           --brand-anchor-y of the bg image, by computing where that point
           lands in the viewport after the bg's cover-crop. min() with
           --hero-anchor-cap keeps the search card above the fold on short
           viewports where strict image-anchoring would push it down. */
        .hero {
            --hero-anchor-cap: 30vh;
            width: 100%;
            padding-top: min(
                calc(var(--brand-anchor-y) * max(100vh, var(--bg-height-per-width))
                     - (max(100vh, var(--bg-height-per-width)) - 100vh) / 2),
                var(--hero-anchor-cap)
            );
        }

        /* ═══════════════════════════════════════════════════════════════
           INTRO ANIMATIONS
           ═══════════════════════════════════════════════════════════════ */

        .intro-hidden {
            opacity: 0;
            transform: translateY(20px);
        }

        .intro-visible {
            animation: intro-reveal 1.2s var(--ease-out) forwards;
        }

        @keyframes intro-reveal {
            from {
                opacity: 0;
                transform: translateY(20px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }


        /* ═══════════════════════════════════════════════════════════════
           HEADER / BRAND
           ═══════════════════════════════════════════════════════════════ */

        .brand {
            text-align: center;
            margin-bottom: var(--sp-12);
        }

        .brand-title {
            font-family: var(--font-display);
            font-size: clamp(2.5rem, 8vw, 4.5rem);
            font-weight: 500;
            font-style: italic;
            letter-spacing: -0.02em;
            line-height: 1.1;
            color: var(--parchment);
            text-shadow: 0 2px 20px rgba(var(--rgb-black), 0.5);
            margin-bottom: var(--sp-2);
        }

        .brand-title .accent {
            color: var(--copper);
            font-style: normal;
        }

        .brand-tagline {
            font-family: var(--font-mono);
            font-size: 0.8125rem;
            font-weight: 400;
            color: var(--parchment-dim);
            letter-spacing: 0.1em;
            text-transform: uppercase;
        }

        /* ═══════════════════════════════════════════════════════════════
           SEARCH CARD
           ═══════════════════════════════════════════════════════════════ */

        /* Wraps the floating picker + the card. Anchors the picker and is kept
           free of transform/filter/opacity so it is NOT a backdrop root — that
           lets the picker's backdrop-filter frost the page background like the
           card does. */
        .search-card-wrap {
            position: relative;
            width: 100%;
        }

        .search-card {
            width: 100%;
            background: var(--card-fill);
            backdrop-filter: blur(30px);
            -webkit-backdrop-filter: blur(30px);
            border: var(--card-border);
            border-radius: var(--radius-lg);
            padding: var(--sp-8) var(--sp-8) var(--sp-1) var(--sp-8);
            box-shadow:
                0 4px 24px rgba(var(--rgb-black), 0.4),
                inset 0 1px 0 rgba(var(--rgb-parchment), 0.05);
        }

        /* Mode picker — absolutely positioned in the card's top-right corner with
           equal top/right inset (--sp-4), so its gap to the top edge matches its
           gap to the right edge. Anchored to .search-card-wrap (position:relative).
           Out of flow, so it overlays the corner instead of pushing the subtitle
           down, and reads as part of the card rather than a tacked-on floating tab. */
        .mode-picker {
            position: absolute;
            top: var(--sp-4);
            right: var(--sp-4);
            z-index: 5;
        }

        .mode-picker-btn {
            font-family: var(--font-mono);
            font-size: 0.72rem;
            font-weight: 500;
            letter-spacing: 0.04em;
            color: var(--parchment-dim);
            /* Transparent fill so the card's own surface shows through — the
               button is the exact same colour/material as the card, framed by the
               card's border. (It's nested in the card, which is a backdrop root,
               so it can't carry its own frosted glass; borrowing the surface is
               how it matches. Copying the card's gradient would stack veils and
               come out lighter than the card.) */
            background: transparent;
            border: var(--card-border);
            border-radius: var(--radius-md);
            padding: var(--sp-2) var(--sp-3);
            cursor: pointer;
            display: inline-flex;
            align-items: center;
            gap: var(--sp-2);
            /* border-color intentionally omitted so the open-state copper edge
               snaps in instantly; bg + text still ease. Matches Filters/Sort. */
            transition: color var(--dur-micro) var(--ease-out),
                        background var(--dur-micro) var(--ease-out);
        }

        /* Border-only hover — the button is transparent (borrows the card
           surface), so a bg fill would break that illusion; just strengthen the
           edge. Neutral parchment edge here; copper is reserved for the open
           state. */
        .mode-picker-btn:hover {
            border-color: rgba(var(--rgb-parchment), 0.3);
        }

        .mode-picker-btn:focus-visible {
            outline: none;
            box-shadow: 0 0 0 3px var(--copper-glow);
        }

        /* Open-state feedback — mirrors .filters-trigger.open / .sort-trigger.open
           so all three menu-triggers read the same "highlighted while open" way. */
        .mode-picker.open .mode-picker-btn {
            background: rgba(var(--rgb-parchment), 0.1);
            border-color: rgba(var(--rgb-copper), 0.8);
            color: var(--parchment);
        }

        .mode-picker-btn .label-pre {
            color: var(--parchment-muted);
            text-transform: uppercase;
            letter-spacing: 0.06em;
            font-size: 0.66rem;
        }

        .mode-picker-btn .mode-name {
            color: var(--parchment);
        }

        .mode-picker-btn .chev {
            width: 1.15em;
            height: 1.15em;
            display: inline-block;
            vertical-align: middle;
            position: relative;
            top: 1px;
            color: var(--parchment-muted);
        }

        .mode-menu {
            display: none;
            position: absolute;
            top: 100%;
            right: 0;
            z-index: 40;
            width: 19rem;
            max-width: min(19rem, calc(100vw - 3rem));
            background: var(--ink-card);
            border: 1px solid rgba(var(--rgb-parchment), 0.14);
            border-radius: var(--radius-md);
            box-shadow: 0 8px 30px rgba(var(--rgb-black), 0.5);
            padding: var(--sp-2);
            margin-top: var(--sp-1);
        }

        .mode-picker.open .mode-menu {
            display: block;
        }

        .mode-menu-item {
            display: block;
            width: 100%;
            text-align: left;
            background: none;
            border: none;
            cursor: pointer;
            padding: var(--sp-3);
            border-radius: var(--radius-sm);
            transition: background var(--dur-micro) var(--ease-out);
        }

        .mode-menu-item:hover {
            background: rgba(var(--rgb-parchment), 0.06);
        }

        .mode-menu-item:focus-visible {
            outline: none;
            box-shadow: 0 0 0 3px var(--copper-glow);
        }

        .mode-menu-item.active {
            background: rgba(var(--rgb-copper), 0.16);
        }

        .mode-menu-item .mi-name {
            display: block;
            font-family: var(--font-mono);
            font-size: 0.78rem;
            font-weight: 600;
            letter-spacing: 0.03em;
            color: var(--parchment);
            margin-bottom: 2px;
        }

        .mode-menu-item.active .mi-name {
            color: var(--copper);
        }

        .mode-menu-item .mi-desc {
            display: block;
            font-family: var(--font-display);
            font-size: 0.78rem;
            font-style: italic;
            line-height: 1.4;
            color: var(--parchment-muted);
        }

        .tab-subtitle {
            font-family: var(--font-display);
            font-size: 0.95rem;
            font-style: italic;
            color: var(--parchment-muted);
            /* Pull the subtitle up to align with the corner picker (margin-top;
               eased by 3px so it sits a hair lower than the button). margin-bottom
               sets the gap to the search box below — kept slightly tighter than
               the default here. */
            margin-top: calc(-1 * var(--sp-3) + 3px);
            margin-bottom: var(--sp-5);
            transition: opacity var(--dur-micro) var(--ease-out);
        }

        /* Panels */
        .panel {
            display: none;
        }

        .panel.active {
            display: block;
            animation: panel-in 0.3s var(--ease-out);
        }

        @keyframes panel-in {
            from { opacity: 0; transform: translateY(8px); }
            to { opacity: 1; transform: translateY(0); }
        }

        /* Fields */
        .field {
            margin-bottom: var(--sp-5);
        }

        .field:last-child {
            margin-bottom: 0;
        }

        /* The single-line query box (Standard / Hypothesis) extends --sp-4 past
           the card's content edge on the right, so its right edge lines up with
           the corner mode picker (which sits --sp-4 from the card edge, vs the
           --sp-8 content padding). Reset on mobile, where the picker is in flow. */
        .panel[data-panel="basic"] .field,
        .panel[data-panel="hypothesis"] .field {
            margin-right: calc(-1 * var(--sp-4));
        }

        /* Below the width where the absolutely-positioned picker reliably clears
           the (longest) subtitle, drop it back into flow (right-aligned) so it
           can't overlap — the overlay only has clearance near the card's full
           width (--main-content cap 972px). Placed AFTER the base .tab-subtitle
           and .field rules above so these resets win: @media adds no specificity,
           so with equal selectors source order decides. */
        @media (max-width: 960px) {
            .mode-picker {
                position: relative;
                top: auto;
                right: auto;
                display: flex;
                justify-content: flex-end;
                margin-bottom: var(--sp-3);
            }
            /* Picker is back in flow above the subtitle, so don't pull it up. */
            .tab-subtitle {
                margin-top: 0;
                margin-bottom: var(--sp-4);
            }
            /* Picker sits at the content edge, so the query box must not bleed
               past the card padding. */
            .panel[data-panel="basic"] .field,
            .panel[data-panel="hypothesis"] .field {
                margin-right: 0;
            }
        }

        .label {
            display: block;
            font-family: var(--font-mono);
            font-size: 0.6875rem;
            font-weight: 600;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--parchment-dim);
            margin-bottom: var(--sp-2);
        }

        .input {
            width: 100%;
            font-family: var(--font-mono);
            font-size: 1.05rem;
            font-weight: 400;
            padding: var(--sp-3) var(--sp-4);
            /* Pure black well (not --ink #0a0a0c) — true black reads cleaner
               for the query fields in dark mode. Light mode overrides this in
               index-light.css, so the hardcoded black stays dark-only. */
            background: rgb(var(--rgb-black));
            border: 1.5px solid rgba(var(--rgb-parchment), 0.22);
            border-radius: var(--radius-md);
            color: var(--parchment);
            box-shadow: inset 0 1px 0 rgba(var(--rgb-parchment), 0.08), 0 1px 2px rgba(var(--rgb-black), 0.4);
            transition: all var(--dur-micro) var(--ease-out);
        }

        .input:hover {
            border-color: rgba(var(--rgb-parchment), 0.35);
        }

        .input:focus {
            outline: none;
            border-color: var(--copper);
            box-shadow: 0 0 0 3px var(--copper-glow);
        }

        /* Standard & Hypothesis boxes are textareas that wrap and auto-grow
           downward (search-core.js sets the height on input). resize:none hands
           height control to the auto-grow handler; max-height caps growth at
           ~6 lines, after which the box scrolls internally. The +3px on
           max-height accounts for the 1.5px top+bottom border under the global
           box-sizing:border-box (max-height includes the border). em scales the
           line count to each box's own font-size (.input vs .input-sm). */
        textarea.input {
            line-height: 1.6;
            resize: none;
            overflow-y: hidden;
            max-height: calc(6 * 1.6em + 1.5rem + 3px);
        }

        .input::placeholder {
            color: transparent;
        }

        #population::placeholder,
        #intervention::placeholder,
        #comparator::placeholder,
        #outcome::placeholder {
            font-family: var(--font-display);
            font-style: italic;
            color: var(--parchment-muted);
            opacity: 0.5;
        }

        /* Fake placeholder for slide animation */
        .input-wrapper {
            position: relative;
        }

        .fake-placeholder {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            padding: var(--sp-3) var(--sp-4);
            font-family: var(--font-display);
            font-size: 1.05rem;
            font-style: italic;
            color: var(--parchment-muted);
            opacity: 0.5;
            pointer-events: none;
            overflow: hidden;
            display: flex;
            align-items: center;
            border-radius: var(--radius-md);
        }

        .fake-placeholder.hidden {
            display: none;
        }

        .placeholder-text {
            white-space: nowrap;
            display: block;
            transition: transform 1s var(--ease-out), opacity 1s var(--ease-out);
        }

        .placeholder-text.slide-up-out {
            transform: translateY(-100%);
            opacity: 0;
        }

        .placeholder-text.slide-up-in {
            transform: translateY(30px);
            opacity: 0;
        }

        .input-sm {
            font-family: var(--font-mono);
            font-size: 0.875rem;
            padding: var(--sp-3) var(--sp-4);
        }

        .fake-placeholder-sm {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            padding: var(--sp-3) var(--sp-4);
            font-family: var(--font-display);
            font-size: 0.875rem;
            font-style: italic;
            color: var(--parchment-muted);
            opacity: 0.5;
            pointer-events: none;
            overflow: hidden;
            display: flex;
            align-items: center;
            border-radius: var(--radius-md);
        }

        .fake-placeholder-sm.hidden {
            display: none;
        }

        .checkbox-label {
            display: inline-flex;
            align-items: center;
            gap: var(--sp-2);
            cursor: pointer;
            -webkit-user-select: none;
            user-select: none;
        }

        .checkbox {
            position: absolute;
            opacity: 0;
            width: 0;
            height: 0;
        }

        .checkbox-box {
            width: 16px;
            height: 16px;
            border: 1px solid rgba(var(--rgb-parchment), 0.3);
            border-radius: 3px;
            background: rgba(var(--rgb-ink), 0.6);
            transition: all var(--dur-micro) var(--ease-out);
            position: relative;
        }

        .checkbox:checked + .checkbox-box {
            background: var(--copper);
            border-color: var(--copper);
        }

        .checkbox:checked + .checkbox-box::after {
            content: '';
            position: absolute;
            left: 5px;
            top: 2px;
            width: 4px;
            height: 8px;
            border: solid var(--ink);
            border-width: 0 2px 2px 0;
            transform: rotate(45deg);
        }

        .checkbox:focus + .checkbox-box {
            box-shadow: 0 0 0 3px var(--copper-glow);
        }

        .checkbox-label:hover .checkbox-box {
            border-color: rgba(var(--rgb-parchment), 0.5);
        }

        .checkbox-text {
            font-family: var(--font-mono);
            font-size: 0.75rem;
            font-weight: 600;
            letter-spacing: 0.05em;
            text-transform: uppercase;
            color: var(--parchment-dim);
        }

        .checkbox:checked ~ .checkbox-text {
            color: var(--copper);
        }

        /* Tooltip */
        .has-tooltip {
            position: relative;
            cursor: help;
        }

        .has-tooltip::after {
            content: attr(data-tooltip);
            position: absolute;
            bottom: calc(100% + 8px);
            left: 50%;
            transform: translateX(-50%);
            padding: var(--sp-3) var(--sp-4);
            background: var(--ink-card);
            border: 1px solid rgba(var(--rgb-parchment), 0.15);
            border-radius: var(--radius-sm);
            font-family: var(--font-sans);
            font-size: 0.8125rem;
            font-weight: 400;
            text-transform: none;
            letter-spacing: normal;
            line-height: 1.5;
            color: var(--parchment-dim);
            white-space: normal;
            width: 280px;
            text-align: left;
            opacity: 0;
            visibility: hidden;
            transition: opacity var(--dur-micro) var(--ease-out), visibility var(--dur-micro) var(--ease-out);
            z-index: 100;
            box-shadow: 0 4px 16px rgba(var(--rgb-black), 0.3);
            pointer-events: none;
        }

        .has-tooltip::before {
            content: '';
            position: absolute;
            bottom: calc(100% + 2px);
            left: 50%;
            transform: translateX(-50%);
            border: 6px solid transparent;
            border-top-color: rgba(var(--rgb-parchment), 0.15);
            opacity: 0;
            visibility: hidden;
            transition: opacity var(--dur-micro) var(--ease-out), visibility var(--dur-micro) var(--ease-out);
            z-index: 101;
            pointer-events: none;
        }

        .has-tooltip:hover::after,
        .has-tooltip:hover::before,
        .has-tooltip:focus-visible::after,
        .has-tooltip:focus-visible::before {
            opacity: 1;
            visibility: visible;
        }

        /* Tooltip positioned below (for elements near top of containers) */
        .has-tooltip-bottom::after {
            bottom: auto;
            top: calc(100% + 8px);
        }

        .has-tooltip-bottom::before {
            bottom: auto;
            top: calc(100% + 2px);
            border-top-color: transparent;
            border-bottom-color: rgba(var(--rgb-parchment), 0.15);
        }

        /* Tooltip aligned to right edge (for elements near right side of containers) */
        .has-tooltip-right::after {
            left: auto;
            right: 0;
            transform: none;
        }

        .has-tooltip-right::before {
            left: auto;
            right: 12px;
            transform: none;
        }

        /* PICO Grid */
        .grid-3 {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: var(--sp-4);
        }

        @media (max-width: 640px) {
            .grid-3 { grid-template-columns: 1fr; }
        }

        .grid-4 {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: var(--sp-4);
        }

        .grid-4 .field {
            margin-bottom: 0;
        }

        @media (max-width: 640px) {
            .grid-4 { grid-template-columns: 1fr; }
        }

        /* Footer Row */
        .form-footer {
            display: flex;
            align-items: flex-end;
            gap: var(--sp-4);
            margin-top: var(--sp-5);
            padding-top: var(--sp-6);
            border-top: 1px solid rgba(var(--rgb-parchment), 0.08);
        }

        .form-footer .field {
            flex: 1;
            margin-bottom: 0;
        }

        /* Select */
        .select {
            width: 100%;
            font-family: var(--font-mono);
            font-size: 0.8125rem;
            padding: var(--sp-3) var(--sp-4);
            padding-right: var(--sp-10);
            /* Pure black well to match the .input fields (see note there);
               light mode overrides this in index-light.css. */
            background: rgb(var(--rgb-black));
            border: 1.25px solid rgba(var(--rgb-parchment), 0.22);
            border-radius: var(--radius-md);
            color: var(--parchment);
            cursor: pointer;
            -webkit-appearance: none;
            appearance: none;
            background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 6L8 10L12 6' stroke='%238a857a' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
            background-repeat: no-repeat;
            background-position: right var(--sp-3) center;
            transition: all var(--dur-micro) var(--ease-out);
        }

        .select:hover {
            border-color: rgba(var(--rgb-parchment), 0.35);
        }

        .select:focus {
            outline: none;
            border-color: var(--copper);
            box-shadow: 0 0 0 3px var(--copper-glow);
        }

        /* Button */
        .btn {
            font-family: var(--font-mono);
            font-size: 0.75rem;
            font-weight: 600;
            letter-spacing: 0.1em;
            text-transform: uppercase;
            padding: var(--sp-4) var(--sp-8);
            border: none;
            border-radius: var(--radius-sm);
            cursor: pointer;
            transition: all var(--dur-micro) var(--ease-out);
            white-space: nowrap;
        }

        .btn-primary {
            background: var(--copper);
            color: var(--ink);
        }

        .btn-primary:hover {
            background: var(--copper-bright);
            transform: translateY(-1px);
            box-shadow: 0 4px 16px var(--copper-glow);
        }

        .btn-primary:active {
            transform: translateY(0);
        }

        .btn:focus-visible {
            outline: none;
            box-shadow: 0 0 0 3px var(--copper-glow);
        }

        .btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            transform: none !important;
        }

        .btn.loading { pointer-events: none; }
        .btn-loading { display: none; }
        .btn.loading .btn-text { display: none; }
        .btn.loading .btn-loading {
            display: inline-flex;
            align-items: center;
            gap: var(--sp-2);
        }

        /* Search split button: the primary segment submits immediately while
           the narrow chevron segment opens the one-off action menu. */
        .search-split {
            position: relative;
            display: inline-flex;
            column-gap: 1px;
            background: transparent;
            align-self: flex-end;
            border-radius: var(--radius-md);
            transition: box-shadow var(--dur-micro) var(--ease-out);
        }

        .search-split:hover {
            box-shadow: 0 4px 16px var(--copper-glow);
        }

        .search-split.disabled:hover {
            box-shadow: none;
        }

        .search-split > .btn-primary:hover,
        .search-split > .btn-primary:active {
            transform: none;
            box-shadow: none;
        }

        .search-split-primary {
            border-radius: var(--radius-md) 2px 2px var(--radius-md);
        }

        .search-split-toggle {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            padding-left: var(--sp-3);
            padding-right: var(--sp-3);
            border-left: none;
            border-radius: 2px var(--radius-md) var(--radius-md) 2px;
            letter-spacing: normal;
        }

        .search-split-chevron {
            width: 0.92rem;
            height: 0.92rem;
            display: inline-block;
            vertical-align: middle;
            position: relative;
            top: 1px;
        }

        .search-action-menu {
            display: none;
            position: absolute;
            top: calc(100% + var(--sp-1));
            right: 0;
            z-index: 50;
            width: 22rem;
            max-width: min(22rem, calc(100vw - 3rem));
            padding: var(--sp-2);
            background: var(--ink-card);
            border: 1px solid rgba(var(--rgb-parchment), 0.14);
            border-radius: var(--radius-md);
            box-shadow: 0 8px 30px rgba(var(--rgb-black), 0.5);
        }

        .search-split.open .search-action-menu {
            display: block;
        }

        .search-split.drop-up .search-action-menu {
            top: auto;
            bottom: calc(100% + var(--sp-1));
        }

        .search-action-menu-item {
            display: block;
            width: 100%;
            padding: var(--sp-3);
            background: none;
            border: none;
            border-radius: var(--radius-sm);
            color: var(--parchment);
            cursor: pointer;
            text-align: left;
            transition: background var(--dur-micro) var(--ease-out);
        }

        .search-action-menu-item:hover {
            background: rgba(var(--rgb-parchment), 0.06);
        }

        .search-action-menu-item:focus-visible {
            outline: none;
            box-shadow: 0 0 0 3px var(--copper-glow);
        }

        .search-action-menu-item:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        .search-action-name {
            display: block;
            font-family: var(--font-mono);
            font-size: 0.78rem;
            font-weight: 600;
            letter-spacing: 0.03em;
        }

        .search-action-desc {
            display: block;
            margin-top: 2px;
            font-family: var(--font-display);
            font-size: 0.78rem;
            font-style: italic;
            line-height: 1.4;
            color: var(--parchment-muted);
        }

        .spinner {
            width: 14px;
            height: 14px;
            border: 2px solid rgba(var(--rgb-ink), 0.3);
            border-top-color: var(--ink);
            border-radius: 50%;
            animation: spin 0.6s linear infinite;
        }

        @keyframes spin {
            to { transform: rotate(360deg); }
        }

        /* Progress */
        .progress-wrap {
            margin-top: var(--sp-5);
            opacity: 0;
            transition: opacity 0.3s var(--ease-out);
        }

        .progress-wrap.visible { opacity: 1; }
        .progress-wrap:focus { outline: none; }

        .progress-bar {
            height: 2px;
            background: rgba(var(--rgb-parchment), 0.1);
            border-radius: 1px;
            overflow: hidden;
        }

        .progress-fill {
            height: 100%;
            width: 0%;
            background: linear-gradient(90deg, var(--copper), var(--gold));
            border-radius: 1px;
            transition: width 0.4s var(--ease-out);
        }

        .progress-text {
            font-size: 0.6875rem;
            color: var(--parchment-muted);
            margin-top: var(--sp-2);
            text-align: center;
            letter-spacing: 0.02em;
        }

        /* Error */
        .error {
            display: none;
            margin-top: var(--sp-5);
            /* Cushion the banner off the card's tight (--sp-1) bottom padding;
               only applies when shown, so the resting layout stays compact. */
            margin-bottom: var(--sp-4);
            padding: var(--sp-4);
            background: rgba(var(--rgb-negative), 0.1);
            border: 1px solid rgba(var(--rgb-negative), 0.3);
            border-radius: var(--radius-sm);
            color: var(--negative);
            font-size: 0.8125rem;
        }

        .error.visible { display: block; }

        /* ═══════════════════════════════════════════════════════════════
           RESULTS
           ═══════════════════════════════════════════════════════════════ */

        .results {
            display: none;
            width: 100%;
            margin-top: var(--sp-12);
            /* Leave clearance above the meta band when scrollIntoView lands
               here, so the centered title and Export button aren't flush
               with the viewport top (which reads as cut off). */
            scroll-margin-top: 64px;
        }

        .results.visible {
            display: block;
            animation: results-in 0.6s var(--ease-out);
        }

        @keyframes results-in {
            from { opacity: 0; transform: translateY(30px); }
            to { opacity: 1; transform: translateY(0); }
        }

        /* Single Results Card (Accordion Container) */
        .results-card {
            background: var(--card-fill);
            backdrop-filter: blur(30px);
            -webkit-backdrop-filter: blur(30px);
            border: var(--card-border);
            border-radius: var(--radius-md);
            overflow: hidden;
        }

        /* Section meta strip ABOVE the results card. The title is centered
           via text-align; the Export button is absolutely positioned on the
           right so the centering is unaffected by the button's width. */
        .results-meta {
            position: relative;
            text-align: center;
            padding: 0 var(--sp-5);
            margin-bottom: var(--sp-2);
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .results-title {
            font-size: 0.75rem;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.08em;
            color: var(--parchment-dim);
        }

        .results-count {
            font-size: 0.75rem;
            color: var(--parchment-muted);
            font-weight: 400;
        }

        /* Minimal info glyph that toggles the in-place search-flow panel.
           Replaces the old always-visible "How we found these papers" summary
           so nothing sits above the papers until the user opens it. Turns
           copper while expanded to read as an active toggle. */
        .search-flow-toggle {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            vertical-align: middle;
            width: 18px;
            height: 18px;
            margin-left: 0.4em;
            padding: 0;
            border: none;
            background: none;
            color: var(--parchment-muted);
            cursor: pointer;
            transition: color var(--dur-micro) var(--ease-out);
        }
        .search-flow-toggle svg { width: 16px; height: 16px; }
        .search-flow-toggle:hover { color: var(--parchment); }
        .search-flow-toggle[aria-expanded="true"] { color: var(--copper); }

        /* "Relevance Score" lives in the toolbar, right-aligned. The
           margin-left:auto pushes it (and only it) to the right edge,
           displacing the sort button leftward. */
        .results-score-label {
            font-size: 0.6875rem;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.08em;
            color: var(--copper);
            margin-left: auto;
        }

        /* Export Controls */
        .export-dropdown {
            position: absolute;
            right: var(--sp-5);
            top: 50%;
            transform: translateY(-50%);
            /* transform creates a stacking context; an explicit z-index is
               required to lift the dropdown (and its menu) above the sibling
               .results-card, which establishes its own stacking context via
               backdrop-filter. */
            z-index: 10;
        }

        /* Export trigger sits in the meta band next to the centered title.
           Styled as plain text (no border/background) so it visually matches
           the .results-title and doesn't feel like an orphaned button. */
        .btn-export {
            display: inline-flex;
            align-items: center;
            gap: 6px;
            padding: 0;
            background: transparent;
            border: none;
            color: var(--parchment-dim);
            font-family: var(--font-mono);
            font-size: 0.75rem;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.08em;
            cursor: pointer;
            transition: color var(--dur-micro) var(--ease-out);
        }

        .btn-export:hover { color: var(--copper-bright); }

        .btn-export svg {
            width: 12px;
            height: 12px;
        }

        .export-menu {
            position: absolute;
            top: calc(100% + 4px);
            right: 0;
            min-width: 160px;
            background: var(--ink-card);
            border: 1px solid rgba(var(--rgb-parchment), 0.1);
            border-radius: var(--radius-sm);
            box-shadow: 0 8px 32px rgba(var(--rgb-black), 0.4);
            z-index: 100;
            opacity: 0;
            visibility: hidden;
            transform: translateY(-8px);
            transition: all var(--dur-micro) var(--ease-out);
        }

        .export-menu.visible {
            opacity: 1;
            visibility: visible;
            transform: translateY(0);
        }

        .export-menu-item {
            display: flex;
            align-items: center;
            gap: var(--sp-3);
            padding: var(--sp-3) var(--sp-4);
            color: var(--parchment);
            font-family: var(--font-mono);
            font-size: 0.75rem;
            cursor: pointer;
            transition: all var(--dur-fast) var(--ease-out);
            border: none;
            background: none;
            width: 100%;
            text-align: left;
        }

        .export-menu-item:hover {
            background: rgba(var(--rgb-copper), 0.15);
            color: var(--parchment);
        }

        .export-menu-item:first-child {
            border-radius: var(--radius-sm) var(--radius-sm) 0 0;
        }

        .export-menu-item:last-child {
            border-radius: 0 0 var(--radius-sm) var(--radius-sm);
        }

        .export-menu-item .format-label {
            font-weight: 500;
        }

        .export-menu-item .format-desc {
            font-size: 0.625rem;
            color: var(--parchment-dim);
        }

        /* Toggle for additional results. Two render targets, routed by
           renderMoreResultsToggle based on state:
           - .more-results-toggle (inside the results card): "Hide" button
             when extras are expanded.
           - .more-results-toggle-outside (sibling of .results-card): "Show"
             button when extras are collapsed, sitting just below the card. */
        .more-results-toggle {
            text-align: center;
            padding: 16px 0 32px;
        }
        .more-results-toggle-outside {
            text-align: center;
            margin-top: var(--sp-5);
            padding-bottom: var(--sp-6);
        }
        /* Collapse to zero footprint when the container is empty so the
           inactive render target doesn't leave a phantom gap. */
        .more-results-toggle:empty,
        .more-results-toggle-outside:empty {
            padding: 0;
            margin: 0;
        }
        /* Info icon embedded inside the divider pill and the Show button.
           Uses the .has-tooltip pattern; cursor:help (from .has-tooltip)
           visually differentiates the icon area from the click target.
           Accidental click bubbles to the host button — harmless. */
        .extras-info-icon {
            display: inline-flex;
            align-items: center;
            margin-left: var(--sp-2);
            vertical-align: middle;
            color: currentColor;
        }
        .extras-info-icon svg {
            width: 16px;
            height: 16px;
        }
        .more-results-btn {
            background: transparent;
            border: 1px solid rgba(var(--rgb-parchment), 0.2);
            border-radius: var(--radius-sm);
            padding: var(--sp-3) var(--sp-6);
            cursor: pointer;
            font-family: var(--font-mono);
            font-size: 0.75rem;
            font-weight: 600;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--parchment-dim);
            transition: all var(--dur-micro) var(--ease-out);
        }
        .more-results-btn:hover {
            border-color: var(--copper-bright);
            color: var(--copper-bright);
            transform: translateY(-1px);
        }
        .more-results-btn:active {
            transform: translateY(0);
        }
        .more-results-btn:focus-visible {
            outline: none;
            box-shadow: 0 0 0 3px var(--copper-glow);
        }

        /* "Additional results" divider rendered above the first extra ONLY
           when extras are contiguous at the tail of the visible list (true
           for relevance sort; not true for year-sort or other per-row
           comparators that interleave). When extras are interleaved, the
           always-on .paper-row.extra background lift carries the signal
           instead. Logic lives in renderPaperList. */
        .extras-divider {
            display: flex;
            align-items: center;
            justify-content: center;
            /* Top padding > bottom so the centered label sits lower in the
               row — keeps a visible gap from the result header above. */
            padding: var(--sp-6) var(--sp-5) var(--sp-4);
            min-height: 76px;
        }
        /* Mirrors .more-results-btn's outlined-ghost visual silhouette so
           the section reads as framed by matching pills above and below the
           extras list. Click collapses the extras (same effect as the hide
           button at the bottom of the list). */
        .extras-divider-label {
            display: inline-block;
            background: transparent;
            border: 1px solid rgba(var(--rgb-parchment), 0.2);
            border-radius: var(--radius-sm);
            padding: var(--sp-3) var(--sp-6);
            cursor: pointer;
            font-family: var(--font-mono);
            font-size: 0.75rem;
            font-weight: 600;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--parchment-dim);
            transition: all var(--dur-micro) var(--ease-out);
        }
        .extras-divider-label:hover {
            border-color: var(--copper-bright);
            color: var(--copper-bright);
            transform: translateY(-1px);
        }
        .extras-divider-label:active {
            transform: translateY(0);
        }
        .extras-divider-label:focus-visible {
            outline: none;
            box-shadow: 0 0 0 3px var(--copper-glow);
        }

        /* Extras share the primary row chrome; the "ADDITIONAL RESULTS"
           divider above the first extra carries the boundary signal. The
           score-num "—" is dimmed so it doesn't read as a missing value. */
        .paper-row.extra .score-num {
            color: var(--parchment-dim);
        }

        /* Paper Row (Accordion Item) */
        .paper-row {
            border-bottom: var(--row-divider);
            transition: background var(--dur-micro) var(--ease-out);
        }

        .paper-row:last-child { border-bottom: none; }

        .paper-row:hover .paper-summary {
            background: rgba(var(--rgb-parchment), 0.03);
        }

        .paper-row.expanded .paper-summary {
            background: rgba(var(--rgb-copper), 0.08);
            border-left: 3px solid var(--copper);
        }

        .paper-summary {
            display: flex;
            align-items: flex-start;
            gap: var(--sp-3);
            padding: var(--sp-4) var(--sp-5);
            transition: all var(--dur-micro) var(--ease-out);
            border-left: 3px solid transparent;
            cursor: pointer;
        }

        .paper-rank {
            flex-shrink: 0;
            width: 28px;
            height: 22px;
            display: flex;
            align-items: center;
            justify-content: center;
            align-self: flex-start;
            margin-top: 4px;
            background: rgba(var(--rgb-parchment), 0.1);
            border-radius: var(--radius-sm);
            font-size: 0.6875rem;
            font-weight: 600;
            color: var(--parchment-dim);
        }

        .paper-info {
            flex: 1;
            min-width: 0;
        }

        .paper-title {
            /* TeX Gyre Heros (vs. Spectral) — paper titles are content text,
               but serif italic emphasis in titles reads as decorative against
               the modern UI; the sans keeps emphasis legible. Weight 400 +
               size 0.95rem (a slight lift over the body sans for row hierarchy);
               the font's size-adjust (in fonts.css) matches its apparent size
               to the rest of the sans content text. */
            font-family: var(--font-sans);
            font-size: 0.95rem;
            font-weight: 400;
            line-height: 1.4;
            color: var(--parchment);
            margin-bottom: var(--sp-1);
        }


        .paper-link {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            width: 22px;
            height: 22px;
            color: var(--parchment-dim);
            opacity: 0.85;
            transition: all var(--dur-micro) var(--ease-out);
            border-radius: 4px;
            margin-left: 6px;
            vertical-align: middle;
            position: relative;
            top: -1px;
        }

        .paper-link:hover {
            color: var(--copper);
            opacity: 1;
            background: rgba(var(--rgb-copper), 0.15);
        }

        .paper-link svg {
            width: 15px;
            height: 15px;
        }

        .paper-meta {
            display: flex;
            /* TeX Gyre Heros (vs. the mono inherited from body) — author/year/
               venue/citation metadata reads as content text alongside the sans
               title above it, not as a code-like signal. */
            font-family: var(--font-sans);
            /* Single line: long author lists / long journal names get
               truncated via per-item ellipsis (below) rather than
               wrapping to a second line. */
            flex-wrap: nowrap;
            /* Horizontal gap is 0 because the bullet's symmetric margins
               below carry the spacing. */
            gap: 0;
            font-size: 0.75rem;
            color: var(--parchment-dim);
            overflow: hidden;
            min-width: 0;
        }

        /* Bullet separator between meta items (year • authors • journal
           • citations). Equal margins on both sides put the bullet at
           the exact midpoint between consecutive items. */
        .paper-meta > span:not(:first-child)::before {
            content: "•";
            margin: 0 var(--sp-2);
            opacity: 0.6;
        }

        /* Year and citations are short fixed-length signals — always
           show in full. Authors and venue compete for the remaining
           space and ellipsis-truncate when the line would otherwise
           overflow. Venue shrinks 5× faster than authors because
           authors typically carry more search-relevant signal. */
        .meta-year,
        .meta-citations {
            flex-shrink: 0;
        }
        .meta-authors,
        .meta-venue {
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            min-width: 0;
        }
        .meta-authors { flex-shrink: 1; }
        .meta-venue   { flex-shrink: 5; }

        .study-tag {
            padding: 1px var(--sp-2);
            background: rgba(var(--rgb-parchment), 0.05);
            border-radius: 3px;
            font-size: 0.5625rem;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.03em;
            color: var(--parchment-dim);
        }

        .study-tag.rct {
            background: rgba(125, 156, 109, 0.2);
            color: var(--positive);
        }

        .study-tag.meta-analysis,
        .study-tag.systematic-review {
            background: rgba(106, 143, 184, 0.2);
            color: var(--info);
        }

        .paper-score {
            flex-shrink: 0;
            text-align: right;
            align-self: center;
            min-width: 40px;
        }

        .score-num {
            font-size: 0.75rem;
            font-weight: 600;
            color: var(--copper);
            line-height: 1;
        }

        .score-bar {
            display: none;
        }

        /* Expand icon */
        .expand-icon {
            flex-shrink: 0;
            width: 16px;
            height: 16px;
            color: var(--parchment-muted);
            transition: transform var(--dur-micro) var(--ease-out);
            margin-top: 4px;
        }

        .paper-row.expanded .expand-icon {
            transform: rotate(180deg);
        }

        /* Expanded Details */
        .paper-details {
            display: none;
            --details-indent: calc(var(--sp-5) + 16px + var(--sp-3) + 28px + var(--sp-3));
            padding: 0 var(--details-indent) var(--sp-5) var(--details-indent);
            animation: slideDown 0.2s var(--ease-out);
        }

        .paper-row.expanded .paper-details {
            display: block;
        }

        @keyframes slideDown {
            from { opacity: 0; transform: translateY(-8px); }
            to { opacity: 1; transform: translateY(0); }
        }

        .detail-section {
            margin-bottom: var(--sp-4);
        }

        .detail-section:last-child { margin-bottom: 0; }

        .detail-label {
            font-size: 0.625rem;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.08em;
            color: var(--parchment-dim);
            margin-bottom: var(--sp-2);
        }

        .detail-abstract {
            font-family: var(--font-sans);
            font-size: 0.875rem;
            line-height: 1.6;
            color: var(--parchment);
        }
        /* Italic placeholder ("Abstract cannot be displayed...") rendered
           via <em> when the abstract is publisher-only. Dimmer than the
           real abstract text to signal "this is metadata, not content." */
        .detail-abstract em {
            color: var(--parchment-dim);
        }

        .score-grid {
            display: flex;
            gap: var(--sp-6);
        }

        .score-item {
            text-align: center;
        }

        .score-item-label {
            font-size: 0.5625rem;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.05em;
            color: var(--parchment-dim);
            margin-bottom: var(--sp-1);
        }

        .score-item-value {
            font-size: 0.9375rem;
            font-weight: 500;
            color: var(--parchment);
        }

        .detail-explain {
            font-family: var(--font-display);
            font-size: 0.8125rem;
            font-style: italic;
            color: var(--parchment-dim);
            padding: var(--sp-3);
            background: rgba(var(--rgb-ink), 0.3);
            border-radius: var(--radius-sm);
        }

        .detail-explanation {
            font-family: var(--font-sans);
            font-size: 0.8125rem;
            line-height: 1.5;
            color: var(--parchment-dim);
            margin-top: var(--sp-5);
        }

        /* Explanation section: skeleton/loading/error states */
        .explanation-skeleton {
            display: flex;
            flex-direction: column;
            gap: 6px;
        }
        .explanation-skeleton .bar {
            height: 12px;
            border-radius: 4px;
            background: linear-gradient(
                90deg,
                rgba(255, 255, 255, 0.04) 0%,
                rgba(255, 255, 255, 0.10) 50%,
                rgba(255, 255, 255, 0.04) 100%
            );
            background-size: 200% 100%;
            animation: explanation-shimmer 1.4s ease-in-out infinite;
        }
        .explanation-skeleton .bar:nth-child(1) { width: 100%; }
        .explanation-skeleton .bar:nth-child(2) { width: 95%; }
        .explanation-skeleton .bar:nth-child(3) { width: 60%; }
        @keyframes explanation-shimmer {
            0%   { background-position: 200% 0; }
            100% { background-position: -200% 0; }
        }
        .explanation-slow,
        .explanation-error {
            font-size: 0.9rem;
            color: rgba(255, 255, 255, 0.6);
            font-style: italic;
        }
        /* "Explanation unavailable" reads as substantive content (it tells
           the user why no LLM paragraph exists), so match the regular
           .detail-explanation typography rather than the transient
           slow/error status styling. */
        .explanation-unavailable {
            color: rgba(255, 255, 255, 0.75);
        }
        .explanation-retry {
            margin-left: 8px;
            background: none;
            border: 1px solid currentColor;
            color: inherit;
            padding: 2px 8px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 0.85rem;
        }
        .explanation-retry:hover {
            background: rgba(255, 255, 255, 0.06);
        }

        /* ═══════════════════════════════════════════════════════════════
           ANALYSIS BLOCK + SCORE BREAKDOWN + CRITERIA PANEL
           ═══════════════════════════════════════════════════════════════ */
        .analysis-block {
            position: relative;
            border: 1px solid rgba(201, 196, 181, 0.5);
            border-radius: 4px;
            padding: var(--sp-4);
            margin-top: var(--sp-6);
        }
        .analysis-block-label {
            position: absolute;
            top: 0;
            left: var(--sp-3);
            transform: translateY(-50%);
            margin: 0;
            padding: 0 var(--sp-2);
            background: var(--ink-card);
        }

        /* "How we found these papers" transparency panel */
        .search-flow-panel {
            margin-top: var(--sp-2);
            margin-bottom: var(--sp-4);
        }
        .sf-details {
            border: 1px solid rgba(201, 196, 181, 0.5);
            border-radius: var(--radius-sm);
            padding: var(--sp-3) var(--sp-4);
            background: rgba(var(--rgb-ink-card), 0.4);
        }
        /* Static title shown once the panel is opened via the header info glyph
           (no longer the click target — that moved to .search-flow-toggle). */
        /* Styled like the "Search Results" label, a touch smaller. */
        .sf-heading {
            font-size: 0.7rem;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.08em;
            color: var(--parchment-dim);
            margin-bottom: var(--sp-3);
        }
        .sf-sources {
            font-size: 0.75rem;
            color: var(--parchment-dim);
            margin-top: var(--sp-3);
            border-top: 1px solid rgba(var(--rgb-parchment), 0.06);
            padding-top: var(--sp-2);
            margin-bottom: var(--sp-2);
        }
        /* Horizontal funnel diagram. Blocks share a top edge (top-justified)
           and shrink downward left-to-right, so the eye reads "the top slice
           is kept at each stage." Heights are sqrt-scaled with a min floor so
           the small final/expansion blocks stay legible. */
        .sf-diagram {
            display: flex;
            align-items: flex-start;
            justify-content: center;
            gap: var(--sp-2);
            margin: var(--sp-5) 0 var(--sp-5);
        }
        .sf-stage {
            flex: 0 1 80px;
            min-width: 0;
            display: flex;
            flex-direction: column;
        }
        /* Each label is rendered as two explicit lines (a 2-element array
           joined with <br>) so all four are visually uniform. Fixed 2-line
           height + bottom-align keeps every block's top edge aligned for the
           top-justified funnel. */
        .sf-stage-label {
            font-family: var(--font-mono);
            font-size: 0.58rem;
            letter-spacing: 0.03em;
            text-transform: uppercase;
            color: var(--parchment-dim);
            text-align: center;
            line-height: 1.2;
            height: 2.4em;
            display: flex;
            align-items: flex-end;
            justify-content: center;
            margin-bottom: var(--sp-2);
            /* Wider than the 80px bar and centered over it (overflows into the
               arrow gaps) so 3-4 word labels wrap at two lines instead of three,
               without widening the bars themselves. flex-grow:0 + min-width:0 on
               .sf-stage keeps the column at its 80px basis despite this child. */
            width: 115px;
            align-self: center;
        }
        /* Each stage is ONE outlined block. A citation-expansion segment, when
           present, lives inside that same block and is set off by a single
           divider line — not its own separate box. The border/background/radius
           therefore sit on the wrapper, and overflow:hidden clips the inner
           segments to the rounded corners. */
        .sf-block {
            display: flex;
            flex-direction: column;
            border: 1px solid var(--parchment-muted);
            background: rgba(var(--rgb-parchment), 0.06);
            border-radius: var(--radius-sm);
            overflow: hidden;
        }
        .sf-seg {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            overflow: hidden;
        }
        /* Citation-expansion sub-stream: a lower section of the shared block,
           set off by a dashed divider line. In the only two-section block
           (Retrieved) each section carries a small origin label — "search" vs
           "cited" — so the split is legible without reading the caption. */
        .sf-seg.expansion {
            border-top: 1px dashed var(--parchment-muted);
        }
        .sf-seg-count {
            font-family: var(--font-mono);
            font-size: 0.7rem;
            color: var(--parchment);
            white-space: nowrap;
        }
        .sf-seg-src {
            font-family: var(--font-mono);
            font-size: 0.5rem;
            letter-spacing: 0.04em;
            text-transform: uppercase;
            color: var(--parchment-muted);
            line-height: 1;
            margin-top: 2px;
        }
        /* Flow connector between stages. The hidden label-clone spacer makes the
           glyph start at the same y as the blocks (no hard-coded label height);
           the 26px glyph box (= H_MIN) centers the arrow in the top band every
           block shares, so it tracks the retained-top edge of the funnel. */
        /* Fixed length so the connectors don't grow to fill the row and fling
           the bars apart — the diagram sizes to its content and sits left. */
        .sf-arrow {
            display: flex;
            flex-direction: column;
            align-self: flex-start;
            flex: 0 0 48px;
            /* Hold the fixed width: the hidden spacer reuses .sf-stage-label
               (width:115px), so without this the arrow's min-content size would
               balloon to 115px and fling the bars apart. The spacer just
               overflows invisibly. */
            min-width: 0;
        }
        /* Long connector: a thin line that grows to fill the gap, capped by a
           CSS-triangle arrowhead. */
        .sf-arrow-glyph {
            height: 30px;
            display: flex;
            align-items: center;
        }
        .sf-arrow-glyph::before {
            content: '';
            flex: 1;
            height: 1px;
            background: var(--parchment-muted);
        }
        .sf-arrow-glyph::after {
            content: '';
            width: 0;
            height: 0;
            border-top: 4px solid transparent;
            border-bottom: 4px solid transparent;
            border-left: 6px solid var(--parchment-muted);
        }
        /* Single expandable list of the distinct queries issued. Sits directly
           under the "Databases searched" line, which now carries the section
           separator. */
        /* Divider that previously sat on .sf-sources now lives on the merged
           row so the sources text + queries toggle share one line. */
        .sf-queries {
            margin-top: var(--sp-3);
            border-top: 1px solid rgba(var(--rgb-parchment), 0.06);
            padding-top: var(--sp-2);
        }
        .sf-queries-summary {
            cursor: pointer;
            font-size: 0.75rem;
            color: var(--parchment-dim);
            list-style: none;
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            gap: var(--sp-3);
        }
        /* Right-hand toggle text + chevron; stays on one line. */
        .sf-queries-label {
            display: inline-flex;
            align-items: center;
            gap: 0.35em;
            white-space: nowrap;
        }
        .sf-queries-summary::-webkit-details-marker { display: none; }
        .sf-queries-summary:hover { color: var(--parchment); }
        /* Reuse the results' .expand-icon chevron; rotate it on open. */
        .sf-queries-summary .expand-icon { margin-top: 0; }
        .sf-queries[open] .sf-queries-summary .expand-icon { transform: rotate(180deg); }
        .sf-queries-list {
            display: flex;
            flex-direction: column;
            gap: var(--sp-2);
            margin-top: var(--sp-2);
        }
        .sf-queries-list code {
            font-family: var(--font-mono);
            font-size: 0.7rem;
            color: var(--parchment-dim);
            white-space: pre-wrap;
            word-break: break-word;
            /* padding-left (not text-indent) so wrapped lines indent too. */
            padding-left: var(--sp-3);
        }
        .criteria-panel {
            padding: var(--sp-1) 0 var(--sp-3) 0;
        }
        .criteria-panel > .detail-label {
            color: var(--parchment-muted);
        }

        .criteria-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: var(--sp-4);
            padding: 3px 0;
            border-top: 1px solid rgba(var(--rgb-parchment), 0.04);
        }
        .criteria-row:first-child { border-top: none; padding-top: 0; }
        .criteria-row:last-child { padding-bottom: 0; }
        .criteria-q-text {
            /* TeX Gyre Heros (not mono) — these are LLM-generated content text,
               not UI chrome. Matches the .rubric-q-text styling used in the
               filter popover. */
            font-family: var(--font-sans);
            font-size: 0.75rem;
            line-height: 1.4;
            color: var(--parchment-dim);
            flex: 1;
            min-width: 0;
            /* Flex layout for the bullet so the dash sits at the column
               edge (not hanging in the gutter) and wrapped text aligns
               with line-1 text within the anonymous flex item. */
            display: flex;
            gap: 0.5em;
        }
        .criteria-q-text::before {
            content: "–";
            flex-shrink: 0;
            color: var(--parchment-muted);
        }
        /* Read-only sibling of .rubric-chip — a transparent outline chip
           (--copper-bright border + text), not a filled one; no hover/cursor
           (this is a display chip, not a filter control). */
        .criteria-chip {
            display: inline-flex;
            align-items: center;
            padding: 3px 8px;
            background: transparent;
            border: 1px solid var(--copper-bright);
            border-radius: var(--radius-sm);
            color: var(--copper-bright);
            font-family: var(--font-mono);
            font-size: 0.625rem;
            white-space: nowrap;
            flex-shrink: 0;
        }
        /* insufficient_info: fade the chips and append a small ⚠ next to
           the "Topical Criteria" label, mirroring the .score-bar-cell.is-faded
           convention. State cascades from the paper-row. */
        .paper-row.has-insufficient-info .criteria-chip { opacity: 0.32; }
        .paper-row.has-insufficient-info .criteria-panel > .detail-label::after {
            content: " ⚠";
            color: rgba(244, 217, 153, 0.75);
        }

        /* Score breakdown — three horizontal bars laid out side-by-side
           across the full expanded-panel width. Each cell stacks: label +
           value on top, bar below. Median tick is a thin vertical line
           overlaid on the bar at the median's position. Median is computed
           once over the loaded result set (frozen; not filter-sensitive). */
        .score-breakdown {
            display: flex;
            align-items: flex-start;
            gap: var(--sp-5);
        }
        .score-bar-cell {
            flex: 1 1 0;
            min-width: 0;
            display: flex;
            flex-direction: column;
            gap: var(--sp-1);
        }
        .score-bar-label {
            font-size: 0.6875rem;
            color: var(--parchment-muted);
            /* Fixed pixel line-height so all three labels keep the same
               box height regardless of whether they carry the 16px ⚠
               glyph — without this, the inflated glyph would push the
               methodology-fit track down vs. the other two bars. */
            line-height: 18px;
        }
        /* Discrete-cell variant: track is a flex row of N small blocks;
           filled blocks (copper) indicate the score, unfilled blocks
           (faint parchment) the remainder. Median tick still overlays
           absolutely positioned on top. */
        .score-bar-track {
            position: relative;
            width: 100%;
            display: flex;
            gap: 2px;
        }
        .score-bar-cell-block {
            flex: 1;
            min-width: 0;
            height: 6px;
            background: rgba(var(--rgb-parchment), 0.14);
            border-radius: 1px;
        }
        .score-bar-cell-block.is-filled {
            background: var(--copper);
            opacity: 0.88;
        }
        .score-bar-tick {
            position: absolute;
            top: -3px; bottom: -3px;
            width: 2px;
            background: var(--parchment);
            opacity: 0.55;
            transform: translateX(-1px);
            pointer-events: none;
        }
        /* applied_insufficient_penalty / insufficient_info: fade the fill.
           The ⚠ glyph is rendered as a real <span class="score-bar-warning">
           inside the label (not a ::after pseudo) so it can carry its own
           tooltip with the paper-specific reason for the fade. */
        .score-bar-cell.is-faded .score-bar-cell-block.is-filled { opacity: 0.32; }
        .score-bar-warning {
            font-size: 1rem;
            color: #facc15;
            vertical-align: -1px;
        }

        /* Score-bar tooltips: 0.5s hover delay before appearing (asymmetric
           — no delay on hide, so the tooltip disappears immediately when
           the mouse leaves). Matches the dwell behavior users expect from
           informational hover tips. */
        .score-bar-cell.has-tooltip:hover::after,
        .score-bar-cell.has-tooltip:hover::before {
            transition-delay: 0.5s;
        }
        /* Suppress the cell-level tooltip when the user is specifically
           hovering the ⚠ glyph inside the label. Without this, the cell
           tooltip would fire alongside the glyph's own tooltip (CSS
           :hover is true for ancestors too). */
        .score-bar-cell.has-tooltip:has(.score-bar-warning:hover)::after,
        .score-bar-cell.has-tooltip:has(.score-bar-warning:hover)::before {
            opacity: 0;
            visibility: hidden;
            transition-delay: 0s;
        }
        /* Score-bar tooltips: hold off the help-cursor (the question-mark
           icon) until the same 0.5s dwell completes. CSS cursor is a
           discrete property, so a 0s-duration animation with a 0.5s delay
           and forwards fill-mode is enough to flip cursor from default to
           help at exactly 0.5s of hover. Cursor reverts to default the
           moment :hover goes away (no JS, no timer to clear). */
        @keyframes score-bar-cursor-help {
            to { cursor: help; }
        }
        .score-bar-cell.has-tooltip {
            cursor: default;
        }
        .score-bar-cell.has-tooltip:hover {
            animation: score-bar-cursor-help 0s 0.5s forwards;
        }

        /* Retraction banner above the bars — does not modify the bars. */
        .score-retracted-banner {
            display: flex;
            align-items: center;
            gap: var(--sp-2);
            padding: var(--sp-2) var(--sp-3);
            margin-bottom: var(--sp-2);
            background: rgba(224, 122, 95, 0.12);
            border-left: 2px solid #e07a5f;
            color: #e07a5f;
            font-size: 0.6875rem;
            border-radius: 2px;
        }

        .empty-results {
            padding: var(--sp-8);
            text-align: center;
            color: var(--parchment-muted);
            font-size: 0.8125rem;
        }
        .empty-results .clear-link {
            color: var(--copper);
            cursor: pointer;
            text-decoration: underline;
            text-decoration-color: rgba(var(--rgb-copper), 0.35);
            text-underline-offset: 2px;
            margin-left: var(--sp-1);
        }
        .empty-results .clear-link:hover { color: var(--copper-bright); }

        /* ═══════════════════════════════════════════════════════════════
           RESPONSIVE
           ═══════════════════════════════════════════════════════════════ */

        @media (max-width: 640px) {
            .main-content {
                padding: var(--sp-6) var(--sp-4);
            }

            /* On narrow screens, lower the cap so the search card stays
               above the fold even when --brand-anchor-y would push it
               further down. */
            .hero {
                --hero-anchor-cap: 18vh;
            }

            .search-card {
                padding: var(--sp-5);
            }

            .brand-title {
                font-size: 2.5rem;
            }

            .form-footer {
                flex-direction: column;
            }

            .btn {
                width: 100%;
                justify-content: center;
            }

            .search-split {
                width: 100%;
            }

            .search-split .search-split-primary {
                flex: 1;
                width: auto;
            }

            .search-split .search-split-toggle {
                flex: none;
                width: auto;
            }

            .search-action-menu {
                width: 100%;
                max-width: 100%;
            }

            .results-meta {
                /* On narrow screens, drop absolute positioning so Export
                   wraps below the centered title rather than overlapping it. */
                display: block;
                text-align: center;
                padding: 0 var(--sp-3);
            }
            .results-meta .export-dropdown {
                position: static;
                transform: none;
                margin-top: var(--sp-2);
                display: inline-block;
            }

            .paper-summary {
                flex-wrap: wrap;
                padding: var(--sp-3) var(--sp-4);
            }

            .paper-info {
                order: 2;
                width: 100%;
                margin-top: var(--sp-2);
            }

            .paper-score {
                order: 1;
                display: flex;
                align-items: center;
                gap: var(--sp-2);
                margin-left: auto;
            }

            .score-bar {
                display: none;
            }

            .expand-icon {
                display: none;
            }

            .paper-details {
                padding: 0 var(--sp-4) var(--sp-5);
            }

            .score-breakdown {
                flex-wrap: wrap;
            }

            .score-grid {
                flex-wrap: wrap;
                gap: var(--sp-4);
            }

            .score-item {
                min-width: 60px;
            }
        }

        /* ═══════════════════════════════════════════════════════════════
           CONSTRAINTS TOGGLE
           ═══════════════════════════════════════════════════════════════ */

        /* Reduce margin on panels before constraints toggle */
        .panel {
            margin-bottom: var(--sp-3);
        }

        /* Label row with inline toggle */
        .label-row-flex {
            display: flex;
            align-items: baseline;
            justify-content: space-between;
            margin-bottom: var(--sp-2);
        }

        .label-row-flex .checkbox-label {
            align-items: center;
        }

        .label-row-flex .checkbox-box {
            position: relative;
            top: 1px;
        }

        .label-row-flex .label {
            margin-bottom: 0;
        }

        [data-panel="hypothesis"] .label-row-flex {
            margin-top: -1px;
        }

        /* Remove extra margin from hypothesis fields */
        #hypothesisFreeform,
        .pico-fields {
            margin-bottom: 0;
        }

        /* Filters occupy the left side in every mode. In structured modes,
           Additional Directions occupies the right side. */
        .search-options-layout {
            display: grid;
            grid-template-columns: minmax(0, 1fr);
        }

        .search-control-row {
            display: grid;
            grid-template-columns: minmax(0, 1fr) auto;
            grid-column: 1;
            grid-row: 1;
            align-items: center;
            column-gap: var(--sp-4);
            /* The active panel contributes --sp-3; this additional --sp-1 makes
               the input-to-controls gap a consistent 16px in every mode. */
            margin-top: var(--sp-1);
            padding-left: var(--sp-4);
        }

        .search-filters-trigger {
            grid-column: 1;
            grid-row: 1;
            justify-self: start;
        }

        .advanced-toggle {
            display: flex;
            align-items: center;
            gap: var(--sp-2);
            margin-left: auto;
            cursor: pointer;
            font-family: var(--font-mono);
            font-size: 0.6875rem;
            font-weight: 600;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--parchment-dim);
            transition: color var(--dur-micro) var(--ease-out);
        }

        .advanced-toggle:hover {
            color: var(--parchment);
        }

        .advanced-toggle input[type="checkbox"] {
            -webkit-appearance: none;
            appearance: none;
            width: 15px;
            height: 15px;
            background: rgba(var(--rgb-ink), 0.6);
            border: 1.5px solid rgba(var(--rgb-parchment), 0.22);
            border-radius: 3px;
            cursor: pointer;
            position: relative;
            transition: all var(--dur-micro) var(--ease-out);
        }

        .advanced-toggle input[type="checkbox"]:checked {
            background: rgba(var(--rgb-ink), 0.8);
            border-color: rgba(var(--rgb-parchment), 0.25);
        }

        /* Hover (over label or box) brightens AND thickens the box border
           (1.5px → 2px), mirroring the portal input's hover. Placed after
           :checked so the highlight persists while hovering a CHECKED box too;
           the bright cream is brighter than the checked border, so it only
           strengthens. Outer size stays 15px under border-box — no jitter. */
        .advanced-toggle:hover input[type="checkbox"] {
            border-width: 2px;
            border-color: rgba(var(--rgb-parchment), 0.35);
        }

        .advanced-toggle input[type="checkbox"]:focus-visible {
            outline: none;
            box-shadow: 0 0 0 3px var(--copper-glow);
        }

        .advanced-toggle input[type="checkbox"]:checked::after {
            content: '';
            position: absolute;
            left: 4px;
            top: 2px;
            width: 4px;
            height: 7px;
            border: solid var(--parchment);
            border-width: 0 1.5px 1.5px 0;
            transform: rotate(45deg);
        }

        .study-type-label {
            display: inline-flex;
            align-items: center;
            gap: 0.5em;
            font-family: var(--font-mono);
            font-size: 0.6875rem;
            font-weight: 600;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--parchment-dim);
        }

        .study-type-label::before {
            content: "–";
            flex-shrink: 0;
            color: var(--parchment-muted);
        }

        .study-type-pills {
            display: flex;
            gap: var(--sp-1);
        }

        .study-type-pill {
            font-family: var(--font-mono);
            font-size: 0.6875rem;
            font-weight: 500;
            letter-spacing: 0.05em;
            padding: var(--sp-1) var(--sp-3);
            background: transparent;
            border: 1px solid transparent;
            border-radius: var(--radius-sm);
            color: var(--parchment-muted);
            cursor: pointer;
            transition: all var(--dur-micro) var(--ease-out);
        }

        .study-type-pill:hover {
            color: var(--parchment-dim);
        }

        .study-type-pill:focus-visible {
            outline: none;
            box-shadow: 0 0 0 3px var(--copper-glow);
        }

        .study-type-pill.active {
            background: rgba(var(--rgb-parchment), 0.1);
            border-color: rgba(var(--rgb-copper), 0.8);
            color: var(--parchment);
        }

        .constraints-expand {
            overflow: hidden;
            max-height: 0;
            opacity: 0;
            transition: all var(--dur-expand) var(--ease-out);
        }

        .constraints-expand.visible {
            max-height: 120px;
            opacity: 1;
            margin-top: var(--sp-4);
        }

        /* Mode-specific Additional Search Directions control. Standard mode
           hides the label and its expansion while leaving Filters in place. */
        .additional-directions {
            grid-column: 2;
            grid-row: 1;
            justify-self: end;
            margin-left: 0;
            min-width: 0;
        }
        .additional-directions[hidden],
        .search-options-layout > .constraints-expand[hidden] {
            display: none;
        }
        /* Keep the moved checkbox compact. */
        .additional-directions input[type="checkbox"] {
            width: 13px;
            height: 13px;
        }
        .additional-directions input[type="checkbox"]:checked::after {
            left: 3px;
            top: 1px;
        }
        .search-options-layout > .constraints-expand {
            grid-column: 1;
            grid-row: 3;
            padding-left: calc(var(--sp-4) + var(--sp-3));
        }
        .search-options-layout > .constraints-expand.visible {
            margin-top: var(--sp-3);
        }

        /* ─── Filters disclosure (search card) ───────────────────────────
           The "Filters" trigger reuses the results-page .chip-caret look, but
           drives an inline accordion panel rather than a popover. Distinct IDs
           (#searchFiltersTrigger / #filtersExpand) avoid colliding with the
           results toolbar. */
        .filters-expand {
            overflow: hidden;
            grid-column: 1;
            grid-row: 2;
            max-height: 0;
            opacity: 0;
            /* Collapsed: keep the study-type pills / year inputs out of the tab
               order and accessibility tree. visibility hides on a 0s step
               delayed to the end of the fade so the close still animates. */
            visibility: hidden;
            transition: max-height var(--dur-expand) var(--ease-out),
                        opacity var(--dur-expand) var(--ease-out),
                        margin-top var(--dur-expand) var(--ease-out),
                        visibility 0s var(--dur-expand);
            /* Trigger row inset (--sp-4) plus an extra step so the pills nest
               visibly under the "Filters" label rather than aligning flush
               with it. */
            padding-left: calc(var(--sp-4) + var(--sp-3));
            /* Bottom inset so the last row's focus ring (the .input:focus
               copper-glow on the Custom year fields, 3px) isn't clipped by this
               panel's overflow:hidden. Folded into max-height since box-sizing is
               border-box. */
            padding-bottom: var(--sp-2);
        }
        .filters-expand.visible {
            max-height: 240px;
            opacity: 1;
            visibility: visible;
            margin-top: var(--sp-4);
            /* Open target: reveal immediately (no visibility delay) so the
               contents fade in with the panel. */
            transition: max-height var(--dur-expand) var(--ease-out),
                        opacity var(--dur-expand) var(--ease-out),
                        margin-top var(--dur-expand) var(--ease-out),
                        visibility 0s;
        }
        /* Inner row holding the study-type label + pills inside the panel.
           Mirrors the spacing the study-type-row used to provide inline. */
        .study-type-row-inner {
            display: flex;
            align-items: center;
            gap: var(--sp-4);
        }
        /* ─── Publication-year filter row ───────────────────────────────────
           Reuses .study-type-label/.study-type-pill (so pill look + the
           light-theme .active override apply unchanged) and .input.input-sm
           for the Custom year fields. */
        .year-row {
            margin-top: var(--sp-3);
            /* Reserve the Custom-input height even while the inputs are hidden,
               so clicking "Custom" reveals them without changing the row height
               and re-centering the label/pills. MUST equal .year-input's
               height below (any mismatch reintroduces the toggle shift). */
            min-height: 1.9rem;
        }
        /* Let both filter rows' pills wrap instead of clipping on narrow
           screens (no-op on desktop, where they fit on one line). */
        .study-type-pills,
        .study-type-row-inner {
            flex-wrap: wrap;
        }
        .year-custom-inputs {
            display: inline-flex;
            align-items: center;
            gap: var(--sp-2);
        }
        .year-custom-inputs[hidden] {
            /* Higher specificity than the rule above, so the `hidden` attribute
               (toggled by JS) reliably collapses the inputs. */
            display: none;
        }
        .year-input {
            width: 4.5rem;
            text-align: center;
            /* Explicit height (matched exactly by .year-row's min-height) so the
               field renders at a deterministic size independent of the font's
               "normal" line-height — that way revealing it never changes the row
               height. Sized a hair larger than the preset pills so the bordered
               inputs read at the same visual weight as the filled active pill.
               Vertical padding is 0 because height now drives the box; the text
               auto-centers. */
            height: 1.9rem;
            font-size: 0.75rem;
            padding: 0 var(--sp-3);
            border-width: 1px;
        }
        /* The shared .input::placeholder is transparent by design; re-enable a
           visible hint for the From/To fields (mirrors the per-field placeholder
           overrides further up the file). */
        .year-input::placeholder {
            color: var(--parchment-muted);
            /* Fainter than the typed value so From/To read as hints. Explicit
               (not the browser default) so it's consistent across engines. */
            opacity: 0.7;
        }
        /* Clear the hint as soon as either field becomes active. The placeholder
           returns automatically if the user leaves an empty field. */
        .year-input:focus::placeholder {
            color: transparent;
            opacity: 0;
        }
        .year-input-sep {
            color: var(--parchment-dim);
            font-family: var(--font-mono);
        }
        /* Narrow screens: stack each filter row's label above its (wrapping)
           pills, let the Custom inputs wrap, and raise the panel ceiling so the
           taller layout isn't clipped by .filters-expand's overflow:hidden. */
        @media (max-width: 640px) {
            .study-type-row-inner {
                flex-direction: column;
                align-items: flex-start;
                gap: var(--sp-2);
            }
            .year-custom-inputs {
                flex-wrap: wrap;
            }
            .filters-expand.visible {
                max-height: 520px;
            }
        }
        /* Borderless caret-disclosure trigger for Filters. Reuses the quiet
           mono-uppercase micro-label styling, with a caret that flips when the
           panel is open. */
        .disclosure-toggle {
            display: inline-flex;
            align-items: center;
            gap: var(--sp-2);
            background: none;
            border: none;
            padding: 0;
            cursor: pointer;
            font-family: var(--font-mono);
            font-size: 0.6875rem;
            font-weight: 600;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--parchment-dim);
            transition: color var(--dur-micro) var(--ease-out);
        }
        .disclosure-toggle:hover,
        .disclosure-toggle.open {
            color: var(--parchment);
        }
        .disclosure-toggle:focus-visible {
            outline: none;
            box-shadow: 0 0 0 3px var(--copper-glow);
            border-radius: var(--radius-sm);
        }
        .disclosure-toggle .chip-caret {
            transition: transform var(--dur-micro) var(--ease-out);
        }
        .disclosure-toggle.open .chip-caret {
            transform: rotate(180deg);
        }
        /* ═══════════════════════════════════════════════════════════════
           RATE LIMIT INDICATOR
           ═══════════════════════════════════════════════════════════════ */

        .rate-limit-indicator {
            font-family: var(--font-mono);
            font-size: 0.6875rem;
            color: var(--parchment-muted);
            text-align: center;
            margin-top: var(--sp-1);
            padding: var(--sp-1) var(--sp-3);
            display: none;
        }

        .rate-limit-indicator.visible {
            display: block;
        }

        .rate-limit-indicator.warning {
            color: var(--gold);
        }

        .rate-limit-indicator.exhausted {
            color: var(--negative);
        }

        .rate-limit-indicator.admin {
            color: var(--positive);
        }

        /* ═══════════════════════════════════════════════════════════════
           PROFILE INFO EXPANDABLE
           ═══════════════════════════════════════════════════════════════ */

        .profile-info-btn {
            display: inline-flex;
            align-items: center;
            gap: var(--sp-2);
            background: none;
            border: none;
            font-family: var(--font-mono);
            font-size: 0.6875rem;
            font-weight: 500;
            color: var(--parchment-muted);
            cursor: pointer;
            padding: 0;
            margin-top: var(--sp-3);
            margin-left: var(--sp-4);
            transition: color var(--dur-micro) var(--ease-out);
        }

        .profile-info-btn:hover {
            color: var(--parchment-dim);
        }

        .profile-info-btn:focus-visible {
            outline: none;
            box-shadow: 0 0 0 3px var(--copper-glow);
            border-radius: var(--radius-sm);
        }

        .profile-info-btn svg {
            width: 16px;
            height: 16px;
        }

        .profile-expand {
            overflow: hidden;
            max-height: 0;
            opacity: 0;
            /* Collapsed: also drop the profile-card radios from the tab order
               and the accessibility tree (max-height/opacity alone don't).
               transition:all keeps visibility visible through the fade-out. */
            visibility: hidden;
            transition: all var(--dur-expand) var(--ease-out);
        }

        .profile-expand.visible {
            max-height: 500px;
            opacity: 1;
            visibility: visible;
            margin-top: var(--sp-5);
        }

        .profile-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: var(--sp-4);
        }

        @media (max-width: 700px) {
            .profile-grid {
                grid-template-columns: 1fr;
            }
        }

        .profile-card {
            /* Column flex (not block) so content top-aligns: a <button> UA-
               centers its content vertically, which shows in the shorter cards
               once the grid stretches all three to equal height. */
            display: flex;
            flex-direction: column;
            width: 100%;
            padding: var(--sp-4);
            background: rgba(var(--rgb-ink), 0.5);
            border-radius: var(--radius-md);
            border: 1px solid rgba(var(--rgb-parchment), 0.06);
            font-family: inherit;
            color: inherit;
            text-align: left;
            cursor: pointer;
            -webkit-appearance: none;
            appearance: none;
            transition: all var(--dur-micro) var(--ease-out);
        }

        .profile-card:hover {
            border-color: rgba(var(--rgb-parchment), 0.12);
            background: rgba(var(--rgb-ink), 0.7);
        }

        /* Selected profile — driven by aria-checked so the picker and the
           #profile dropdown share one source of truth. Distinct from the quiet
           hover so the active tier reads clearly. */
        .profile-card[aria-checked="true"] {
            border-color: rgba(var(--rgb-copper), 0.8);
            background: rgba(var(--rgb-ink), 0.7);
        }

        .profile-card:focus-visible {
            outline: none;
            box-shadow: 0 0 0 3px var(--copper-glow);
        }

        .profile-name {
            font-size: 0.8125rem;
            font-weight: 600;
            color: var(--parchment);
            margin-bottom: var(--sp-1);
            display: flex;
            align-items: center;
            gap: var(--sp-2);
        }

        .profile-desc {
            display: block;
            font-family: var(--font-sans);
            font-size: 0.75rem;
            color: var(--parchment-dim);
            line-height: 1.5;
        }

        /* ═════════════════════════════════════════════════════════════════
           Filter / sort toolbar
           Adds an in-results search box, single Filters popover (Paper type
           segmented control + Year preset chips/dual slider + Score range
           dual slider with histogram), Sort menu, and active-filter chip row.
           ═════════════════════════════════════════════════════════════════ */

        /* ─── Control bar (search + single Filters button + sort) ───── */
        .control-bar {
            display: flex;
            align-items: center;
            gap: var(--sp-3);
            flex-wrap: wrap;
            padding: var(--sp-3) var(--sp-5);
            border-bottom: var(--row-divider);
        }

        .search-input-wrap {
            position: relative;
            flex: 1 1 200px;
            min-width: 180px;
            max-width: 280px;
        }
        .search-input-wrap svg {
            position: absolute;
            left: 8px;
            top: 50%;
            transform: translateY(-50%);
            width: 12px;
            height: 12px;
            color: var(--parchment-muted);
            pointer-events: none;
        }
        .search-input {
            width: 100%;
            padding: 6px 10px 6px 26px;
            background: rgba(var(--rgb-parchment), 0.06);
            border: 1px solid rgba(var(--rgb-parchment), 0.12);
            border-radius: var(--radius-sm);
            color: var(--parchment);
            font-family: var(--font-mono);
            font-size: 0.75rem;
            transition: border-color var(--dur-micro) var(--ease-out);
        }
        /* Hover → subtle brightened well + border, matching the Filters /
           Sort triggers' hover so all three control-bar controls respond the
           same. Placed before the focus/has-value rules below so their
           brighter blue border still wins when the box is also focused or
           filtered. */
        .search-input:hover {
            background: rgba(var(--rgb-parchment), 0.1);
            border-color: rgba(var(--rgb-parchment), 0.2);
        }
        /* Focused (transient) or has a value (persistent) → bright blue
           border, matching the tab-style active state used by the
           Filters / Sort triggers and the filter-menu buttons. */
        .search-input:focus,
        .search-input-wrap.has-value .search-input {
            outline: none;
            border-color: rgba(var(--rgb-copper), 0.8);
        }
        /* Focus adds a transient 2px glow ring (--copper-glow resolves per
           theme — blue in both dark and light), echoing the portal's query
           field. Focus only — the persistent has-value state keeps just the
           brightened border so a filtered-but-unfocused box stays calm. */
        .search-input:focus {
            box-shadow: 0 0 0 2px var(--copper-glow);
        }
        .search-input::placeholder { color: var(--parchment-muted); }
        .search-clear {
            position: absolute;
            right: 4px;
            top: 50%;
            transform: translateY(-50%);
            width: 18px;
            height: 18px;
            display: none;
            align-items: center;
            justify-content: center;
            background: transparent;
            border: none;
            border-radius: var(--radius-sm);
            color: var(--parchment-muted);
            cursor: pointer;
            padding: 0;
            font-size: 0.75rem;
            line-height: 1;
        }
        .search-clear:hover {
            color: var(--parchment);
            background: rgba(var(--rgb-parchment), 0.08);
        }
        .search-input-wrap.has-value .search-clear { display: flex; }
        .search-input-wrap.has-value .search-input { padding-right: 26px; }
        .paper-list mark {
            background: rgba(128, 153, 212, 0.28);
            color: inherit;
            padding: 0 1px;
            border-radius: 2px;
        }

        /* The single Filters trigger button. */
        .filters-trigger {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            padding: 6px 12px;
            background: rgba(var(--rgb-parchment), 0.05);
            border: 1px solid rgba(var(--rgb-parchment), 0.12);
            border-radius: var(--radius-sm);
            color: var(--parchment-dim);
            font-family: var(--font-mono);
            font-size: 0.75rem;
            cursor: pointer;
            /* color + background ease; border-color omitted so the copper edge
               snaps instantly (was `all`, which animated the border too). */
            transition: color var(--dur-micro) var(--ease-out),
                        background var(--dur-micro) var(--ease-out);
        }
        .filters-trigger:hover {
            background: rgba(var(--rgb-parchment), 0.1);
            color: var(--parchment);
            border-color: rgba(var(--rgb-parchment), 0.2);
        }
        /* Tab-style active — same look as the paper-type / year buttons
           inside the popover, so a "highlighted trigger" reads the same
           regardless of which control bar it lives in. */
        .filters-trigger.active {
            background: rgba(var(--rgb-parchment), 0.1);
            border-color: rgba(var(--rgb-copper), 0.8);
            color: var(--parchment);
        }
        .filters-trigger svg {
            width: 12px; height: 12px;
        }
        .filters-trigger-badge {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            min-width: 16px;
            height: 16px;
            padding: 0 5px;
            background: var(--copper);
            color: var(--ink);
            border-radius: 999px;
            font-size: 0.5625rem;
            font-weight: 700;
        }
        /* Sized to match the mode-picker chevron (its .chev is 1em ≈ 0.72rem) so
           all three menu-trigger carets read the same; opacity keeps them subtle. */
        .chip-caret { opacity: 0.6; width: 0.85rem; height: 0.85rem; display: inline-block; vertical-align: middle; position: relative; top: 1px; }

        .sort-control {
            display: inline-flex;
            align-items: center;
            gap: 6px;
            font-size: 0.75rem;
            color: var(--parchment-muted);
        }
        .sort-trigger {
            display: inline-flex;
            align-items: center;
            gap: 6px;
            background: rgba(var(--rgb-parchment), 0.06);
            border: 1px solid rgba(var(--rgb-parchment), 0.12);
            border-radius: var(--radius-sm);
            color: var(--parchment-dim);
            font-family: var(--font-mono);
            font-size: 0.75rem;
            padding: 5px 10px;
            cursor: pointer;
            /* color + background ease; border-color omitted so the copper edge
               snaps instantly (was `all`, which animated the border too). */
            transition: color var(--dur-micro) var(--ease-out),
                        background var(--dur-micro) var(--ease-out);
        }
        .sort-icon {
            width: 14px;
            height: 14px;
            opacity: 0.85;
        }
        .sort-trigger:hover {
            color: var(--parchment);
            border-color: rgba(var(--rgb-parchment), 0.2);
            background: rgba(var(--rgb-parchment), 0.1);
        }
        /* Dim the result list (but not the toolbar or active-filter
           chips) while a Filters or Sort popover is open, so the popover
           is comfortably readable. Toggle is body.popover-open, set
           by openPopoverAt/closePopover. */
        body.popover-open #paperList {
            opacity: 0.2;
            pointer-events: none;
        }

        /* Visual feedback while a popover is open against this trigger —
           matches the tab-style active so .active and .open read as the
           same "highlighted" state. */
        .filters-trigger.open,
        .sort-trigger.open {
            background: rgba(var(--rgb-parchment), 0.1);
            border-color: rgba(var(--rgb-copper), 0.8);
            color: var(--parchment);
        }

        /* Menu-style popover (for Sort) — vertical list of button-style
           items mirroring the .year-preset buttons in the filter popover.
           display:flex is set only on the .open variant; otherwise it ties
           with .popover.open's display:block on specificity and the latter
           wins by source order. */
        .popover.popover-menu {
            min-width: 0;
            max-width: none;
            padding: 6px;
            flex-direction: column;
            align-items: flex-start;
            gap: 8px;
        }
        .popover.popover-menu.open { display: flex; }
        .menu-item {
            padding: 6px 10px;
            background: rgba(var(--rgb-parchment), 0.05);
            border: 1px solid rgba(var(--rgb-parchment), 0.1);
            border-radius: var(--radius-sm);
            color: var(--parchment-dim);
            font-family: var(--font-mono);
            font-size: 0.6875rem;
            text-align: left;
            cursor: pointer;
            transition: all var(--dur-micro) var(--ease-out);
        }
        .menu-item:hover {
            background: rgba(var(--rgb-parchment), 0.1);
            color: var(--parchment);
            border-color: rgba(var(--rgb-parchment), 0.2);
        }
        /* Active state matches the filter menu's paper-type / year
           buttons — parchment-tint background, bright blue edge,
           parchment text. */
        .menu-item.active {
            background: rgba(var(--rgb-parchment), 0.1);
            border-color: rgba(var(--rgb-copper), 0.8);
            color: var(--parchment);
        }
        .menu-item .menu-item-check { display: none; }

        /* Active filters row (chips below the toolbar). */
        .active-filters {
            display: none;
            flex-wrap: wrap;
            gap: 6px;
            padding: var(--sp-3) var(--sp-5) 0;
            align-items: center;
        }
        .active-filters.visible {
            display: flex;
            /* Faint separator beneath the chips, present ONLY when chips exist
               (this row is display:none until results-filters.js adds .visible),
               to set the active filters apart from the first result below. The
               padding-bottom overrides the base row's 0 bottom padding to give
               the line breathing room; weight matches the control-bar's bottom
               divider. Light mode recolors it to a dark hairline (index-light.css). */
            padding-bottom: var(--sp-3);
            border-bottom: 1px solid rgba(var(--rgb-parchment), 0.06);
        }
        .active-chip {
            display: inline-flex;
            align-items: center;
            gap: 6px;
            padding: 3px 8px 3px 10px;
            background: rgba(var(--rgb-copper), 0.18);
            /* 0.8 (vs. the original 0.4) — joins the system-wide
               bright-blue-edge "active" treatment used by triggers and
               filter-menu buttons. The blue-tinted background + pill
               shape + copper-bright text still signal "applied filter
               label" rather than "interactive button". */
            border: 1px solid rgba(var(--rgb-copper), 0.8);
            border-radius: 999px;
            font-size: 0.625rem;
            color: var(--copper-bright);
        }
        .active-chip-clear {
            cursor: pointer;
            opacity: 0.7;
            padding: 0 2px;
            border-radius: 3px;
        }
        .active-chip-clear:hover {
            opacity: 1;
            color: var(--negative);
            background: rgba(var(--rgb-negative), 0.15);
        }
        .clear-all-link {
            margin-left: var(--sp-2);
            font-size: 0.625rem;
            color: var(--parchment-muted);
            cursor: pointer;
            text-decoration: underline;
            text-decoration-color: rgba(var(--rgb-parchment), 0.2);
            text-underline-offset: 2px;
        }
        .clear-all-link:hover { color: var(--parchment); }

        /* ─── Popover (single, with all widgets stacked) ────────────── */
        .popover {
            position: absolute;
            display: none;
            z-index: 100;
            /* Widened (from 320–340) to fit the rubric chip row even when
               multiple chips carry two-digit counts. */
            min-width: 470px;
            max-width: 490px;
            /* Bound height so a long topical rubric doesn't push the
               popover off-screen — 96px reservation covers the trigger
               row + safe margins. */
            max-height: calc(100vh - 96px);
            overflow-y: auto;
            background: rgba(20, 20, 24, 0.97);
            backdrop-filter: blur(20px);
            -webkit-backdrop-filter: blur(20px);
            border: 1px solid rgba(var(--rgb-parchment), 0.12);
            border-radius: var(--radius-md);
            padding: var(--sp-4);
            box-shadow: 0 12px 40px rgba(var(--rgb-black), 0.55);
            animation: popoverIn 0.12s var(--ease-out);
        }
        .popover.open { display: block; }
        @keyframes popoverIn {
            from { opacity: 0; transform: translateY(-4px); }
            to { opacity: 1; transform: translateY(0); }
        }
        .popover-section {
            padding: var(--sp-3) 0;
            border-bottom: 1px solid rgba(var(--rgb-parchment), 0.18);
        }
        .popover-section:first-child { padding-top: 0; }
        .popover-section:last-child { border-bottom: none; padding-bottom: 0; }

        .filter-widget-label {
            font-size: 0.5625rem;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.08em;
            color: var(--parchment-dim);
            margin-bottom: var(--sp-2);
            display: flex;
            justify-content: space-between;
            align-items: baseline;
        }
        .filter-widget-value {
            font-weight: 500;
            letter-spacing: 0;
            color: var(--copper-bright);
            font-size: 0.6875rem;
            text-transform: none;
        }

        /* Segmented control — 3-way Paper-type filter (All/Primary/Reviews). */
        .segmented-control {
            display: inline-flex;
            background: rgba(var(--rgb-parchment), 0.05);
            border: 1px solid rgba(var(--rgb-parchment), 0.1);
            border-radius: var(--radius-sm);
            padding: 2px;
        }
        .segmented-control button {
            /* 1px transparent border at base so the active blue edge
               appears without shifting layout (same trick the main-portal
               .tab uses). Natural casing (no uppercase + no letter-
               spacing). Font-size larger than the topical-rubric chips
               below so the primary filters read with stronger hierarchy. */
            padding: 4px 14px;
            background: transparent;
            border: 1px solid transparent;
            color: var(--parchment-dim);
            font-family: var(--font-mono);
            font-size: 0.6875rem;
            cursor: pointer;
            transition: all var(--dur-micro) var(--ease-out);
            border-radius: 3px;
        }
        .segmented-control button:hover {
            color: var(--parchment);
            background: rgba(var(--rgb-parchment), 0.05);
        }
        /* Active state — adapted from the main portal's .tab.active with
           the border opacity bumped to 0.8 (vs. the portal's 0.5) because
           the smaller filter buttons need more emphasis. */
        .segmented-control button.active {
            background: rgba(var(--rgb-parchment), 0.1);
            border-color: rgba(var(--rgb-copper), 0.8);
            color: var(--parchment);
        }

        .year-presets { display: flex; gap: 4px; flex-wrap: wrap; }
        .year-preset {
            padding: 5px 11px;
            background: rgba(var(--rgb-parchment), 0.05);
            border: 1px solid rgba(var(--rgb-parchment), 0.1);
            border-radius: var(--radius-sm);
            color: var(--parchment-dim);
            font-family: var(--font-mono);
            font-size: 0.6875rem;
            cursor: pointer;
            transition: all var(--dur-micro) var(--ease-out);
        }
        .year-preset:hover {
            background: rgba(var(--rgb-parchment), 0.1);
            color: var(--parchment);
        }
        /* Active state — see .segmented-control button.active for rationale. */
        .year-preset.active {
            background: rgba(var(--rgb-parchment), 0.1);
            border-color: rgba(var(--rgb-copper), 0.8);
            color: var(--parchment);
        }
        .year-custom { margin-top: 10px; }

        /* Dual-handle range slider — two stacked native range inputs.
           Track + fill are decorative layers behind them. The inputs have
           transparent tracks; only their thumbs receive pointer events. */
        .dual-slider {
            position: relative;
            height: 24px;
        }
        .dual-slider-track {
            position: absolute;
            top: 50%;
            left: 0;
            right: 0;
            height: 4px;
            background: rgba(var(--rgb-parchment), 0.1);
            border-radius: 2px;
            transform: translateY(-50%);
            pointer-events: none;
        }
        .dual-slider-fill {
            position: absolute;
            top: 50%;
            /* 3px (vs. the 4px track) + slight brightness drop on the
               copper fill — quiets the selected-range indicator without
               making it feel anemic. */
            height: 3px;
            background: rgba(var(--rgb-copper), 0.85);
            border-radius: 2px;
            transform: translateY(-50%);
            pointer-events: none;
        }
        .dual-slider input[type="range"] {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 24px;
            margin: 0;
            background: transparent;
            -webkit-appearance: none;
            appearance: none;
            pointer-events: none;
        }
        .dual-slider input[type="range"]::-webkit-slider-runnable-track {
            background: transparent;
            height: 4px;
            border: none;
        }
        .dual-slider input[type="range"]::-moz-range-track {
            background: transparent;
            height: 4px;
            border: none;
        }
        /* Slider thumbs match the tab-style active language: bright blue
           0.8 edge, smaller (12px vs. 14px) and quieter so the parchment
           fill doesn't dominate against the now-translucent blue fill. */
        .dual-slider input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: var(--parchment);
            border: 2px solid rgba(var(--rgb-copper), 0.8);
            cursor: pointer;
            pointer-events: auto;
            box-shadow: 0 1px 3px rgba(var(--rgb-black), 0.4);
            margin-top: -4px;
            transition: transform var(--dur-fast), background var(--dur-fast);
        }
        .dual-slider input[type="range"]::-moz-range-thumb {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: var(--parchment);
            border: 2px solid rgba(var(--rgb-copper), 0.8);
            cursor: pointer;
            pointer-events: auto;
            box-shadow: 0 1px 3px rgba(var(--rgb-black), 0.4);
            transition: transform var(--dur-fast), background var(--dur-fast);
        }
        .dual-slider input[type="range"]:hover::-webkit-slider-thumb,
        .dual-slider input[type="range"]:focus::-webkit-slider-thumb { transform: scale(1.15); }
        .dual-slider input[type="range"]:hover::-moz-range-thumb,
        .dual-slider input[type="range"]:focus::-moz-range-thumb { transform: scale(1.15); }
        .dual-slider input[type="range"]:focus { outline: none; }
        /* When handles overlap, lift the actively-grabbed one above the other. */
        .dual-slider input.on-top { z-index: 2; }

        .dual-slider-values {
            display: flex;
            justify-content: space-between;
            align-items: center;
            font-family: var(--font-mono);
            font-size: 0.6875rem;
            color: var(--copper-bright);
            margin-top: 6px;
        }
        .dual-slider-empty {
            font-size: 0.6875rem;
            color: var(--parchment-muted);
            font-style: italic;
            padding: 4px 0;
        }

        /* Score slider stack: histogram on top, slider below. */
        .score-slider-wrap {
            display: flex;
            flex-direction: column;
            gap: 4px;
        }
        .histogram {
            display: flex;
            align-items: flex-end;
            height: 28px;
            gap: 1px;
            /* Native range thumb extends ~7px past each end of the visible
               track. Padding the histogram by that amount aligns bin
               midpoints with slider values. */
            padding: 0 7px;
        }
        .histogram-bar {
            flex: 1;
            min-height: 1px;
            background: rgba(var(--rgb-parchment), 0.15);
            border-radius: 1px 1px 0 0;
            transition: background var(--dur-micro) var(--ease-out), opacity var(--dur-micro) var(--ease-out);
        }
        /* Matches the .dual-slider-fill — same hue + opacity so the
           "selected score range" reads as one continuous blue across
           the histogram and the slider track. */
        .histogram-bar.kept {
            background: rgba(var(--rgb-copper), 0.85);
        }

        /* ─── Topical rubric filters (per-question multi-select chips) ─
           One row per question; chips are independent toggles that filter
           papers by their answer to that question (5-level Likert from
           hybrid/rubric/models.py::AnswerLevel). Counts are computed once
           at dataset load and stay static. */
        .rubric-q {
            position: relative;
            display: flex;
            flex-direction: column;
            gap: 6px;
            /* Asymmetric: less top padding than bottom because the
               question text below has ~3px of invisible line-height
               leading above its glyphs, while the chip row above ends
               crisply at the button border. Keeps the inner divider
               optically centered between chips and text. */
            padding: 9px 0 12px 0;
        }
        /* Fainter than the section dividers' 18% and inset 12px from each
           end, so the line reads as a subordinate break rather than a
           full section divider. */
        .rubric-q::before {
            content: "";
            position: absolute;
            left: 12px;
            right: 12px;
            top: 0;
            height: 1px;
            background: rgba(var(--rgb-parchment), 0.1);
        }
        .rubric-q:first-child { padding-top: 2px; }
        .rubric-q:first-child::before { display: none; }
        .rubric-q-text {
            /* TeX Gyre Heros (vs. the default mono) — these are content text
               (LLM-generated questions about papers), not UI chrome. */
            font-family: var(--font-sans);
            font-size: 0.75rem;
            color: var(--parchment-dim);
            line-height: 1.4;
        }
        .rubric-chips {
            display: flex;
            gap: 4px;
            flex-wrap: wrap;
        }
        /* Visual base mirrors .year-preset so the rubric chips feel
           native to the popover. Active = copper fill + parchment text
           (matching the active color of the paper-type / year buttons),
           so the brighter text reinforces the selected state. */
        .rubric-chip {
            display: inline-flex;
            align-items: center;
            gap: 4px;
            padding: 3px 8px;
            background: rgba(var(--rgb-parchment), 0.05);
            border: 1px solid rgba(var(--rgb-parchment), 0.1);
            border-radius: var(--radius-sm);
            color: var(--parchment-dim);
            font-family: var(--font-mono);
            font-size: 0.625rem;
            cursor: pointer;
            transition: all var(--dur-micro) var(--ease-out);
        }
        .rubric-chip:hover {
            background: rgba(var(--rgb-parchment), 0.1);
            color: var(--parchment);
        }
        .rubric-chip.active {
            background: var(--copper);
            border-color: var(--copper);
            color: var(--parchment);
        }
        .rubric-chip-count {
            opacity: 0.7;
            font-weight: 500;
        }
        .rubric-chip.active .rubric-chip-count { opacity: 0.85; }

        .custom-criteria-input-row {
            display: flex;
            gap: 0.5em;
            margin-top: 0.5em;
        }
        #customCriterionInput {
            flex: 1;
            padding: 0.4em 0.6em;
            font: inherit;
            font-size: 0.85em;
            background: rgba(var(--rgb-black), 0.2);
            color: inherit;
            border: 1px solid rgba(255,255,255,0.18);
            border-radius: 4px;
        }
        #customCriterionInput:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }
        .btn-evaluate-custom {
            padding: 0.4em 1.4em;
            min-width: 5em;
            font-family: var(--font-mono);
            font-size: 0.75rem;
            font-weight: 600;
            letter-spacing: 0.1em;
            text-transform: uppercase;
            background: var(--copper);
            color: var(--ink);
            border: none;
            border-radius: var(--radius-sm);
            cursor: pointer;
            transition: all var(--dur-micro) var(--ease-out);
        }
        .btn-evaluate-custom:hover:not(:disabled) {
            background: var(--copper-bright);
            box-shadow: 0 4px 16px var(--copper-glow);
        }
        .btn-evaluate-custom:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }
        .btn-evaluate-custom.loading {
            cursor: default;
            opacity: 1;
        }
        .btn-evaluate-custom.loading .btn-text { display: none; }
        .btn-evaluate-custom.loading .btn-loading {
            display: inline-flex;
            align-items: center;
            justify-content: center;
        }
        .custom-criteria-message {
            margin-top: 0.4em;
            font-size: 0.85em;
            opacity: 0.85;
        }
        .custom-criteria-message.error { color: #ff9090; }
        .custom-rubric-row {
            position: relative;
        }
        /* Reserve a gutter on the right so a long criterion can't run
           under the absolute-positioned remove button. Flex layout
           puts the bullet in its own column so wrapped lines align
           with the first line of text (hanging indent). */
        .custom-rubric-row .rubric-q-text {
            display: flex;
            align-items: baseline;
            gap: 0.3em;
            padding-right: 1.8em;
        }
        .custom-marker {
            color: var(--copper-bright);
            font-weight: 700;
            cursor: help;
            flex-shrink: 0;
        }
        .custom-rubric-remove {
            position: absolute;
            right: 0;
            top: 0;
            background: transparent;
            color: inherit;
            border: 0;
            cursor: pointer;
            font-size: 1.1em;
            opacity: 0.6;
            padding: 0 0.4em;
        }
        .custom-rubric-remove:hover { opacity: 1; }

        /* Filtered count goes copper to signal active filtering. */
        .results-count.filtered { color: var(--copper-bright); }

        @media (max-width: 640px) {
            .control-bar { gap: 6px; }
            .sort-control { margin-left: 0; width: 100%; margin-top: 4px; }
        }

        /* ═══════════════════════════════════════════════════════════════
           RUBRIC EDITOR
           ═══════════════════════════════════════════════════════════════ */

        .rubric-card {
            position: relative;
            width: 100%;
            max-width: 972px;
            margin: var(--sp-8) auto 0;
            background: var(--card-fill);
            backdrop-filter: blur(30px);
            -webkit-backdrop-filter: blur(30px);
            border: var(--card-border);
            border-radius: var(--radius-lg);
            /* Vertical padding tightened (sp-6 vs sp-8 horizontal) to lift
               the header closer to the top edge while keeping horizontal
               breathing room. */
            padding: var(--sp-6) var(--sp-8);
            box-shadow: 0 4px 24px rgba(var(--rgb-black), 0.4),
                        inset 0 1px 0 rgba(var(--rgb-parchment), 0.05);
            display: none;
        }
        .rubric-card.visible {
            display: block;
        }

        .rubric-card-header {
            display: flex;
            align-items: center;
            gap: var(--sp-3);
            margin-bottom: var(--sp-2);
        }
        .rubric-card-title {
            /* Mirrors .results-title — mono, all caps, dim. The card itself
               provides the visual hierarchy; the title doesn't need to shout. */
            font-family: var(--font-mono);
            font-size: 0.6875rem;
            font-weight: 600;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--parchment-dim);
            margin: 0;
        }
        /* The has-tooltip pattern sets cursor: help on the host element;
           override on the seg-control so the inner buttons keep their
           pointer cursor without flickering. */
        .seg-control.has-tooltip { cursor: default; }
        /* Delay the seg-control tooltip by 1s on hover so a casual mouseover
           doesn't trigger it; clear the delay on un-hover so dismissal is
           instant. */
        .seg-control.has-tooltip:hover::after,
        .seg-control.has-tooltip:hover::before {
            transition-delay: 1s;
        }
        .rubric-intro {
            /* Mirrors .tab-subtitle — display italic for an explanatory
               line under a title-like header. */
            font-family: var(--font-display);
            font-style: italic;
            font-size: 0.875rem;
            line-height: 1.5;
            color: var(--parchment-muted);
            margin-bottom: var(--sp-6);
        }

        .rubric-list {
            display: flex;
            flex-direction: column;
            gap: var(--sp-3);
            margin-bottom: var(--sp-4);
        }

        .rubric-row {
            display: flex;
            align-items: flex-start;
            gap: var(--sp-3);
            padding: var(--sp-4);
            background: rgba(var(--rgb-parchment), 0.03);
            border: 1px solid rgba(var(--rgb-parchment), 0.06);
            border-radius: var(--radius-md);
            transition: background var(--dur-micro) var(--ease-out), border-color var(--dur-micro) var(--ease-out);
        }
        .rubric-row:hover {
            background: rgba(var(--rgb-parchment), 0.05);
            border-color: rgba(var(--rgb-parchment), 0.1);
        }

        .rubric-body {
            flex: 1;
            min-width: 0;
        }
        .rubric-text {
            /* Matches .paper-title — TeX Gyre Heros sans for content text in lists. */
            font-family: var(--font-sans);
            font-size: 0.8125rem;
            line-height: 1.4;
            color: var(--parchment);
            cursor: text;
            padding: 2px 4px;
            border-radius: var(--radius-sm);
            transition: background var(--dur-micro) var(--ease-out);
        }
        .rubric-text:hover {
            background: rgba(var(--rgb-parchment), 0.04);
        }
        .rubric-text.empty {
            color: var(--parchment-muted);
            font-style: italic;
        }

        .rubric-text-edit {
            display: none;
            flex-direction: column;
            gap: var(--sp-2);
        }
        .rubric-row.editing .rubric-text { display: none; }
        .rubric-row.editing .rubric-text-edit { display: flex; }
        /* When the row is in edit mode the body grows tall (textarea +
           Cancel/Done row), and centering the criticality + delete in the
           whole tall body would float them between the textarea and the
           buttons. Align them to the top so they sit on the same line as
           the textarea. */
        .rubric-row.editing .seg-control,
        .rubric-row.editing .rubric-delete {
            align-self: flex-start;
        }

        .rubric-textarea {
            width: 100%;
            font-family: var(--font-sans);
            font-size: 0.8125rem;
            line-height: 1.4;
            color: var(--parchment);
            background: rgba(var(--rgb-ink), 0.6);
            border: 1px solid rgba(var(--rgb-parchment), 0.15);
            border-radius: var(--radius-sm);
            padding: var(--sp-2) var(--sp-3);
            resize: none;
            outline: none;
            overflow: hidden;
        }
        .rubric-textarea:focus {
            border-color: var(--copper);
        }

        .rubric-edit-actions {
            display: flex;
            align-items: center;
            gap: var(--sp-2);
            justify-content: flex-end;
        }
        .rubric-char-count {
            margin-right: auto;
            font-family: var(--font-mono);
            font-size: 0.65rem;
            color: var(--parchment-muted);
        }
        .rubric-char-count.near-limit { color: var(--parchment-dim); }
        .rubric-char-count.at-limit { color: var(--negative); }

        /* Three-way criticality control — mirrors the Paper Type segmented
           control (parchment-tint container, copper-blue active border).
           1px transparent border on each button keeps the active border
           from shifting layout. */
        .seg-control {
            display: inline-flex;
            background: rgba(var(--rgb-parchment), 0.05);
            border: 1px solid rgba(var(--rgb-parchment), 0.1);
            border-radius: var(--radius-sm);
            padding: 2px;
            flex-shrink: 0;
            align-self: center;
        }
        .seg-control button {
            font-family: var(--font-mono);
            font-size: 0.6875rem;
            letter-spacing: 0.06em;
            text-transform: uppercase;
            padding: 4px 14px;
            background: transparent;
            border: 1px solid transparent;
            border-radius: 3px;
            color: var(--parchment-dim);
            cursor: pointer;
            transition: all var(--dur-micro) var(--ease-out);
        }
        .seg-control button:hover {
            color: var(--parchment);
            background: rgba(var(--rgb-parchment), 0.05);
        }
        .seg-control button.active {
            background: rgba(var(--rgb-parchment), 0.1);
            border-color: var(--copper-bright);
            color: var(--parchment);
        }

        .rubric-delete {
            background: transparent;
            color: var(--parchment-muted);
            border: 0;
            cursor: pointer;
            font-size: 1.1rem;
            line-height: 1;
            padding: 0 6px;
            opacity: 0.6;
            transition: opacity var(--dur-micro) var(--ease-out), color var(--dur-micro) var(--ease-out);
            flex-shrink: 0;
            align-self: center;
        }
        .rubric-delete:hover {
            opacity: 1;
            color: var(--negative);
        }

        .rubric-footer-row {
            display: flex;
            align-items: center;
            gap: var(--sp-3);
            flex-wrap: wrap;
        }
        /* Push the primary Run button to the far right; editor actions
           (Add, Reset) stay grouped on the left. */
        .rubric-footer-row .rubric-run-btn {
            margin-left: auto;
        }
        .rubric-footer-row .btn-secondary,
        .rubric-edit-actions .btn-secondary {
            font-family: var(--font-mono);
            font-size: 0.7rem;
            font-weight: 500;
            letter-spacing: 0.06em;
            text-transform: uppercase;
            padding: var(--sp-2) var(--sp-3);
            background: transparent;
            color: var(--parchment-dim);
            border: 1px solid rgba(var(--rgb-parchment), 0.15);
            border-radius: var(--radius-sm);
            cursor: pointer;
            transition: background var(--dur-micro) var(--ease-out),
                        color var(--dur-micro) var(--ease-out),
                        border-color var(--dur-micro) var(--ease-out);
        }
        .rubric-footer-row .btn-secondary:hover:not(:disabled),
        .rubric-edit-actions .btn-secondary:hover:not(:disabled) {
            background: rgba(var(--rgb-parchment), 0.05);
            color: var(--parchment);
            border-color: var(--copper-bright);
        }
        .rubric-footer-row .btn-secondary:disabled,
        .rubric-edit-actions .btn-secondary:disabled {
            opacity: 0.4;
            cursor: not-allowed;
        }
        /* Cancel/Done are slightly smaller than the footer buttons since
           they sit inside an editing row that should feel tight. */
        .rubric-edit-actions .btn-secondary {
            font-size: 0.625rem;
            padding: 3px 8px;
        }
        .rubric-count-hint {
            font-family: var(--font-mono);
            font-size: 0.65rem;
            color: var(--parchment-muted);
        }
        .rubric-count-hint.at-limit { color: var(--negative); }

        /* Read-only mode (post-search lock state) */
        .rubric-card.readonly .rubric-list,
        .rubric-card.readonly .rubric-footer-row,
        .rubric-card.readonly .rubric-intro {
            display: none;
        }
        .rubric-card:not(.readonly) .rubric-readonly-list {
            display: none;
        }
        .rubric-readonly-list {
            display: flex;
            flex-direction: column;
            gap: var(--sp-3);
            margin-top: var(--sp-2);
        }
        .rubric-readonly-item {
            display: flex;
            align-items: baseline;
            gap: var(--sp-4);
            color: var(--parchment);
            font-family: var(--font-sans);
            font-size: 0.8125rem;
            line-height: 1.4;
        }
        .rubric-readonly-text { flex: 1; }
        /* Plain-text label that forms a tidy left column; min-width keeps
           the criterion text aligned regardless of which priority label
           is in use. */
        .rubric-readonly-crit {
            font-family: var(--font-mono);
            font-size: 0.65rem;
            font-weight: 600;
            letter-spacing: 0.06em;
            text-transform: uppercase;
            color: var(--parchment-muted);
            min-width: 80px;
            text-align: right;
            flex-shrink: 0;
        }

        .rubric-close-btn {
            /* Pinned to the card's top-right corner (just inside the
               border radius), independent of the header padding. */
            position: absolute;
            top: var(--sp-3);
            right: var(--sp-3);
            background: transparent;
            color: var(--parchment-muted);
            border: 0;
            cursor: pointer;
            font-size: 1.3rem;
            line-height: 1;
            padding: 2px 6px;
            opacity: 0.6;
            transition: opacity var(--dur-micro) var(--ease-out), color var(--dur-micro) var(--ease-out);
        }
        .rubric-close-btn:hover {
            opacity: 1;
            color: var(--parchment);
        }

        /* Inline error — mirrors the site .error style. Hidden by default;
           shown when /api/rubric-preview fails. */
        .rubric-error {
            display: none;
            margin-top: var(--sp-2);
            padding: var(--sp-4);
            background: rgba(var(--rgb-negative), 0.1);
            border: 1px solid rgba(var(--rgb-negative), 0.3);
            border-radius: var(--radius-sm);
            color: var(--negative);
            font-size: 0.8125rem;
        }
        .rubric-error.visible { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
        .rubric-error-message { flex: 1; }
        .rubric-error .btn-secondary {
            font-family: var(--font-mono);
            font-size: 0.7rem;
            font-weight: 500;
            letter-spacing: 0.06em;
            text-transform: uppercase;
            padding: var(--sp-2) var(--sp-3);
            background: transparent;
            color: var(--negative);
            border: 1px solid rgba(var(--rgb-negative), 0.3);
            border-radius: var(--radius-sm);
            cursor: pointer;
            flex-shrink: 0;
            transition: background var(--dur-micro) var(--ease-out),
                        border-color var(--dur-micro) var(--ease-out);
        }
        .rubric-error .btn-secondary:hover {
            background: rgba(var(--rgb-negative), 0.15);
            border-color: var(--negative);
        }

        /* Loading skeleton */
        .rubric-loading {
            color: var(--parchment-muted);
            font-family: var(--font-mono);
            font-size: 0.8rem;
            text-align: center;
            padding: var(--sp-4);
        }
        .rubric-skeleton {
            display: flex;
            flex-direction: column;
            gap: var(--sp-3);
        }
        .rubric-skeleton-row {
            height: 56px;
            background: linear-gradient(
                90deg,
                rgba(var(--rgb-parchment), 0.03) 0%,
                rgba(var(--rgb-parchment), 0.07) 50%,
                rgba(var(--rgb-parchment), 0.03) 100%
            );
            background-size: 200% 100%;
            border-radius: var(--radius-md);
            animation: skeleton-shimmer 1.6s infinite linear;
        }
        @keyframes skeleton-shimmer {
            0% { background-position: 200% 0; }
            100% { background-position: -200% 0; }
        }

        /* Screen-reader-only utility: visually hidden, still announced. */
        .visually-hidden {
            position: absolute;
            width: 1px;
            height: 1px;
            padding: 0;
            margin: -1px;
            overflow: hidden;
            clip: rect(0 0 0 0);
            white-space: nowrap;
            border: 0;
        }
