@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&family=Spline+Sans:wght@400;500;600;700&display=swap');

  :root {
    /* Color Schema 1 */
    --bg: #f5f3f8;
    --bg-grid: #e4deec;
    --panel: #ffffff;
    --panel-2: #fbfafd;
    --panel-border: #d6cfde;
    --edge: #8c7ea1;
    --ink: #23004c;
    --ink-soft: #6c5c82;
    --accent: #7a00e6;
    --accent-2: #da3a16;
    --accent-soft: #ebd9fb;
    --shadow: rgba(35, 0, 76, 0.10);
    --shadow-strong: rgba(35, 0, 76, 0.20);
    --menu-h: 38px;
    --tool-h: 52px;
    --font-display: 'Fraunces', Georgia, serif;
    --font-body: 'Spline Sans', sans-serif;
    /* The CHROME — menus, toolbar, panels, dialogs — uses the system UI face so
       the app matches the desktop it is running on. The brand faces stay on the
       map itself and on titles, which is the content. Setting the brand face on
       menus and buttons was the single loudest "this is a web page" tell. */
    --font-ui: system-ui, 'Segoe UI Variable Text', 'Segoe UI', -apple-system,
               BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
    /* One deliberate type ramp instead of nineteen ad-hoc values (10 → 28px,
       twelve of them visible at once). --fs-xs is the floor: below 11px a
       control label is uncomfortable at monitor distance. */
    --fs-xs: 11px;      /* dense labels, counts, captions */
    --fs-sm: 12px;      /* secondary chrome */
    --fs-md: 13px;      /* chrome default */
    --fs-lg: 15px;      /* panel titles */
    --fs-xl: 18px;      /* dialog titles */
    --fs-2xl: 24px;     /* the rare display number */

    /* Motion tokens. Kept few on purpose — three durations and three curves
       cover everything here, and a small set is what keeps motion feeling like
       one system rather than a pile of one-off transitions.
       --spring overshoots slightly (the >1 y-values) for sheets and pops;
       --ease-out is the workhorse for anything entering. */
    --dur-fast: 120ms;
    --dur-base: 200ms;
    --dur-slow: 320ms;          /* the brief's ceiling — nothing should exceed this */
    --ease-out: cubic-bezier(.22, 1, .36, 1);
    --ease-in-out: cubic-bezier(.4, 0, .2, 1);
    --spring: cubic-bezier(.34, 1.56, .64, 1);

    /* Height the software keyboard is covering; JS keeps it current. */
    --kb-inset: 0px;
  }
  [data-theme="dark"], [data-theme="black"] {
    --bg: #120026;
    --bg-grid: #1c0335;
    --panel: #1f0043;
    --panel-2: #260050;
    --panel-border: #342446;
    --edge: #9089a3;
    --ink: #f5f3f8;
    --ink-soft: #a89bbd;
    --accent: #9533eb;
    --accent-2: #e05839;
    --accent-soft: #2c0056;
    --shadow: rgba(0, 0, 0, 0.45);
    --shadow-strong: rgba(0, 0, 0, 0.65);
  }

  /* Black, for OLED panels (the S24 Ultra among them), where a true-black pixel
     is an unlit one: deeper contrast and less power than the purple dark theme.
     It is a THIRD choice rather than a redefinition of dark — the purple dark is
     the app's own look and someone on an LCD is better off with it, where black
     surfaces just read as flat grey.

     Only the surfaces go black. The ink, accents and edge colour stay as dark's,
     so nothing loses contrast, and panels keep a hair of lift (#0b0b0d) plus a
     visible border — pure black on pure black would erase every panel edge. */
  [data-theme="black"] {
    --bg: #000000;
    --bg-grid: #0d0d10;      /* the dot grid, barely there */
    --panel: #000000;        /* panels are black too — that is the point */
    --panel-2: #0a0a0c;      /* only nested fills lift, so inputs stay findable */
    --panel-border: #3a3a42; /* the ONLY separator left, so it has to be seen */
    --accent-soft: #24103a;
    /* A shadow on black is invisible, so panels are defined by their border. */
    --shadow: rgba(0, 0, 0, 0.9);
    --shadow-strong: rgba(0, 0, 0, 1);
  }
  * { box-sizing: border-box; margin: 0; padding: 0; }
  #app-root {
    position: fixed; inset: 0; font-family: var(--font-body);
    color: var(--ink); background: var(--bg); overflow: hidden;
    user-select: none; -webkit-user-select: none;
    display: flex; flex-direction: column;
    /* keep the chrome clear of notches / home-indicator on edge-to-edge phones */
    padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
  }

  /* ---------- Chrome typography ----------
     Applied at the container so every control inherits it, rather than being
     repeated on each rule. An explicit line-height goes with it: the chrome had
     none, so vertical rhythm was whatever each font happened to default to and
     drifted between panels. Node text and .insp-title/.gal-title keep the brand
     faces — those are content, not chrome. */
  #menubar, #toolbar, .menu-dropdown, #inspector, .modal-panel, #ctx-menu,
  #outline, #gallery, #filter-menu, #export-menu, #hint {
    font-family: var(--font-ui);
    line-height: 1.45;
  }

  /* ---------- Menu bar ---------- */
  #menubar {
    flex: 0 0 auto; min-height: var(--menu-h);
    background: var(--panel); border-bottom: 1px solid var(--panel-border);
    display: flex; align-items: center; padding: 4px 10px; z-index: 200; gap: 2px;
    overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; scrollbar-width: thin;
  }
  #menubar .menu-item, #menubar .doc-title, #menubar .logo, #menubar #save-pip { flex: none; }
  #menubar .logo { width: 22px; height: 22px; border-radius: 6px; background: linear-gradient(135deg,var(--accent),var(--accent-2)); margin-right: 10px; display:flex;align-items:center;justify-content:center;color:#fff;font-weight:800;font-size:13px;font-family:var(--font-display);}
  .menu-item { position: relative; padding: 6px 11px; border-radius: 7px; font-size: 13.5px; cursor: pointer; }
  .menu-item:hover, .menu-item.open { background: var(--bg-grid); }
  .menu-sep { color: var(--panel-border); margin: 0 6px; }
  #save-pip { font-size: var(--fs-xs); font-weight: 600; color: var(--ink-soft); opacity: 0; transition: opacity .25s; margin-left: 8px; }
  #save-pip.on { opacity: .85; }
  .doc-title { font-size: 13.5px; color: var(--ink-soft); padding: 6px 8px; }
  .doc-title:hover { background: var(--bg-grid); border-radius: 7px; cursor: text; color: var(--ink); }
  .menu-dropdown {
    /* fixed, not absolute: #menubar is a scroll container (overflow-x below),
       and scroll containers clip absolutely-positioned descendants — the
       dropdown would render as nothing but a sliver of shadow. JS sets
       left/top from the menu item's viewport rect on open. */
    position: fixed; min-width: 220px;
    max-height: calc(100vh - 64px); overflow-y: auto;
    background: var(--panel); border: 1px solid var(--panel-border);
    border-radius: 10px; box-shadow: 0 10px 30px var(--shadow-strong);
    padding: 5px; display: none; z-index: 700;
  }
  .menu-dropdown.show { display: block; }
  .menu-row { display: flex; justify-content: space-between; gap: 24px; align-items:center; padding: 7px 11px; border-radius: 7px; font-size: 13px; cursor: pointer; }
  .menu-row:hover { background: var(--bg-grid); }
  .menu-row .k { color: var(--ink-soft); font-size: 11px; }
  .menu-row.disabled { opacity: 0.4; cursor: default; }
  .menu-row.disabled:hover { background: transparent; }
  .menu-divider { height: 1px; background: var(--panel-border); margin: 4px 6px; }
  .menu-check { width: 14px; display:inline-block; color: var(--accent); }

  /* ---------- Toolbar ---------- */
  #toolbar {
    flex: 0 0 auto;
    background: var(--panel-2); border-bottom: 1px solid var(--panel-border);
    display: flex; align-items: center; padding: 0 12px; z-index: 190; gap: 3px;
    min-height: var(--tool-h);
    /* Buttons never fit a phone-width toolbar; scroll rather than clip so every
       control stays reachable instead of silently running off the right edge. */
    overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; scrollbar-width: thin;
  }
  .tb-btn {
    border: none; background: transparent; color: var(--ink); cursor: pointer;
    height: 36px; min-width: 36px; padding: 0 8px; border-radius: 9px; font-size: 13px;
    display: flex; align-items: center; justify-content: center; gap: 6px;
    transition: background .12s, color .12s; position: relative;
    flex: none;
  }
  .tb-btn:hover { background: var(--bg-grid); }
  .tb-btn:active { transform: scale(.94); }
  .tb-btn.active { background: var(--accent); color: #fff; }
  .tb-btn:disabled { opacity: .3; cursor: default; }
  .tb-btn svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
  .tb-btn .tip {
    position: absolute; top: 42px; left: 50%; transform: translateX(-50%);
    background: var(--ink); color: var(--bg); font-size: 11px; padding: 4px 8px;
    border-radius: 6px; white-space: nowrap; opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity .15s, visibility .15s; z-index: 400;
  }
  .tb-btn:hover .tip { opacity: .95; visibility: visible; }
  .tb-divider { width: 1px; height: 26px; background: var(--panel-border); margin: 0 5px; flex: none; }
  .tb-spacer { flex: 1 0 12px; }
  .style-swatch {
    width: 26px; height: 26px; border-radius: 50%;
    background: conic-gradient(from 0deg, #7a00e6, #da3a16, #268500, #007fad, #d515b9, #7a00e6);
    border: 2px solid var(--panel); box-shadow: 0 0 0 1.5px var(--panel-border);
  }
  #tb-search {
    display: flex; align-items: center; gap: 6px; background: var(--bg-grid);
    border-radius: 9px; padding: 0 10px; height: 36px; flex: none;
    box-shadow: 0 0 0 0 transparent; transition: box-shadow .12s;
  }
  #tb-search:focus-within { box-shadow: 0 0 0 2px var(--accent); }
  #tb-search input { border: none; background: transparent; outline: none; color: var(--ink); font-family: var(--font-body); font-size: 13px; width: 120px; }
  #tb-search svg { width: 15px; height: 15px; stroke: var(--ink-soft); fill: none; stroke-width: 2; }

  /* ── Window Controls Overlay ─────────────────────────────────────────────
     Installed, the app drew a browser title bar ABOVE its own menu bar: two
     bars where a desktop app has one. With `window-controls-overlay` first in
     the manifest's display_override, the page owns that strip and the menu bar
     becomes the title bar — the arrangement Electron apps use.

     Every value is an env() WITH A FALLBACK, so a browser that does not grant
     the overlay (or a plain tab) computes exactly what it did before and this
     block is inert. `titlebar-area-width` excludes the window buttons, which is
     what stops the menu bar running underneath them. */
  @media (display-mode: window-controls-overlay) {
    #menubar {
      margin-left: env(titlebar-area-x, 0px);
      width: env(titlebar-area-width, 100%);
      height: env(titlebar-area-height, var(--menu-h));
      min-height: env(titlebar-area-height, var(--menu-h));
      -webkit-app-region: drag; app-region: drag;
    }
    /* Only the menus opt out of dragging; the title and save pip stay draggable
       so the bar can be grabbed where a title bar normally is. */
    #menubar .menu-item { -webkit-app-region: no-drag; app-region: no-drag; }
  }

  /* ── Touch sizing for the app chrome ─────────────────────────────────────
     Coarse-pointer only; desktop keeps its compact bar. Measured before this:
     toolbar buttons 36x36, menu items 43.8x28, zoom buttons 32x32 — all under
     the 44px floor, and unlike the canvas handles these are NOT affected by
     the zoom transform, so the numbers were already the on-screen truth. */
  @media (pointer: coarse) {
    :root { --menu-h: 48px; --tool-h: 56px; }
    .menu-item { min-height: 44px; display: flex; align-items: center; font-size: 15px; padding: 0 14px; }
    .doc-title { min-height: 44px; display: flex; align-items: center; font-size: 15px; }
    .tb-btn { height: 44px; min-width: 44px; padding: 0 11px; }
    .tb-btn svg { width: 21px; height: 21px; }
    #tb-search { height: 44px; }
    #tb-search input { font-size: 16px; width: 108px; }   /* 16px: iOS won't auto-zoom */
    /* NOTE: #zoom-ctl button is overridden further down, next to its own base
       rule — same specificity means source order wins, and the base is defined
       after this block. Same trap the node-handle rules carry a warning about. */
    .menu-row { min-height: 44px; font-size: 15px; }
    .ctx-item { min-height: 44px; display: flex; align-items: center; font-size: 15px; }
    .gal-tab { min-height: 44px; }
    .tpl-ov-btn { min-height: 40px; font-size: 13px; }
    .fold-item { min-height: 44px; }
    .fold-new { min-height: 44px; }
    /* the tooltip is a hover affordance; on touch it only ever gets in the way */
    .tb-btn .tip { display: none; }
  }

  /* ---------- Canvas ---------- */
  #canvas-wrap {
    flex: 1 1 auto; position: relative; min-height: 0;
    cursor: grab; overflow: hidden;
    background-color: var(--bg);
    background-image: radial-gradient(var(--bg-grid) 1.3px, transparent 1.3px);
    background-size: 24px 24px;
  }
  #canvas-wrap.panning { cursor: grabbing; }
  #canvas { position: absolute; top: 0; left: 0; transform-origin: 0 0; will-change: transform; }
  /* #canvas has no intrinsic size (every child is position:absolute, so none
     contribute to auto-sizing) - it collapses to 0x0. #edges was width/height
     100%, i.e. also 0x0, and a zero-sized outermost <svg> renders nothing at
     all regardless of overflow: visible, per the SVG spec - so the connector
     lines were never painted. A fixed, generously large box sidesteps that;
     overflow:visible still lets paths outside it paint on very large maps. */
  #edges { position: absolute; top: 0; left: 0; width: 6000px; height: 6000px; overflow: visible; pointer-events: none; }

  /* ---------- Nodes ---------- */
  .node {
    position: absolute; min-width: 88px; max-width: 280px; padding: 9px 15px;
    background: var(--node-bg, var(--panel)); border: 2px solid var(--node-border, var(--panel-border));
    color: var(--node-ink, var(--ink)); box-shadow: 0 2px 8px var(--shadow);
    font-size: 14px; line-height: 1.35; cursor: pointer;
    transition: box-shadow .15s, border-color .15s; word-wrap: break-word; overflow-wrap: break-word;
    /* drag/long-press is handled entirely in JS via Pointer Events; stop the
       browser's own scroll/zoom gestures from competing with it on touch */
    touch-action: none;
  }
  .node.shape-rounded { border-radius: 11px; }
  .node.shape-pill { border-radius: 999px; }
  .node.shape-rect { border-radius: 3px; }
  .node.shape-cloud { border-radius: 42% 58% 54% 46% / 56% 44% 60% 40%; }
  .node.shape-hex { clip-path: polygon(9% 0,91% 0,100% 50%,91% 100%,9% 100%,0 50%); padding-left: 24px; padding-right: 24px; }
  .node.shape-circle { border-radius: 50%; text-align: center; padding: 22px; min-width: 96px; }
  .node.shape-ellipse { border-radius: 50% / 50%; padding-left: 24px; padding-right: 24px; }
  .node.shape-diamond { clip-path: polygon(50% 0,100% 50%,50% 100%,0 50%); padding: 20px 34px; text-align: center; }
  .node.shape-skew { clip-path: polygon(12% 0,100% 0,88% 100%,0 100%); padding-left: 24px; padding-right: 24px; }
  .node.shape-octagon { clip-path: polygon(26% 0,74% 0,100% 26%,100% 74%,74% 100%,26% 100%,0 74%,0 26%); padding-left: 22px; padding-right: 22px; }
  /* underline / none drop the box entirely — a bare label on the canvas */
  .node.shape-underline { border-width: 0 0 2.5px 0; border-radius: 0; background: transparent; box-shadow: none; padding: 5px 6px; }
  .node.shape-none { border-color: transparent; background: transparent; box-shadow: none; }

  /* Border overrides — width/style only; the color still comes from the node's swatch. */
  .node.bd-thick { border-width: 4px; }
  .node.bd-dashed { border-style: dashed; }
  .node.bd-dotted { border-style: dotted; }
  .node.bd-none { border-color: transparent; }

  /* Per-node text size. em-based so the root's larger base size still scales. */
  .node.sz-s { font-size: 11.5px; }
  .node.sz-l { font-size: 17px; }
  .node.sz-xl { font-size: 21px; }
  .node.root.sz-s { font-size: 16px; }
  .node.root.sz-l { font-size: 25px; }
  .node.root.sz-xl { font-size: 31px; }

  .node.al-left { text-align: left; }
  .node.al-right { text-align: right; }
  .node.al-left .node-meta { justify-content: flex-start; }
  .node.al-right .node-meta { justify-content: flex-end; }

  .node.root { font-family: var(--font-display); font-size: 20px; font-weight: 600; padding: 14px 26px; }
  .node.selected { box-shadow: 0 0 0 3px var(--accent), 0 6px 18px var(--shadow-strong); z-index: 30; }
  .node:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
  .node.dragging { opacity: .55; cursor: grabbing; z-index: 999; }
  .node.drop-target { box-shadow: 0 0 0 3px var(--accent-soft), 0 0 0 6px var(--accent); }
  .node.dim { opacity: .16; }
  .node-text { outline: none; min-height: 1em; }
  .node-text[contenteditable="true"] { cursor: text; user-select: text; }
  .node-icon { margin-right: 5px; font-size: 1.05em; }
  .node-image { display: block; width: 100%; max-height: 140px; object-fit: cover; border-radius: 7px; margin-bottom: 7px; }
  .node-meta { display: flex; gap: 6px; align-items: center; margin-top: 5px; flex-wrap: wrap; }
  .node-tag { font-size: 10px; padding: 1px 7px; border-radius: 999px; background: var(--accent-soft); color: var(--ink); font-weight: 600; }
  .node-due { font-size: 10px; font-weight: 600; color: var(--ink-soft); }
  .node-due.overdue { color: var(--accent-2); }
  .node-check { width: 15px; height: 15px; flex: none; border-radius: 4px; border: 2px solid var(--ink-soft); display: inline-flex; align-items: center; justify-content: center; font-size: 11px; }
  .node-check.done { background: var(--accent); border-color: var(--accent); color: #fff; }
  .node-prio { font-size: 11px; font-weight: 700; }
  .node-pct { font-size: 10px; font-weight: 700; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
  .node-prog { display: block; height: 4px; border-radius: 999px; background: var(--bg-grid); margin-top: 6px; overflow: hidden; }
  .node-prog i { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
  .node-badges { position: absolute; top: -9px; right: -9px; display: flex; gap: 3px; }
  .node-badge { width: 18px; height: 18px; background: var(--accent); color: #fff; border-radius: 50%; font-size: 10px; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 5px var(--shadow); }
  .node-badge-link, .node-badge-diagram { cursor: pointer; transition: transform .1s; }
  .node-badge-link:hover, .node-badge-diagram:hover { transform: scale(1.18); }
  @media (pointer: coarse) { .node-badge-link, .node-badge-diagram { width: 24px; height: 24px; font-size: 12px; } }

  /* on-node + handle (Mindomo signature) */
  .node-plus {
    position: absolute; width: 22px; height: 22px; border-radius: 50%;
    background: var(--accent); color: #fff; font-size: 16px; font-weight: 400;
    display: none; align-items: center; justify-content: center; cursor: pointer;
    box-shadow: 0 2px 7px var(--shadow-strong); z-index: 40; line-height: 1;
    transition: transform .1s;
  }
  /* Cancel the canvas zoom so the handle keeps a constant on-screen size —
     see applyTransform(). transform-origin stays centred, so the anchor point
     JS computed from offsetWidth (which ignores transforms) remains correct. */
  .node-plus { transform: scale(var(--inv-zoom, 1)); transform-origin: 50% 50%; }
  .node-plus:hover { transform: scale(calc(var(--inv-zoom, 1) * 1.2)); }
  .node.selected .node-plus { display: flex; }
  /* S Pen hovering a topic: the stylus is the one input on a phone with a real
     hover state, so the add handle appears under it without costing a tap to
     select first. The outline says which topic the pen is over — on a 6.8"
     screen the nib hides a fair bit of what is beneath it. */
  .node.pen-hover .node-plus { display: flex; }
  .node.pen-hover { outline: 2px solid var(--accent-soft); outline-offset: 2px; }

  .node-collapse {
    position: absolute; width: 20px; height: 20px; border-radius: 50%;
    background: var(--panel); border: 2px solid var(--node-border, var(--panel-border));
    color: var(--ink); font-size: 13px; font-weight: 700; display: flex; align-items: center; justify-content: center;
    cursor: pointer; z-index: 20; box-shadow: 0 2px 5px var(--shadow); transition: transform .1s;
  }
  .node-collapse { transform: scale(var(--inv-zoom, 1)); transform-origin: 50% 50%; }
  .node-collapse:hover { transform: scale(calc(var(--inv-zoom, 1) * 1.18)); }

  /* Bump on-node hit targets closer to the ~44px touch minimum on coarse-pointer
     (touch) devices; JS reads the actual rendered size back to keep them centered.
     Must come after both base rules above to win the cascade at equal specificity. */
  @media (pointer: coarse) {
    /* 44px, not 30 — combined with the inverse-zoom scale above this is the
       real on-screen size a finger gets, at any magnification. */
    .node-plus { width: 44px; height: 44px; font-size: 26px; }
    .node-collapse { width: 44px; height: 44px; font-size: 20px; }
    /* The base rule transitions `transform` for the desktop hover pop — but the
       counter-zoom rides on the same property, so on touch the handle eased
       toward its new size over 100ms and lagged the zoom (measured 36.5px
       mid-transition where it settles at 44). There is no hover on touch, so
       drop the transition and let the counter-scale track the zoom exactly. */
    .node-plus, .node-collapse { transition: none; }
  }

  .boundary { position: absolute; border: 2px dashed var(--accent-soft); border-radius: 18px; background: color-mix(in srgb, var(--accent-soft) 16%, transparent); pointer-events: none; z-index: 1; }
  .boundary-label { position: absolute; top: -11px; left: 16px; font-size: 11px; font-weight: 700; background: var(--accent); color: #fff; padding: 1px 9px; border-radius: 8px; }

  /* on-node formatting toolbar, shown only while editing a node's text */
  .fmt-bar { position: absolute; display: flex; gap: 2px; background: var(--ink); border-radius: 8px; padding: 3px; box-shadow: 0 4px 12px var(--shadow-strong); z-index: 50; }
  .fmt-bar button { width: 26px; height: 26px; border: none; border-radius: 5px; background: transparent; color: var(--bg); font-size: 13px; cursor: pointer; }
  .fmt-bar button:hover { background: rgba(255,255,255,.18); }

  /* ---------- Inspector ---------- */
  #inspector {
    position: absolute; top: calc(var(--menu-h) + var(--tool-h) + 14px); right: 14px; width: 250px;
    background: var(--panel); border: 1px solid var(--panel-border); border-radius: 16px;
    box-shadow: 0 10px 34px var(--shadow); z-index: 90; padding: 16px;
    transform: translateX(calc(100% + 28px)); transition: transform .25s cubic-bezier(.2,.8,.2,1);
    max-height: calc(100vh - var(--menu-h) - var(--tool-h) - 28px); overflow-y: auto;
    pointer-events: none;
  }
  #inspector.open { transform: translateX(0); pointer-events: auto; }

  /* ── Inspector as a bottom sheet on touch ─────────────────────────────────
     As a right-hand panel it was 92% of a phone's width, so opening it covered
     the node you'd just tapped, that node's + handle, and the zoom controls —
     real touch hit-testing caught the panel swallowing taps meant for them.
     A bottom sheet is the platform answer: it owns the lower half and leaves
     the canvas above it live. JS keeps the selected node in that live strip. */
  @media (pointer: coarse) {
    /* 56vh is right in portrait but eats a landscape phone alive: at 851x393 it
       left a 64px strip of live canvas — less than one node — and the sheet is
       only worth opening because the canvas above it stays usable. Clamp it so a
       minimum strip always survives; portrait is tall enough that the 56vh arm
       still wins there, so this changes nothing on a normal phone. */
    :root {
      --sheet-h: min(56vh, calc(100vh - var(--menu-h) - var(--tool-h) - 132px));
      /* Which shape the inspector currently takes. JS reads this rather than
         re-deriving the breakpoint, so the gesture and the layout can never
         disagree about whether there is a bottom sheet to drag. */
      --sheet-mode: bottom;
    }
    #inspector {
      top: auto; left: 0; right: 0; bottom: 0; width: 100%;
      max-height: var(--sheet-h);
      border-radius: 18px 18px 0 0; border-bottom: none;
      padding: 8px 16px calc(16px + var(--kb-inset) + env(safe-area-inset-bottom));
      box-shadow: 0 -8px 30px var(--shadow-strong);
      transform: translateY(100%);
      transition: transform var(--dur-slow) var(--spring);
      overscroll-behavior: contain;   /* scrolling the sheet must not scroll the page behind it */
    }
    #inspector.open { transform: translateY(0); }
    .kb-open #inspector { max-height: calc(var(--sheet-h) - var(--kb-inset)); }
    /* grab-handle affordance — sticky so it stays put while the sheet scrolls */
    #inspector::before {
      content: ""; position: sticky; top: 0; z-index: 2;
      display: block; width: 40px; height: 4px; margin: 0 auto 10px;
      border-radius: 999px; background: var(--panel-border);
    }
    /* lift the zoom controls clear of the sheet rather than letting it bury them */
    #zoom-ctl { transition: transform var(--dur-base) var(--ease-out); }
    /* #zoom-ctl already sits 134px off the bottom, so lifting it by the FULL
       sheet height double-counted that clearance. Harmless in portrait (just
       higher than needed), fatal in landscape: at 851x393 it put the control at
       top:-19px, entirely off the screen. Lift only by however much the sheet
       exceeds the clearance it already has, and not at all when it doesn't. */
    #inspector.open ~ #zoom-ctl { transform: translateY(min(0px, calc(134px - var(--sheet-h) - 8px))); }
  }

  /* ── Inspector as a SIDE sheet on a landscape phone ───────────────────────
     A bottom sheet trades vertical space for width, which is the right trade on
     a portrait phone and the wrong one on a rotated phone: clamping it kept the
     canvas strip usable (127px) but never generous. Landscape has width to
     spare and no height, so the platform answer is to move the sheet to the
     side and give the canvas its full height back.

     Gated on width as well as height. 412x480 split-screen portrait is short
     but narrow, and a 360px side sheet there would cover nearly the whole
     window — that case stays a (clamped) bottom sheet.

     This block MUST follow the coarse block above: media queries add no
     specificity, so an earlier position would silently lose the cascade. */
  @media (pointer: coarse) and (max-height: 500px) and (min-width: 600px) {
    :root { --sheet-mode: side; }
    #inspector {
      /* Same top as the desktop panel's base rule. --menu-h/--tool-h are
         min-heights, so their sum (104px) understates the chrome's real 109px
         and anchoring to it exactly put the sheet 5px UNDER the toolbar; the
         base rule's +14px gap is an already-proven clearance. */
      top: calc(var(--menu-h) + var(--tool-h) + 14px); bottom: 0; left: auto; right: 0;
      width: min(360px, 42vw); max-height: none;
      border-radius: 18px 0 0 18px; border-bottom: 1px solid var(--panel-border);
      padding: 16px calc(16px + env(safe-area-inset-right)) calc(16px + var(--kb-inset) + env(safe-area-inset-bottom)) 16px;
      box-shadow: -8px 0 30px var(--shadow-strong);
      transform: translateX(100%);
      transition: transform var(--dur-slow) var(--spring);
    }
    #inspector.open { transform: translateX(0); }
    .kb-open #inspector { max-height: none; }
    /* No grab handle: a side sheet doesn't dismiss downward, and drawing the
       affordance would promise a gesture that isn't there. */
    #inspector::before { display: none; }
    /* The sheet now covers the right edge, where the zoom controls live, so move
       them aside instead of up. */
    #inspector.open ~ #zoom-ctl { transform: translateX(calc(-1 * min(360px, 42vw) - 8px)); }
  }
  .insp-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
  .insp-title { font-family: var(--font-display); font-size: 15px; font-weight: 600; }
  .insp-close { border: none; background: transparent; color: var(--ink-soft); font-size: 22px; line-height: 1; cursor: pointer; width: 28px; height: 28px; border-radius: 7px; }
  .insp-close:hover { background: var(--bg-grid); color: var(--ink); }
  .insp-section { margin-bottom: 15px; }
  .insp-label { font-size: var(--fs-xs); font-weight: 600; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 7px; }
  .swatch-row { display: flex; gap: 7px; flex-wrap: wrap; }
  .swatch { width: 26px; height: 26px; border-radius: 7px; cursor: pointer; border: 2px solid transparent; transition: transform .1s; }
  .swatch:hover { transform: scale(1.12); }
  .swatch.sel { border-color: var(--ink); }
  .shape-row { display: flex; gap: 6px; flex-wrap: wrap; }
  .shape-opt { flex: 1; min-width: 42px; height: 32px; border: 1.5px solid var(--panel-border); background: var(--bg); cursor: pointer; font-size: var(--fs-xs); color: var(--ink-soft); display: flex; align-items: center; justify-content: center; border-radius: 6px; transition: all .12s; text-transform: capitalize; }
  .shape-opt:hover { border-color: var(--accent); }
  .shape-opt.sel { background: var(--accent); color: #fff; border-color: var(--accent); }
  .insp-input, .insp-textarea { width: 100%; padding: 8px 10px; border: 1.5px solid var(--panel-border); border-radius: 8px; background: var(--bg); color: var(--ink); font-family: var(--font-body); font-size: 13px; outline: none; }
  .insp-input:focus, .insp-textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
  .insp-textarea { resize: vertical; min-height: 60px; }
  .insp-btn { width: 100%; padding: 9px; border: none; border-radius: 8px; cursor: pointer; font-family: var(--font-body); font-size: 13px; font-weight: 600; background: var(--bg-grid); color: var(--ink); transition: background .12s; margin-top: 6px; }
  .insp-btn:hover { background: var(--accent-soft); }
  .insp-btn.danger:hover { background: #da3a16; color: #fff; }
  .icon-grid { display: flex; gap: 5px; flex-wrap: wrap; }
  .icon-opt { width: 28px; height: 28px; font-size: 16px; cursor: pointer; border-radius: 6px; display: flex; align-items: center; justify-content: center; transition: background .1s; }
  .icon-opt:hover { background: var(--bg-grid); }
  .icon-opt.sel { background: var(--accent-soft); box-shadow: inset 0 0 0 1.5px var(--accent); }
  /* Icon library: scrolls inside itself so the rest of the inspector stays reachable. */
  .icon-lib { max-height: 208px; overflow-y: auto; margin-top: 7px; padding-right: 2px; }
  .icon-grp { font-size: var(--fs-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-soft); margin: 9px 0 5px; }
  .icon-grp:first-child { margin-top: 0; }


  /* Segmented control — border / size / align pickers */
  .seg-row { display: flex; gap: 5px; }
  .seg-row.wrap { flex-wrap: wrap; }
  .seg-row.wrap .seg-opt { flex: 1 1 30%; }
  .seg-opt { flex: 1; padding: 6px 4px; border: 1.5px solid var(--panel-border); border-radius: 6px; cursor: pointer; font-size: var(--fs-xs); font-weight: 600; text-align: center; color: var(--ink-soft); transition: all .1s; }
  .seg-opt:hover { border-color: var(--accent); }
  .seg-opt.sel { background: var(--accent); color: #fff; border-color: var(--accent); }

  .insp-row { display: flex; gap: 7px; align-items: center; }
  .insp-color { width: 44px; height: 32px; flex: none; padding: 0; border: 1.5px solid var(--panel-border); border-radius: 8px; background: none; cursor: pointer; }
  .insp-mini { flex: 1; padding: 7px; border: 1.5px solid var(--panel-border); border-radius: 8px; background: var(--bg); color: var(--ink-soft); font-family: var(--font-body); font-size: var(--fs-xs); font-weight: 600; cursor: pointer; }
  .insp-mini:hover:not(:disabled) { border-color: var(--accent); color: var(--ink); }
  .insp-mini:disabled { opacity: .5; cursor: default; }
  .insp-count { font-size: var(--fs-xs); font-weight: 700; color: var(--accent); font-variant-numeric: tabular-nums; }
  .insp-hint { font-size: var(--fs-xs); color: var(--ink-soft); margin-top: 6px; line-height: 1.4; }
  .insp-hint-inline { font-size: var(--fs-xs); font-weight: 500; color: var(--ink-soft); text-transform: none; letter-spacing: 0; }
  .insp-field { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
  .insp-field > span { flex: none; width: 58px; font-size: var(--fs-xs); color: var(--ink-soft); }
  .insp-range { width: 100%; accent-color: var(--accent); cursor: pointer; }

  /* Rendered note body — used in the inspector preview and presenter mode. */
  .note-rich { font-size: 12.5px; line-height: 1.5; }
  .note-rich h3, .note-rich h4, .note-rich h5, .note-rich h6 { font-family: var(--font-display); margin: 8px 0 4px; line-height: 1.25; }
  .note-rich h3 { font-size: 1.25em; } .note-rich h4 { font-size: 1.12em; } .note-rich h5, .note-rich h6 { font-size: 1em; }
  .note-rich p { margin: 0 0 6px; }
  .note-rich ul, .note-rich ol { margin: 0 0 6px; padding-left: 18px; }
  .note-rich li { margin-bottom: 2px; }
  .note-rich li.nb-task { list-style: none; margin-left: -18px; }
  .note-rich blockquote { margin: 0 0 6px; padding-left: 9px; border-left: 2.5px solid var(--accent-soft); color: var(--ink-soft); }
  .note-rich hr { border: none; border-top: 1px solid var(--panel-border); margin: 8px 0; }
  .note-preview { margin-top: 8px; padding: 9px 11px; background: var(--bg-grid); border-radius: 8px; color: var(--ink-soft); }
  /* ── Touch sizing for form controls ──────────────────────────────────────
     Coarse-pointer only, so desktop keeps its denser 13px layout.

     16px is not cosmetic: iOS Safari auto-zooms any focused input under 16px
     and does NOT zoom back out, stranding the page at 2x. Every inspector and
     modal field measured 13px before this.

     Heights target the 44px floor. The icon grid is the one deliberate
     exception — 74 icons at 44px would push the picker off-screen on a 360px
     phone, so it takes 40px, still a comfortable finger target in a dense
     grid of same-kind items. Recorded here rather than left implicit. */
  @media (pointer: coarse) {
    .insp-input, .insp-textarea, select.insp-input {
      font-size: 16px; min-height: 44px; padding: 10px 12px; border-radius: 10px;
    }
    .insp-textarea { min-height: 88px; }
    .insp-btn { font-size: 15px; min-height: 46px; padding: 12px; border-radius: 10px; }
    .insp-mini { font-size: 14px; min-height: 44px; border-radius: 10px; }
    .insp-close { width: 44px; height: 44px; font-size: 26px; }
    /* 16px matters here even though a colour swatch shows no text: WebKit has
       no native colour picker, so on iOS <input type="color"> degrades to a
       TEXT field — and a 13px text field is exactly what triggers the auto-zoom. */
    .insp-color { width: 56px; height: 44px; font-size: 16px; }
    .seg-opt { min-height: 44px; font-size: 13px; }
    .shape-opt { height: 44px; min-width: 56px; font-size: var(--fs-xs); }
    .prio-opt { min-height: 44px; font-size: 13px; }
    .swatch { width: 38px; height: 38px; }
    .icon-opt { width: 40px; height: 40px; font-size: 21px; }
    .icon-lib { max-height: 260px; }
    /* a 16px slider thumb is hard to grab; give the track real height */
    .insp-range { min-height: 44px; }
    .cmt-del { width: 44px; height: 44px; font-size: 18px; }
  }
  .prio-row { display: flex; gap: 6px; }
  .prio-opt { flex: 1; padding: 6px; border: 1.5px solid var(--panel-border); border-radius: 6px; cursor: pointer; font-size: var(--fs-xs); font-weight: 700; text-align: center; transition: all .1s; }
  .prio-opt.sel { color: #fff; border-color: transparent; }

  /* ---------- Outline panel ---------- */
  #outline {
    position: absolute; top: calc(var(--menu-h) + var(--tool-h) + 14px); left: 14px; width: 280px;
    background: var(--panel); border: 1px solid var(--panel-border); border-radius: 16px;
    box-shadow: 0 10px 34px var(--shadow); z-index: 85; padding: 14px;
    transform: translateX(calc(-100% - 28px)); transition: transform .25s cubic-bezier(.2,.8,.2,1);
    max-height: calc(100vh - var(--menu-h) - var(--tool-h) - 120px); overflow-y: auto;
    pointer-events: none;
  }
  #outline.open { transform: translateX(0); pointer-events: auto; }
  .outline-item { padding: 5px 6px; border-radius: 6px; cursor: pointer; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .outline-item:hover { background: var(--bg-grid); }
  .outline-item.sel { background: var(--accent-soft); }

  /* ---------- Minimap ---------- */
  #minimap { position: absolute; bottom: 14px; right: 14px; width: 168px; height: 112px; background: var(--panel); border: 1px solid var(--panel-border); border-radius: 12px; box-shadow: 0 6px 20px var(--shadow); z-index: 80; overflow: hidden; }
  #minimap canvas { display: block; cursor: pointer; }

  /* ---------- Zoom control (bottom-right, Mindomo-style) ---------- */
  #zoom-ctl {
    position: absolute; bottom: 134px; right: 14px; display: flex; align-items: center;
    background: var(--panel); border: 1px solid var(--panel-border); border-radius: 999px;
    box-shadow: 0 6px 20px var(--shadow); z-index: 80; overflow: hidden; padding: 2px;
  }
  #zoom-ctl button { width: 32px; height: 32px; border: none; background: transparent; color: var(--ink); cursor: pointer; font-size: 18px; border-radius: 50%; }
  #zoom-ctl button:hover { background: var(--bg-grid); }
  /* must follow the base rule above to win at equal specificity */
  @media (pointer: coarse) { #zoom-ctl button { width: 44px; height: 44px; font-size: 21px; } }
  #zoom-pct { font-size: 12px; padding: 0 8px; color: var(--ink-soft); min-width: 44px; text-align: center; }

  /* ---------- Context menu ---------- */
  #ctx-menu { position: absolute; background: var(--panel); border: 1px solid var(--panel-border); border-radius: 10px; box-shadow: 0 8px 24px var(--shadow-strong); z-index: 500; padding: 5px; display: none; min-width: 170px; }
  .ctx-item { padding: 8px 12px; border-radius: 7px; cursor: pointer; font-size: 13px; display: flex; justify-content: space-between; gap: 16px; }
  .ctx-item:hover { background: var(--bg-grid); }
  .ctx-item .k { color: var(--ink-soft); font-size: 11px; }
  .ctx-sep { height: 1px; background: var(--panel-border); margin: 4px 6px; }

  /* ---------- Template Gallery (startup modal) ---------- */
  #gallery-bg { position: absolute; inset: 0; background: rgba(20,18,14,.45); z-index: 600; display: none; align-items: center; justify-content: center; }
  #gallery {
    width: min(1040px, 94vw); height: min(720px, 92vh); background: var(--panel);
    border-radius: 18px; box-shadow: 0 30px 80px var(--shadow-strong); display: flex; flex-direction: column; overflow: hidden;
  }
  #gal-top { height: 64px; border-bottom: 1px solid var(--panel-border); display: flex; align-items: center; padding: 0 18px; gap: 8px; position: relative; }
  #gal-top .brand { display: flex; align-items: center; gap: 9px; font-family: var(--font-display); font-weight: 700; font-size: 19px; margin-right: 18px; }
  #gal-top .brand .logo { width: 26px; height: 26px; border-radius: 7px; background: linear-gradient(135deg,var(--accent),var(--accent-2)); display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px;}
  .gal-tab { display: flex; align-items: center; gap: 8px; padding: 9px 18px; border-radius: 10px; font-size: 14px; cursor: pointer; border: 1px solid transparent; color: var(--ink-soft); }
  .gal-tab svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 2; }
  .gal-tab:hover { background: var(--bg-grid); }
  .gal-tab.active { background: color-mix(in srgb, var(--accent) 12%, transparent); border-color: var(--accent-soft); color: var(--accent); }
  #gal-close { position: absolute; right: 18px; top: 50%; transform: translateY(-50%); width: 34px; height: 34px; border-radius: 50%; border: none; background: transparent; font-size: 22px; color: var(--ink-soft); cursor: pointer; }
  #gal-close:hover { background: var(--bg-grid); }
  #gal-body { flex: 1; display: flex; overflow: hidden; }
  #gal-side { width: 230px; border-right: 1px solid var(--panel-border); padding: 18px 14px; overflow-y: auto; flex: none; }
  .gal-side-item { padding: 9px 14px; border-radius: 9px; font-size: 14.5px; cursor: pointer; color: var(--ink); }
  .gal-side-item:hover { background: var(--bg-grid); }
  .gal-side-item.active { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); font-weight: 600; }
  .gal-side-head { font-size: 11px; font-weight: 700; color: var(--ink-soft); letter-spacing: .08em; margin: 18px 14px 8px; }
  #gal-main { flex: 1; padding: 22px 26px; overflow-y: auto; }
  #gal-search { display: flex; align-items: center; gap: 10px; border-bottom: 2px solid var(--panel-border); padding: 6px 4px 12px; margin-bottom: 22px; transition: border-color .12s; }
  #gal-search:focus-within { border-color: var(--accent); }
  #gal-search svg { width: 22px; height: 22px; stroke: var(--ink-soft); fill: none; stroke-width: 2; }
  #gal-search input { border: none; background: transparent; outline: none; color: var(--ink); font-family: var(--font-body); font-size: 19px; width: 100%; }
  #gal-search input::placeholder { color: var(--ink-soft); }
  .tpl-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
  .tpl-card { position: relative; cursor: pointer; }
  .tpl-thumb { height: 168px; border: 1px solid var(--panel-border); border-radius: 14px; background: var(--panel-2); overflow: hidden; position: relative; transition: border-color .15s, box-shadow .15s; display: flex; align-items: center; justify-content: center; }
  .tpl-card:hover .tpl-thumb { border-color: var(--accent); box-shadow: 0 8px 22px var(--shadow); }
  .tpl-thumb svg { width: 100%; height: 100%; }
  .tpl-name { text-align: center; font-size: 17px; margin-top: 12px; font-family: var(--font-display); }
  .tpl-overlay { position: absolute; inset: 0; background: color-mix(in srgb, var(--panel) 80%, transparent); display: none; flex-direction: column; align-items: center; justify-content: center; gap: 12px; }
  .tpl-card:hover .tpl-overlay { display: flex; }
  .tpl-ov-text { font-size: 13px; color: var(--ink-soft); text-align: center; padding: 0 24px; line-height: 1.4; }
  .tpl-ov-btns { display: flex; gap: 10px; }
  .tpl-ov-btn { padding: 10px 22px; border-radius: 9px; font-size: 14px; font-weight: 600; cursor: pointer; border: 1.5px solid var(--panel-border); background: var(--panel); color: var(--ink); }
  .tpl-ov-btn.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
  .tpl-ov-btn.danger:hover { background: #da3a16; color: #fff; border-color: #da3a16; }
  .tpl-ov-btn:hover { transform: translateY(-1px); }
  .tpl-ov-btns.compact { gap: 6px; flex-wrap: wrap; justify-content: center; }
  .tpl-ov-btns.compact .tpl-ov-btn { padding: 7px 12px; font-size: 12.5px; }
  .doc-thumb-icon {
    width: 64px; height: 64px; border-radius: 50%; background: var(--accent-soft); color: var(--accent);
    font-family: var(--font-display); font-size: 28px; font-weight: 600;
    display: flex; align-items: center; justify-content: center;
  }
  .gal-empty { color: var(--ink-soft); font-size: 14px; padding: 40px; text-align: center; }

  /* ---------- export menu / hint ---------- */
  #export-menu { display: none; position: absolute; z-index: 550; background: var(--panel); border: 1px solid var(--panel-border); border-radius: 10px; box-shadow: 0 8px 24px var(--shadow-strong); padding: 5px; min-width: 170px; }
  #filter-menu { display: none; position: absolute; z-index: 550; background: var(--panel); border: 1px solid var(--panel-border); border-radius: 12px; box-shadow: 0 8px 24px var(--shadow-strong); padding: 14px; width: 210px; }
  #filter-menu select { cursor: pointer; }

  /* ---------- Cloud Sync ---------- */
  .sync-row { display: flex; align-items: center; justify-content: space-between; cursor: pointer; padding: 4px 0; }
  .toggle { width: 40px; height: 22px; border-radius: 11px; background: var(--panel-border); position: relative; cursor: pointer; transition: background .15s; flex-shrink: 0; }
  .toggle.on { background: var(--accent); }
  .toggle-knob { width: 18px; height: 18px; border-radius: 50%; background: var(--panel); position: absolute; top: 2px; left: 2px; transition: left .15s; box-shadow: 0 1px 3px var(--shadow); }
  .toggle.on .toggle-knob { left: 20px; background: #fff; }
  #hint { position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); background: var(--ink); color: var(--bg); padding: 9px 16px; border-radius: 10px; font-size: 12px; z-index: 800; opacity: 0; transition: opacity .3s; pointer-events: none; max-width: 80vw; text-align: center; }

  /* Persistent (not auto-dismissing, unlike #hint) — stays until reloaded or dismissed */
  #update-banner {
    display: none; position: absolute; top: 0; left: 0; right: 0; z-index: 900;
    background: var(--accent); color: #fff; padding: 9px 16px; font-size: 13px;
    align-items: center; justify-content: center; gap: 14px; box-shadow: 0 4px 14px var(--shadow-strong);
  }
  #update-reload-btn { border: none; background: rgba(255,255,255,.22); color: #fff; font-weight: 700; font-size: 12.5px; padding: 5px 14px; border-radius: 7px; cursor: pointer; }
  #update-reload-btn:hover { background: rgba(255,255,255,.34); }
  #hint.show { opacity: .94; }

  /* right dependency column */
  .st-notstarted { background: #b9b9b9; } .st-inprogress { background: #4a90d9; } .st-blocked { background: #d9534f; } .st-done { background: #4caf72; }
  /* responsive */
  @media (max-width: 760px) {
    #gal-side { width: 160px; }
    #tb-search input { width: 70px; }
    #minimap { display: none; }
    #inspector, #outline { width: calc(100vw - 28px); }
  }

  /* The More button and the combined ☰ menu belong to the phone chrome;
     nothing else ever shows them. */
  #toolbar [data-t="more"] { display: none; }
  #menubar [data-menu="All"] { display: none; }
  /* Section heading inside the combined menu. */
  .menu-head {
    font-size: var(--fs-xs); font-weight: 700; text-transform: uppercase;
    letter-spacing: .06em; color: var(--ink-soft); padding: 6px 12px 2px;
  }

  /* On a phone the sheet and its dialogs are primary content held at reading
     distance, not chrome glanced at across a desk — so they raise their own
     floor rather than inherit the desktop chrome's 11px. Measured before this:
     49 elements in the open sheet sat under 12px, including every section label
     and every shape name. Done by re-pointing the ramp inside those panels, so
     no rule has to be restated. */
  @media (pointer: coarse) {
    #inspector, .modal-panel, #outline { --fs-xs: 12.5px; --fs-sm: 13.5px; }
  }

  /* ── Phone command bar ───────────────────────────────────────────────────
     Measured on an S24 Ultra (412x915): every command sat in the top 162px of
     a 915px screen — above the comfortable one-handed zone — and the toolbar
     was 760px of content in a 412px viewport, so 7 of its 13 controls were
     off-screen behind a horizontal scroll with no affordance.

     #app-root is a flex column, so `order` moves the bar to the BOTTOM in flow.
     That keeps every existing button and its wiring, costs no overlay and needs
     no manual padding on the canvas — and puts the commands under the thumb.
     Five stay out; the rest wait behind More rather than off the edge. */
  @media (pointer: coarse) and (max-width: 760px) {
    /* One ☰ replaces the six named menus: they needed 590px of bar in a 412px
       viewport, so 178px of them sat behind a horizontal scroll. */
    #menubar [data-menu="All"] { display: block; font-size: 19px; line-height: 1; }
    #menubar [data-menu="File"], #menubar [data-menu="Edit"], #menubar [data-menu="Tools"],
    #menubar [data-menu="View"], #menubar [data-menu="Window"], #menubar [data-menu="Help"],
    #menubar .menu-sep { display: none; }
    /* The combined list is every menu at once, so it has to scroll, and it
       should not be a 220px column on a 412px screen. */
    .menu-all {
      width: min(92vw, 360px); max-height: calc(100vh - var(--menu-h) - var(--tool-h) - 24px);
      overflow-y: auto; overscroll-behavior: contain;
    }
    #canvas-wrap { order: 2; }
    #toolbar {
      order: 3;
      border-top: 1px solid var(--panel-border); border-bottom: none;
      overflow: visible; flex-wrap: wrap; gap: 2px;
      justify-content: space-evenly; align-content: center;
      padding: 4px 6px max(env(safe-area-inset-bottom), 4px);
    }
    .tb-divider, .tb-spacer { display: none; }
    /* Everything hides, then the five primary actions come back. Listing what
       SHOWS rather than what hides means a tool added later joins the overflow
       by default instead of silently pushing a primary action off the bar. */
    #toolbar > .tb-btn, #toolbar > #tb-search { display: none; }
    #toolbar > [data-t="undo"], #toolbar > [data-t="redo"], #toolbar > [data-t="add"],
    #toolbar > [data-t="style"], #toolbar > [data-t="more"] { display: flex; }
    #toolbar.expanded > .tb-btn, #toolbar.expanded > #tb-search { display: flex; }
    /* Search wants a row of its own; it is a field, not a 44px square. */
    #toolbar.expanded > #tb-search { flex: 1 1 100%; order: 99; height: 44px; }
    #toolbar.expanded > #tb-search input { width: 100%; }
    #toolbar.expanded { padding-top: 6px; row-gap: 4px; }

    /* Everything else that lived at the bottom now sits ON TOP of the bar, so
       lift it by exactly the bar's own height — `--tool-h` rather than a second
       constant, so the two cannot drift apart. The safe-area inset moves to the
       bar for the same reason: it is the thing touching the screen edge now. */
    #inspector { bottom: var(--tool-h); padding-bottom: calc(16px + var(--kb-inset)); }
    #hint { bottom: calc(var(--tool-h) + 14px); }
    /* The sheet now starts a bar-height higher, so the zoom control has to be
       lifted by that much again or it ends up behind the sheet — on a 412x480
       split screen it overlapped by 48px. */
    #inspector.open ~ #zoom-ctl {
      transform: translateY(min(0px, calc(134px - var(--sheet-h) - var(--tool-h) - 8px)));
    }
    /* The expanded bar is a transient overflow menu, so it may cover the sheet
       while it is open — the toolbar's z-index (190) already puts it above.
       The zoom pill is the exception: half-covered it just looks clipped, and
       you cannot zoom while choosing a tool anyway, so it steps aside. */
    #toolbar.expanded ~ #zoom-ctl { opacity: 0; pointer-events: none; }
    #zoom-ctl { transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out); }
  }

  /* ── Software-keyboard inset ──────────────────────────────────────────────
     The keyboard overlays the viewport without resizing it, so a scrolling
     panel's lower half ends up underneath it. --kb-inset is the covered
     height (JS keeps it current from visualViewport); panels reserve it as
     bottom padding and shrink their max-height to match, which keeps the
     focused field and the buttons below it reachable. */
  #inspector, .modal-panel, #outline {
    padding-bottom: calc(16px + var(--kb-inset));
    transition: max-height var(--dur-base) var(--ease-out);
  }
  .kb-open #inspector { max-height: calc(100vh - var(--menu-h) - var(--tool-h) - 28px - var(--kb-inset)); }
  .kb-open .modal-panel { max-height: calc(86vh - var(--kb-inset)); }

  /* ── Motion ───────────────────────────────────────────────────────────────
     Compositor-only (transform/opacity), short, and every one of them is
     listed in the reduced-motion block below. Nothing animates a layout
     property, so none of this can cost a reflow on a hot path. */
  .modal-panel { animation: sheet-in var(--dur-slow) var(--spring) both; }
  @keyframes sheet-in {
    from { opacity: 0; transform: translateY(14px) scale(.98); }
    to   { opacity: 1; transform: none; }
  }
  .menu-dropdown.show { animation: pop-in var(--dur-fast) var(--ease-out) both; transform-origin: top left; }
  @keyframes pop-in {
    from { opacity: 0; transform: scale(.97) translateY(-4px); }
    to   { opacity: 1; transform: none; }
  }
  #ctx-menu { animation: pop-in var(--dur-fast) var(--ease-out) both; }
  /* press feedback — the tap-down cue a native control gives you */
  .tb-btn:active, .insp-btn:active, .insp-mini:active,
  .seg-opt:active, .prio-opt:active, .shape-opt:active, .tpl-ov-btn:active {
    transform: scale(.96); transition: transform var(--dur-fast) var(--ease-out);
  }
  .node:active { transition: transform var(--dur-fast) var(--ease-out); }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { transition-duration: .001ms !important; animation-duration: .001ms !important; }
    /* the transforms above are decoration, not layout — drop them outright so
       nothing is left mid-scale when animation is disabled */
    .modal-panel, .menu-dropdown.show, #ctx-menu { animation: none !important; }
    .tb-btn:active, .insp-btn:active, .insp-mini:active,
    .seg-opt:active, .prio-opt:active, .shape-opt:active, .tpl-ov-btn:active { transform: none !important; }
  }

  /* ---------- Shared modal shell (Gantt, revision history) ---------- */
  .modal-bg { display: none; position: absolute; inset: 0; background: rgba(20,18,14,.45); z-index: 600; align-items: center; justify-content: center; }
  /* The connector editor is the one panel that must NOT cover the canvas: its
     "position" control is the drag handle on the connector itself, so a
     full-screen backdrop would block the very gesture it documents. */
  #conn-bg { inset: 0 0 0 auto; width: min(340px, 94vw); background: none; pointer-events: none;
             align-items: flex-start; justify-content: flex-end; }
  #conn-bg .modal-panel { pointer-events: auto; margin: 12px; max-height: calc(100% - 24px); overflow: auto; }
  .modal-panel { width: min(460px, 92vw); max-height: 86vh; overflow-y: auto; background: var(--panel); border-radius: 18px; box-shadow: 0 30px 80px var(--shadow-strong); padding: 22px; }
  .modal-wide { width: min(920px, 94vw); }

  /* ---------- Gantt ---------- */
  .gantt-grid { display: flex; border: 1px solid var(--panel-border); border-radius: 10px; overflow: hidden; }
  .gantt-labels { flex: none; width: 190px; border-right: 1px solid var(--panel-border); overflow: hidden; background: var(--bg); }
  .gantt-corner, .gantt-label { height: 34px; display: flex; align-items: center; padding: 0 8px; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-bottom: 1px solid var(--bg-grid); }
  .gantt-corner { font-weight: 700; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-soft); height: 30px; background: var(--panel); }
  .gantt-label { cursor: pointer; }
  .gantt-label:hover { background: var(--accent-soft); }
  .gantt-scroll { flex: 1; overflow-x: auto; overflow-y: hidden; }
  .gantt-track { position: relative; }
  .gantt-months { position: relative; height: 30px; border-bottom: 1px solid var(--panel-border); }
  .gantt-month { position: absolute; top: 0; height: 30px; display: flex; align-items: center; justify-content: center; font-size: var(--fs-xs); font-weight: 700; color: var(--ink-soft); border-left: 1px solid var(--panel-border); font-variant-numeric: tabular-nums; }
  .gantt-row { position: relative; height: 34px; border-bottom: 1px solid var(--bg-grid); }
  .gantt-bar { position: absolute; top: 6px; height: 22px; border: 1.5px solid; border-radius: 6px; cursor: pointer; overflow: hidden; display: flex; align-items: center; min-width: 20px; }
  .gantt-bar i { position: absolute; left: 0; top: 0; bottom: 0; opacity: .38; }
  .gantt-bar span { position: relative; padding: 0 6px; font-size: var(--fs-xs); font-weight: 700; color: var(--ink); white-space: nowrap; font-variant-numeric: tabular-nums; }
  .gantt-bar.late { box-shadow: 0 0 0 2px var(--accent-2); }
  .gantt-today { position: absolute; top: 30px; bottom: 0; width: 2px; background: var(--accent-2); opacity: .65; z-index: 2; }
  .gantt-empty { padding: 34px 10px; text-align: center; font-size: 14px; color: var(--ink); }
  .gantt-empty span { font-size: 12.5px; color: var(--ink-soft); }

  /* ---------- Mindomo-parity additions ---------- */
  .node { max-width: var(--node-maxw, 280px); }
  /* numbering prefix */
  .node-num { margin-right: 6px; font-weight: 700; font-size: .85em; color: var(--accent); font-variant-numeric: tabular-nums; }
  /* floating topics: subtle dashed halo so they read as intentionally unattached */
  .node.free { outline: 1.5px dashed var(--panel-border); outline-offset: 4px; }
  /* multi-selection */
  .node.msel { box-shadow: 0 0 0 3px var(--accent-soft), 0 2px 8px var(--shadow); }
  .node.msel.selected { box-shadow: 0 0 0 3px var(--accent), 0 6px 18px var(--shadow-strong); }
  /* map-wide text size (Mindomo theme S/M/L/XL) */
  #canvas.gsz-s .node { font-size: 12px; } #canvas.gsz-s .node.root { font-size: 17px; }
  #canvas.gsz-l .node { font-size: 16.5px; } #canvas.gsz-l .node.root { font-size: 23px; }
  #canvas.gsz-xl .node { font-size: 20px; } #canvas.gsz-xl .node.root { font-size: 28px; }
  /* map-wide box density — how much padding a topic wraps around its text.
     Separate from text size on purpose: a map imported at another tool's
     coordinates needs the boxes to shrink, not the words. */
  #canvas.dens-compact .node { padding: 4px 10px; }
  #canvas.dens-compact .node.root { padding: 8px 16px; }
  /* Tight also pulls the leading in: on a wrapped topic the line height, not
     the padding, is what decides the box's height. */
  #canvas.dens-tight .node { padding: 1px 7px; border-width: 1px; line-height: 1.22; }
  #canvas.dens-tight .node.root { padding: 4px 12px; }
  /* comment badge + list */
  .node-badge-comment { background: var(--info, #007fad); }
  .cmt { border: 1px solid var(--bg-grid); border-radius: 8px; padding: 7px 9px; margin-bottom: 7px; background: var(--bg); }
  .cmt-head { display: flex; justify-content: space-between; align-items: center; font-size: var(--fs-xs); color: var(--ink-soft); margin-bottom: 3px; }
  .cmt-del { border: none; background: transparent; color: var(--ink-soft); cursor: pointer; font-size: 14px; line-height: 1; padding: 0 2px; }
  .cmt-del:hover { color: #da3a16; }
  .cmt-body { font-size: 12.5px; line-height: 1.45; }
  /* drill chip */
  #drill-chip { position: absolute; top: 12px; left: 50%; transform: translateX(-50%); z-index: 450;
    display: flex; align-items: center; gap: 10px; background: var(--accent); color: #fff;
    padding: 7px 14px; border-radius: 999px; font-size: 12.5px; font-weight: 600; box-shadow: 0 6px 18px var(--shadow-strong); }
  #drill-chip button { border: none; background: rgba(255,255,255,.22); color: #fff; font-weight: 700; font-size: 11.5px; padding: 4px 10px; border-radius: 999px; cursor: pointer; }
  #drill-chip button:hover { background: rgba(255,255,255,.34); }
  /* theme preset cards */
  .preset-card { display: flex; flex-direction: column; align-items: center; gap: 5px; padding: 8px 4px;
    border: 1.5px solid var(--panel-border); border-radius: 9px; background: var(--bg); cursor: pointer;
    font-family: var(--font-ui); font-size: var(--fs-xs); font-weight: 600; color: var(--ink-soft); transition: border-color .12s; }
  .preset-card:hover { border-color: var(--accent); color: var(--ink); }
  .preset-dots { display: flex; gap: 3px; }
  .preset-dots i { width: 12px; height: 12px; border-radius: 50%; display: block; }
  /* properties stats */
  .prop-stats { display: flex; flex-wrap: wrap; gap: 8px; }
  .prop-stat { flex: 1; min-width: 64px; text-align: center; background: var(--bg); border: 1px solid var(--bg-grid); border-radius: 8px; padding: 8px 4px; }
  .prop-stat b { display: block; font-size: 17px; font-variant-numeric: tabular-nums; }
  .prop-stat span { font-size: var(--fs-xs); color: var(--ink-soft); text-transform: uppercase; letter-spacing: .04em; }
  .prop-dates { width: 100%; font-size: 11px; color: var(--ink-soft); margin-top: 4px; }
  .outline-free-sep { margin: 10px 0 4px; font-size: var(--fs-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-soft); }

  /* ---------- Folder library ---------- */
  .fold-item { display: flex; align-items: center; gap: 8px; padding: 7px 10px; border-radius: 8px;
    font-size: 13px; cursor: pointer; color: var(--ink-soft); transition: background .1s; }
  .fold-item:hover { background: var(--bg-grid); color: var(--ink); }
  .fold-item.active { background: var(--accent-soft); color: var(--ink); font-weight: 600; }
  .fold-item.drop-on { background: var(--accent); color: #fff; }
  .fold-ico { flex: none; font-size: 13px; }
  .fold-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .fold-count { flex: none; font-size: var(--fs-xs); opacity: .7; font-variant-numeric: tabular-nums; }
  .fold-new { width: 100%; margin-top: 10px; padding: 8px; border: 1.5px dashed var(--panel-border);
    background: transparent; border-radius: 8px; color: var(--ink-soft); font-family: var(--font-body);
    font-size: 12px; font-weight: 600; cursor: pointer; }
  .fold-new:hover { border-color: var(--accent); color: var(--accent); }

  /* breadcrumb bar spans the whole grid row */
  .fold-bar { grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between;
    gap: 12px; flex-wrap: wrap; padding: 2px 2px 10px; border-bottom: 1px solid var(--panel-border); margin-bottom: 4px; }
  .fold-crumbs { font-size: 12.5px; color: var(--ink-soft); }
  .fold-crumbs a { color: var(--accent); cursor: pointer; }
  .fold-crumbs a:hover { text-decoration: underline; }
  .fold-crumbs span { opacity: .5; margin: 0 2px; }
  .fold-actions { display: flex; gap: 6px; }

  .fold-card .fold-thumb { display: flex; align-items: center; justify-content: center; }
  .fold-thumb-ico { font-size: 42px; line-height: 1; }
  .fold-card.drop-on .tpl-thumb { outline: 2.5px solid var(--accent); outline-offset: -2px; }
  .tpl-card.dragging { opacity: .45; }
  .tpl-card[draggable="true"] { cursor: grab; }

  /* install-as-app instructions */
  .install-row { display: flex; flex-direction: column; gap: 2px; padding: 9px 0; border-bottom: 1px solid var(--bg-grid); }
  .install-row:last-of-type { border-bottom: none; }
  .install-row > b { font-size: 12.5px; }
  .install-row > span { font-size: 12px; color: var(--ink-soft); line-height: 1.45; }

  /* ---------- Revision history ---------- */
  .hist-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--bg-grid); }
  .hist-row:last-child { border-bottom: none; }
  .hist-when { font-size: 13px; font-weight: 600; }
  .hist-meta { font-size: 11px; color: var(--ink-soft); margin-top: 2px; }
  .hist-row .insp-mini { flex: none; width: auto; padding: 6px 14px; }

  /* ── Desktop sizing for the app chrome ───────────────────────────────────
     Until this block there was NO desktop rule in the stylesheet: every media
     query was `pointer: coarse` or `max-width`, so the base styles were a
     compromise that touch then enlarged, and the chrome measured identically at
     1440, 1920 and 2560. The base sizes are also touch-ish for a mouse —
     52px toolbar, 36px buttons, 31px menu rows — which cost 90px of vertical
     chrome before any map was drawn, 8% of a 1080p screen.

     A mouse resolves a 26px target as easily as a 44px one, so this trades that
     back for canvas and for the tighter rhythm a desktop app reads with. */
  @media (pointer: fine) {
    :root { --menu-h: 30px; --tool-h: 38px; }
    .menu-item { padding: 3px 9px; font-size: var(--fs-sm); border-radius: 5px; }
    #menubar { padding: 2px 8px; }
    #menubar .logo { width: 18px; height: 18px; font-size: var(--fs-xs); margin-right: 8px; }
    .doc-title { font-size: var(--fs-sm); }
    #toolbar { padding: 0 8px; gap: 2px; }
    .tb-btn { height: 26px; min-width: 26px; padding: 0 6px; border-radius: 6px; font-size: var(--fs-sm); }
    .tb-btn svg { width: 16px; height: 16px; }
    .tb-btn .tip { top: 32px; }
    .tb-divider { height: 18px; margin: 0 4px; }
    .style-swatch { width: 20px; height: 20px; }
    #tb-search { height: 26px; border-radius: 6px; padding: 0 8px; }
    #tb-search input { font-size: var(--fs-sm); width: 140px; }
    #tb-search svg { width: 14px; height: 14px; }
    .menu-dropdown { padding: 4px; border-radius: 8px; }
    .menu-row { padding: 4px 10px; font-size: var(--fs-sm); border-radius: 5px; }
    .menu-divider { margin: 3px 6px; }
    /* Panels grow with the display instead of staying at a laptop's fixed
       width — on a large monitor those sizes left notes barely editable and the
       gallery showing four cards on a screen with room for eight. Every one is
       clamped, so a 1024px laptop keeps roughly what it had. */
    #inspector { width: clamp(250px, 21vw, 380px); padding: 13px; }
    #outline { width: clamp(280px, 20vw, 400px); }
    #gallery { width: min(clamp(1040px, 76vw, 1500px), 94vw); height: min(clamp(720px, 82vh, 1000px), 92vh); }
    #gal-side { width: clamp(230px, 15vw, 300px); }
    .modal-panel { width: min(clamp(460px, 32vw, 560px), 92vw); }
    .modal-wide { width: min(clamp(920px, 62vw, 1200px), 94vw); }
    /* The connector editor is a strip beside the canvas, not a centred dialog,
       so it sizes to its own container rather than inheriting the wider modal. */
    #conn-bg { width: min(clamp(360px, 26vw, 460px), 94vw); }
    #conn-bg .modal-panel { width: calc(100% - 24px); }
    .insp-label { font-size: var(--fs-xs); }
    .insp-input, .insp-textarea { font-size: var(--fs-sm); padding: 6px 9px; }

    /* One step tighter again, for people working on a laptop or who simply
       want more canvas. A device preference (View > Compact interface), not a
       map one — it is stored per browser, so a map does not carry one screen's
       density to another. Nested here so touch never gets it. */
    .ui-compact { --menu-h: 26px; --tool-h: 32px; }
    .ui-compact .menu-item { padding: 2px 7px; font-size: var(--fs-xs); }
    .ui-compact #menubar { padding: 1px 6px; }
    .ui-compact .tb-btn { height: 22px; min-width: 22px; padding: 0 5px; }
    .ui-compact .tb-btn svg { width: 14px; height: 14px; }
    .ui-compact .tb-btn .tip { top: 27px; }
    .ui-compact .tb-divider { height: 15px; }
    .ui-compact #tb-search { height: 22px; }
    .ui-compact .menu-row { padding: 3px 9px; font-size: var(--fs-xs); }
    .ui-compact #inspector { padding: 10px; }
    .ui-compact .insp-section { margin-bottom: 9px; }
  }


  /* ---------- keyboard focus ----------
     A mouse user learns what is interactive by hovering; a keyboard user never
     gets that cue, so focus has to be drawn at least as loudly as hover. The
     stylesheet had 36 :hover rules and exactly one :focus-visible rule (on
     .node), leaving every other control on the browser's default ring — which
     is inconsistent across engines and easy to lose against the dark theme.
     One token, applied to everything focusable, so the app answers "where am I"
     the same way everywhere.

     This block lives at the END of the file deliberately: several of the rules
     it overrides (.tb-btn, .menu-item, the coarse-pointer blocks) have equal
     specificity, so an earlier position would silently lose the cascade. */
  :root { --focus-ring: 2px solid var(--accent); --focus-offset: 2px; }
  .menu-item:focus-visible,
  .doc-title:focus-visible,
  .tb-btn:focus-visible,
  .mi:focus-visible,
  .insp-btn:focus-visible, .insp-mini:focus-visible,
  .zoom-btn:focus-visible,
  button:focus-visible,
  [role="button"]:focus-visible,
  [tabindex]:not(.node):focus-visible,
  a:focus-visible, select:focus-visible, summary:focus-visible {
    outline: var(--focus-ring); outline-offset: var(--focus-offset);
  }
  /* Inputs already draw a border+glow on :focus; keep that as their indicator
     rather than stacking a second ring on top of it. */
  .insp-input:focus-visible, .insp-textarea:focus-visible, #tb-search input:focus-visible { outline: none; }
  /* Tooltips were hover-only, so the one place the toolbar explains its icons
     was invisible to anyone navigating by keyboard. */
  .tb-btn:focus-visible .tip { opacity: .95; visibility: visible; }

  /* Storage-full warning. Same shape as the update banner but red and
     role="alert": this one means work is being lost right now. */
  #storage-banner {
    display: none; position: absolute; top: 0; left: 0; right: 0; z-index: 950;
    background: var(--accent-2); color: #fff; padding: 9px 16px; font-size: 13px;
    align-items: center; justify-content: center; gap: 14px; box-shadow: 0 4px 14px var(--shadow-strong);
  }
  #storage-banner button {
    border: 1px solid #fff; background: transparent; color: #fff; font: inherit;
    font-weight: 600; padding: 3px 12px; border-radius: 999px; cursor: pointer;
  }

  /* Library multi-select. The tick sits on the card corner rather than in a
     hover overlay: a selection has to stay visible while you pick the next
     card, which a hover-only control cannot do. */
  .gal-pick {
    position: absolute; top: 8px; left: 8px; z-index: 3;
    width: 24px; height: 24px; border-radius: 7px; cursor: pointer;
    border: 1.5px solid var(--panel-border); background: var(--panel);
    color: #fff; font-size: 13px; line-height: 1; font-weight: 700;
    display: flex; align-items: center; justify-content: center;
    opacity: 0; transition: opacity var(--dur-fast) var(--ease-out);
  }
  .tpl-card { position: relative; }
  .tpl-card:hover .gal-pick, .tpl-card:focus-within .gal-pick { opacity: 1; }
  .tpl-card.gal-picked .gal-pick { opacity: 1; background: var(--accent); border-color: var(--accent); }
  .tpl-card.gal-picked { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 14px; }
  /* Touch has no hover, so the control can never be revealed by one. */
  @media (pointer: coarse) {
    .gal-pick { opacity: 1; width: 30px; height: 30px; }
  }
  .fold-bar.bulk { background: var(--accent-soft); border-radius: 10px; padding: 6px 10px; }

  /* An action inside a toast — used to offer Undo straight after a destructive
     library change, where sending the user hunting through a menu is too slow. */
  .hint-action {
    pointer-events: auto;      /* #hint is click-through; its action must not be */
    margin-left: 10px; border: 1px solid currentColor; background: transparent;
    color: inherit; font: inherit; font-weight: 600; cursor: pointer;
    padding: 2px 10px; border-radius: 999px; line-height: 1.4;
  }
  .hint-action:hover { background: rgba(255,255,255,.16); }

  /* ---------- print / "Save as PDF" ---------- */
  @media print {
    #menubar, #toolbar, #inspector, #outline, #minimap, #zoom-ctl, #hint,
    .node-plus, .node-collapse, #ctx-menu, #export-menu, #gallery-bg { display: none !important; }
    #canvas-wrap { cursor: default; background-image: none; }
    body, #app-root { background: #fff; }
  }
