/* =============================================================
   MOBILE LAYER — loaded after the theme stylesheet, so rules are
   prefixed with `body` to outrank the theme's plain #id rules no
   matter which theme is swapped in.

   Deliberately small: the existing mobile layout (spacing, branch
   title row, bottom branch-tree drawer) is left exactly as it was.
   This file only:
     · drops the old top button strip
     · keeps things clear of the on-screen keyboard (--kb-inset,
       kept live by mobile.js)
   ============================================================= */

/* ── The bottom dock ─────────────────────────────────────────────
   Five things stack at the bottom of a phone screen and each one used to
   compose its own offset out of raw numbers, so they drifted apart — most
   visibly in the installed app, which draws edge-to-edge under the
   gesture bar. One set of variables now, and everything measures from it.

   The ride height, --kb-lift, is not computed here. Deriving it from
   clientHeight, the safe-area insets and the interactive-widget mode was
   wrong twice on the installed Android app — the error showed up as a
   black strip between the branch drawer and the keyboard. mobile.js now
   measures where the browser actually puts this dock and publishes the
   exact distance to the top of the keyboard. */
:root {
    /* Height of the on-screen keyboard, published by mobile.js */
    --kb-inset: 0px;
    --cf-safe-bottom: env(safe-area-inset-bottom, 0px);

    /* The branch-tree drawer's grab handle */
    --cf-handle: 40px;

    /* The floor the bottom furniture stands on: clear of the gesture bar,
       and clear of the drawer handle for anything sitting on top of it. */
    --cf-dock: calc(var(--cf-handle) + var(--cf-safe-bottom));

    /* How far that furniture rides up to meet the keyboard. Published by
       mobile.js from a measurement of this very dock; the fallback here is
       only for the moment before the first measurement lands. */
    --kb-lift: 0px;

    /* Bottom alert band. Messages belong out of the prose, not across it,
       so the band is the mic's own row — the gesture pill sits level with
       the mic button and fills the empty space to its left. Same value as
       #dictationRoot's `bottom`; the phone layer restates it because the
       phone puts the mic above the branch-tree handle. */
    --cf-alert-bottom: calc(1.25rem + var(--cf-safe-bottom));
    /* Toasts are a block, not a pill, so they stack a full mic-cluster
       higher — otherwise the two collide when one action fires both. */
    --cf-alert-stack: 5.2rem;

    /* Phone furniture that floats over the page (diagnostics readout,
       mindmap toolbar) needs an opaque backdrop. The theme flag on <body>
       is what flips it, since these panels are drawn by JS and so sit
       outside the theme stylesheet's reach. */
    --cf-scrim: rgba(0, 0, 0, 0.88);
}

body[data-cf-theme="light"] {
    --cf-scrim: rgba(245, 235, 209, 0.94);
}

/* Phone-only furniture, injected by mobile.js / quickActions.js — hidden
   unless the phone layer below turns it on (a touchscreen laptop would
   otherwise get unstyled buttons). */
#mobileOverlayExit,
#newBranchBtn,
#quickFanItems {
    display: none;
}

/* ── Gesture feedback pill ───────────────────────────────────────
   A swipe replaces everything on screen, so it has to name where it
   landed — otherwise a mis-swipe reads as losing your text. Brief, and
   as far out of the prose as it goes: level with the mic button, filling
   the empty run of screen to its left. A receipt, not a status bar.

   Left-anchored rather than centred because that's what leaves room —
   a centred pill wide enough to hold a branch title would reach under
   the mic. max-width stops it short of the mic cluster on the right.

   Styled outside the phone media query because gestures.js keys off the
   pointer type rather than the width — a tablet gets the gestures, so it
   has to get the feedback too. Nothing else ever creates this element. */
