/* static/css/reading-tools.css — reading-page overlays + ultra-wide re-centre.
 *
 * LAST of the seven stylesheets split out of the former single ``gallae.css``
 * (tasks/FILE_SPLIT_TASKS.md Phase 1). It holds the reading-page tooling that
 * layers on top of the v2 navigator: the ultra-wide (≥1800px) re-centre, the
 * feedback / "report a bug" panel, the post minimap, and the mobile reading
 * affordances (the slide-out rail + sheets). MUST load LAST — the ultra-wide
 * re-centre deliberately re-overrides ``reading-v2.css``'s ≥880px rules by
 * later source order (media queries add no specificity), so it can only win
 * from here.
 * ---------------------------------------------------------------------------
 * Ultra-wide (≥1800px): re-centre the composite.
 * ---------------------------------------------------------------------------
 * From 880–1800px the [rail · card · chat] composite is right-anchored (see
 * the ≥880px block) — sensible while the fluid card/chat are still growing and
 * the right gutter would otherwise be wasted. But above 1800px the card
 * (50rem) and chat box (500px) have both hit their fixed maxima, so the
 * composite is a constant width sitting in a very wide viewport; a right-pin
 * then reads as lopsided, with a huge empty left gutter. So at ultra-wide we
 * revert to CENTRING the whole composite (the pre-right-anchor behaviour).
 *
 * Only the container margin matters when JS is on: pathnav-overlays.js's pinners
 * position the rail + chat off the active card's MEASURED box, so centring the
 * card re-centres both overlays for free. The rail / chat rules below restore
 * the 50vw-centred no-JS fallbacks to match (same selectors + specificity as
 * the ≥880px originals, so this later block wins). This block must stay AFTER
 * the ≥880px block in source order — media queries add no specificity. */
@media (min-width: 1800px) {
    .container--post-detail.container {
        /* cardLeft = 50vw + (D + rail-reserve)/2 − card; margin-left =
         * cardLeft − overhang. (D + rail-reserve)/2 is --rv-card-right-offset. */
        margin-left: calc(
            50vw + var(--rv-card-right-offset) - var(--rv-card-width)
                - var(--rv-card-overhang)
        );
        margin-right: auto;
    }
    /* No-JS rail fallback: right edge stops half a rem left of the centred
     * card's left edge (50vw + --rv-card-right-offset − card). */
    .container--post-detail .post-metacol {
        right: calc(
            50vw + var(--rv-card-width) - var(--rv-card-right-offset) + 0.5rem
        );
    }
    /* No-JS chat-box fallback: left edge just past the centred card's right
     * edge (a constant 50vw + --rv-card-right-offset), width shrinking to the
     * space left to the viewport's 1rem right margin, capped at the preferred
     * width. Same high-specificity selector as the ≥880px original. */
    .container--post-detail .contribution[data-active] > .contribution-meta-mount,
    .container--post-detail .post-detail[data-active] ~ .post-meta-mount {
        left: calc(
            50vw + var(--rv-card-right-offset) + var(--rv-comment-rail-gap)
        );
        width: min(
            var(--rv-comment-rail-width),
            calc(
                50vw - var(--rv-card-right-offset)
                    - var(--rv-comment-rail-gap) - 1rem
            )
        );
    }
}

/* ---------------------------------------------------------------------------
 * Feedback / "Report a bug" panel.
 * ---------------------------------------------------------------------------
 * A fixed-position feedback panel for logged-in users (rendered by
 * ``templates/_feedback_widget.html``, included from ``base.html``). It has no
 * visible trigger of its own — it opens on ``feedback:open`` from the nav's
 * "report a bug" item on desktop (dropping down below the sticky header — see
 * the ≥880px rule) and from the slide-out rail on mobile (a bottom-sheet).
 *
 * Z-index sits at 999 — one below the toast stack's 1000 (a success toast
 * should read *over* the panel) but above all other page chrome (incl. the
 * sticky nav's 100, so the desktop dropdown paints over the header edge).
 * Palette matches the hand-written v0 set: accent #1a4f8b, card bg #f5f0e8. */
