/* Holotype UI — layout.css
   The book frame, the two-page spread, panes, card mounting,
   section bookmarks, responsive breakpoints, and the titlebar.
   Part 2/7 of the former styles.css; files are linked
   from index.html in order, so the cascade is unchanged. */

        /* ---- The book ----
           The whole interface is one open field-notebook resting on
           the camp stump: leather cover edges, a parchment spread
           inside, a real cast shadow. Fixed size — it fills most of
           the viewport, the camp scene framing it. */
        .page {
            position: relative;
            z-index: 1;
            width: min(1500px, 95vw);
            height: 95vh;
            margin: 2.5vh auto 0;
            display: flex;
            flex-direction: column;
            /* The leather cover: one uniform oxblood grain drawn as a
               single `cover` image — no tiling, so there are no repeat
               seams, and the grain is even enough that the slight
               `cover` scaling never reads as blur. A light dark wash
               sits over it for text contrast. The decorative
               bookbinding stitch is drawn separately in `.page::before`
               so it can never drift behind the titlebar text. */
            background-color: #1f0f10;
            /* The oxblood leather, under a dark wash for text contrast, with a
               soft warm sheen raked in from the upper-left so the cover catches
               the same lamplight as the rest of the book (see body::after). */
            background-image:
                /* A restrained warm glint (not a wash) raked from upper-left. */
                radial-gradient(105% 78% at 26% 12%,
                    rgba(255, 228, 190, 0.07), transparent 46%),
                linear-gradient(rgba(0, 0, 0, 0.36), rgba(0, 0, 0, 0.36)),
                url("/static/assets/leather.png");
            background-repeat: no-repeat;
            background-position: center;
            background-size: cover;
            border-radius: 7px;
            /* Heavy antique book-board, not a flat panel: a gentle inner bevel
               (lamplit top/left, shade falling away bottom/right) domes the
               cover; a lit top chamfer over a stacked dark bottom edge reads as
               the board's thickness; then the heavy cast shadow sits it on the
               camp stump. Bevels kept soft + low so the lit-top/shaded-side
               bands blend at the rounded corners instead of notching. */
            box-shadow:
                inset 0 3px 5px rgba(255, 230, 192, 0.10),
                inset 5px 0 7px rgba(255, 230, 192, 0.045),
                inset 0 -12px 24px rgba(0, 0, 0, 0.50),
                inset -10px 0 24px rgba(0, 0, 0, 0.42),
                /* the board's EDGE THICKNESS — a stacked dark bottom edge
                   (~10px of cover depth). NB: no hard top edge-line or 2px rim
                   here on purpose — those drew a thin black hairline along the
                   top where it met the rounded side corners. */
                0 3px 0 rgba(36, 15, 15, 0.95),
                0 6px 0 rgba(20, 9, 9, 0.95),
                0 10px 1px rgba(6, 2, 3, 0.90),
                /* the heavy book's cast shadow on the camp stump */
                0 42px 86px rgba(0, 0, 0, 0.82),
                0 14px 30px rgba(0, 0, 0, 0.62);
            overflow: hidden;
        }
        /* Gilt tooled border — a fine brass rule stamped just inside the
           cover edge, the way an oxblood-bound field book is finished.
           Drawn in CSS (not baked into the leather) so it stays sharp at
           every window size and never crosses the titlebar text. The
           box-shadow rings read as the line impressed into the leather
           (dark groove inside) with a faint gilt catch-light on top. */
        .page::before {
            content: "";
            position: absolute;
            inset: 7px;
            border: 1px solid rgba(190, 158, 110, 0.50);
            border-radius: 3px;
            box-shadow:
                inset 0 1px 0 rgba(232, 220, 196, 0.12),
                inset 0 0 0 2px rgba(0, 0, 0, 0.32);
            pointer-events: none;
            z-index: 4;
        }

        /* ---- Three-region game-like layout ----
           main is a CSS grid: pinned card-pane on the left, scrolling
           content-pane on the right, action-dock pinned to the
           bottom of the viewport. The header / top-nav stays above
           main; the dock is sticky-bottom so it's always reachable.

           Below the 1200 px breakpoint the grid collapses to a single
           column; the card stops being sticky and renders inline,
           the dock stays bottom-sticky, and inline action buttons
           inside #result-details come back as full-width tap targets. */
        /* ---- The open spread ----
           #app is the parchment two-page spread, inset into the
           leather cover. Left page = the specimen card; right page =
           the active section. The grid keeps the card / nav / content
           / dock regions; per-state rules below collapse to one page.
           The book is a fixed height — each pane scrolls internally
           rather than the whole document scrolling. */
        #app {
            flex: 1;
            min-height: 0;
            position: relative;
            margin: 0 15px 15px;
            /* A deep warm-brown parchment: the vellum texture sits
               under a heavy dark wash so it reads as grain, not as a
               light surface — the bone-coloured text needs the dark
               ground for contrast. */
            background:
                linear-gradient(rgba(27, 22, 16, 0.88),
                                rgba(27, 22, 16, 0.88)),
                url("/static/assets/vellum.png") center / cover,
                #1b1610;
            border-radius: 2px;
            box-shadow:
                inset 0 0 0 1px rgba(0, 0, 0, 0.55),
                inset 0 3px 18px rgba(0, 0, 0, 0.5);
            display: grid;
            grid-template-columns: 468px minmax(0, 1fr);
            grid-template-rows: minmax(0, 1fr) auto;
            grid-template-areas:
                "card content"
                "dock dock";
            gap: 0;
            overflow: hidden;
            /* Height of the floating dock plinth — the card page reserves this
               much bottom clearance so its content can scroll clear of the
               plinth (the content page deliberately does NOT, so its items stay
               visible to the right of and behind the dock). */
            --dock-clear: 112px;
        }
        .card-pane {
            grid-area: card;
            min-width: 0;
            min-height: 0;
            overflow-y: auto;
            /* clear the floating dock plinth (the card page sits entirely above
               the plinth's left half) */
            padding: 24px 22px var(--dock-clear);
            scroll-padding-bottom: var(--dock-clear);
            /* the binding seam between the two pages */
            border-right: 1px solid rgba(0, 0, 0, 0.4);
            box-shadow: inset -12px 0 26px -16px rgba(0, 0, 0, 0.7);
        }
        .card-pane[hidden] { display: none; }

        /* ---- Card mounting ----
           .card-mount keeps the genome card positioned. The specimen pin now
           lives on the card's PORTRAIT (.genome-card-art-window::before, colour
           keyed per creature) rather than a single brass pin through the card's
           top edge — that pin clashed with the ornate rarity frame added later. */
        .card-mount { position: relative; }
        /* Placeholder for the left page before any specimen is pinned —
           a few loose naturalist's doodles, each treated as a crisp
           paper scrap pinned to the page. The sketches are cropped in
           (the source art has a wide empty margin) and given a hard
           edge + drop shadow so they read as physical scraps rather
           than the soft, blurred wash of the earlier feather mask. */
        .card-placeholder {
            position: relative;
            margin: 24px auto 0;
            max-width: 340px;
            min-height: 500px;
        }
        .card-placeholder[hidden] { display: none; }
        .sketch-scrap {
            position: absolute;
            margin: 0;
            overflow: hidden;
            border: 1px solid rgba(0, 0, 0, 0.6);
            box-shadow: 0 7px 18px rgba(0, 0, 0, 0.62);
        }
        .sketch-scrap img {
            display: block;
            width: 100%;
            /* Zoom past the source art's empty cream border so the
               scrap is mostly creature, not blank paper. */
            transform: scale(1.34);
        }
        .sketch-scrap.sketch-main {
            width: 64%;
            left: 17%;
            top: 92px;
            transform: rotate(-2.5deg);
            z-index: 2;
        }
        .sketch-scrap.sketch-up {
            width: 37%;
            left: 3%;
            top: 4px;
            transform: rotate(4deg);
            z-index: 1;
        }
        .sketch-scrap.sketch-low {
            width: 40%;
            right: 2%;
            bottom: 14px;
            transform: rotate(-6deg);
            z-index: 3;
        }
        /* Mate staging — the chosen partner pinned below the main card. */
        .mate-stage {
            margin: 16px auto 0;
            max-width: 300px;
            text-align: center;
        }
        .mate-stage[hidden] { display: none; }
        .mate-stage-amp {
            font-family: var(--font-serif);
            font-size: 28px;
            color: var(--copper);
            line-height: 1;
            margin: 0 0 10px;
        }
        /* The staged partner reuses the result-card frame vocabulary —
           copper border-box, brass pinstripe, double-rule plate — so it
           reads as a smaller sibling of the main card rather than a
           plain box. A soft copper glow echoes the mating-stage cards. */
        .mate-stage-card {
            position: relative;
            border: 6px solid transparent;
            border-radius: 4px;
            background:
                linear-gradient(var(--bg-vellum), var(--bg-vellum)) padding-box,
                linear-gradient(var(--copper), var(--copper)) border-box;
            box-shadow:
                inset 0 0 0 1px var(--brass),
                inset 0 0 0 3px var(--bg-vellum),
                inset 0 0 0 4px var(--rule),
                0 10px 26px rgba(0, 0, 0, 0.55),
                0 0 30px rgba(201, 139, 86, 0.32);
            padding-bottom: 14px;
            overflow: hidden;
        }
        .mate-stage-name {
            display: block;
            width: 100%;
            margin: 14px 0 10px;
            font-family: var(--font-serif);
            font-size: 14px;
            color: var(--ink);
            background: var(--bg-ink);
            border: 1px solid var(--rule-strong);
            border-radius: 2px;
            box-shadow: var(--well-inset);   /* shared carved-in field well */
            padding: 8px 12px;
            text-align: center;
        }
        .mate-stage-name:focus {
            outline: none;
            border-color: var(--copper);
        }
        .mate-stage-actions {
            display: flex;
            gap: 8px;
            justify-content: center;
        }
        .mate-stage-actions button { padding: 8px 16px; }
        /* Breeding agency — "Favour" loci control on the mate-stage.
           Each shared locus can be leaned toward a parent; favouring a
           locus biases the cross so it breeds truer that way. */
        .mate-favour { margin: 12px 0; text-align: left; }
        .mate-favour[hidden] { display: none; }
        .mate-favour-head {
            font-family: var(--font-serif);
            font-size: 12px;
            font-weight: 600;
            letter-spacing: 0.04em;
            color: var(--ink-muted);
            margin-bottom: 6px;
        }
        .mate-favour-hint {
            font-weight: 400;
            font-style: italic;
            color: var(--ink-faint);
        }
        .mate-favour-list {
            max-height: 134px;
            overflow-y: auto;
            border: 1px solid var(--rule);
            background: var(--bg-ink);
        }
        .mf-row {
            display: grid;
            grid-template-columns: 1fr auto auto;
            gap: 6px;
            align-items: center;
            padding: 5px 8px;
            border-top: 1px solid var(--rule);
        }
        .mf-row:first-child { border-top: none; }
        .mf-locus {
            font-family: var(--font-serif);
            font-size: 12px;
            color: var(--ink);
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .mf-pick {
            font-family: var(--font-serif);
            font-size: 10px;
            padding: 2px 8px;
            max-width: 92px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            background: transparent;
            color: var(--ink-muted);
            border: 1px solid var(--rule-strong);
            border-radius: 0;
            cursor: pointer;
            transition: color var(--motion-quick),
                        border-color var(--motion-quick),
                        background var(--motion-quick);
        }
        .mf-pick:hover { border-color: var(--copper); color: var(--ink); }
        .mf-pick.is-on {
            background: var(--copper-dim);
            border-color: var(--copper);
            color: var(--brass);
        }

        /* Brew cross — the two parent cards pinned above the brewing
           offspring while a mating runs in the background. */
        .brew-cross {
            margin: 8px auto 0;
            max-width: 320px;
            text-align: center;
        }
        .brew-cross[hidden] { display: none; }
        .brew-cross-parents {
            display: grid;
            grid-template-columns: 1fr auto 1fr;
            align-items: center;
            gap: 6px;
        }
        .brew-cross-amp {
            font-family: var(--font-serif);
            font-size: 22px;
            color: var(--copper);
            line-height: 1;
        }
        .brew-cross-card { padding-bottom: 10px; }
        .brew-cross-parents .brew-cross-card {
            animation: mating-drift 5.5s ease-in-out infinite;
            will-change: transform;
        }
        #brew-parent-b { animation-delay: -2.75s; }
        .brew-cross-card .mc-plate { font-size: 8.5px; }
        .brew-cross-card .mc-title { font-size: 13.5px; }
        .brew-cross-arrow {
            font-size: 19px;
            color: var(--copper);
            line-height: 1;
            margin: 5px 0;
        }
        .brew-cross-child {
            max-width: 224px;
            margin: 0 auto;
        }
        /* Brewing child — a small "drawing…" placeholder card. */
        .brew-cross-child .bcc-art {
            position: relative;
            margin: 0 14px;
            aspect-ratio: 1 / 1;
            background: var(--bg-ink);
            border: 1px solid var(--rule-strong);
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .brew-cross-child .bcc-spin {
            width: 22px;
            height: 22px;
            border: 2px solid var(--rule-strong);
            border-top-color: var(--copper);
            border-radius: 50%;
            animation: brew-spin 0.9s linear infinite;
        }
        /* (The brew-child chalk body-form cycle was retired with the ceremony
           round-2 rework, #259; the legacy brew wait is the .bcc-spin spinner.) */
        @media (prefers-reduced-motion: reduce) {
            .brew-cross-parents .brew-cross-card,
            .brew-cross-child .bcc-spin { animation: none; }
        }
        /* Goal-aware species picker — each species the player hasn't
           fully catalogued shows how many of its alleles are still
           undiscovered, so picking species reads as compendium
           progress. (The drawer layout itself lives in components.css
           under .cat-drawer; this is just the badge's base typography —
           it's repositioned into the drawer corner there.) */
        .sc-goal {
            font-size: 10px;
            font-weight: 600;
            letter-spacing: 0.04em;
            color: var(--copper);
            font-feature-settings: "lnum", "tnum";
        }
        .species-chip.is-selected .sc-goal { color: var(--brass); }
        .content-pane {
            grid-area: content;
            min-width: 0;
            min-height: 0;
            overflow-y: auto;
            padding: 4px 24px 20px;
            /* The floating-dock breakpoints (>=761 px) override padding-bottom
               to --dock-clear so the pane's OWN bottom controls (the reveal
               "What now?" CTAs + inline footer) scroll clear of the opaque
               plinth instead of being buried under it. Phone keeps this 20px
               because the dock reverts to in-flow there. scroll-padding keeps
               keyboard/anchor scrolls clear of the plinth. */
            scroll-padding-bottom: var(--dock-clear);
        }
        /* Slim, in-keeping scrollbars for the book's pages AND every scrollable
           modal / inner list (the arena + breeding-lab modals, the mate-picker
           grid, the VP history) so none of them falls back to the chunky default
           system scrollbar. Firefox via scrollbar-width/color; WebKit below. */
        .content-pane, .card-pane,
        .modal, .bl-picker-grid, .vp-history, .stage-body {
            scrollbar-width: thin;
            scrollbar-color: var(--rule-strong) transparent;
        }
        .content-pane::-webkit-scrollbar,
        .card-pane::-webkit-scrollbar,
        .modal::-webkit-scrollbar,
        .bl-picker-grid::-webkit-scrollbar,
        .stage-body::-webkit-scrollbar,
        .vp-history::-webkit-scrollbar { width: 10px; height: 10px; }
        .content-pane::-webkit-scrollbar-track,
        .card-pane::-webkit-scrollbar-track,
        .modal::-webkit-scrollbar-track,
        .bl-picker-grid::-webkit-scrollbar-track,
        .stage-body::-webkit-scrollbar-track,
        .vp-history::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.22); }
        .content-pane::-webkit-scrollbar-thumb,
        .card-pane::-webkit-scrollbar-thumb,
        .modal::-webkit-scrollbar-thumb,
        .bl-picker-grid::-webkit-scrollbar-thumb,
        .stage-body::-webkit-scrollbar-thumb,
        .vp-history::-webkit-scrollbar-thumb {
            background: var(--rule-strong);
            border: 2px solid transparent;
            background-clip: padding-box;
        }
        /* ---- Paged detail + section bookmarks ----
           The result detail is a set of book-pages; one shows at a
           time. Ribbon bookmarks down the book's outer edge flip
           between them — minimal scrolling, a real flippable book. */
        .book-page[hidden] { display: none; }
        .book-bookmarks {
            position: absolute;
            right: 0;
            top: 50%;
            transform: translateY(-50%);
            display: none;
            flex-direction: column;
            gap: 5px;
            z-index: 8;
        }
        main.state-result .book-bookmarks { display: flex; }
        /* The book's section tabs — large ribbon bookmarks down the
           outer edge of the page; the only navigation in the book
           body now that the mode menu lives in the titlebar. */
        .book-bookmark {
            writing-mode: vertical-rl;
            padding: 26px 11px;
            font-family: var(--font-serif);
            font-size: 12px;
            font-weight: 600;
            letter-spacing: 0.22em;
            text-transform: uppercase;
            font-feature-settings: "smcp", "lnum";
            background: linear-gradient(to left,
                rgba(10, 7, 5, 0.92), rgba(22, 15, 11, 0.74));
            border: 1px solid var(--rule-strong);
            border-right: 0;
            border-radius: 6px 0 0 6px;
            color: var(--ink-muted);
            cursor: pointer;
            box-shadow: -4px 0 10px rgba(0, 0, 0, 0.4);
            transition: color var(--motion-quick),
                        background var(--motion-quick),
                        padding var(--motion-quick);
        }
        .book-bookmark:hover { color: var(--ink); }
        /* The open section's bookmark is a struck-brass ribbon, pulled
           slightly further out to mark the page — the same brass-face +
           bevel as the active nav tab and the masthead nameplate, so the
           whole frame shares one "selected" metal. */
        .book-bookmark.is-active {
            color: var(--bg-ink);
            background: var(--brass-face);
            border-color: var(--copper);
            padding-right: 15px;
            text-shadow: var(--text-emboss);
            box-shadow: var(--bevel-raised), -5px 0 14px rgba(0, 0, 0, 0.5);
        }

        /* The toolbar — a brass tool-row anchored to the foot of the
           book, spanning both pages, not a floating pill. */
        /* HUD: both resource orbs on the LEFT (energy, points), the action pill
           to their right, and an alleles XP bar along the bottom row. */
        /* The action dock is a content-width PLINTH that FLOATS over the foot of
           the book's left page — it hugs its contents and rounds off on the
           right. It's positioned absolutely (out of the grid flow) so the card/
           content panes reclaim the full page height and stay visible to the
           RIGHT of (and above) the plinth, rather than a full-width band being
           reserved under them. The panes add bottom clearance (--dock-clear) so
           their own content can scroll clear of the floating plinth. */
        .action-dock {
            /* Float over the foot of the two-column book (out of grid flow);
               the phone breakpoint reverts to position:static + grid-area:dock
               so the single-column, document-scroll layout is unchanged. */
            grid-area: dock;          /* ignored while absolute; used on phone */
            justify-self: start;      /* ditto */
            position: absolute;
            left: 0;
            bottom: 0;
            z-index: 50;
            width: max-content;
            max-width: 100%;
            min-width: 0;
            margin: 0;
            padding: 9px 16px 9px 14px;
            display: grid;
            grid-template-columns: auto auto;   /* resources | actions */
            grid-template-rows: auto auto;
            align-items: stretch;
            column-gap: 12px;
            row-gap: 9px;
            /* dark-wood plinth, rounded on the right only */
            background: linear-gradient(180deg, #241d16, #16110d);
            border: 1px solid #000;
            border-left: 0;
            border-radius: 0 14px 14px 0;
            box-shadow: inset 0 1px 0 rgba(212, 175, 55, 0.16),
                        0 6px 18px rgba(0, 0, 0, 0.55);
        }
        .action-dock[hidden] { display: none; }
        /* Brass cap-rail along the very top edge of the plinth. */
        .action-dock::before {
            content: "";
            position: absolute;
            left: 0; right: 0; top: 0;
            height: 3px;
            border-radius: 0 3px 0 0;
            background: linear-gradient(90deg, #6e5024, #caa15e, #e7c884, #a9823f, #6e5024);
            opacity: 0.7;
            pointer-events: none;
        }
        /* Resources on the left (col 1), the action pill in col 2, and the XP
           bar left-justified on its own row 2. */
        .dock-plaque  { grid-column: 1; grid-row: 1; align-self: center; }
        .dock-center  { grid-column: 2; grid-row: 1; min-width: 0; display: flex; align-items: center; justify-content: flex-start; }
        .dock-xpbar   { grid-column: 1 / -1; grid-row: 2; }

        /* Registered so the fill/brightness custom props INTERPOLATE — the
           battery fill and the points-glow then animate smoothly. inherits:true
           so each cell's ::before reads its parent's value. */
        @property --fill { syntax: "<number>"; inherits: true; initial-value: 0; }
        @property --lit  { syntax: "<number>"; inherits: true; initial-value: 0; }

        /* ---- Resource instruments — two metallic plates whose TEXTURE is the
           live readout. Each is a dark instrument face; the metal lives in
           ::before and is revealed by STATE, not always-on:
             • Energy  — copper that FILLS left→right like a battery (--fill,
               JS = battle_energy / energy_max).
             • Discovery — amber that BRIGHTENS with points, full at ≥50 (--lit).
           Figures stay light so they read on both the dark face and lit metal.
           Hover adds a brass rim only (border colour on the always-2px border →
           no layout shift, no filter → no jitter). ---- */
        .dock-plaque {
            display: inline-flex;
            align-items: stretch;
            gap: 7px;
        }
        .dock-stat {
            --fill: 0;
            --lit: 0;
            position: relative;
            display: inline-flex;
            align-items: center;
            gap: 9px;
            min-width: 96px;
            padding: 9px 14px;
            border: 2px solid rgba(0, 0, 0, 0.55);
            border-radius: 6px;
            cursor: pointer;
            overflow: hidden;
            background: linear-gradient(180deg, #261e15, #120e09);
            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12),
                        inset 0 -3px 5px rgba(0, 0, 0, 0.4),
                        0 1px 2px rgba(0, 0, 0, 0.55);
            transition: border-color var(--motion-base),
                        --fill var(--motion-base),
                        --lit var(--motion-base);
        }
        .dock-stat[hidden] { display: none; }
        /* The metal texture rides a REAL child element on its own compositing
           layer (translateZ), NOT a ::before — so its opacity can animate on the
           compositor without repainting/re-rasterizing the icon (the WebKit
           jitter), and a pseudo-on-parent-:hover transition is avoided. */
        .dock-stat-tex {
            position: absolute;
            inset: 0;
            z-index: 0;
            pointer-events: none;
            background-size: cover;
            background-position: center;
            transform: translateZ(0);
            transition: opacity 280ms cubic-bezier(0.2, 0.7, 0.2, 1);
        }
        .dock-stat:hover { border-color: rgba(231, 200, 132, 0.75); }
        .dock-stat-ico { display: inline-flex; line-height: 0; position: relative; z-index: 1; }
        .dock-stat-svg {
            width: 24px; height: 24px;
            stroke: currentColor;
            fill: none;
            stroke-width: 1.7;
            stroke-linecap: round;
            stroke-linejoin: round;
            /* No drop-shadow filter — even a static filter re-rasterizes on
               repaint in WebKit. Legibility comes from a scrim in .dock-stat-tex
               (below), matching the action buttons. */
        }
        .dock-stat-col { display: inline-flex; flex-direction: column; line-height: 1.04; position: relative; z-index: 1; }
        .dock-stat-num {
            font-family: var(--font-serif);
            font-size: 21px;
            font-weight: 700;
            font-variant-numeric: lining-nums;
        }
        .dock-stat-lab {
            font-family: var(--font-serif);
            font-size: 8.5px;
            font-weight: 700;
            letter-spacing: 0.14em;
            text-transform: uppercase;
        }
        /* ⚡ Energy — copper fills left→right by --fill (battery). The whole
           plate glows warmer the FULLER it is (alpha ramps with --fill) — a nudge
           to go spend energy in the Arena while you have it. */
        .dock-stat--energy {
            color: #f0d3ad;
            box-shadow: inset 0 1px 0 rgba(255,255,255,0.12),
                        inset 0 -3px 5px rgba(0,0,0,0.4),
                        0 1px 2px rgba(0,0,0,0.55),
                        0 0 8px 1px rgba(232,150,92, calc(var(--fill) * 0.6)),
                        0 0 18px 4px rgba(232,150,92, calc(var(--fill) * 0.32));
        }
        .dock-stat--energy .dock-stat-tex {
            background-image:
                radial-gradient(34% 72% at 22% 50%, rgba(0,0,0,0.5), rgba(0,0,0,0) 72%),
                url(/static/assets/dock-copper.webp);
            clip-path: inset(0 calc(100% - var(--fill) * 100%) 0 0);
        }
        .dock-stat--energy .dock-stat-num { color: #f7e2c4; text-shadow: 0 1px 2px rgba(0,0,0,0.8); }
        .dock-stat--energy .dock-stat-lab { color: #d9b48a; text-shadow: 0 1px 1px rgba(0,0,0,0.7); }
        /* ⚗ Discovery — amber brightens with points, full at ≥50 (--lit); hover
           reveals it fully regardless. A gold glow ramps with --lit too — the
           richer your balance, the more it invites a spend. */
        .dock-stat--points {
            color: #f2dd9a;
            box-shadow: inset 0 1px 0 rgba(255,255,255,0.12),
                        inset 0 -3px 5px rgba(0,0,0,0.4),
                        0 1px 2px rgba(0,0,0,0.55),
                        0 0 8px 1px rgba(212,175,55, calc(var(--lit) * 0.65)),
                        0 0 18px 4px rgba(212,175,55, calc(var(--lit) * 0.38));
        }
        .dock-stat--points .dock-stat-tex {
            background-image:
                radial-gradient(34% 72% at 22% 50%, rgba(0,0,0,0.5), rgba(0,0,0,0) 72%),
                url(/static/assets/dock-amber.webp);
            opacity: calc(0.05 + 0.95 * var(--lit));
        }
        /* Hover lights the amber to FULL. On a real child on its own layer, the
           opacity animates on the compositor — the flask icon isn't repainted,
           so it lights up smoothly with no jitter (unlike the old ::before). */
        .dock-stat--points:hover .dock-stat-tex { opacity: 1; }
        .dock-stat--points .dock-stat-num { color: #f7e6a8; text-shadow: 0 1px 2px rgba(0,0,0,0.8); }
        .dock-stat--points .dock-stat-lab { color: #d9bd72; text-shadow: 0 1px 1px rgba(0,0,0,0.7); }

        /* ---- Alleles XP bar — left-justified under the buttons (matching the
           left-anchored dock), a fixed-ish brass-tracked strip rather than a
           full-width centered rule. ---- */
        .dock-xpbar {
            display: flex;
            align-items: center;
            gap: 9px;
            margin: 0;
            padding: 0 2px;
            background: transparent;
            border: 0;
            cursor: pointer;
            color: var(--ink-muted);
        }
        .dock-xpbar[hidden] { display: none; }
        .dock-xpbar-mark { color: var(--gold-leaf); font-size: 11px; }
        .dock-xpbar-track {
            flex: 0 1 340px;
            height: 6px;
            border-radius: 4px;
            background: linear-gradient(#1c130c, #2a1d12);
            border: 1px solid #3a2a1a;
            box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.8);
            overflow: hidden;
        }
        .dock-xpbar-fill {
            display: block;
            height: 100%;
            width: 0%;
            background: linear-gradient(90deg, #b9714a, var(--gold-leaf));
            box-shadow: 0 0 6px var(--gold-leaf-glow);
            transition: width var(--motion-base);
        }
        .dock-xpbar-count {
            font-family: var(--font-serif);
            font-size: 9.5px;
            font-weight: 600;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            white-space: nowrap;
        }
        .dock-xpbar:hover .dock-xpbar-count { color: var(--copper); }

        /* (Hero material faces — Mate / Battle — are defined AFTER the base
           .dock-button rules below, so their single-class selectors win by
           source order without out-specifying the phone media-query overrides.) */
        /* The toolbar's tool-row — sits flush in the dock strip at the
           foot of the book. The outer .action-dock catches no events
           so the strip around the row stays click-through. */
        .action-dock-pill {
            display: inline-flex;
            align-items: stretch;
            gap: 7px;
            padding: 4px 10px;
            background: transparent;
            border: 0;
            box-shadow: none;
            border-radius: 0;
            pointer-events: auto;
        }
        .action-dock-pill[hidden] { display: none; }
        /* Empty-dock state: hide the pill entirely so we don't
           render a floating empty bar on Compendium / Upload. */
        .action-dock:empty { display: none; }

        /* Dock buttons — taller, icon + small-caps label stacked.
           Tooltip carries the full description on hover so the label
           can stay terse and unique ("FASTA" vs "JSON",
           "Mate" vs "Cross", etc.). */
        .dock-button {
            position: relative;
            width: 74px;
            min-height: 56px;
            padding: 7px 4px 6px;
            border: 2px solid rgba(0, 0, 0, 0.55);
            border-radius: 5px;
            color: #cda45e;                 /* brass icon/label */
            cursor: pointer;
            display: inline-flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            gap: 4px;
            /* A dark instrument FACE; the material texture lives in ::before and
               is almost imperceptible at rest, blooming vibrant on hover. The
               2px border is always present (only its colour changes on hover →
               a thicker brass rim with no layout shift, no filter → no jitter).
               No overflow:hidden so the ::after tooltip isn't clipped. */
            background: linear-gradient(180deg, #241d15, #110d09);
            box-shadow: inset 0 1px 0 rgba(255,255,255,0.12),
                        inset 0 -3px 5px rgba(0,0,0,0.45),
                        0 1px 2px rgba(0,0,0,0.55);
            /* timing hardcoded (not var) to dodge any var()-in-transition
               resolution gaps in WebKit */
            transition: border-color 280ms cubic-bezier(0.2, 0.7, 0.2, 1);
        }
        /* The texture layer is a REAL child element (.dock-tex), NOT a ::before —
           WebKit is flaky transitioning a pseudo-element on a parent's :hover,
           which made the reveal pop instead of fade. It's on its own compositing
           layer (translateZ) so its opacity animates on the compositor without
           repainting the icon/label. NO filters anywhere (WebKit jitters +
           won't fade filters): the tool jet is pre-brightened in the asset, and
           a radial scrim baked into this layer keeps the icon legible over the
           revealed texture (replacing the icon drop-shadow). */
        .dock-tex {
            position: absolute;
            inset: 0;
            border-radius: inherit;
            z-index: 0;
            pointer-events: none;
            background-image:
                radial-gradient(44% 42% at 50% 44%, rgba(0,0,0,0.5), rgba(0,0,0,0) 72%),
                url(/static/assets/dock-tool.webp);
            background-size: cover;
            background-position: center;
            opacity: 0.08;
            transform: translateZ(0);
            transition: opacity 280ms cubic-bezier(0.2, 0.7, 0.2, 1);
        }
        .dock-button svg {
            width: 26px;
            height: 26px;
            stroke: currentColor;
            fill: none;
            stroke-width: 1.7;
            stroke-linecap: round;
            stroke-linejoin: round;
            position: relative;
            z-index: 1;
        }
        .dock-button-label {
            position: relative;
            z-index: 1;
            font-family: var(--font-serif);
            font-size: 10px;
            font-weight: 700;
            letter-spacing: 0.14em;
            text-transform: uppercase;
            font-feature-settings: "smcp", "lnum";
            line-height: 1;
            text-shadow: 0 1px 2px rgba(0,0,0,0.85);
        }
        /* Hover = REVEAL: the .dock-tex layer fades from 0.08 → full opacity, and
           the rim warms to brass. Pure opacity on a real child element → fades in
           AND out reliably in WebKit, no jitter. */
        .dock-button:hover:not(:disabled),
        .dock-button--hero:hover:not(:disabled) {
            border-color: rgba(231, 200, 132, 0.8);
        }
        .dock-button:hover:not(:disabled) .dock-tex,
        .dock-button--hero:hover:not(:disabled) .dock-tex {
            opacity: 1;
        }
        .dock-button:active:not(:disabled) .dock-tex,
        .dock-button--hero:active:not(:disabled) .dock-tex {
            opacity: 0.78;
        }
        .dock-button:disabled,
        .dock-button--hero:disabled {
            opacity: 0.45;
            cursor: not-allowed;
            filter: grayscale(0.35);
        }
        /* primary/danger keep their icon-colour hint over the jet face (no
           background swap — the material stays). */
        .dock-button.dock-button--primary:not(:disabled) { color: #e7c884; }
        .dock-button.dock-button--danger:not(:disabled) { color: var(--oxblood-soft); }

        /* ---- Hero verbs (Mate / Battle) — larger, each with its own biological
           material in the ::before texture layer (defined AFTER the base block so
           single-class rules win by source order): Mate = a chimeric scale→
           feather hide, Battle = dark armoured dragon-scale. Tool buttons keep
           jet. Each shows quiet at rest and blooms on hover. ---- */
        .dock-button--hero {
            width: 88px;
            min-height: 60px;
        }
        .dock-button--hero svg { width: 30px; height: 30px; stroke-width: 1.8; }
        .dock-button--hero .dock-button-label { font-size: 10.5px; }
        /* Mate — chimeric scale/feather hide, pre-saturated in the asset
           (dock-hide.webp) so its iridescence pops on the opacity reveal without
           a runtime filter. */
        .dock-button--hero-mate { color: #f1e6cd; }
        .dock-button--hero-mate .dock-tex {
            background-image:
                radial-gradient(44% 42% at 50% 44%, rgba(0,0,0,0.5), rgba(0,0,0,0) 72%),
                url(/static/assets/dock-hide.webp);
        }
        .dock-button--hero-mate .dock-button-label { color: #f1e6cd; }
        /* Battle — dark armoured dragon-scale (iron + oxblood). */
        .dock-button--hero-battle { color: #f0d9b0; }
        .dock-button--hero-battle .dock-tex {
            background-image:
                radial-gradient(44% 42% at 50% 44%, rgba(0,0,0,0.5), rgba(0,0,0,0) 72%),
                url(/static/assets/dock-battle.webp);
        }
        .dock-button--hero-battle .dock-button-label { color: #f0d9b0; }
        /* Custom tooltip — serif italic, appears above the button on
           hover. Native `title` attribute is fine for screen readers
           but its visuals don't fit the journal voice. */
        .dock-button::after {
            content: attr(data-tooltip);
            position: absolute;
            bottom: calc(100% + 8px);
            left: 50%;
            transform: translateX(-50%) translateY(4px);
            padding: 5px 10px;
            background: var(--bg-vellum);
            border: 1px solid var(--rule-strong);
            color: var(--ink);
            font-family: var(--font-serif);
            font-style: italic;
            font-size: 12px;
            white-space: nowrap;
            border-radius: 1px;
            opacity: 0;
            pointer-events: none;
            transition:
                opacity var(--motion-quick),
                transform var(--motion-quick);
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
            z-index: 60;
        }
        .dock-button:hover::after,
        .dock-button:focus-visible::after {
            opacity: 1;
            transform: translateX(-50%) translateY(0);
        }
        /* Dock groups — Diablo-style split: persistent CORE actions
           on the left (always visible, disabled when not applicable),
           CONTEXT actions on the right (swap by state). */
        .dock-group {
            display: inline-flex;
            align-items: center;
            gap: 4px;
        }
        .dock-group--context .dock-button {
            color: var(--ink);
        }

        /* Vertical separator between dock-button groups. The
           --strong variant separates core / context. */
        .dock-divider {
            width: 1px;
            align-self: stretch;
            margin: 6px 4px;
            background: var(--rule);
            pointer-events: none;
        }
        .dock-divider--strong {
            background: var(--rule-strong);
            margin: 4px 10px;
        }

        /* The dock replaces the inline action footer whenever the
           two-column book is shown (≥ 761 px — desktop AND the scaled
           tablet band). Below that the grid is single-column and the
           inline buttons return as wide tap targets. */
        @media (min-width: 761px) {
            .result-footer--inline .result-footer-actions
              > button.primary,
            .result-footer--inline .result-footer-actions
              > button.secondary {
                display: none;
            }
            /* The dock floats (position:absolute, bottom-left) over the foot of
               the book at these widths; it's opaque and reaches ~200 px into the
               content pane, so bottom-left content — the reveal page's "What
               now?" CTAs + inline footer — was hidden under it. Reserve the same
               --dock-clear bottom padding .card-pane uses so those controls
               scroll clear of the plinth. (Tablet re-states this in its own
               padding shorthand below; phone leaves the dock in-flow so the base
               20px stands.) */
            .content-pane { padding-bottom: var(--dock-clear); }
        }

        /* The left card-pane is always the book's left page — on the
           Library page it carries a placeholder (no specimen pinned).
           Only the loading and auth states drop it: mating-loading has
           its own parent-plate stage, and auth is the pre-app gate. */
        main.state-loading     .card-pane,
        main.state-auth-login  .card-pane,
        main.state-auth-otp    .card-pane {
            display: none;
        }
        /* Use #app on these so specificity beats the base #app rule
           that sets the two-column grid. */
        #app.state-loading,
        #app.state-auth-login,
        #app.state-auth-otp {
            grid-template-columns: minmax(0, 1fr);
            grid-template-areas:
                "content"
                "dock";
        }
        /* Starter generation keeps the genome card on screen as a live
           skeleton (see renderSkeletonCard): the card-pane stays
           visible and the two-column result grid is restored so the
           card already sits in its final slot. Scoped to
           .loading-skeleton so the mating path — which has its own
           parent-plate stage — is left untouched. */
        main.state-loading.loading-skeleton .card-pane {
            display: block;
        }
        #app.state-loading.loading-skeleton {
            grid-template-columns: 460px minmax(0, 1fr);
            grid-template-areas:
                "card content"
                "dock dock";
        }
        /* Top-nav stays hidden during auth/loading regardless of
           grid placement (the JS already toggles its [hidden] attr). */

        /* result-details is the wrapper that holds the depth view
           on the result state. Visible only when state-result. */
        #result-details {
            display: none;
        }
        main.state-result #result-details {
            display: block;
        }

        /* ---- Scaled two-column book (tablets: 761–1200 px) ----
           The 11" iPad (1194 px landscape, 834 px portrait) keeps the
           two-page book — but the card column shrinks so both pages fit
           without cramping, and the page stays FIXED-height so the
           leather cover renders at one consistent `cover` scale across
           every tab (a variable-height page rescaled the leather per
           tab and blurred it). The side bookmarks shrink and the
           content pane reserves clearance so the ribbons never overlap
           the text. */
        @media (min-width: 761px) and (max-width: 1200px) {
            #app {
                /* Narrower card column; clamp keeps it sane across the
                   whole band (≈300 px at 834 → ≈400 px near 1200). */
                grid-template-columns: clamp(296px, 33vw, 408px) minmax(0, 1fr);
            }
            /* keep the bottom clearance for the floating dock on the tablet book */
            .card-pane { padding: 18px 16px var(--dock-clear); }
            .content-pane {
                /* Bottom = --dock-clear so the reveal CTAs clear the floating
                   dock (same as desktop); this shorthand would otherwise reset
                   the >=761 padding-bottom back to 20px. */
                padding: 4px 16px var(--dock-clear);
                /* Clear the ribbon bookmarks pinned to the page's
                   outer edge so they don't sit over the text. */
                padding-right: 46px;
            }
            /* Slimmer ribbons at tablet width. */
            .book-bookmark {
                padding: 18px 7px;
                font-size: 11px;
                letter-spacing: 0.16em;
            }
            /* The titlebar can't hold nameplate + 6-item menu + account
               drawer in one row at tablet width — let them wrap. */
            .header-row { flex-wrap: wrap; row-gap: 8px; }
            #top-nav { flex-wrap: wrap; }
        }

        /* ---- Phone fallback (≤ 760 px) ----
           Below the book's minimum comfortable width the two pages
           can't sit side by side, so the grid folds to a single
           scrolling column — the document scrolls again here, by
           necessity. Inline action buttons return as wide tap targets
           via the @media (min-width: 761px) rule above (bypassed). */
        @media (max-width: 760px) {
            body { overflow: auto; }
            .page {
                width: 96vw;
                height: auto;
                min-height: 95vh;
                margin: 2.5vh auto;
            }
            /* Phone: the ☰ menu rides on row 1 beside the nameplate, and the
               nav buttons drop to their own full-width row below — using
               flex `order` so the menu (source order: after nav) flows onto
               the first line, and a 100% basis on the nav to force the wrap. */
            .header-row {
                flex-wrap: wrap;
                row-gap: 8px;
            }
            .titlebar-name { order: 0; }
            .header-menu { order: 1; }
            #top-nav {
                order: 2;
                flex-basis: 100%;
                flex-wrap: wrap;
            }
            #app {
                grid-template-columns: minmax(0, 1fr);
                grid-template-rows: auto auto auto;
                grid-template-areas:
                    "card"
                    "content"
                    "dock";
                overflow: visible;
            }
            .card-pane,
            .content-pane {
                overflow: visible;
            }
            .card-pane {
                max-width: 480px;
                margin: 0 auto;
                width: 100%;
                border-right: 0;
                box-shadow: none;
                /* dock is in-flow on phone — drop the floating-dock clearance */
                padding-bottom: 18px;
            }
            /* Narrow screens drop the side bookmarks and stack every
               detail page — scrolling is acceptable here. */
            main.state-result .book-bookmarks { display: none; }
            .book-page[hidden] { display: block; }

            /* Phone HUD: tighten the resource instruments, drop dock-button
               labels to icon-only (heroes keep a terse label), and let the
               center pill wrap so the plinth never overflows the screen. */
            /* Phone reverts the dock to in-flow (the single-column layout
               document-scrolls; floating it would detach it from the foot). */
            .action-dock { position: static; column-gap: 5px; padding: 6px 8px 6px 6px; max-width: 100%; }
            .dock-stat { min-width: 0; padding: 5px 9px; gap: 6px; }
            .dock-stat-svg { width: 18px; height: 18px; }
            .dock-stat-num { font-size: 14px; }
            .dock-stat-lab { font-size: 7.5px; letter-spacing: 0.12em; }
            /* The center column sizes to content; on phone the pill WRAPS its
               buttons to extra rows so the plinth stays within the screen. */
            .action-dock-pill { flex-wrap: wrap; justify-content: flex-start; gap: 3px; padding: 2px; }
            .dock-button { width: 40px; min-height: 44px; flex: 0 0 auto; }
            .dock-button .dock-button-label { display: none; }
            .dock-button svg { width: 23px; height: 23px; }
            .dock-button--hero { width: 46px; min-height: 44px; }
            .dock-button--hero .dock-button-label { display: inline; font-size: 8px; }
            .dock-button--hero svg { width: 25px; height: 25px; }
            .dock-xpbar-count { font-size: 8.5px; }
        }

        /* ---- The book's titlebar ----
           A slim engraved strip across the leather cover's top edge:
           the brass nameplate at left, the account drawer at right. */
        header {
            flex: 0 0 auto;
            /* Roomier top/side padding so the brass masthead + nav sit well
               inside the gilt tooled border instead of kissing it. */
            padding: 18px 32px 12px;
            /* No own background — the whole .page leather already
               carries a uniform dark wash, so the titlebar sits flush
               in tone with the side and foot borders. A hairline seam
               keeps it reading as an engraved strip. */
            border-bottom: 1px solid rgba(0, 0, 0, 0.5);
            box-shadow: 0 1px 0 rgba(232, 220, 196, 0.05);
        }
        .header-row {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 16px;
        }
        /* "Holotype" — a struck-brass nameplate riveted to the leather
           cover (the shared .fj-nameplate--brass kit, so the masthead is
           the same metal as the Leaderboard board labels). Scoped overrides
           keep the wordmark at its larger display size + spacing. */
        header h1.fj-nameplate {
            margin: 0;
            font-size: 18px;
            letter-spacing: 0.17em;
            padding: 4px 14px;
        }
        /* The old tagline is dropped; the colophon stays as a tiny
           hand-written sub-line — the stranger-world hint. */
        header p { display: none; }
        .header-colophon {
            margin: 1px 0 0;
            font-style: normal;
            font-size: 14px;
            color: var(--ink-pen);
            opacity: 0.72;
        }

        /* The ☰ overflow menu occupies the right slot of the nav row; its
           dropdown is positioned relative to this wrapper (see components.css). */
        .header-menu { position: relative; flex: 0 0 auto; }
        .header-menu[hidden] { display: none; }

        /* (The header status strip was retired — Compendium progress + the PvP
           wallet moved into the action dock HUD; see .action-dock in this file.) */

        /* Field-station seal — a faint ink stamp closing the landing
           page, as if the requisition sheet had been filed. */
        .field-seal {
            display: flex;
            justify-content: center;
            margin: 10px 0 2px;
        }
        .field-seal-mark {
            width: 92px;
            height: 92px;
            color: var(--brass);
            opacity: 0.22;
            transform: rotate(-9deg);
        }