#gestureHint {
    position: fixed;
    left: 0.9rem;
    right: auto;
    bottom: var(--cf-alert-bottom);
    z-index: 9900;
    max-width: calc(100vw - 6.5rem);
    padding: 0.45em 0.9em;
    border-radius: 999px;
    background-color: var(--dict-bg, #000d00);
    color: var(--dict-fg-bright, #00ff00);
    border: 1px solid var(--dict-border, #004400);
    font-family: inherit;
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-transform: lowercase;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;

    opacity: 0;
    /* Rides the keyboard, exactly like the mic and the drawer. Grows from
       its left edge, since that's the edge it's anchored to. */
    transform-origin: left center;
    transform: translate3d(0, calc(-1 * var(--kb-lift)), 0) scale(0.92);
    transition: opacity 0.14s ease, transform 0.18s cubic-bezier(0.32, 0.72, 0, 1);
}

#gestureHint.visible {
    opacity: 0.94;
    transform: translate3d(0, calc(-1 * var(--kb-lift)), 0) scale(1);
}

@media (prefers-reduced-motion: reduce) {
    #gestureHint {
        transition: opacity 0.1s linear;
    }
}

/* ── Dock diagnostics ────────────────────────────────────────────
   Off unless the ☰ drawer's "dock" button turns it on. Pinned top-left
   because the whole point is to be readable while the keyboard is up. */
#cfDockDebug {
    position: fixed;
    top: calc(env(safe-area-inset-top, 0px) + 3rem);
    left: 0.5rem;
    z-index: 10100;
    margin: 0;
    padding: 0.5em 0.7em;
    background: var(--cf-scrim);
    color: var(--dict-fg-bright, #00ff00);
    border: 1px solid var(--dict-border, #004400);
    border-radius: 4px;
    font-family: 'Courier New', Courier, monospace;
    font-size: 0.62rem;
    line-height: 1.45;
    white-space: pre;
    pointer-events: none;
}

/* ── Toasts ──────────────────────────────────────────────────────
   The themes pin these bottom-right with no z-index, which put them
   underneath the floating mic on desktop and behind the branch-tree
   handle on a phone — a message you cannot read. Same band as the
   gesture pill, one step up so the two stack rather than overlap. */
body .toast {
    bottom: calc(var(--cf-alert-bottom) + var(--cf-alert-stack));
    z-index: 9900;
}

/* ── Full-screen overlay layering ────────────────────────────────
   The mindmap and the full tree are `.popup`s, which the themes park at
   z-index 1000 — under the floating mic (9500) and level with the mobile
   branch drawer, so both punched through the mindmap. Overlays outrank
   the app chrome now, and the chrome steps aside entirely while one is
   open. Kept below the modals at 10000 so a snapshot or clipboard dialog
   still opens over the mindmap.

   These live outside the phone media query on purpose — the mic floats
   over the mindmap on desktop too — and are `body`-prefixed because the
   theme stylesheet is appended after this file at runtime. */
body #mindmapPopup,
body #fullTreePopup {
    z-index: 9800;
}

body.overlay-open #dictationRoot,
body.overlay-open #branchTitleContainer,
body.overlay-open #wordCounter {
    display: none;
}

body.overlay-open #rightSidebar {
    visibility: hidden;
}

/* The drawer is a lighter thing than a full overlay — the mic stays on
   screen, it just belongs behind the dimming like everything else. Its own
   z-index (9500) is far above the drawer's, so it has to be dropped under
   the overlay explicitly, and made deaf so a stray tap on it closes the
   menu instead of starting a recording. The two themes disagree on the
   overlay's own z-index (100 dark, 1000 light), so this goes below both. */
body.menu-open #dictationRoot {
    z-index: 50;
    pointer-events: none;
}