.feedback-widget {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: 999;
    /* A shrink-to-fit box holding just the panel, right-aligned (flex-end).
     * Desktop repositions it below the nav header (≥880px rule); on mobile the
     * panel itself becomes a bottom-sheet, so this box's placement is moot. */
    display: flex;
    flex-direction: column-reverse;
    align-items: flex-end;
    gap: 0.5rem;
}

/* Desktop: drop the panel down from just under the sticky nav header,
 * right-aligned (the "report a bug" nav item that opens it sits at the
 * header's right). Below 880px the mobile bottom-sheet rules take over. */
@media (min-width: 880px) {
    .feedback-widget {
        top: calc(var(--nav-height) + 0.5rem);
        bottom: auto;
    }
}

/* Expanded panel — the card holding the form. Width is capped so it never
 * overflows a narrow phone viewport; on a wide screen it stays compact. */
.feedback-panel {
    width: min(320px, calc(100vw - 2rem));
    padding: 0.85rem 1rem 1rem;
    border: 1px solid var(--border-subtle);
    border-radius: 0.6rem;
    background: var(--surface);
    box-shadow: 0 6px 20px var(--shadow-lg);
}

.feedback-panel-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.6rem;
}

.feedback-panel-title {
    margin: 0;
    font-size: 1rem;
    color: var(--text);
}

.feedback-panel-close {
    border: 0;
    background: transparent;
    color: var(--text-subtle);
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
    padding: 0 0.2rem;
}

/* Re-declare ``background`` (and out-specify the global ``button:hover`` with
 * ``:not(:disabled)``): the rest-state ``background: transparent`` above is
 * outranked once hovered, which otherwise paints a --surface-muted box behind
 * the ×. Same trap as .nav-report-bug:hover in pages.css. */
.feedback-panel-close:hover:not(:disabled) {
    background: transparent;
    color: var(--text);
}

.feedback-panel-close:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* The form itself. ``fieldset``/``legend`` carry the category radios; the
 * textarea fills the panel width. */
.feedback-form-categories {
    margin: 0 0 0.6rem;
    padding: 0;
    border: 0;
}

.feedback-form-categories legend {
    padding: 0;
    margin-bottom: 0.3rem;
    font-size: 0.85rem;
    color: var(--text-muted);
}

.feedback-form-category {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    margin-right: 0.75rem;
    font-size: 0.9rem;
    color: var(--text);
}

.feedback-form-message textarea {
    width: 100%;
    box-sizing: border-box;
    font: inherit;
    resize: vertical;
}

.feedback-form-submit {
    margin-top: 0.6rem;
    padding: 0.45rem 1rem;
    border: 0;
    border-radius: 0.4rem;
    background: var(--accent);
    color: var(--on-accent);
    font-size: 0.95rem;
    cursor: pointer;
}

.feedback-form-submit:hover {
    background: var(--accent-hover);
}

.feedback-form-submit:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Inline validation errors — muted red, consistent with the rest of the v0
 * error styling (#c0392b). */
.feedback-form-errors {
    margin: 0.3rem 0;
    padding-left: 1.1rem;
    color: var(--danger);
    font-size: 0.85rem;
}

/* Post-submit confirmation swapped in over the form. */
.feedback-thanks-message {
    margin: 0 0 0.6rem;
    color: var(--success-text);
    font-size: 0.95rem;
}

.feedback-thanks-again {
    padding: 0.4rem 0.85rem;
    border: 1px solid var(--accent);
    border-radius: 0.4rem;
    background: transparent;
    color: var(--accent);
    font-size: 0.9rem;
    cursor: pointer;
}

.feedback-thanks-again:hover {
    background: var(--accent-tint);
}

.feedback-thanks-again:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* ============================================================
 * Post minimap (UserPreferences.enable_post_minimap)
 * ============================================================
 * A floating, collapsible SVG map of the post's riff tree, fixed in the
 * top-right of the reading page. Markup: works/_post_minimap.html; the SVG
 * is built by static/js/minimap.js, its geometry by minimap-layout.js.
 * Desktop only — there's no room beside the single-column card stack on a
 * narrow screen.
 *
 * The visual layer (node / edge fill, stroke, the active ring) is
 * deliberately all here, so the look can be retuned without touching JS —
 * the feature is expected to take a couple of passes to get right. All
 * colours are the shared tokens.css theme tokens (``var(--…)``), so the
 * minimap tracks light / dark mode automatically with no separate
 * dark-theme block.
 */

/* Two presentations, switched at the 880px post-detail pivot:
 *   - Desktop (≥880px): a small floating card fixed top-left (this block).
 *   - Mobile  (<880px): a fullscreen modal opened from the slide-out rail
 *     (the ``@media (max-width: 879px)`` block at the end of this section).
 * The container generates no box of its own — its backdrop + panel are
 * position:fixed, and the desktop rule below re-establishes it as the
 * floating flex box. ``[x-cloak]`` (display:none !important) still wins
 * until Alpine boots, so there's no flash either way. */
.post-minimap {
    display: contents;
}

@media (min-width: 880px) {
    .post-minimap {
        position: fixed;
        /* Clear the (non-sticky, ~3.2rem) nav bar plus a gap; once the
         * reader scrolls, the fixed panel floats over the content. */
        top: calc(3.2rem + 1rem);
        /* Top-LEFT. The reading composite is right-anchored at desktop
         * (880–1800px — see the .container--post-detail page-shell rule), so
         * the RIGHT edge is now filled by the card + its chat box and the LEFT
         * margin holds the most empty space. ``align-items: flex-start``
         * (below) left-aligns both the collapsed tab and the wider open panel
         * against this edge, so the panel grows rightward into the open space. */
        left: 1rem;
        /* Above page content, below the feedback widget (999) and toast
         * stack (1000) so neither is ever occluded. */
        z-index: 900;
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        gap: 0.5rem;
    }
}

/* Collapsed trigger — the floating tab. Quiet, like the feedback pill. */
.post-minimap-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg-nav);
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 2px 8px var(--shadow-md);
    transition: color 0.12s, border-color 0.12s, background 0.12s;
}

.post-minimap-tab:hover {
    color: var(--accent);
    border-color: var(--accent);
    background: var(--accent-tint);
}

.post-minimap-tab:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.post-minimap-tab-glyph {
    /* Holds the inline folded-map SVG (works/_icon_map.html). inline-flex
     * drops the baseline gap a bare inline <svg> would leave under it. */
    display: inline-flex;
    font-size: 1rem;
}

/* Expanded panel — the card holding the SVG. Capped at 300px wide per the
 * feature spec; ``max-height`` + ``overflow`` keep a tall tree on-screen. */
.post-minimap-panel {
    width: min(300px, calc(100vw - 2rem));
    max-height: 50vh;
    overflow: auto;
    padding: 0.6rem;
    border: 1px solid var(--border-subtle);
    border-radius: 0.6rem;
    background: var(--surface);
    box-shadow: 0 6px 20px var(--shadow-lg);
}

/* Cap, don't stretch. minimap.js stamps an intrinsic px size on the SVG
 * (one pixel per viewBox unit), so it renders at its natural size — a
 * small tree stays small instead of being blown up to fill the panel.
 * ``max-width:100%`` only ever scales a tree that's wider than the panel
 * *down*; ``height:auto`` then preserves the aspect ratio.
 *
 * ``margin-inline:auto`` centres a drawing that's narrower than the panel
 * (the lone node of a riff-less post, the thin spine of a no-siblings
 * chain) so it sits in the middle rather than hugging the left edge. It's
 * a no-op once a tree is wide enough to fill the panel width — there's no
 * slack to distribute — so a wide, right-heavy tree keeps its root wherever
 * the layout placed it instead of being force-centred. */
.post-minimap-svg {
    display: block;
    max-width: 100%;
    height: auto;
    margin-inline: auto;
}

/* ---- The tree drawing. Tweak freely; no JS depends on these. ---- */

.minimap-edge {
    stroke: var(--border);
    stroke-width: 1.5;
}

.minimap-edge--on-path {
    stroke: var(--accent);
    stroke-width: 2;
}

/* Each node is filled with its card author's colour (minimap.js copies the
 * card's --card-author-color onto the circle). The fallback (--surface, the
 * panel background) makes a node HOLLOW — used for AI cowriter cards and
 * deleted tombstones, which minimap.js deliberately leaves without the var.
 * State (spine / active) rides the STROKE, so it never fights the fill. */
.minimap-node {
    fill: var(--card-author-color, var(--surface));
    stroke: var(--text-faint);
    stroke-width: 1.5;
    cursor: pointer;
    transition: fill 0.12s, stroke 0.12s;
}