@media (max-width: 768px) {

    /* The mic's row, which on a phone sits above the branch-tree handle —
       the same expression #dictationRoot uses for its own `bottom`. */
    :root {
        --cf-alert-bottom: calc(var(--cf-dock) + 0.9rem);
    }

    /* Left-anchored like the pill, and riding the keyboard like everything
       else down there. Its own row is clear of the mic, so it gets the
       full width. */
    body .toast {
        left: 0.9rem;
        right: 0.9rem;
        max-width: none;
        transform: translate3d(0, calc(-1 * var(--kb-lift)), 0);
    }

    /* The top strip is gone — Prev / Branch / Delete / Complete now
       live in the ☰ drawer. */
    body #mobileButtons {
        display: none !important;
    }

    /* The word counter kept the desktop's absolute positioning (top: 4.5%),
       which dropped it below the hamburger. Put it back in the flex row and
       centre both on the same line.

       The top padding is the other half of viewport-fit=cover: the
       installed app draws under the status bar, so without this the
       hamburger sits behind the clock. Zero in a browser tab, where the
       browser's own chrome is already holding that space. */
    body #topMobile {
        align-items: center;
        padding-top: env(safe-area-inset-top, 0px);
        box-sizing: border-box;
    }

    body #hamburgerMenu {
        line-height: 1;
    }

    body #wordCounter {
        position: static;
        top: auto;
        right: auto;
        width: auto;
        margin-left: auto;
        padding-right: 0.8em;
        line-height: 1;
    }

    /* The app owns its scrolling — the editor scrolls, the page never does.
       Without this, iOS drags the whole layout down to chase the caret and
       everything fixed slides in behind the keyboard. */
    html,
    body {
        overscroll-behavior: none;
    }

    /* The theme sets `height: 100vh`, which is the LARGE viewport — it does
       not shrink for the keyboard. Under the installed app's
       interactive-widget=resizes-content the layout viewport does shrink,
       so body ended up taller than the screen; body centres its children,
       so the overflow was split top and bottom and the branch title row
       fell off the top. `100dvh` tracks the viewport that's actually
       there, and is never larger than 100vh in the other mode either. */
    body {
        overflow: hidden;
        height: 100dvh;
    }

    /* Floating mic rides above the keyboard instead of hiding behind it.
       Offset via transform, not `bottom` — `bottom` relayouts every frame. */
    body #dictationRoot {
        bottom: calc(var(--cf-dock) + 0.9rem);
        transform: translate3d(0, calc(-1 * var(--kb-lift)), 0);
        transition: transform 0.22s cubic-bezier(0.32, 0.72, 0, 1);
        will-change: transform;
    }

    /* The button keeps its size when the keyboard arrives. It used to shrink
       to 2.9rem to give the text room, but a control that changes shape under
       your thumb is a control you have to re-aim at every time — and the
       satellites orbit off this number, so the whole cluster resized with it.
       Fading it slightly is enough to say "you are typing now". */
    body.keyboard-open #dictationBtn {
        opacity: 0.9;
    }

    /* The panel opens above the mic, which rides above the keyboard — so
       the room it has is the visible strip, and --vv-height IS the visible
       strip, measured. The old `100dvh - --kb-inset` subtracted the
       keyboard from a height that had already had the keyboard taken out
       of it in the installed app, leaving a two-line scrollable stub. */
    body.keyboard-open #dictationPanel {
        max-height: calc(var(--vv-height, 100dvh) - 9rem);
    }

    /* The branch-tree drawer handle (40px) always overlays the bottom of
       the editor, so the text has to end above it — otherwise the last
       lines slide underneath as you type. */
    body #editor,
    body #highlighting {
        padding-bottom: calc(var(--cf-dock) + 3rem);
    }

    /* …plus the keyboard, when it's up. */
    body.keyboard-open #editor,
    body.keyboard-open #highlighting {
        padding-bottom: calc(var(--cf-dock) + var(--kb-lift) + 3rem);
    }

    /* Momentum scrolling inside the editor, contained so a flick at the
       end of the text doesn't bounce the whole page. */
    body #editor {
        -webkit-overflow-scrolling: touch;
        overscroll-behavior: contain;
    }

    @media (prefers-reduced-motion: reduce) {
        body #dictationRoot,
        body #rightSidebar {
            transition: none;
        }
    }

    /* The branch-tree drawer. Collapse state AND the keyboard offset are
       folded into one transform so the slide stays on the compositor —
       animating `bottom` alongside it was the source of the jitter. */
    /* Docked above the gesture bar rather than at the physical bottom
       edge — collapsing reveals the sheet's TOP strip, so bottom padding
       would have gone off-screen and left the handle you grab underneath
       the system UI. With the sheet itself lifted, the strip it reveals is
       exactly the handle, and --cf-dock (handle + safe) stays the right
       floor for everything stacked on top of it. */
    body #rightSidebar {
        bottom: var(--cf-safe-bottom);
        transform: translate3d(0, calc(100% - var(--cf-handle) + 1px - var(--kb-lift)), 0);
        transition: transform 0.32s cubic-bezier(0.32, 0.72, 0, 1);
        will-change: transform;
        contain: paint;
    }

    body #rightSidebar.uncollapsed {
        transform: translate3d(0, calc(-1 * var(--kb-lift)), 0);
    }

    /* An open tree sheet and an open keyboard fight for the same space */
    body.keyboard-open #rightSidebar.uncollapsed {
        max-height: calc(var(--vv-height, 100dvh) - 4rem);
    }

    /* ── Touch responsiveness ────────────────────────────────── */

    /* Kills the ~300ms click delay and the grey flash on tap */
    body button,
    body .menu-header,
    body #hamburgerMenu,
    body .tree-node,
    body .branch-link {
        touch-action: manipulation;
        -webkit-tap-highlight-color: transparent;
    }

    /* Real-time blur behind a moving element is the most expensive thing a
       phone GPU can be asked for — the frosted look isn't worth the frames */
    body .thumbnail-display,
    body .mm-zoom-btn {
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    /* Long branch lists: skip rendering rows that are off-screen */
    body #directoryTree {
        content-visibility: auto;
        contain-intrinsic-size: auto 600px;
    }

    /* ── Drawer ──────────────────────────────────────────────── */

    /* The theme slides this with `left`, which relayouts the whole drawer
       every frame. Pin it and animate transform instead. */
    body #mobileMenu {
        left: 0;
        transform: translate3d(-102%, 0, 0);
        transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1);
        will-change: transform;
        contain: paint;
    }

    body #mobileMenu.hidden {
        left: 0;
        transform: translate3d(-102%, 0, 0);
    }

    body #mobileMenu.visible {
        left: 0;
        transform: translate3d(0, 0, 0);
    }

    /* No sideways slide of the app behind the drawer on a phone.
       #rightSidebar — the branch-tree sheet — lives INSIDE #container and is
       position:fixed. A transform on #container makes it the containing
       block for that fixed child, so the sheet stops measuring from the
       viewport and starts measuring from a box that is only 95% as tall:
       its `bottom` jumps up and the drawer appears to crack open every time
       the menu is opened. The drawer and its overlay carry the transition
       on their own, so the slide bought nothing worth that. */
    body #container {
        transition: none;
    }

    body #container.menu-open {
        transform: none;
    }

    /* ── Drawer quick actions — glyph with a small caption ───── */

    body .mobile-quick-actions button {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 0.15em;
        min-height: 54px;
        padding: 0.35em 0.2em;
    }

    /* Toggles in the drawer (shake, haptics) read their state from here */
    body .mobile-quick-actions button.active {
        color: var(--dict-fg-bright, #00ff00);
    }

    body .mobile-quick-actions button.active .qa-label {
        opacity: 1;
    }

    body .mobile-quick-actions .qa-glyph {
        font-size: 1.15rem;
        line-height: 1;
    }

    body .mobile-quick-actions .qa-label {
        font-size: 0.55rem;
        letter-spacing: 0.12em;
        text-transform: uppercase;
        opacity: 0.65;
        line-height: 1;
    }

    /* ── Section bands ───────────────────────────────────────────
       Banded by what the button acts on, sized by reach rather than by
       importance on paper: file and view are small and up top, branch moves
       are mid, the things you hit mid-sentence — undo/redo, complete,
       dictate — are the big targets nearest the bottom edge where a thumb
       already is, and the set-and-forget switches are smallest, last.

       The banding is deliberately faint. A drawer that has to be scrolled
       to reach the bottom row would undo the whole point, so the labels
       cost one short line each and the rows tighten to pay for them. */

    body .qa-section {
        margin-bottom: 0.55em;
    }

    body .qa-section-label {
        font-size: 0.5rem;
        letter-spacing: 0.22em;
        text-transform: uppercase;
        opacity: 0.4;
        margin: 0 0 0.25em 0.15em;
        line-height: 1;
    }

    /* The rows inside a band read as one block, so they sit tighter than
       the gap between bands. */
    body .qa-section .mobile-quick-actions {
        margin-bottom: 4px;
    }

    body .qa-section .mobile-quick-actions:last-child {
        margin-bottom: 0;
    }

    /* File and view: one row each, and neither is worth a big target. */
    body .qa-file .mobile-quick-actions button,
    body .qa-view .mobile-quick-actions button {
        min-height: 46px;
        padding: 0.3em 0.15em;
    }

    body .qa-file .mobile-quick-actions .qa-glyph,
    body .qa-view .mobile-quick-actions .qa-glyph {
        font-size: 1.05rem;
    }

    /* Settings: four switches you set once. Smallest band, and last, which
       is what buys the editor band its size without lengthening the drawer. */
    body .qa-misc .mobile-quick-actions button {
        min-height: 40px;
        padding: 0.25em 0.1em;
        gap: 0.1em;
    }

    body .qa-misc .mobile-quick-actions .qa-glyph {
        font-size: 0.9rem;
    }

    body .qa-misc .mobile-quick-actions .qa-label {
        font-size: 0.45rem;
        letter-spacing: 0.06em;
    }

    /* Editor: the bottom band, and the one you use without looking. */
    body .qa-editor .mobile-quick-actions button {
        min-height: 68px;
        padding: 0.5em 0.2em;
        gap: 0.25em;
    }

    body .qa-editor .mobile-quick-actions .qa-glyph {
        font-size: 1.6rem;
    }

    body .qa-editor .mobile-quick-actions .qa-label {
        font-size: 0.62rem;
        opacity: 0.8;
    }

    /* ── New-branch satellite on the mic ─────────────────────── */

    /* Sits on the mic's rim, positioned by angle rather than by corner:
       --nb-angle is measured clockwise from top-centre (0deg = straight up,
       45deg = the top-right corner). The second rotate cancels the arc
       rotation so the glyph stays upright.

       The orbit is derived from the mic's own diameter, so the satellite
       stays on the rim at every size — hardcoding it meant the cluster
       changed proportions whenever the mic shrank for the keyboard. The
       press nudges it outward along the same arc rather than scaling it,
       which would drag it off the rim. */
    body #newBranchBtn {
        /* Upper-right. Settings sit at -55° and mode at 0° (dictation.css),
           so the three land at even 55° steps across the top of the rim. */
        --nb-angle: 55deg;
        --nb-nudge: 0rem;
        --nb-spin: 0deg;
        --nb-radius: calc(var(--dict-size) / 2 + 0.12rem + var(--nb-nudge));

        position: absolute;
        top: 50%;
        left: 50%;
        margin: -0.8rem 0 0 -0.8rem;
        transform: rotate(var(--nb-angle))
                   translateY(calc(-1 * var(--nb-radius)))
                   rotate(calc(var(--nb-spin) - var(--nb-angle)));
        width: 1.6rem;
        height: 1.6rem;
        z-index: 3;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 0;
        border-radius: 50%;
        cursor: pointer;
        font-family: inherit;
        font-size: 0.85rem;
        line-height: 1;
        background-color: var(--dict-bg, #000d00);
        color: var(--dict-fg, #00bb00);
        border: 1px solid var(--dict-border, #004400);
        -webkit-tap-highlight-color: transparent;
        touch-action: manipulation;
        transition: transform 0.24s cubic-bezier(0.32, 0.72, 0, 1),
                    color 0.15s ease, border-color 0.15s ease;
    }

    body #newBranchBtn:active {
        --nb-nudge: 0.2rem;
        color: var(--dict-fg-bright, #00ff00);
        border-color: var(--dict-fg-bright, #00ff00);
    }

    /* Open turns the + into an × without moving it: spin the glyph in
       place, leaving the orbit alone. */
    body #newBranchBtn.open {
        --nb-spin: 135deg;
        color: var(--dict-fg-bright, #00ff00);
        border-color: var(--dict-fg-bright, #00ff00);
    }

    /* ── Quick action fan ────────────────────────────────────── */

    /* Rises from the mic; absolute, so opening shifts nothing else in the
       cluster. The gap clears the + on the rim and keeps the stack out
       from under the thumb that opened it. */
    body #quickFanItems {
        position: absolute;
        bottom: calc(100% + 1.75rem);
        right: 0;
        display: flex;
        flex-direction: column;
        gap: 0.4rem;
        align-items: flex-end;
    }

    body .qf-item {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 0.05em;
        width: 3rem;
        height: 3rem;
        padding: 0;
        border-radius: 50%;
        cursor: pointer;
        font-family: inherit;
        background-color: var(--dict-bg, #000d00);
        color: var(--dict-fg, #00bb00);
        border: 1px solid var(--dict-border, #004400);
        box-shadow: inset 0 0 12px var(--dict-glow, rgba(0, 255, 0, 0.25));
        -webkit-tap-highlight-color: transparent;
        touch-action: manipulation;

        opacity: 0;
        pointer-events: none;
        transform: translate3d(0, calc(var(--qf-i) * 0.7rem), 0) scale(0.7);
        transition: transform 0.24s cubic-bezier(0.32, 0.72, 0, 1),
                    opacity 0.16s ease;
    }

    body #quickFanItems[data-open="true"] .qf-item {
        opacity: 1;
        pointer-events: auto;
        transform: translate3d(0, 0, 0) scale(1);
        /* Nearest button leads, the rest follow it out */
        transition-delay: calc((var(--qf-i) - 1) * 40ms);
    }

    body .qf-item:active {
        color: var(--dict-fg-bright, #00ff00);
        border-color: var(--dict-fg-bright, #00ff00);
        transform: scale(0.94);
    }

    /* Destructive, and at the far end of the stack — mark it so a fumbled
       tap is at least recognisable on the way down. */
    body .qf-item[data-act="delete"] {
        color: var(--dict-danger, #ff4444);
    }

    body .qf-item[data-act="delete"]:active {
        color: var(--dict-danger, #ff4444);
        border-color: var(--dict-danger, #ff4444);
    }

    body .qf-glyph {
        font-size: 1.05rem;
        line-height: 1;
    }

    body .qf-label {
        font-size: 0.48rem;
        letter-spacing: 0.1em;
        text-transform: uppercase;
        opacity: 0.65;
        line-height: 1;
    }

    @media (prefers-reduced-motion: reduce) {
        body .qf-item {
            transition: opacity 0.1s linear;
        }
    }

    @media (prefers-reduced-motion: reduce) {
        body #newBranchBtn {
            transition: opacity 0.1s linear;
        }
    }

    /* ── Full-screen overlay exit ────────────────────────────── */

    body #mobileOverlayExit {
        position: fixed;
        top: calc(env(safe-area-inset-top, 0px) + 0.35rem);
        left: 0.35rem;
        z-index: 10050;
        display: none;
        align-items: center;
        min-height: 2.5rem;
        padding: 0 0.6em;
        background: transparent;
        border: none;
        color: var(--dict-fg-bright, #00ff00);
        font-family: inherit;
        font-size: 0.8rem;
        letter-spacing: 0.12em;
        line-height: 1;
        cursor: pointer;
        -webkit-tap-highlight-color: transparent;
    }

    body #mobileOverlayExit.visible {
        display: flex;
    }

    /* The mic must not sit on top of an overlay */
    body.overlay-open #dictationRoot {
        display: none;
    }

    /* ── Directory tree popup ────────────────────────────────── */

    body #fullTreePopup .popup-content {
        margin: 0;
        width: 100%;
        max-width: none;
        height: 100dvh;
        max-height: none;
        border-radius: 0;
        padding: calc(env(safe-area-inset-top, 0px) + 3.5rem) 0.75rem
                 calc(var(--cf-safe-bottom) + 1rem);
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
    }

    body #fullTreePopup h2 {
        margin: 0 0 0.75rem 0;
        font-size: 0.8rem;
        letter-spacing: 0.18em;
        text-transform: uppercase;
        text-align: center;
    }

    /* The tiny × is replaced by the fixed exit button */
    body #fullTreePopup .close-btn {
        display: none;
    }

    /* ASCII tree lines must not wrap — scroll them sideways instead */
    body #fullTreeContent {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        font-size: 0.82rem;
    }

    body #fullTreeContent .tree-node {
        min-height: 2.4rem;
        display: flex;
        align-items: center;
        white-space: nowrap;
        padding: 0.15rem 0.2rem;
    }

    body .drag-mode-bar {
        display: flex;
        flex-wrap: wrap;
        gap: 0.35rem;
        margin-bottom: 0.75rem;
    }

    body .drag-mode-toggle {
        min-height: 2.4rem;
        font-size: 0.75rem;
    }

    /* ── Mindmap ─────────────────────────────────────────────── */

    body #mindmapPopup {
        height: 100dvh;
    }

    body #mindmap-container {
        height: 100dvh;
        /* Own every gesture — no page scroll or browser zoom stealing them */
        touch-action: none;
        overscroll-behavior: none;
    }

    /* One non-wrapping strip that scrolls sideways: wrapping made the
       buttons overlap the map and each other. Left padding clears the
       fixed "‹ back". */
    body #mindmap-toolbar {
        top: 0;
        left: 0;
        right: 0;
        box-sizing: border-box;
        padding: calc(env(safe-area-inset-top, 0px) + 0.3rem) 0.4rem 0.3rem 5.25rem;
        background: var(--cf-scrim);
        border-bottom: 1px solid var(--dict-border, #004400);
        display: flex;
        flex-wrap: nowrap;
        align-items: center;
        justify-content: flex-start;
        gap: 0.1rem;
        overflow-x: auto;
        overflow-y: hidden;
        -webkit-overflow-scrolling: touch;
        touch-action: pan-x;
        scrollbar-width: none;
    }

    body #mindmap-toolbar::-webkit-scrollbar {
        display: none;
    }

    body #mindmap-toolbar > * {
        flex: 0 0 auto;
    }

    body .mm-tool-btn {
        min-height: 2.4rem;
        padding: 0 0.5em;
        font-size: 0.7rem;
        letter-spacing: 0.5px;
        white-space: nowrap;
    }

    body .mm-toolbar-sep {
        width: 0.5rem;
    }

    /* Superseded by the fixed exit button */
    body #mindmap-toolbar .mm-close-btn {
        display: none;
    }

    /* Fixed width — the desktop focus-expand pushed the row off-screen */
    body #mindmap-search,
    body #mindmap-search:focus {
        width: 6rem;
        min-height: 2.4rem;
        padding: 0 0.35em;
    }

    body #mindmap-search-count {
        min-width: 2.2em;
        font-size: 0.65rem;
    }

    body #mindmap-zoom-controls {
        bottom: calc(1.25rem + var(--cf-safe-bottom));
        right: 1rem;
    }

    body .mm-zoom-btn {
        width: 2.9rem;
        height: 2.9rem;
    }
}