.minimap-node:hover {
    stroke: var(--accent);
}

/* Spine (path to the active card): accent ring, fill untouched. */
.minimap-node--on-path {
    stroke: var(--accent);
}

/* "You are here" — the ringed active card. A thicker accent ring is the
 * halo; the author-colour fill stays put so the node keeps its identity.
 * Every token flips with the theme, so this tracks light / dark on its
 * own — no separate dark-mode rule. */
.minimap-node--active {
    stroke: var(--accent);
    stroke-width: 2.5;
}

/* ============================================================
 * Mobile reading affordances (<880px): the slide-out rail + modals
 * ============================================================
 * Below the post-detail pivot the minimap can't float in a margin (the
 * single-column stack fills the width, so a floating panel — top-right on
 * desktop — would overlap the cards) and the feedback pill wastes reading
 * space, so both move behind a thin right-edge "reading tools" tab that
 * slides a small panel out; the minimap and the bug form then open as
 * full-width sheet modals. Desktop is untouched — everything below is scoped
 * to the narrow breakpoint, and these mobile-only bits (backdrops, the modal
 * header) are hidden at every width by default and shown only inside it.
 * Colours are theme tokens, so dark mode tracks automatically.
 *
 * z-index ladder (mobile): rail 960/961 < modal backdrop 980 < modal content
 * 981 < toast stack 1000 (a toast must still read over an open modal). */

.mobile-rail,
.post-minimap-backdrop,
.post-minimap-modal-header,
.feedback-backdrop {
    display: none;
}

@media (max-width: 879px) {
    /* ---- Mobile metacol restructure (every visible spine card) ----
     * Promote .post-metacol's two halves to direct flex children of the card
     * <article> so `order` can drop the action band (-bottom) BELOW the body
     * while the identity row (-top) stays above it. Unlike desktop (which pins
     * the rail to the [data-active] card only), the mobile reading view shows
     * the byline + action band on EVERY visible card, so you can see who wrote
     * each card and act on it without selecting it first. The visible cards are
     * the root post (always on the spine) plus the on-active-path riffs; off-
     * path siblings are display:none above, so [data-on-active-path] scopes the
     * riffs to exactly the visible ones. The metacol's base display:none on
     * inactive cards is overridden here by display:contents (rule (b)). Desktop
     * (>=880) uses the fixed rail in the mutually-exclusive min-width:880px
     * block and never sees any of this. The post <article> carries BOTH
     * post-detail and contribution-body, so it's scoped via .post-detail; the
     * riff article via .contribution[data-on-active-path] > .contribution-body. */

    /* (a) each visible card's <article> -> vertical flex container */
    .container--post-detail .post-detail,
    .container--post-detail .contribution[data-on-active-path] > .contribution-body {
        display: flex;
        flex-direction: column;
    }

    /* (b) dissolve the metacol wrapper so -top/-bottom join the article's flex
     *     flow. The display:contents also REVEALS the rail on inactive spine
     *     cards, overriding the base display:none. The post's base hide is
     *     ``.post-detail:not([data-active]) > .post-metacol`` (3 classes + 1
     *     attr), so the non-active post reveal here repeats that :not() compound
     *     to match its specificity (and sits later in source) and win. */
    .container--post-detail .post-detail[data-active] > .post-metacol,
    .container--post-detail .post-detail:not([data-active]) > .post-metacol,
    .container--post-detail .contribution[data-on-active-path] > .contribution-body > .post-metacol {
        display: contents;
    }

    /* (c) identity row above the body; action band below it (and below the draft
     *     footer / riff inline-edit form, which stay at the default order 0). */
    .container--post-detail .post-detail > .post-metacol > .post-metacol-top,
    .container--post-detail .contribution[data-on-active-path] > .contribution-body > .post-metacol > .post-metacol-top {
        order: -1;
    }
    /* The root post is the one card with a title header (.post-header). Put that
     * title FIRST — above the identity row — so the post reads title-then-byline.
     * order:-2 < the -1 on .post-metacol-top. Riffs have no .post-header, so this
     * is post-only by structure, not an is-root flag, and their identity row
     * stays at the top of the card. */
    .container--post-detail .post-detail > .post-header {
        order: -2;
    }
    .container--post-detail .post-detail > .post-metacol > .post-metacol-bottom,
    .container--post-detail .contribution[data-on-active-path] > .contribution-body > .post-metacol > .post-metacol-bottom {
        order: 10;
        /* Separate the relocated action band from the body above (independent of
         * however the body's last element ends) and lift it off the card's
         * bottom edge (the card itself has no bottom padding). */
        margin-top: 0.5rem;
        margin-bottom: 0.5rem;
    }

    /* (d) identity items on ONE wrapping row, overriding the base column layout
     *     (.post-metacol-top, -bottom { flex-direction: column; gap: 0 }). */
    .container--post-detail .post-detail > .post-metacol > .post-metacol-top,
    .container--post-detail .contribution[data-on-active-path] > .contribution-body > .post-metacol > .post-metacol-top {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: baseline;
        column-gap: 0.75rem;
        row-gap: 0.25rem;
    }
    /* (e) Author badge stays left; everything after it (permadate, edit-history)
     *     is pushed to the right edge of the full-width identity row. The badge
     *     is the single first child (the .author-badge span), so an auto
     *     inline-end margin on it absorbs the row's free space. */
    .container--post-detail .post-detail > .post-metacol > .post-metacol-top > :first-child,
    .container--post-detail .contribution[data-on-active-path] > .contribution-body > .post-metacol > .post-metacol-top > :first-child {
        margin-inline-end: auto;
    }

    /* (f) Action-band split (mobile only): the two "big actions" — comment
     *     (message) and cowrite (compose a riff) — sit at the LEFT, immediately
     *     reachable; everything else (edit / delete / subscribe / like) pushes
     *     to the RIGHT edge. The desktop fixed rail (min-width:880px) is a
     *     separate, mutually-exclusive block, so its column order is untouched.
     *     cowrite is LAST in the DOM (which is exactly what keeps the desktop
     *     column ordering as-is), so rather than reorder the markup we float
     *     comment + cowrite to the front with a negative `order` and let
     *     cowrite's margin-inline-end:auto absorb the row's free space — shoving
     *     the default-order:0 group (edit/delete/subscribe/like, kept in DOM
     *     order) to the right. Same idiom as (e) above. */
    .container--post-detail .post-metacol-bottom > .post-metacol-icon--comment {
        order: -2;
    }
    .container--post-detail .post-metacol-bottom > .post-metacol-icon--cowrite {
        order: -1;
        margin-inline-end: auto;
    }

    /* ---- Slide-out "reading tools" rail ---- */
    /* No box of its own; the tab + panel are fixed straight to the viewport,
     * so the tab can stay pinned at the edge while only the panel slides. */
    .mobile-rail {
        display: contents;
    }

    .mobile-rail-tab {
        position: fixed;
        right: 0;
        top: 50%;
        transform: translateY(-50%);
        z-index: 961;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 1.7rem;
        height: 3.2rem;
        padding: 0;
        border: 1px solid var(--border);
        border-right: 0;
        border-radius: 0.5rem 0 0 0.5rem;
        background: var(--bg-nav);
        color: var(--text-muted);
        cursor: pointer;
        box-shadow: -2px 2px 8px var(--shadow-md);
    }

    .mobile-rail-tab:focus-visible {
        outline: 2px solid var(--accent);
        outline-offset: 2px;
    }

    .mobile-rail-tab-glyph {
        font-size: 1.1rem;
        line-height: 1;
    }

    /* The action panel slides in from off the right edge to abut the tab.
     * Closed: parked off-screen by its own width plus the tab's; visibility
     * hidden so its buttons aren't focusable while away. */
    .mobile-rail-panel {
        position: fixed;
        right: 1.7rem;
        top: 50%;
        z-index: 960;
        display: flex;
        flex-direction: column;
        gap: 0.5rem;
        padding: 0.5rem;
        border: 1px solid var(--border);
        border-right: 0;
        border-radius: 0.6rem 0 0 0.6rem;
        background: var(--surface);
        box-shadow: -4px 4px 16px var(--shadow-lg);
        transform: translate(calc(100% + 1.7rem), -50%);
        visibility: hidden;
        transition: transform 0.2s ease, visibility 0.2s;
    }

    .mobile-rail-panel--open {
        transform: translate(0, -50%);
        visibility: visible;
    }

    .mobile-rail-action {
        display: inline-flex;
        align-items: center;
        gap: 0.45rem;
        padding: 0.5rem 0.8rem;
        border: 1px solid var(--border);
        border-radius: 999px;
        background: var(--bg-nav);
        color: var(--text-muted);
        font-size: 0.9rem;
        line-height: 1;
        white-space: nowrap;
        cursor: pointer;
    }

    .mobile-rail-action:hover,
    .mobile-rail-action:focus-visible {
        color: var(--accent);
        border-color: var(--accent);
        background: var(--accent-tint);
    }

    .mobile-rail-action:focus-visible {
        outline: 2px solid var(--accent);
        outline-offset: 2px;
    }

    .mobile-rail-action-glyph {
        display: inline-flex;
        font-size: 1rem;
    }

    /* ---- Minimap as a full-width sheet modal ---- */
    /* The desktop in-place tab is gone here; the rail's Map button opens it. */
    .post-minimap-tab {
        display: none;
    }

    .post-minimap-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 980;
        background: rgba(0, 0, 0, 0.5);
    }

    /* Full width, most of the height, anchored to the bottom so a strip of
     * the dimmed page stays visible above it (you can still see where you
     * are). A flex column: fixed header + scrollable map body. */
    .post-minimap-panel {
        position: fixed;
        inset: auto 0 0 0;
        width: 100vw;
        height: 88vh;
        max-height: none;
        z-index: 981;
        display: flex;
        flex-direction: column;
        padding: 0;
        border: 0;
        border-radius: 0.8rem 0.8rem 0 0;
        background: var(--surface);
        box-shadow: 0 -6px 24px var(--shadow-lg);
        overflow: hidden;
    }

    .post-minimap-modal-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 0.75rem 1rem;
        border-bottom: 1px solid var(--border-subtle);
    }

    .post-minimap-modal-title {
        font-size: 1rem;
        color: var(--text);
    }

    .post-minimap-modal-close {
        border: 0;
        background: transparent;
        color: var(--text-subtle);
        font-size: 1.6rem;
        line-height: 1;
        padding: 0 0.3rem;
        cursor: pointer;
    }

    /* See .feedback-panel-close:hover — same reset, same reason. */
    .post-minimap-modal-close:hover:not(:disabled) {
        background: transparent;
        color: var(--text);
    }

    .post-minimap-modal-close:focus-visible {
        outline: 2px solid var(--accent);
        outline-offset: 2px;
    }

    /* The scroll viewport. minimap.js measures THIS box (clientWidth/Height)
     * to scale the SVG so nodes stay tappable; a big tree then overflows and
     * pans, a small one is centred by the SVG's auto margins. */
    .post-minimap-svg-mount {
        flex: 1;
        min-height: 0;
        display: flex;
        overflow: auto;
        /* Keep single-finger native scroll-panning, but cede multi-finger
         * gestures to the JS pinch-to-zoom handler (a non-passive touchmove
         * preventDefault) instead of letting the browser pinch-zoom. */
        touch-action: pan-x pan-y;
    }

    /* Let the JS-scaled SVG exceed the viewport so it can pan, and centre it
     * when it's smaller. ``flex: none`` stops the flex mount from shrinking an
     * oversized map back down to fit (which would undo the tappable-node
     * scaling); ``max-width: none`` lifts the desktop cap. In flexbox an
     * ``auto`` margin never goes negative, so an oversized map sits at the
     * start (fully scrollable) while a small one is centred. */
    .post-minimap-svg {
        flex: none;
        max-width: none;
        margin: auto;
    }

    /* ---- "Report a bug" as a bottom-sheet modal ---- */
    /* On mobile the nav "report a bug" item is hidden; the slide-out rail's bug
     * button opens this same panel as a bottom-sheet instead. */
    .nav-report-bug {
        display: none;
    }

    .feedback-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 980;
        background: rgba(0, 0, 0, 0.5);
    }

    .feedback-panel {
        position: fixed;
        inset: auto 0 0 0;
        width: 100vw;
        max-width: none;
        max-height: 85vh;
        overflow-y: auto;
        z-index: 981;
        border-radius: 0.8rem 0.8rem 0 0;
        box-shadow: 0 -6px 24px var(--shadow-lg);
    }
}
