/* ProcDocs — Color Schema 1 (deep violet / vivid purple) aesthetic */

:root {
  color-scheme: light;
  --caret: #1A1A1A;         /* light mode: dark caret */
  /* Surfaces — mirrored from the Expense repo's One UI light theme:
     a neutral grey ground with pure-white cards, replacing the previous
     purple-tinted surface system. */
  --bg: #F2F2F2;
  --bg-elev: #ffffff;
  --bg-sunk: #E9E9E9;
  --surface: #ffffff;

  /* Text — Expense's neutral ink ramp (#1A1A1A / secondary / tertiary).
     The old note here claimed the muted steps cleared 4.5:1 "on every
     surface including --bg-sunk". Measured, they did not: #6B6B6B is
     4.39:1 and #6A6A6A is 4.46:1 on --bg-sunk (#E9E9E9) — and section
     labels, toolbar pills and counts all sit on exactly that surface,
     now at 12.5-13px where AA asks 4.5. #666666 clears it everywhere
     (5.74 white / 5.13 --bg / 4.73 sunk). The mute and faint steps had
     drifted one hex unit apart — imperceptible, so they merge here
     rather than pretending to be a hierarchy. Dark theme was already
     clear (6.4-7.4:1) and is unchanged. */
  --ink: #1A1A1A;
  --ink-soft: #4A4A4A;
  --ink-mute: #666666;
  --ink-faint: #666666;

  /* Lines */
  --border: #E2E2E2;
  --border-soft: #ECECEC;

  /* Brand — Expense's single chromatic action color: deep indigo #23004C
     (Color Schema 1). The three roles stay split (identity / solid fill
     under white text / accent-colored text) because dark mode moves them
     in opposite directions; in light mode indigo is dark enough to serve
     all three (white on #23004C: 15.9:1). */
  --accent: #23004C;
  --accent-hover: #3A2266;
  --accent-btn: #23004C;
  --accent-text: #23004C;
  --accent-soft: #8F82A8;
  --accent-bg: #ECE9F1;
  --accent-bg-strong: #DAD4E4;
  --accent-ring: rgba(35, 0, 76, 0.35);

  /* Semantic — teal/green/danger come from Color Schema 1; gold has no
     schema equivalent (no amber in the 8-swatch set) so it stays as a
     deliberate outside-the-schema addition for the warning/duplicate role.
     --warning is the same role at fill strength (white sits on it: 5.02:1);
     --neutral-folder is the built-in "Uncategorized" folder's chip colour,
     previously a one-off taupe (#8a7f70) that belonged to no palette. */
  --gold: #d97706;
  /* --gold is the identity/border value (3:1 is enough for a rule or an
     inset outline). As TEXT it only reaches 3.19:1 on an elevated
     surface, so gold-coloured text uses --gold-text, which moves the same
     way --accent-text does: darker in light, lighter in dark. */
  --gold-text: #B45309;
  --warning: #B45309;
  --neutral-folder: #6B6B6B;
  /* The one documented non-accent button fill — see .perf-report-btn. */
  --btn-dark: #1A1A1A;
  --btn-dark-hover: #000000;
  --teal: #007FAD;
  /* --teal is a fill/stroke; as 10px text on the Gantt grid tint it is
     only 3.68:1. Same split as --danger-text and --success-text. */
  --teal-text: #00607E;
  --moss: #268500;
  --danger: #DA3A16;
  /* Same split as --accent-text and --gold-text: #DA3A16 is right as a
     fill and a border but only reaches 4.14:1 as TEXT on the page
     background. --danger-text is the readable variant. */
  --danger-text: #B02D0F;
  /* The other half of that split, for the opposite job: a FILL that has to
     carry white text on top of it (destructive buttons, error toasts, the
     swipe-to-delete action). In light mode #DA3A16 already clears white at
     4.57:1, so --danger-solid is the same colour here and exists only so
     the dark override has somewhere to land. */
  --danger-solid: #DA3A16;
  --danger-bg: #FBE1D8;
  --success: #268500;
  /* Same three-way split as --danger. #268500 is a fill; as TEXT on
     --success-bg it only reaches 3.92:1 (the "Saved" pill). */
  --success-text: #1F6E00;
  --success-solid: #268500;
  --success-bg: #E0EEDA;

  /* Shadow — Expense's neutral soft-elevation scale (the purple-cast
     shadows went with the purple surfaces). */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 1px rgba(0, 0, 0, 0.03);
  --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.06);
  --shadow-lg: 0 12px 30px rgba(0, 0, 0, 0.10);
  --shadow-fab: 0 8px 24px rgba(35, 0, 76, 0.30), 0 2px 6px rgba(0, 0, 0, 0.12);
  /* Exact "Apple-grade" card treatment — literally rgba(35,0,76,X) in light
     mode; color-mix against --ink so the same declarations stay correct in
     dark mode too (--ink is #F5F3F8 there) instead of needing a parallel
     set of dark-mode rgba overrides. */
  --shadow-card: 0 1px 3px color-mix(in srgb, var(--ink) 6%, transparent);
  --card-border: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
  --badge-border: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
  --ink-60: color-mix(in srgb, var(--ink) 60%, transparent);
  --ink-70: color-mix(in srgb, var(--ink) 70%, transparent);
  --fade-in-tint: color-mix(in srgb, var(--ink) 10%, transparent);
  --active-surface: color-mix(in srgb, var(--teal) 20%, transparent);
  --icon-hover: color-mix(in srgb, var(--teal) 80%, transparent);

  /* Corner radius is the single most identifying thing about One UI, and
     the fastest way to read as generic Material/web instead is to sit in
     the 8-12px band — which is exactly where this scale was. Retargeted to
     One UI's proportions: small controls 16-20, cards and rows 20-24,
     sheets 28-32. --sm stays comparatively tight on purpose: it lands on
     26-32px icon buttons and swatches, where a 16px radius would round the
     element almost to a circle. --xl is used almost exclusively for sheet
     top corners, so it takes the full jump. */
  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 22px;
  --radius-xl: 28px;

  /* Spacing scale — ADVISORY, deliberately not retrofitted.
     Audit L-4 asked for a decision here rather than an open TODO, and the
     decision is: these stay the vocabulary for NEW work, and existing
     component CSS keeps its own tuned pixel values.
     Retrofitting would mean rewriting every margin and padding in a 7k-line
     stylesheet whose values were tuned by eye against real content, at
     five breakpoints, each duplicated in a forced-mode mirror — a large
     mechanical diff with a real chance of visual regression and almost no
     user-visible upside. The cost isn't justified by the consistency win.
     So: reach for --space-* when adding something; don't churn what's
     already tuned. This comment IS the decision, not a deferral. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;

  /* iOS-style spring/eased motion — one shared curve+duration set so every
     sheet/modal/transition in the app moves the same way instead of each
     picking its own timing. */
  --ease-ios: cubic-bezier(0.16, 1, 0.3, 1);
  --duration-fast: 0.15s;
  --duration-base: 0.25s;
  --duration-sheet: 0.32s;

  /* Set by initKeyboardAvoidance() in app.js via the visualViewport API —
     how much of the layout viewport the on-screen keyboard currently
     covers, so fixed bottom chrome can shift clear of it instead of
     ending up hidden behind it. 0 when the keyboard is closed. */
  --keyboard-inset: 0px;

  /* A raised surface's own border already reads as "lifted" against a pale
     background, so this stays invisible in light mode. In dark mode a
     bottom-heavy shadow barely registers against a near-black page, so
     raised elements (.modal/.step.expanded/.card/.linked-card/.snapshot-item)
     also get a faint top highlight — the classic "light hits the top edge"
     cue — to read as physically lifted rather than just a different shade
     of black. */
  --elevate-highlight: transparent;

  /* One UI's own faces (SamsungOne / SamsungSharpSans) aren't licensed for
     the web, and no webfont may be added here anyway (no build step, and a
     font request is a render-blocking dependency this app doesn't have).
     The faithful substitute is the system stack led by Roboto, which is
     what an Android phone actually renders and which shares One UI's
     geometric, slightly-rounded letterforms. On a Samsung device this
     resolves to the device's own One UI face via system-ui — i.e. the real
     thing, for free, exactly where it matters most. */
  --font-display: system-ui, Roboto, 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-body: system-ui, Roboto, 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-mono: 'JetBrains Mono', 'Menlo', monospace;

  --topbar-h: 60px;
  --folder-taskbar-h: 52px;
}

[data-theme="dark"] {
  color-scheme: dark;
  --caret: #F0F0F0;         /* dark mode: light caret */
  /* True black, not near-black. One UI's dark theme is built for the OLED
     panels Samsung ships: a pure-black ground lets those pixels switch off
     entirely, which is both the power saving and the visual signature
     (elevated surfaces read as genuinely floating against nothing). #0C0A10
     was a very dark violet-grey — correct for an LCD-era dark theme, but on
     an OLED it lights every pixel to render "almost off". The elevated and
     sunk tokens keep their tint so the depth hierarchy survives; only the
     ground goes to zero. */
  --bg: #000000;
  --bg-elev: #171717;
  --bg-sunk: #0D0D0D;
  --surface: #1C1C1C;

  /* Expense's neutral dark ink ramp — the purple-tinted greys go with the
     purple surfaces. */
  --ink: #F0F0F0;
  --ink-soft: #C6C6C6;
  --ink-mute: #A0A0A0;
  --ink-faint: #949494;

  --border: #2A2A2A;
  --border-soft: #202020;

  /* Expense's dark accent: #A07CFF. The three roles still move in
     opposite directions from light mode: the fill darkens so white labels
     clear AA, the text lightens so it reads on dark tints. */
  --accent: #A07CFF;
  --accent-hover: #8B61F2;
  --accent-btn: #6E3BD8;
  --accent-text: #C3A8FF;
  --accent-soft: #C3A8FF;
  --accent-bg: #231C38;
  --accent-bg-strong: #322950;
  --accent-ring: rgba(160, 124, 255, 0.4);

  --danger: #FF6B4A;
  --danger-text: #FF6B4A;
  /* #FF6B4A is lightened so it still reads as a stroke, dot or outline
     against a true-black ground — but that same lift drops white text on
     top of it to 2.82:1, well under AA. As a fill carrying white it needs
     to go the other way: #C42B0C is 5.68:1 against white, and still 3.7:1
     against the black page, so the button's own edge stays visible. */
  --danger-solid: #C42B0C;
  --danger-bg: #341811;
  --success: #4CC22E;
  /* As text on --success-bg this already clears AA, so --success-text is
     just the same colour. The fill is the problem: white on #4CC22E is
     2.32:1 — the worst pairing in the app, and it was invisible to a
     static sweep because success toasts only exist for three seconds. */
  --success-text: #4CC22E;
  --success-solid: #1E7A0C;
  --success-bg: #10230A;
  --teal: #33B8E0;
  --teal-text: #33B8E0;
  /* --gold had no dark override at all, so #d97706 (a mid-amber tuned for
     a white page) was being used as TEXT on near-black in the Performance
     duplicate flags and the Gantt critical-path accent — the same "token
     mirrored without re-checking the resulting pairing" mistake as
     --accent-hover. 8.92:1 on the elevated surface now. */
  --gold: #F5A524;
  --gold-text: #F5A524;
  --warning: #B45309;
  --neutral-folder: #A0A0A0;
  --btn-dark: #2A2A2A;
  --btn-dark-hover: #383838;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5), 0 2px 4px rgba(0, 0, 0, 0.3);
  --shadow-lg: 0 20px 40px rgba(0, 0, 0, 0.6), 0 8px 16px rgba(0, 0, 0, 0.4);
  --shadow-fab: 0 8px 24px rgba(160, 124, 255, 0.30), 0 2px 6px rgba(0, 0, 0, 0.4);
  --elevate-highlight: rgba(255, 255, 255, 0.07);

  /* Dark-mode counterparts of the light-mode-only "exact rgba(35,0,76,X)"
     tokens. Defined as static per-theme values (matching every other
     token in this file) rather than color-mix(var(--ink)) — nested
     var() inside a custom property that's itself referenced via var()
     elsewhere did not reliably re-resolve per theme in testing, even
     though --ink itself cascaded correctly; shadows specifically stay
     black-based here (not --ink-based) to match --shadow-sm/md/lg above. */
  --shadow-card: 0 1px 3px rgba(0, 0, 0, 0.3);
  --card-border: 1px solid rgba(240, 240, 240, 0.08);
  --badge-border: 1px solid rgba(240, 240, 240, 0.10);
  --ink-60: rgba(240, 240, 240, 0.6);
  --ink-70: rgba(240, 240, 240, 0.7);
  --fade-in-tint: rgba(240, 240, 240, 0.10);
  --active-surface: rgba(51, 184, 224, 0.2);
  --icon-hover: rgba(51, 184, 224, 0.8);
}
/* (The selectable accent-color themes are gone — the app has exactly two
   themes, Light and Dark, mirrored from the Expense repo.) */
/* Dark mode: a faint top highlight on the surfaces that are meant to read
   as "raised above the page" — see the --elevate-highlight comment above.
   Scoped (not applied via the token's light-mode value) so it never fights
   an element's own border-color on other edges. */
[data-theme="dark"] .modal,
[data-theme="dark"] .linked-card,
[data-theme="dark"] .snapshot-item {
  border-top-color: var(--elevate-highlight);
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body { margin: 0; padding: 0; height: 100%; }
body {
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;
  letter-spacing: -0.005em;
}
button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }
input, textarea, select { font-family: inherit; color: inherit; }

/* Keyboard focus ring — inputs already get a custom accent ring on :focus;
   these are the buttons/rows that had no focus styling at all, so keyboard
   focus was either invisible or fell back to a browser default that looked
   nothing like the rest of the app. :focus-visible so it doesn't show on
   mouse/touch clicks, only real keyboard navigation. */
.iconbtn:focus-visible, .tab:focus-visible, .ghost-btn:focus-visible,
.primary-btn:focus-visible, .list-item:focus-visible, .folder-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Caret visibility — universal. The caret otherwise inherits the TEXT
   colour, which is how a white caret ends up on a white background
   (white-coloured text from the colour picker, pastes, or imports).
   A dedicated --caret variable (dark in light mode, light in dark mode)
   is forced onto EVERY element with !important, so no field in any tab
   — bodies, subjects, titles, table cells, dialogs — can escape it.
   JS sets the same variable inline on <html> as a second enforcement
   layer (see applyTheme). */
* {
  caret-color: var(--caret, #23004C) !important;
}

/* Inside the rich editor, some elements keep a LIGHT background even in
   dark mode (shaded table cells, highlight spans, pasted/imported
   content with inline backgrounds). A light caret would vanish there,
   so those spots force a dark caret. */
[data-theme="dark"] .rich-editor [style*="background"],
[data-theme="dark"] .rich-editor [style*="background"] * {
  caret-color: #23004C !important;
}

/* Mouse cursor (pointer) visibility. Audit result: this app applies NO
   custom cursor images — only system cursors. The one that can vanish
   is the OS I-beam over editable text: on Windows it's a thin inverting
   line that washes out on white. System cursors can't be recoloured by
   CSS, so text-editing surfaces get a theme-safe dual-tone SVG I-beam
   (core + contrasting halo — visible on ANY background), falling back
   to the system `text` cursor. Arrows, hands, grips and resize cursors
   stay system defaults (they're outlined and never invisible). No
   !important here, so JS-driven cursors (e.g. col-resize while dragging
   a table column) still take precedence. */
:root {
  --cursor-text: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cg fill='none' stroke-linecap='round'%3E%3Cpath d='M8.5 4h2.6M12.9 4h2.6M12 4v16M8.5 20h2.6M12.9 20h2.6' stroke='%23ffffff' stroke-width='4' stroke-opacity='.9'/%3E%3Cpath d='M8.5 4h2.6M12.9 4h2.6M12 4v16M8.5 20h2.6M12.9 20h2.6' stroke='%2323004C' stroke-width='1.8'/%3E%3C/g%3E%3C/svg%3E") 12 12, text;
  /* One knob for the mobile item-text size across Processes, Checklists,
     Templates and Insights, consumed by the "MOBILE TYPOGRAPHY" block at
     the very bottom of this file. A 0.7 (30% cut) was tried and REVERTED:
     ~11px step/item text read as disconnected from the rest of the phone
     layout. At 1 the item text sits on the app's own 16/15px mobile floor,
     text runs larger than the accent-mono numbering (normal hierarchy),
     and the iOS focus snap-back to 16px becomes nearly invisible. */
  --mobile-text-shrink: 1;
  /* Vertical room the floating buttons need above the bottom nav, so
     scrollable content can never come to rest underneath them. Derived
     from the FABs themselves: 72px bottom offset + 56px height + 20px
     breathing room. Consumed by .detail-content's mobile padding-bottom. */
  --fab-clearance: 148px;
}
[data-theme="dark"] {
  --cursor-text: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cg fill='none' stroke-linecap='round'%3E%3Cpath d='M8.5 4h2.6M12.9 4h2.6M12 4v16M8.5 20h2.6M12.9 20h2.6' stroke='%2323004C' stroke-width='4' stroke-opacity='.9'/%3E%3Cpath d='M8.5 4h2.6M12.9 4h2.6M12 4v16M8.5 20h2.6M12.9 20h2.6' stroke='%23F5F3F8' stroke-width='1.8'/%3E%3C/g%3E%3C/svg%3E") 12 12, text;
}
input:not([type="color"]):not([type="file"]):not([type="checkbox"]):not([type="radio"]):not([type="range"]),
textarea,
[contenteditable="true"], [contenteditable=""],
.rich-editor, .rich-editor td, .rich-editor th,
.template-subject-input, .detail-title-input,
.perf-cell-value, .perf-th-label,
.var-window-input, .proc-import-text, .perf-dur-input {
  cursor: var(--cursor-text);
}

/* TOPBAR */
.topbar {
  height: var(--topbar-h);
  background: var(--bg-elev);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 24px;
  gap: 24px;
  position: relative;
  z-index: 10;
}
.topbar-left, .topbar-right { display: flex; align-items: center; gap: 6px; }
.topbar-left { gap: 32px; }

.brand { display: flex; align-items: center; gap: 10px; }
.brand-mark {
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
  color: white;
  border-radius: var(--radius-md);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 25%, transparent);
}
.brand-name {
  font-weight: 700;
  font-size: 17px;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.tabs { display: flex; gap: 4px; }
.tab {
  font-size: 13.5px;
  font-weight: 500;
  padding: var(--d-tab-pad);
  border-radius: var(--radius-md);
  color: var(--ink-mute);
  letter-spacing: -0.005em;
  transition: all 0.15s;
}
.tab:hover { color: var(--ink); background: var(--bg-sunk); }
.tab:active { transform: scale(0.96); }
.tab.active {
  color: var(--accent-text);
  background: var(--accent-bg);
  font-weight: 600;
}

.iconbtn {
  width: var(--d-iconbtn); height: var(--d-iconbtn);
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-md);
  color: var(--ink-mute);
  transition: all 0.15s;
}
.iconbtn:hover { background: var(--bg-sunk); color: var(--ink); }
.iconbtn:active { background: var(--bg-sunk); transform: scale(0.9); }
.iconbtn.small { width: 26px; height: 26px; font-size: 12px; border-radius: var(--radius-sm); }

/* BACKUP BANNER */
.backup-banner {
  background: var(--accent-bg);
  color: var(--ink);
  padding: 12px 24px;
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 13px;
  border-bottom: 1px solid var(--accent-bg-strong);
  transition: padding 0.15s;
}
/* Mobile only (see app.js scroll listener) — once the page has scrolled
   past it, shrink to a single-line pill instead of holding the full 64px
   permanently. */
.backup-banner.collapsed {
  padding: 5px 24px;
  gap: 10px;
}
.backup-banner.collapsed #backupBannerText {
  font-size: 11.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.backup-banner.collapsed .link-btn { padding: 3px 9px; font-size: 11.5px; }
.backup-banner.collapsed .iconbtn.small { width: 22px; height: 22px; }
.link-btn {
  background: var(--accent-btn);
  color: white;
  font-weight: 600;
  font-size: 12.5px;
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  transition: background 0.15s, transform 0.1s;
  flex-shrink: 0;
}
.link-btn:hover { background: var(--accent-hover); }
.link-btn:active { transform: translateY(1px); }
.backup-banner .iconbtn { margin-left: auto; flex-shrink: 0; }

/* LAYOUT — the folder task bar (above, in normal flow) is the whole nav
   now; .layout just holds the detail pane full-width below it. */
.layout {
  height: calc(100vh - var(--topbar-h) - var(--folder-taskbar-h));
  position: relative;
}

/* FOLDER TASK BAR — persistent, sits between .topbar and .layout at every
   breakpoint. Left side scrolls horizontally through main-folder pills;
   right side holds the controls that used to live in the sidebar header/
   footer (sort, new folder, + New, trash, save indicator). */
/* ── Screen title ────────────────────────────────────────────────────────
   One UI's most identifying layout move, and the app had no equivalent at
   all: measured before this, the largest text on the default screen was an
   18px "⋯" glyph, with every row title at 15px. Nothing anchored a screen
   or told you at a glance which section you were in without reading the
   nav.

   Hidden by default and shown only at mobile widths (see the two mobile
   blocks) — at desktop widths the top tab bar already names the active
   section, so a 30px title there would just say it twice. */
.screen-title { display: none; }

.folder-taskbar {
  height: var(--folder-taskbar-h);
  background: var(--bg-elev);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 16px;
}
.folder-taskbar-scroll-wrap {
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
}
.folder-taskbar-scroll {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.folder-taskbar-scroll::-webkit-scrollbar { display: none; }
.folder-taskbar-fade {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 28px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s;
  z-index: 2;
}
.folder-taskbar-fade-l { left: 0; background: linear-gradient(to right, var(--bg-elev), transparent); }
.folder-taskbar-fade-r { right: 0; background: linear-gradient(to left, var(--bg-elev), transparent); }
.folder-taskbar-scroll-wrap.can-scroll-left .folder-taskbar-fade-l { opacity: 1; }
.folder-taskbar-scroll-wrap.can-scroll-right .folder-taskbar-fade-r { opacity: 1; }
.folder-taskbar-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.folder-taskbar-empty { font-size: 12.5px; color: var(--ink-faint); white-space: nowrap; }
/* Mobile-only overflow button — see the mobile media query / forced-mobile
   blocks below, which hide #sortBtn/#newFolderBtn/#trashBtn and show this
   instead, freeing up most of the row for the breadcrumb scroll area.
   Hidden by default (desktop/tablet keep the three individual buttons —
   there's room for them there). */
.folder-taskbar-more-btn { display: none; }
.select-compact { height: 30px; padding: 0 26px 0 9px; font-size: 12px; max-width: 150px; }
.primary-btn-sm { height: 30px; padding: 0 11px; font-size: 13px; }

/* Breadcrumb nav — Home icon, then chevron-separated tappable folder
   segments down to whatever's currently open/active, plus (when a
   document is open — see renderFolderBreadcrumb) a trailing, non-tappable
   segment for its own title. Horizontal scroll on .folder-taskbar-scroll
   (overflow-x: auto, already set above) is kept as a fallback for
   genuinely deep folder trees, but it's no longer the primary way long
   content gets handled: .breadcrumb-segment is now a shrinkable flex item
   (flex-shrink, min-width below) so segments give up space to each other
   before the row ever needs to scroll, each one individually ellipsis-
   truncating via .breadcrumb-segment-label as it shrinks. Home never
   shrinks (icon-only, trivial width); the immediate parent folder
   (.breadcrumb-segment-nearest, set in JS) and the open file's own
   segment (.breadcrumb-segment-file) get larger min-width floors than
   other ancestors, so they're the last to compress and stay identifiable
   longest, per the priority the request called out. */
.breadcrumb-home,
.breadcrumb-segment {
  position: relative;
  display: inline-flex; align-items: center; gap: 6px;
  border-radius: var(--radius-md);
  color: var(--ink-mute);
  cursor: pointer;
  background: transparent;
  border: none;
  transition: background-color 0.1s, color 0.1s;
}
.breadcrumb-home {
  padding: 7px;
  flex-shrink: 0;
}
/* Home is a path segment again — the first one — not a disguised toggle.
   Its pressed/"pane-open" chip styling went with the toggle behaviour; the
   folder tree is now #folderTreeBtn in the folder bar's actions, which
   carries the open state on .active like every other icon button. */
.iconbtn.small.active {
  background: var(--accent-btn);
  color: #fff;
}
.iconbtn.small.active:hover { background: var(--accent-hover); color: #fff; }
.breadcrumb-segment {
  padding: var(--d-crumb-pad);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  flex-shrink: 1;
  min-width: 0;
}
/* Vertical-only expansion — segments sit close together horizontally in the
   scrolling taskbar, so widening the hit area sideways would make adjacent
   segments overlap; there's nothing above/below to collide with. */
.breadcrumb-home::before,
.breadcrumb-segment::before { content: ''; position: absolute; inset: -7px 0; }
.breadcrumb-home:hover,
.breadcrumb-segment:hover { background: var(--bg-sunk); color: var(--ink); }
.breadcrumb-home.open,
.breadcrumb-segment.open { background: var(--accent-bg); color: var(--accent-text); }
.breadcrumb-home.current,
.breadcrumb-segment.current { color: var(--ink); font-weight: 700; }
.breadcrumb-home.current:not(.open),
.breadcrumb-segment.current:not(.open) { background: var(--accent-bg); color: var(--accent-text); }
.breadcrumb-segment-icon { display: inline-flex; flex-shrink: 0; }
.breadcrumb-segment-icon svg { width: 14px; height: 14px; }
.breadcrumb-segment-label {
  flex: 0 1 auto;
  min-width: 22px;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.breadcrumb-segment-nearest .breadcrumb-segment-label { min-width: 48px; }
/* The open document's own trailing segment — display-only (a <span>, not
   a <button>: no hover/pointer affordance, nothing to click), and given
   the largest truncation budget of any segment since it's the primary
   "where am I" signal once a document is open. */
.breadcrumb-segment-file {
  cursor: default;
}
.breadcrumb-segment-file:hover { background: var(--accent-bg); color: var(--accent-text); }
.breadcrumb-segment-file .breadcrumb-segment-label {
  min-width: 64px;
  max-width: 280px;
}
.breadcrumb-chevron {
  display: inline-flex; align-items: center; flex-shrink: 0;
  color: var(--ink-faint);
}

/* Desktop "+" button (#newMenuBtn) is a plain .iconbtn.small, styled
   identically to its sort/new-folder/trash siblings — no control-specific
   CSS of its own needed. Its dropdown menu: */
@keyframes menuPopIn { from { opacity: 0; transform: scale(0.85); } to { opacity: 1; transform: scale(1); } }
.new-menu-pop {
  position: fixed;
  z-index: 320;
  min-width: 168px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: 6px;
  transform-origin: top left;
  animation: menuPopIn var(--duration-base) var(--ease-settle, cubic-bezier(0.22, 0.61, 0.36, 1));
}
.new-menu-item {
  display: flex; align-items: center; gap: 9px;
  width: 100%;
  padding: 8px 10px;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-soft);
  border-radius: var(--radius-sm);
  text-align: left;
  transition: background-color 0.1s, color 0.1s;
}
.new-menu-item svg { flex-shrink: 0; color: var(--ink-faint); }
.new-menu-item:hover { background: var(--bg-sunk); color: var(--ink); }
.new-menu-item:hover svg { color: var(--accent); }
.folder-tree { flex: 1; overflow-y: auto; padding: 4px 10px 16px; min-height: 0; }
.folder-node { user-select: none; }
/* Sibling folders (any depth) get breathing room; top-level folders are
   "groups" and get extra room plus a light divider, so the eye can tell
   "new group starting" from "next folder in this group" at a glance. */
.folder-node + .folder-node { margin-top: 7px; }
#folderTree > .folder-node + .folder-node {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border-soft);
}
.folder-row {
  display: flex; align-items: center; gap: 8px;
  padding: 11px 10px 11px 8px;
  border-radius: var(--radius-md);
  cursor: pointer;
  font-size: 14px;
  color: var(--ink);
  position: relative;
  transition: background-color 0.1s, color 0.1s;
  font-weight: 600;
}
.folder-row:hover { background: var(--bg-sunk); }
.folder-row:active { background: var(--accent-bg); }
.folder-row.active {
  background: var(--accent-bg);
  color: var(--accent-text);
  font-weight: 600;
}
/* Trailing hint that a folder row navigates a level deeper (single-level
   drill-down — no more inline expand/collapse). Muted by default, echoing
   the row's own text; brightens with the row on hover for one consistent
   affordance instead of a second competing hover state. */
.folder-row-drill {
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  margin-left: auto;
  color: var(--ink-faint);
}
.folder-dot { width: 8px; height: 8px; border-radius: 3px; background: var(--ink-faint); flex-shrink: 0; }
/* Main tree: an outline folder icon tinted with the folder's own colour —
   carries both "this is a folder" (shape) and "this folder's colour"
   (tint) in one glyph, instead of stacking a separate icon + dot. */
.folder-icon { display: inline-flex; flex-shrink: 0; }
.folder-icon svg { width: 18px; height: 18px; }
.folder-dot-btn { cursor: pointer; transition: transform 0.12s, box-shadow 0.12s; }
.folder-dot-btn:hover { transform: scale(1.35); box-shadow: 0 0 0 2px var(--accent-ring); }

/* The folder pane drawer's CSS lived here (in-flow desktop sidebar +
   mobile slide-in, ~60 lines). Removed with the drawer: the browse map
   shows the same tree in the main pane. .layout stays a flex row — the
   detail pane's min-width:0 is what lets wide content (tables, the steps
   grid) shrink instead of forcing the row wider than the viewport. */
.layout { display: flex; }
.layout > .detail-pane { flex: 1; min-width: 0; }
/* Root row — the section's own "All items" level in the breadcrumb's
   drill-down dropdown; reads a little heavier than a folder row. */
.folder-row-root { font-weight: 600; }
.folder-row-root .folder-label { color: var(--ink); }
.folder-row-root.active { background: var(--accent-bg); color: var(--accent-text); }

/* Expand/collapse chevron. Its own hit target on the row — tapping it
   expands in place, tapping anywhere else on the row navigates (see
   buildFolderRowEl), so browsing a folder's contents never has to cost
   you the document you already have open. */
.folder-caret {
  width: 16px; height: 16px;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-faint);
  flex-shrink: 0;
  border-radius: 4px;
  transition: transform 0.15s, background-color 0.1s;
}
.folder-caret:hover { background: var(--border-soft); color: var(--ink); }
.folder-caret.collapsed { transform: rotate(-90deg); }
.folder-caret.empty { visibility: hidden; }
.folder-children {
  overflow: hidden;
  max-height: 4000px;
  opacity: 1;
  transition: max-height 0.18s ease, opacity 0.15s ease;
}
.folder-children.collapsed { max-height: 0; opacity: 0; }

/* Compact folder colour picker */
.folder-color-pop {
  position: fixed;
  z-index: 320;
  /* Same density pass as the map: 3x2 grid of LARGER swatches in a smaller
     pop — the old 6-in-a-row layout spent 196px to offer 24px targets,
     which is both roomier and harder to hit than this. */
  width: 128px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg, 0 8px 24px rgba(0,0,0,0.18));
  padding: 8px;
}
.folder-details-pop {
  position: fixed;
  z-index: 320;
  width: 232px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg, 0 8px 24px rgba(0,0,0,0.18));
  padding: 10px;
}
.folder-details-name {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 6px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.folder-details-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-size: 11px;
  color: var(--ink-mute);
  padding: 3px 0;
}
.folder-details-row b { color: var(--ink); font-weight: 500; }
.folder-color-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
}
.folder-color-swatch {
  width: 100%;
  aspect-ratio: 1;
  border: 1px solid rgba(0,0,0,0.15);
  border-radius: var(--radius-sm);
  padding: 0;
  cursor: pointer;
  transition: transform 0.1s, box-shadow 0.1s;
}
.folder-color-swatch:hover { transform: scale(1.15); box-shadow: 0 0 0 2px var(--accent); }
.folder-color-swatch.sel { box-shadow: 0 0 0 2px var(--accent); }

/* Files inherit their folder's colour, lightened 30-40% (see lightenColor())
   so the tree reads "same family, quieter" rather than identical to the
   folder's own strong-colored icon. */
.list-item-file-icon {
  display: inline-flex;
  vertical-align: -2px;
  margin-right: 6px;
  flex-shrink: 0;
}
.list-item-file-icon svg { width: 16px; height: 16px; }

/* The row's leading icon carries BOTH facts at once: its colour is the folder
   tint, its shape is the insight kind. A separate kind badge sat beside it for
   one release and read as two small marks competing rather than one
   identifying mark — the shape is doing the work, so it belongs on the icon
   that is already there. Notes keep the plain page glyph: they are the
   default, and marking everything marks nothing. */
.list-item-file-icon.kind-goal svg,
.list-item-file-icon.kind-challenge svg,
.list-item-file-icon.kind-prompt svg,
.list-item-file-icon.kind-question svg,
.list-item-file-icon.kind-learning svg { stroke-width: 1.75; }
/* Question and Learning stay stroked (like the goal target) — filled, the
   question mark loses its counter and the book loses its pages. */
.list-item-file-icon.kind-question svg,
.list-item-file-icon.kind-learning svg { fill: none; stroke: currentColor; }
/* Spark and bolt are solid glyphs; the target is an outline. */
.list-item-file-icon.kind-challenge svg,
.list-item-file-icon.kind-prompt svg { fill: currentColor; stroke: none; }
.list-item-file-icon.kind-goal svg { fill: none; stroke: currentColor; }

/* The dropdown's footer row (.folder-panel-setting) went with the dropdown;
   its knowledge-graph button is now #insGraphBarBtn in the folder bar. */
.folder-label { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.folder-empty-note {
  font-size: 11.5px; color: var(--ink-faint); font-style: italic;
  padding: 4px 10px 6px 20px;
}
.tree-empty { text-align: center; color: var(--ink-mute); padding: 40px 16px; }
.tree-empty .list-empty-mark { font-size: 34px; opacity: 0.4; margin-bottom: 8px; }

/* Items inside a folder's dropdown level (single-level drill-down) */
.list-item-nested {
  margin-left: 8px;
  margin-right: 4px;
}
.list-item-nested .list-item-checkbox { display: none; }

.ghost-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px;
  border-radius: var(--radius-md);
  font-size: 13px;
  color: var(--ink-soft);
  background: transparent;
  font-weight: 500;
  transition: all 0.15s;
}
.ghost-btn:hover { background: var(--bg-sunk); color: var(--ink); }
.ghost-btn:active { background: var(--accent-bg); color: var(--accent-text); transform: scale(0.96); }
/* Reset-all-variables sits in the Variables section heading; inherits
   .ghost-btn styling, only un-inherits the heading's uppercase treatment. */
.tpl-vars-actions { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; }
.tpl-hdr-btn { text-transform: none; letter-spacing: normal; font-weight: 500; }
/* Variables now live in the ~280px metadata rail — actions sit on their
   own wrapped row below the title instead of squeezing into it. */
.tpl-vars-actions-row { display: flex; flex-wrap: wrap; gap: 6px; margin: -2px 0 8px; }
.rail-section .tpl-hdr-btn { font-size: 11px; padding: 4px 8px; }
.rail-subhead {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-faint); margin-bottom: 4px;
}
.rail-subhead:not(:first-child) { margin-top: 4px; }

/* Every <select> in the app, styled everywhere except the one part a user
   actually clicks: the native dropdown arrow. One rule here reaches every
   select — folder pickers, sort, step status, template font/size, gantt
   filters — instead of restyling each one individually. The padding-right
   is !important because plenty of the more specific classes below set
   their own padding shorthand at higher specificity, and the arrow needs
   guaranteed room regardless of which of them wins. */
select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237A6E93' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 7px center;
  background-size: 12px 12px;
  padding-right: 24px !important;
}
[data-theme="dark"] select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23948DA6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
}
.select {
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 6px 10px;
  font-size: 12px;
  color: var(--ink-soft);
  cursor: pointer;
  font-weight: 500;
  transition: border-color var(--duration-fast), transform var(--duration-fast);
}
.select:hover { border-color: var(--ink-faint); }
.select:active { transform: scale(0.98); }
.select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
.primary-btn {
  /* --accent-btn, not --accent: white sits on this. See the token comment. */
  display: inline-flex; align-items: center; gap: 5px;
  padding: 7px 14px;
  border-radius: var(--radius-md);
  background: var(--accent-btn);
  color: white;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
  transition: all 0.15s;
  box-shadow: 0 1px 2px color-mix(in srgb, var(--accent) 20%, transparent);
}
.primary-btn:hover { background: var(--accent-hover); box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 30%, transparent); transform: translateY(-1px); }
.primary-btn:active { transform: translateY(0) scale(0.96); box-shadow: 0 1px 2px color-mix(in srgb, var(--accent) 20%, transparent); }
.primary-btn.full { width: 100%; justify-content: center; }
.danger-btn {
  padding: 8px 16px;
  border-radius: var(--radius-md);
  background: var(--danger-solid);
  color: white;
  font-size: 13px;
  font-weight: 600;
  transition: opacity var(--duration-fast), transform var(--duration-fast);
}
.danger-btn:hover { opacity: 0.9; }
.danger-btn:active { opacity: 0.8; transform: scale(0.96); }

.list-item {
  padding: 14px 16px;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all 0.12s;
  border: 1px solid transparent;
  margin-bottom: 4px;
}
.list-item-nested { padding: 5px 10px; margin-bottom: 3px; }
/* The "⋯" more-actions button is sized for the old full-size list row —
   32px pushed a compact nested row up to ~42px tall. Shrinking it to fit
   is what actually gets the row into the 32-36px target, not just padding. */
.list-item-nested .list-item-more { width: 24px; height: 24px; font-size: 15px; }
/* Files are the "quiet" tier: regular weight, medium-neutral text, no
   background hover/active (folders own that treatment) — an underline
   signals hover/active instead, so files read as lighter-weight than
   folders even when selected.
   This used to be a two-column flex — title on the left, right-aligned
   snippet capped at 42% of the width on the right. With the snippet gone
   the title is the only child, so the flex row and the width cap both go
   with it and the title simply takes the whole space the "⋯" doesn't. */
.list-item-nested .list-item-main { min-width: 0; }
.list-item-nested .list-item-title {
  font-size: 13px; margin-bottom: 0;
  font-weight: 400; color: var(--ink-soft);
}
.list-item:hover { background: transparent; border-color: transparent; }
/* This tier deliberately has no background hover (files stay visually
   "quiet" — see the comment above; an underline signals hover instead),
   so press feedback here is opacity-only rather than the background/scale
   treatment used elsewhere, to not fight that existing design. */
.list-item:active { opacity: 0.7; }
.list-item-nested:hover .list-item-title { text-decoration: underline; text-underline-offset: 2px; }
.list-item.active {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
}
.list-item-nested.active .list-item-title {
  font-weight: 600; color: var(--ink);
  text-decoration: underline; text-underline-offset: 2px;
}
.list-item-title {
  font-weight: 600;
  font-size: 14px;
  color: var(--ink);
  margin-bottom: 4px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  letter-spacing: -0.01em;
}
.list-empty {
  flex: 1;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  color: var(--ink-mute);
  font-size: 13px;
  padding: 40px;
  text-align: center;
  gap: 14px;
}
.list-empty-mark {
  width: 56px; height: 56px;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent-bg);
  border-radius: var(--radius-xl);
  color: var(--accent);
  font-size: 28px;
}

.save-indicator {
  font-size: 11px;
  color: var(--success-text);
  background: var(--success-bg);
  padding: 4px 10px;
  border-radius: 20px;
  border: 1px solid transparent;
  opacity: 0;
  transition: opacity 0.3s;
  pointer-events: none;
  font-weight: 600;
}
.save-indicator.show { opacity: 1; }

/* DETAIL PANE */
.detail-pane { height: 100%; background: var(--bg-elev); overflow-y: auto; position: relative; }
.detail-empty {
  height: 100%;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  color: var(--ink-mute);
  gap: 14px;
  padding: 40px;
  text-align: center;
}
.detail-empty-mark {
  width: 72px; height: 72px;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent-bg);
  border-radius: var(--radius-xl);
  color: var(--accent);
  font-size: 36px;
}
.detail-empty-title { font-size: 18px; color: var(--ink); font-weight: 600; letter-spacing: -0.015em; }
.detail-empty-sub { font-size: 13px; max-width: 320px; line-height: 1.5; }
.detail-empty-cta { margin-top: 4px; }

/* Browse view (renderBrowseView) — the main pane's default state once the
   section has anything in it. .detail-empty is built to centre a single
   message, so browse-mode undoes that and turns it into a scrolling list. */
.detail-empty.browse-mode {
  display: block;
  align-items: stretch;
  justify-content: flex-start;
  text-align: left;
  color: var(--ink);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* 40px of bottom padding put the last row underneath the floating "+",
     so the final document in the last folder was permanently half-covered
     and un-tappable. The FAB is 56px tall and sits ~24px above the bottom
     nav, so the list needs to be able to scroll clear of both. */
  padding: 14px 16px 116px;
}
/* The browse view IS the map now (Prototype A, chosen over the inset cards
   and then extended from Processes/Checklists to every browse section) —
   the whole section as one compact drill-down tree. The card rules that
   used to live here are gone; the map rules further down are the base. */
.browse-view { max-width: 840px; margin: 0 auto; }


/* The count was bare text pinned to the right margin, reading as a stray
   digit. As a tonal chip it belongs to the header it sits in. */
.browse-count {
  margin-left: auto;
  min-width: 24px;
  padding: 2px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--folder-tint) 15%, transparent);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
  color: var(--ink-mute);
  font-variant-numeric: tabular-nums;
  /* Neutralise the header's tightened tracking — it is set for a title, and
     on a two-digit number it reads as a cramped pair rather than a count. */
  letter-spacing: 0;
}
.browse-group-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  padding: 10px 8px 6px;
}
/* Insights grouped by KIND — the group header carries the kind glyph and
   a trailing count, matching the folder headers' weight. */
.kind-group-label { display: flex; align-items: center; gap: 7px; }
.kind-group-label svg { flex-shrink: 0; }
.kind-group-label .browse-count { margin-left: auto; }

/* ── Map mode (Processes + Checklists) ───────────────────────────────
   The Inventory-style drill-down chosen over the card treatment: the whole
   section as ONE compact tree, documents as quiet leaf rows carrying their
   own metadata. Same DOM as the cards — these rules strip the card chrome
   and re-tune the geometry, so the collapse machinery, the whole-strip
   toggle and the a11y contract carry over untouched. */
.browse-view .browse-group {
  background: transparent;
  border: none;
  border-radius: 0;
  box-shadow: none;
  margin-bottom: 0;
  overflow: visible;
}
.browse-view .browse-group > .folder-row {
  background: transparent;
  border-bottom: 1px solid var(--border-soft);
  /* Compacted on request — the map's whole point is density. Mobile keeps
     44px document rows (the touch minimum); desktop rows drop to ~32px. */
  border-radius: 0;
  font-size: 14.5px;
  font-weight: 650;
  letter-spacing: -0.008em;
  padding-top: 6px;
  padding-bottom: 6px;
}
.browse-view .browse-group > .folder-row:hover { background: var(--bg-sunk); }
.browse-view .browse-group > .folder-row:active { background: var(--accent-bg); }
/* The cards drop the header hairline when collapsed (it would imply hidden
   content); in the map the hairline is the ROW SEPARATOR, so it stays. */
.browse-view .browse-group:has(> .folder-children.collapsed) > .folder-row {
  border-bottom-color: var(--border-soft);
}
/* Indent capped at 40% of the width — Inventory's trick, so a deep tree at
   a narrow width never crushes the names; past the cap the chevrons carry
   the hierarchy alone. */
.browse-view .folder-row {
  padding-left: min(calc(8px + var(--nest-depth, 0) * 18px), 40%);
}
.browse-view .folder-children { padding: 0; }
.browse-view .list-item-nested {
  margin: 0;
  min-height: 32px;
  padding: 2px 10px;
  padding-left: min(calc(17px + var(--nest-depth, 1) * 18px), 45%);
  border-bottom: 1px solid var(--border-soft);
  border-radius: 0;
}
.browse-view .list-item-nested .list-item-title { font-size: 13.5px; }
.mp-meta {
  flex: none;
  font-size: 11.5px;
  color: var(--ink-mute);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  padding-right: 2px;
}
.mp-done { color: var(--success-text); }
.detail-content { max-width: 840px; margin: 0 auto; padding: var(--d-detail-pad); }

/* Metadata rail — reclaims the wide dead margins that used to sit on
   either side of the fixed 840px reading column. Below the breakpoint
   .detail-rail just falls into normal flow after .detail-main (today's
   stacked layout, unchanged); above it, .detail-content widens and
   .detail-body-grid becomes a two-column grid. Tags/backrefs/snapshots/
   step-notes move here instead of competing with the actual content for
   the same vertical scroll. */
@media (min-width: 1280px) {
  /* Widened from an earlier 1160px/280px cap that left large empty margins
     on wide screens — the Template editor got this fix first (body-heavy
     rich-text editor made the gap most obvious there), then generalized to
     every rail-having screen for one consistent width across Processes/
     Checklists/Templates/Insights, instead of Templates alone being wider. */
  .detail-content:has(.detail-body-grid.has-rail) { max-width: 1600px; }
  .detail-body-grid.has-rail {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--rail-w, 320px);
    column-gap: 28px;
    align-items: start;
  }
  /* Collapsible rails animate their own track down to a slim icon-bar width
     instead of the column vanishing outright — .detail-main's 1fr track
     reclaims the freed space either way. */
  .detail-body-grid.has-rail:has(.detail-rail.collapsible) {
    transition: grid-template-columns 0.18s ease-in-out;
  }
  .detail-body-grid.has-rail.rail-collapsed {
    grid-template-columns: minmax(0, 1fr) 44px;
    column-gap: 20px;
  }
}
.detail-main { min-width: 0; }
.detail-rail {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 32px;
  padding-top: 28px;
  border-top: 1px solid var(--border-soft);
}
/* Moved into the mobile metadata bottom sheet — it's the sheet's own
   top content now, not a continuation after .detail-main. */
#mobileRailModalBody .detail-rail { margin-top: 0; padding-top: 0; border-top: none; }
@media (min-width: 1280px) {
  .detail-rail { margin-top: 0; padding-top: 0; border-top: none; }
  /* Collapsible rails only: a real side pane — sticky within .detail-pane's
     own scroll (so it tracks the viewport without needing position:fixed),
     independent scroll, full available height, light-neutral background
     matching the folder pane, subtle left divider. */
  .detail-rail.collapsible {
    position: sticky;
    top: 24px;
    max-height: calc(100vh - 48px);
    overflow-y: auto;
    overflow-x: hidden;
    gap: 0;
    background: var(--bg-elev);
    border-left: 1px solid var(--border-soft);
    padding: 0 16px;
  }
}
/* Each section reads as its own card — a flat list of uppercase labels
   with no visual grouping was the "everything looks the same, feels
   cluttered" complaint on the mobile metadata sheet. Reset to a plain
   divider row instead inside the ≥1280px collapsible side-pane, where
   it's a dense persistent column rather than a sheet the user opens
   deliberately, so cards there would feel heavier than useful. */
.rail-section {
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: 14px 16px;
}
.rail-section-title {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-mute);
  margin-bottom: 10px;
}
.rail-section-title svg { color: var(--ink-faint); }
/* .detail-rail's own flex gap already spaces sections out — the tags
   block's usual bottom margin (meant for sitting above unrelated content
   in the old stacked layout) would just double up here. */
.rail-section .detail-tags { margin-bottom: 0; }

/* Single unified empty state for the whole sheet/pane instead of every
   section repeating its own "select a step" line — much less to read
   before anything is selected. */
.rail-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 56px 24px;
  text-align: center;
  color: var(--ink-faint);
  font-size: 13px;
}
.rail-empty-state svg { opacity: 0.5; }

/* ---- Collapsible rail: header, collapsed icon-bar, section rhythm ----
   The collapse/pane concept only makes sense once the rail is a real
   side-by-side column (≥1280px) — below that it's back to today's plain
   stacked flow, so all of this chrome stays hidden there regardless of
   the persisted collapsed/expanded preference. */
.rail-header, .rail-collapsed-icons { display: none; }
.rail-header-title { font-size: 15px; font-weight: 700; color: var(--ink); letter-spacing: -0.01em; }
.rail-chevron {
  width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-faint);
  background: transparent; border: none; padding: 0; cursor: pointer;
  border-radius: var(--radius-sm);
  transition: transform 0.18s ease-in-out, color 0.12s, background-color 0.12s;
}
.rail-chevron:hover { color: var(--ink); background: var(--bg-sunk); }
.rail-chevron svg { width: 16px; height: 16px; }
.rail-chevron.collapsed { transform: rotate(180deg); }

.rail-icon-btn {
  width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-70);
  background: transparent; border: none; border-radius: var(--radius-sm);
  transition: color 0.12s, background-color 0.12s;
}
.rail-icon-btn svg { width: 18px; height: 18px; }
.rail-icon-btn:hover { color: var(--icon-hover); background: var(--bg-sunk); }
.rail-icon-btn.active { color: var(--accent); }

.rail-section-body { font-size: 13px; color: var(--ink-soft); }
.rail-section-body.rail-empty { color: var(--ink-faint); }

@media (min-width: 1280px) {
  .detail-rail.collapsible .rail-header {
    display: flex; align-items: center; justify-content: space-between;
    padding: 16px 0 14px;
    margin-bottom: 6px;
    border-bottom: 1px solid var(--border-soft);
    flex-shrink: 0;
  }
  /* Sections separate purely by whitespace, like the reference "Edit Node"
     panel — no divider lines between them, just generous vertical rhythm. */
  .detail-rail.collapsible .rail-content { display: flex; flex-direction: column; gap: 26px; padding: 20px 0 24px; }
  .detail-rail.collapsible .rail-section {
    background: none;
    border: none;
    border-radius: 0;
    padding: 0;
  }

  /* Collapsed: icon bar only, no title, no section content. Width comes
     out to ~44px (6px+6px padding + 32px icon buttons) — inside the 40-48
     target range. */
  .detail-rail.collapsible.collapsed { padding: 0 6px; align-items: center; }
  .detail-rail.collapsible.collapsed .rail-header-title,
  .detail-rail.collapsible.collapsed .rail-content { display: none; }
  .detail-rail.collapsible.collapsed .rail-collapsed-icons { display: flex; flex-direction: column; gap: 4px; padding-top: 4px; }
}

.detail-meta-row {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: var(--space-2, 8px);
  font-size: 12px; color: var(--ink-mute);
  flex-wrap: wrap;
}
.detail-meta-row .dot { color: var(--ink-faint); }

/* Action bar — Run/Duplicate/Import/Markdown/Delete etc, always its own
   row directly under the folder-select row above, never wrapped in with
   it. Pill buttons: muted gray at rest, purple on hover/focus.
   overflow-x/nowrap/hidden-scrollbar is unconditional (not just a mobile
   override) — a scrollable row is a no-op when everything already fits
   (desktop/wide tablet), and self-solves at any width in between without
   needing a breakpoint-specific copy. This closed a real gap: the old
   mobile-only copies only existed at ≤480px and the dedicated mobile
   breakpoint, so a 6-pill row (Processes: Run/Dup/Imp/MD/Upd MD/Delete)
   genuinely overflowed uncorrected in the 481–768px tablet-ish range. */
.detail-action-bar {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: center;
  gap: 8px;
  margin-bottom: var(--space-3, 12px);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.detail-action-bar::-webkit-scrollbar { display: none; }
.action-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  height: var(--d-pill-h);
  padding: 0 var(--d-pill-pad-x);
  border-radius: 20px;
  border: var(--card-border);
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-mute);
  background: var(--bg);
  transition: background-color 0.15s, color 0.15s, transform 0.1s;
}
.action-pill svg { flex-shrink: 0; }
.action-pill:hover, .action-pill:focus-visible { background: var(--accent-bg); color: var(--accent-text); }
/* Destructive at rest, not just on hover — same neutral pill shape as the
   other secondary actions, but red icon/text so it reads as different at
   a glance instead of only once you're already interacting with it
   (matters more now that it can be icon-only, with no "Delete" text to
   fall back on). */
.action-pill.action-pill-danger { color: var(--danger-text); }
.action-pill.action-pill-danger:hover, .action-pill.action-pill-danger:focus-visible { background: var(--danger-bg); color: var(--danger-text); }
/* Pressed state — Violet fill, white text/icon, plus the app-wide 1.02
   tap-scale used for tactile feedback everywhere else. */
.action-pill:active { background: var(--accent-btn); border-color: var(--accent); color: #fff; transform: scale(1.02); }
/* ...except Delete, which presses red-on-red rather than switching to the
   accent color — a destructive action shouldn't flash purple. */
.action-pill.action-pill-danger:active { background: var(--danger-solid); border-color: var(--danger-text); color: #fff; }
/* Run gets primary/filled weight — iOS convention of one accent action
   among otherwise-secondary controls in a toolbar. */
.action-pill.action-pill-primary {
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
  border-color: transparent;
  color: #fff;
}
.action-pill.action-pill-primary:hover, .action-pill.action-pill-primary:focus-visible {
  background: linear-gradient(135deg, var(--accent-hover) 0%, var(--accent) 100%);
  color: #fff;
}
/* Category/folder pill — a real button now (opens the folder-picker
   modal), not a native <select>, so it can be sized identically to every
   other pill in the row with no OS-chevron quirks to fight. Violet-tinted
   rather than the neutral gray of the secondary pills or Run's solid
   fill — it's a selection/context indicator, not a triggerable action,
   and shouldn't be mistaken for one at a glance. */
.action-pill.action-pill-category {
  background: var(--accent-bg);
  border: 1px solid var(--accent-soft);
  color: var(--accent-text);
}
.action-pill.action-pill-category:hover, .action-pill.action-pill-category:focus-visible { border-color: var(--accent-soft); background: var(--accent-bg); }
.action-pill-category-icon { display: inline-flex; }
/* Visible at desktop width (via the shared .pill-label-full class, same
   one every other pill uses) so the folder name doesn't disappear
   entirely just because the affordance switched from a native <select>
   to a button — only mobile drops it down to icon+tooltip, where the
   uniform-44px constraint leaves no room for text. */
.action-pill-category-label { max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pill-label-short { display: none; }
.detail-title-input {
  font-size: 32px;
  font-weight: 700;
  letter-spacing: -0.03em;
  color: var(--ink);
  background: transparent;
  border: none;
  outline: none;
  width: 100%;
  padding: 0;
  margin-bottom: var(--d-title-mb);
  line-height: 1.2;
}
.detail-title-input::placeholder { color: var(--ink-faint); }
.detail-description {
  font-size: 15px;
  color: var(--ink-soft);
  background: transparent;
  border: none;
  outline: none;
  width: 100%;
  resize: none;
  font-family: inherit;
  line-height: 1.55;
  margin-bottom: var(--d-desc-mb);
  /* Desktop keeps its two-line reserve (the field is rows=1 + autosized
     now); mobile zeroes this so an empty description costs one line. */
  min-height: var(--d-desc-min-h);
}

.detail-tags {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-bottom: var(--d-tags-mb);
  align-items: center;
}
.tag-pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11.5px;
  padding: 4px 10px;
  border-radius: 20px;
  background: var(--accent-bg);
  color: var(--accent-text);
  border: 1px solid var(--accent-bg-strong);
  font-weight: 500;
}
.tag-pill button { position: relative; color: var(--accent-text); font-size: 13px; line-height: 1; padding: 0; }
/* Modest expansion (not the full 44px used elsewhere) — pills wrap tightly
   next to each other, so a full-size invisible hit area would overlap
   neighboring pills and risk removing the wrong tag. */
.tag-pill button::before { content: ''; position: absolute; inset: -8px; }
.tag-pill button:hover { color: var(--danger-text); }
.tag-add-input {
  background: var(--bg-sunk);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  outline: none;
  font-size: 12px;
  width: 100px;
  color: var(--ink);
  padding: 5px 10px;
  transition: border-color 0.15s, background 0.15s;
}
.tag-add-input:focus { border-color: var(--accent); background: var(--surface); }
.tag-add-input::placeholder { color: var(--ink-faint); }

.detail-toolbar {
  display: flex; gap: 6px;
  margin-bottom: 16px;
  padding: 10px 14px;
  background: var(--bg-sunk);
  border-radius: var(--radius-md);
  flex-wrap: wrap;
  align-items: center;
}
.detail-toolbar .ghost-btn { padding: 6px 12px; font-size: 12px; }

.detail-section-title {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-mute);
  margin: var(--d-sect-mt) 0 var(--d-sect-mb);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
/* A soft, low-contrast card behind the two main content blocks (Steps,
   Checklist items) so they read as a distinct section against the plain
   page background — a tint shift rather than a border/shadow, since the
   surrounding page is already var(--bg-elev). */
.section-card {
  background: var(--bg-sunk);
  border-radius: var(--radius-lg);
  padding: var(--d-card-pad);
  margin-bottom: 16px;
}

/* Templates: the single unified body editor/preview card. Pure White
   (not --bg-sunk) since it holds the raw editor and the rendered preview
   as two states of the same surface, not a tinted grouping box. */
.tpl-editor-card {
  background: var(--surface);
  border: var(--card-border);
  border-radius: var(--radius-lg);
  padding: 18px;
  margin-bottom: 16px;
}
.tpl-editor-card .rich-editor,
.tpl-editor-card #tplBodyPlain { margin-bottom: 0; }

/* Preview toggle switch (raw <-> rendered, same box, no page-level motion) */
.tpl-preview-switch {
  display: inline-flex; align-items: center; gap: 7px;
  cursor: pointer; user-select: none;
  font-family: var(--font-body); font-weight: 400; text-transform: none; letter-spacing: 0;
}
.tpl-preview-switch input {
  position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none;
}
.tpl-switch-track {
  position: relative; width: 32px; height: 18px; border-radius: 999px;
  background: var(--bg-sunk); border: 1px solid var(--border);
  transition: background-color 0.16s ease, border-color 0.16s ease;
  flex-shrink: 0;
}
.tpl-switch-thumb {
  position: absolute; top: 1px; left: 1px; width: 14px; height: 14px; border-radius: 50%;
  background: var(--surface); box-shadow: var(--shadow-sm);
  transition: transform 0.16s cubic-bezier(0.4, 0, 0.2, 1);
}
.tpl-preview-switch input:checked + .tpl-switch-track { background: var(--accent); border-color: var(--accent); }
.tpl-preview-switch input:checked + .tpl-switch-track .tpl-switch-thumb { transform: translateX(14px); }
.tpl-preview-switch input:focus-visible + .tpl-switch-track { box-shadow: 0 0 0 3px var(--accent-ring); }
.tpl-switch-label { font-size: 11px; color: var(--ink-mute); text-transform: none; letter-spacing: 0; }

/* STEPS */
.rich-editor {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 12px 14px;
  background: var(--bg);
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink);
  min-height: 60px;
  outline: none;
  margin-bottom: 14px;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.rich-editor:focus { border-color: var(--accent); background: var(--surface); box-shadow: 0 0 0 3px var(--accent-ring); }
.rich-editor:empty::before {
  content: attr(data-placeholder);
  color: var(--ink-faint);
  pointer-events: none;
}
.rich-editor p { margin: 0 0 8px; }
.rich-editor p:last-child { margin: 0; }
.rich-editor h1, .rich-editor h2, .rich-editor h3 { margin: 12px 0 6px; font-weight: 700; letter-spacing: -0.02em; }
.rich-editor code {
  font-family: var(--font-mono);
  font-size: 13px;
  background: var(--bg-sunk);
  padding: 2px 6px;
  border-radius: 4px;
}
.rich-editor a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.rich-editor ul, .rich-editor ol { padding-left: 22px; margin: 6px 0 10px; }

.rich-toolbar {
  display: flex; gap: 2px;
  padding: 5px;
  background: var(--bg-sunk);
  border-radius: var(--radius-md);
  margin-bottom: 8px;
  flex-wrap: wrap;
}
.rich-toolbar button {
  padding: 5px 10px;
  font-size: 12px;
  color: var(--ink-soft);
  border-radius: var(--radius-sm);
  font-weight: 600;
  transition: all 0.12s;
}
.rich-toolbar button:hover { background: var(--surface); color: var(--accent-text); box-shadow: var(--shadow-sm); }
.rich-toolbar .sep { width: 1px; background: var(--border); margin: 5px 3px; }
.toolbar-color {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 26px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  overflow: hidden;
}
.toolbar-color:hover { background: var(--surface); }
.toolbar-color-swatch {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink-soft);
  border-bottom: 3px solid var(--swatch-color, currentColor);
  line-height: 1;
  padding-bottom: 1px;
  pointer-events: none;
}
.toolbar-color input[type="color"] {
  position: absolute;
  inset: 0;
  opacity: 0;
  width: 100%;
  height: 100%;
  border: none;
  padding: 0;
  cursor: pointer;
}


.notes-section {
  margin-top: 16px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 14px 16px;
}
.notes-section-title {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-mute);
  margin-bottom: 12px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
/* Micro-divider between the note-add row and the notes list below it —
   both are plain (unboxed) rows of similar visual weight, unlike the
   toolbar/editor pair (toolbar already reads as its own boxed control)
   or the two linked-item sections (each already has its own border). */
.note-add { display: flex; gap: 8px; margin-bottom: 12px; padding-bottom: 12px; align-items: stretch; border-bottom: 1px solid var(--border-soft); }
.note-add textarea {
  flex: 1;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  font-size: 13px;
  color: var(--ink);
  outline: none;
  resize: vertical;
  min-height: 38px;
  font-family: inherit;
  line-height: 1.45;
  transition: border-color 0.15s;
}
.note-add textarea:focus { border-color: var(--accent); }
.note-flag-select {
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 4px 8px;
  font-size: 14px;
}
.note-add-btn {
  padding: 7px 14px;
  background: var(--accent-btn);
  color: white;
  border-radius: var(--radius-sm);
  font-size: 12px;
  font-weight: 600;
  transition: background 0.15s;
}
.note-add-btn:hover { background: var(--accent-hover); }
.notes-list { display: flex; flex-direction: column; gap: 8px; }
.note-item {
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  position: relative;
}
.note-item-header {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 4px;
  font-size: 11px;
  color: var(--ink-mute);
  font-weight: 500;
}
.note-flag { font-size: 14px; }
.note-item-text {
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink);
  white-space: pre-wrap;
}
.note-delete {
  position: absolute; top: 8px; right: 8px;
  font-size: 12px;
  color: var(--ink-faint);
  opacity: 0;
  transition: opacity 0.15s;
  padding: 2px 6px;
  border-radius: 3px;
}
.note-item:hover .note-delete { opacity: 1; }
.note-delete:hover { color: var(--danger-text); background: var(--danger-bg); }
.notes-empty {
  font-size: 12px;
  color: var(--ink-mute);
  font-style: italic;
  text-align: center;
  padding: 8px;
}

.linked-section {
  margin-top: 16px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 14px 16px;
}
.linked-list { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.linked-card {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
  transition: border-color 0.15s;
}
.linked-card:hover { border-color: var(--accent-soft); }
.linked-card-header {
  padding: 10px 14px;
  display: flex; align-items: center; gap: 10px;
  cursor: pointer;
  font-size: 13px;
}
.linked-card-header:hover { background: var(--bg-sunk); }
.linked-card-icon { font-size: 16px; color: var(--ink-mute); }
.linked-card-title { flex: 1; font-weight: 600; color: var(--ink); }
.linked-card-body {
  padding: 12px 16px 14px;
  border-top: 1px solid var(--border-soft);
  display: none;
  font-size: 13px;
  color: var(--ink-soft);
  line-height: 1.55;
  background: var(--surface);
}
.linked-card.expanded .linked-card-body { display: block; }
.linked-card-unlink {
  font-size: 11px;
  color: var(--ink-mute);
  padding: 3px 8px;
  border-radius: 3px;
  font-weight: 500;
}
.linked-card-unlink:hover { color: var(--danger-text); background: var(--danger-bg); }

.add-row { display: flex; gap: 6px; margin-top: 10px; align-items: center; }
.add-step-btn {
  background: transparent;
  border: 1px dashed var(--border);
  border-radius: var(--radius-md);
  padding: var(--d-btn-pad);
  min-height: var(--d-btn-min-h);
  font-size: 13px;
  color: var(--ink-mute);
  width: 100%;
  font-weight: 500;
  transition: all 0.15s;
}
.add-step-btn:hover {
  border-color: var(--accent);
  color: var(--accent-text);
  background: var(--accent-bg);
  border-style: solid;
}

/* ===========================================================
   STEPS TABULAR GRID (Prototype B) — replaces the old collapsible step
   cards with a single flat table. Parent (step) rows and child (sub-step)
   rows share the same six columns; hierarchy reads through background
   tint + indentation rather than nested boxes. Colors below are Color
   Schema 1 by way of the app's existing theme tokens (--ink is the
   schema's deep indigo, --accent its violet, --teal/--success/--danger
   already resolve to the schema's teal-blue/green/red-orange in both
   themes) rather than new hardcoded hex, so dark mode stays correct for
   free. */
/* Adaptive size tokens for the Steps grid — same tabular row structure
   (# | Title | Status | Actions, always a single line, never stacked)
   at every breakpoint; only these tokens change. Desktop values here are
   the default/fallback; the three breakpoints below redefine the same
   custom properties for tablet / standard-mobile / small-mobile, so every
   rule that consumes var(--st-*) adapts automatically with no duplicated
   selectors per tier. */
.steps-grid-card {
  --st-outer-pad: 16px;
  --st-row-pad: 12px;
  --st-cell-pad: 6px;
  --st-gap: 5px;
  --st-gap-title-status: 9px;
  --st-gap-status-actions: 11px;
  --st-indent: 16px;
  --st-num-w-parent: 30px;
  --st-num-w-child: 72px;
  --st-title-min: 300px;
  --st-status-w: 34px;
  --st-actions-w: 36px;
  --st-row-h-parent: 38px;
  --st-row-h-child: 34px;
  --st-font-num: 12.5px;
  --st-font-title-parent: 14.5px;
  --st-font-title-child: 13.5px;
  --st-font-pill: 11.5px;
  --st-pill-h: 22px;
  --st-pill-pad-x: 8px;
  --st-icon-size: 17px;
  /* Density multiplies whichever breakpoint's --st-* tokens are already
     active (see .steps-grid-card[data-density] below) rather than needing
     its own breakpoint × density matrix — every rule that consumes
     --st-* through this scale picks up all three density levels at every
     screen size for free. */
  --density-scale: 1;
  padding: calc(var(--st-outer-pad) * var(--density-scale));
}
/* Compact/Roomy per requirements 8/10 — deliberately not applied to
   --st-title-min, --st-num-w-*, --st-status-w or --st-actions-w (see the
   properties that consume --density-scale further down): those are
   structural column widths/wrap thresholds, not "spacing", and shrinking
   them at Compact would start clipping multi-digit step numbers or the
   touch-sized status/actions columns rather than just showing more
   content per screen. */
.steps-grid-card[data-density="compact"] { --density-scale: 0.82; }
.steps-grid-card[data-density="roomy"] { --density-scale: 1.22; }
@media (max-width: 900px) {
  .steps-grid-card {
    --st-outer-pad: 20px;
    --st-row-pad: 14px;
    --st-cell-pad: 5px;
    --st-gap: 4px;
    --st-gap-title-status: 7px;
    --st-gap-status-actions: 9px;
    --st-indent: 14px;
    --st-num-w-parent: 56px;
    --st-num-w-child: 64px;
    --st-title-min: 260px;
    --st-status-w: 40px;
    --st-actions-w: 44px;
    --st-row-h-parent: 50px;
    --st-row-h-child: 46px;
    --st-font-num: 15px;
    --st-font-title-parent: 16px;
    --st-font-title-child: 14px;
    --st-font-pill: 13px;
    --st-pill-h: 24px;
    --st-pill-pad-x: 8px;
    --st-icon-size: 18px;
  }
}
@media (max-width: 480px) {
  .steps-grid-card {
    /* outer-pad/row-pad/num-w were the biggest fixed contributors to the
       dead space before step numbers/text on phones (measured live: ~69px
       from the screen edge to a top-level step's "1"). Cut here rather
       than at .detail-content (shared by every screen, not just Steps) or
       tbody's own padding (see the base .steps-table tbody rule) so the
       reduction is scoped to exactly the element the request called out.
       num-w still keeps enough room for a right-aligned 3-char sub-step
       number ("5.1") without crowding it against the title text. */
    --st-outer-pad: 2px;
    --st-row-pad: 2px;
    --st-cell-pad: 4px;
    --st-gap: 3px;
    --st-gap-title-status: 5px;
    --st-gap-status-actions: 7px;
    --st-indent: 19px; /* sized so a child's right-aligned DIGITS land under the first letter of its parent TITLE (user-annotated) */
    /* Number columns widened alongside the bigger --st-font-num below —
       at 14px a "5.2" is materially wider than at 11px, and without the
       extra room it would push into the title text rather than sitting
       in its own column. */
    /* Density pass: the fixed columns (num + status + actions + gaps) left
       the title ~43% of a phone's width — every non-text column trimmed. */
    --st-num-w-parent: 24px;
    --st-num-w-child: 30px;
    --st-title-min: 220px;
    --st-status-w: 16px;
    --st-actions-w: 22px;
    --st-row-h-parent: 44px;
    --st-row-h-child: 34px;
    /* Title fonts here are flattened by the 16px mobile field floor (see
       the comment below) — the numbering stays a step smaller than the
       text, the normal hierarchy. */
    --st-font-num: 13px;
    --st-font-title-parent: 13px;
    --st-font-title-child: 11px;
    --st-font-pill: 8.25px;
    --st-pill-h: 17px;
    --st-pill-pad-x: 4px;
    --st-icon-size: 17px;
  }
  /* The title is a real <textarea>, and an app-wide rule (further down,
     "iOS Safari zooms the page when a focused field is < 16px") forces
     every input/textarea to font-size:16px !important at this width, so
     --st-font-title-* below that floor is unreachable here regardless.
     A visual-scale trick was tried here previously to fake a smaller
     look, but scaling a flex child shrinks it in from BOTH sides (even
     with transform-origin pinned to the left, the far edge retracts),
     opening a large dead gap before the status dot — worse than just
     living with the real 16px. Parent vs. child is still told apart by
     weight/color, and rows grow to fit however many lines 16px text
     wraps to (no line cap — see .steps-title-input). */
}
@media (max-width: 360px) {
  .steps-grid-card {
    --st-outer-pad: 2px;
    --st-row-pad: 2px;
    --st-cell-pad: 4px;
    --st-gap: 3px;
    --st-gap-title-status: 5px;
    --st-gap-status-actions: 7px;
    --st-indent: 17px;
    --st-num-w-parent: 22px;
    --st-num-w-child: 28px;
    --st-title-min: 220px;
    --st-status-w: 16px;
    --st-actions-w: 22px;
    --st-row-h-parent: 44px;
    --st-row-h-child: 34px;
    --st-font-num: 12.5px;
    --st-font-title-parent: 12px;
    --st-font-title-child: 10px;
    --st-font-pill: 8.25px;
    --st-pill-h: 16px;
    --st-pill-pad-x: 4px;
    --st-icon-size: 16px;
  }
}
/* Forced layout mode wins over the viewport's real width, matching the
   rest of the app's body[data-layout-mode] override convention. */
body[data-layout-mode="desktop"] .steps-grid-card {
  --st-outer-pad: 16px; --st-row-pad: 12px; --st-cell-pad: 6px; --st-gap: 5px; --st-gap-title-status: 9px; --st-gap-status-actions: 11px; --st-indent: 16px;
  --st-num-w-parent: 30px; --st-num-w-child: 72px; --st-title-min: 300px; --st-status-w: 34px; --st-actions-w: 36px;
  --st-row-h-parent: 40px; --st-row-h-child: 36px; --st-font-num: 16px; --st-font-title-parent: 17px;
  --st-font-title-child: 15px; --st-font-pill: 14px; --st-pill-h: 26px; --st-pill-pad-x: 8px; --st-icon-size: 17px;
}
body[data-layout-mode="mobile"] .steps-grid-card {
  /* Mirrors the ≤480px block above — see the comment there. If you touch
     one, touch the other. */
  --st-outer-pad: 2px; --st-row-pad: 2px; --st-cell-pad: 4px; --st-gap: 3px; --st-gap-title-status: 5px; --st-gap-status-actions: 7px; --st-indent: 19px;
  --st-num-w-parent: 24px; --st-num-w-child: 30px; --st-title-min: 220px; --st-status-w: 16px; --st-actions-w: 24px;
  --st-row-h-parent: 44px; --st-row-h-child: 34px; --st-font-num: 13px; --st-font-title-parent: 13px;
  --st-font-title-child: 11px; --st-font-pill: 8.25px; --st-pill-h: 17px; --st-pill-pad-x: 4px; --st-icon-size: 17px;
}

.steps-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 4px 0 14px;
}
.steps-search-wrap { position: relative; flex: 1 1 180px; min-width: 140px; }
.steps-search-icon {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  color: var(--ink-faint);
  pointer-events: none;
}
.steps-search-input {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--d-search-pad);
  font-size: 13px;
  color: var(--ink);
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.steps-search-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
/* Real button + action sheet, not a native <select> — see the markup
   comment in renderProcessDetail() for why. Sized/shaped like a
   .steps-sort-seg box so the filter pill and sort boxes read as one
   family of compact controls. */
.steps-status-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  padding: 7px 12px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-mute);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all 0.15s;
  white-space: nowrap;
}
.steps-status-btn svg { flex-shrink: 0; }
.steps-status-btn:hover { color: var(--ink); background: var(--bg-sunk); }
.steps-status-btn.active { background: var(--accent-btn); color: #fff; border-color: var(--accent); }
.steps-status-label { overflow: hidden; text-overflow: ellipsis; }
.steps-sort-seg {
  display: flex;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
  flex-shrink: 0;
}
.steps-sort-seg button {
  padding: 7px 12px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-mute);
  background: var(--surface);
  border: none;
  border-right: 1px solid var(--border);
  cursor: pointer;
  transition: all 0.15s;
}
.steps-sort-seg button:last-child { border-right: none; }
.steps-sort-seg button:hover { color: var(--ink); background: var(--bg-sunk); }
.steps-sort-seg button.active { background: var(--accent-btn); color: #fff; }

.steps-table-wrap {
  overflow-x: auto;
  border-radius: var(--radius-lg);
  border: var(--card-border);
  -webkit-overflow-scrolling: touch;
}
/* The grid used to be a real <table> laid out with the browser's default
   table-layout:auto — which measures every column's preferred width off
   its content across ALL rows, so an empty cell (e.g. the Step Title cell
   on a sub-step row) still claimed a share of the auto-distributed leftover
   width. Two separate Step/Sub-step # and # Title columns meant the empty
   one of each pair inflated to eat up to half the table, producing a huge
   dead gap before the populated column. Fixed by merging Step #/Sub-step #
   and Step Title/Sub-step Title into ONE #+Title cell (.stcol-title) that
   both row types share, and laying every row out as a flex row (same
   technique already proven for the mobile stacked layout) so column widths,
   gaps and per-row-type indentation are all explicit instead of emergent. */
.steps-table { width: 100%; border-collapse: collapse; font-size: 13.5px; display: block; }
/* Same horizontal inset as tbody below. Without it the header row is 4px
   wider on each side than the rows it labels, so every right-aligned
   column (the status dot, the ⋮) sat 4px off from the body's — the last
   piece of the header/row misalignment, after the padding-specificity and
   glyph-sizing fixes. */
.steps-table thead {
  display: block;
  padding: 0 calc(4px * var(--density-scale));
  /* On a long process the column labels scrolled away, leaving an unlabelled
     grid — the Gantt table already pins its header (.gt-table th), so this is
     the app's own precedent rather than a new pattern. thead is display:block
     here, so the stickiness belongs on it, not on the th. */
  position: sticky;
  top: 0;
  z-index: 3;
  /* --bg-sunk, NOT --bg-elev. This backdrop exists only so body rows don't
     show through the pinned header, so it has to match what surrounds it —
     and the th cells, the body rows and .steps-grid-card are all --bg-sunk.
     --bg-elev is white in light mode and matches none of them. The th cells
     cover the row exactly, so the mismatch showed up as a 4px white stripe
     down each side of the header (this element's own `padding: 0 4px`,
     which is what aligns the header's columns with the rows beneath it and
     must stay) — a white "border" framing the header at every width, in
     both themes. Fix the colour, not the padding. */
  background: var(--bg-sunk);
}
.steps-table thead tr { display: flex; align-items: center; }
.steps-table thead th {
  text-align: left;
  font-size: 15px;
  font-weight: 500;
  color: color-mix(in srgb, var(--ink) 70%, transparent);
  padding: 12px 18px;
  background: var(--bg-sunk);
  border-bottom: var(--card-border);
  white-space: nowrap;
}
/* Status/Actions are icon-only columns (a colored dot, a vertical dots
   menu) — the header repeats the same glyph instead of the word
   "Status"/"Actions", both so it fits these now very narrow columns
   (see the tight gap tokens below) and so it reads as one icon-only
   language end to end. Screen readers still get "Status"/"Actions"
   from the th's aria-label; the glyph itself is aria-hidden in markup. */
/* Only the VERTICAL padding is set here. Horizontal padding and
   justification deliberately fall through to the shared .stcol-status /
   .stcol-actions rules that the body cells also use — overriding them
   here (with `padding: 12px 0; text-align: center`) is exactly what put
   the header's dot and ⋮ out of line with every row beneath it. */
.steps-table thead th.stcol-status,
.steps-table thead th.stcol-actions {
  font-size: 13px;
  padding-top: 12px;
  padding-bottom: 12px;
}
/* Header glyphs: same classes as the body's, so they inherit the same box
   sizes and land on the same centres — just muted and non-interactive.
   Scoped under `thead` for specificity: a bare `.steps-status-dot-head`
   ties with `.steps-status-dot` (both 0,1,0) and loses on source order,
   which left the header dot at the base 9px instead of the 10px the body
   uses inside its button. */
.steps-table thead .steps-status-dot-head {
  width: 10px;
  height: 10px;
  background: var(--ink-faint);
}
/* display:inline-flex is load-bearing, not cosmetic: .steps-row-menu-btn
   sizes itself with width/height, and a <span> defaults to display:inline
   where those are ignored — the header ⋮ collapsed to its 5px glyph and
   sat 2px off the rows' 29px button centre. */
.steps-table thead .steps-row-menu-head {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-faint);
  cursor: default;
  pointer-events: none;
}
.steps-table thead .steps-row-menu-head::before { content: none; }
.steps-table tbody { display: flex; flex-direction: column; gap: calc(5px * var(--density-scale)); padding: calc(4px * var(--density-scale)); }
/* Every row — desktop, tablet, or mobile — is the same flex row
   (# | Title | Status | Actions), never stacked. Only the size tokens
   on .steps-grid-card change per breakpoint; the structure and every
   selector below stay identical, so alignment can't drift between
   tiers. Row height is min-height only here (no max) — a title that
   wraps to 3, 4, or however many lines it actually needs grows the row
   to match on desktop/tablet, full stop. Mobile gets an ADDITIONAL
   max-height + fade/"more" affordance on top of this (see the
   body:not([data-layout-mode="desktop"]) / body[data-layout-mode="mobile"]
   blocks further down) — capped there instead of here so desktop/tablet
   keep the fully-readable-inline behavior this comment used to describe
   for every breakpoint; only mobile trades that for a fixed card size +
   tap-to-expand popup, per the request that revised it. */
/* WBS treatment (the approved desktop prototype): flat rows on hairline
   separators instead of floating cards — the number column carries the
   hierarchy, so the card chrome was double-encoding it. Parents read as
   headers through weight, not background. Mobile keeps its tuned card
   presentation via the mirrors below. */
.steps-table tr.steps-row {
  display: flex;
  align-items: center;
  border-bottom: 1px solid var(--border-soft);
  transition: background-color 0.12s, min-height 0.15s, max-height 0.15s;
}
.steps-table tr.steps-row-parent { min-height: calc(var(--st-row-h-parent) * var(--density-scale)); }
.steps-table tr.steps-row-child { min-height: calc(var(--st-row-h-child) * var(--density-scale)); }
.steps-table tr.steps-row-parent .steps-title-input { font-weight: 650; }
.steps-table tr.steps-row:hover { background: var(--bg-sunk); }
.steps-table tr.steps-row.selected { background: var(--accent-bg); box-shadow: inset 3px 0 0 var(--accent); }
/* Folding is a mobile-outline behavior (tap the row — see
   wireStepsTableRows); the desktop WBS always shows the whole plan and
   its flatten ignores `collapsed` by design. */
.steps-table td, .steps-table th { box-sizing: border-box; }

/* Presence markers (description / links / notes / attachments) at the end
   of the title cell. Not buttons — a tap on them is just a row tap — and
   their title attributes give desktop hover the details. */
.step-flags {
  display: flex;
  align-items: center;
  gap: 3px;
  flex-shrink: 0;
  margin-left: 5px;
  color: var(--ink-faint);
}
.step-flags:empty { display: none; }
.step-flag { display: inline-flex; align-items: center; }
.step-flag svg { width: 10px; height: 10px; }

.steps-sort-pill { display: none; }

/* ---- Outline insight kind ----
   A conventional outliner: one bullet per row, indented by depth, with a
   guide rail so a deep row's parent is still readable. Rows are textareas
   (autosized) so long text wraps instead of clipping. */
.ins-outline-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-top: 18px; }
.ins-outline-hint { font-size: 11px; color: var(--ink-faint); }
.ins-outline { display: flex; flex-direction: column; }
.ins-ol-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 2px 0;
  margin-left: calc(var(--ol-d, 0) * 22px);
  position: relative;
}
/* Guide rail for every nested row — the vertical line an outliner uses to
   tie a child back to its parent. (Class, not an attribute-substring
   selector: browsers serialize inline custom properties inconsistently.) */
.ins-ol-row.ol-nested::before {
  content: '';
  position: absolute;
  left: -11px; top: 0; bottom: 0;
  border-left: 1px solid var(--border-soft);
}
.ins-ol-bullet {
  flex-shrink: 0;
  width: 18px; height: 18px;
  margin-top: 4px;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  position: relative;
  border-radius: 50%;
}
.ins-ol-bullet::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--ink-mute);
}
/* A row with children gets the classic halo; collapsed makes it solid, so
   "there is more inside" is visible without expanding. */
.ins-ol-bullet.has-kids { background: var(--accent-bg); }
.ins-ol-bullet.has-kids.collapsed { background: var(--accent-bg-strong); }
.ins-ol-bullet.has-kids.collapsed::after { background: var(--accent); }
.ins-ol-bullet::before { content: ''; position: absolute; inset: -8px; }
.ins-ol-text {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: none;
  outline: none;
  resize: none;
  overflow: hidden;
  font: inherit;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink);
  padding: 2px 0;
}
.ins-ol-text::placeholder { color: var(--ink-faint); }

/* ---- Batch select & delete ---- */
.batch-bar {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: 24px;
  z-index: 46;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px 8px 16px;
  border-radius: 999px;
  background: var(--bg-elev);
  border: var(--card-border);
  box-shadow: var(--shadow-lg);
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.batch-bar .danger-btn:disabled { opacity: 0.45; cursor: default; }
.batch-check {
  width: 20px; height: 20px;
  flex-shrink: 0;
  margin-right: 6px;
  border-radius: 50%;
  border: 2px solid var(--border);
  background: var(--bg-elev);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
}
.batch-check.on { background: var(--accent-btn); border-color: var(--accent-btn); }
.batch-check.on::after { content: '✓'; color: #fff; font-size: 12px; font-weight: 700; line-height: 1; }
tr.steps-row.batch-selected, .checklist-item.batch-selected, tr.steps-dgrid-row.batch-selected { background: var(--accent-bg) !important; }
/* The pinned cell paints its own background, which would mask the row
   tint exactly where the checkbox sits. */
tr.steps-dgrid-row.batch-selected .dg-fix { background: var(--accent-bg); }
/* Inner controls go inert while selecting — the row IS the control. */
.steps-table.batch-mode tr.steps-row * { pointer-events: none; }
/* Contents of the CELLS go inert, not the cells: an inert td drops out
   of hit-testing entirely and clicks resolve to the TABLE (measured), so
   the row handler never fires. Inert cell-contents leave the td as the
   hit target, bubbling to the row — which IS the control. */
.steps-dgrid.batch-mode td * { pointer-events: none; }
.steps-dgrid.batch-mode .dg-fix { text-align: center; }
.steps-dgrid.batch-mode .batch-check { margin: 0 auto; display: inline-flex; }
.checklist-items.batch-mode .checklist-item * { pointer-events: none; }
.cl-batch-btn { padding: 3px 10px; min-height: 0; font-size: 11.5px; }
/* Outscores the blanket mobile 44px ghost-btn floor — this is a compact
   header control; its hit area is padded by the header row itself. */
body:not([data-layout-mode="desktop"]) .detail-section-title .cl-batch-btn { min-height: 30px; padding: 3px 10px; }
body[data-layout-mode="mobile"] .detail-section-title .cl-batch-btn { min-height: 30px; padding: 3px 10px; }

/* Long-press drag-reorder (steps + checklist rows). The lifted row floats
   above its list; the accent line marks the drop slot. !important is
   deliberate: these are transient gesture states that must beat whatever
   background/shadow the row's own state (selected, hover) carries. */
.row-dragging {
  opacity: 0.92;
  position: relative;
  z-index: 6;
  background: var(--bg-elev) !important;
  box-shadow: var(--shadow-lg) !important;
  transition: none !important;
}
.drop-ind-before { box-shadow: inset 0 3px 0 var(--accent) !important; }
.drop-ind-after { box-shadow: inset 0 -3px 0 var(--accent) !important; }

.stcol-title {
  flex: 1 1 var(--st-title-min);
  min-width: 0;
  display: flex;
  align-items: center;
  padding-left: calc(var(--st-row-pad) * var(--density-scale));
}
/* Indent scales with actual nesting depth (--row-depth, set inline per
   row from data-depth in stepRowHtml) rather than a flat "any child"
   bump, so a sub-sub-step reads as visibly deeper than a sub-step
   instead of looking identical to it. Capped at 3 levels' worth via
   min() so a 4th+ level doesn't keep eating into the title column —
   depth still keeps incrementing in the data/numbering ("1.1.1.1" etc.),
   it just stops visually indenting further past that, matching the
   step/sub-step/sub-sub-step boundary called out everywhere else. */
.steps-row-child .stcol-title {
  /* Every level indents further than its parent — no plateau. The old
     `min(indent * depth, indent * 3)` hard-stopped at 3, so depth 3, 4 and
     5 all sat at the same offset and were indistinguishable, which is
     exactly what a demote can now produce.
     Instead of a flat cap, the increment DECAYS: full --st-indent for the
     first two levels, then half of it for every level past that. Depth
     keeps increasing forever (requirement 15) while the growth slows to a
     crawl, so a deep tree still can't eat the title column on a phone
     (requirement 16). At the 12px mobile indent that's 12/24/30/36/42…
     — each level clearly past the one above it, but level 6 costs 54px
     rather than the 72px a linear ramp would have taken. */
  padding-left: calc(
    (var(--st-row-pad)
      + var(--st-indent) * min(var(--row-depth, 1), 2)
      + var(--st-indent) * 0.5 * max(var(--row-depth, 1) - 2, 0)
    ) * var(--density-scale));
}
/* A persistent thin rail on every non-top-level row — independent of
   the indent cap above, so depth is still legible at a glance even once
   indentation itself has maxed out. border-box so the border eats into
   the row's existing width instead of adding to it — otherwise child
   rows would sit 2px wider than parent rows and drift out of alignment. */
.steps-row-child { border-left: 2px solid var(--border-soft); box-sizing: border-box; }
.steps-row-num {
  flex-shrink: 0;
  box-sizing: border-box;
  /* The bullet is a tap target: it opens edit mode directly (see
     wireStepsTableRows) — the one row region the textarea never covers. */
  cursor: pointer;
  font-variant-numeric: tabular-nums;
  color: var(--accent-text);
  font-family: var(--font-mono, ui-monospace, monospace);
  font-weight: 600;
  font-size: calc(var(--st-font-num) * var(--density-scale));
  /* Right-aligned so the visible gap to the title is exactly --st-gap
     regardless of digit count ("1" vs. "10.10") — left-aligned text in
     this fixed-width box would leave whatever slack the box has beyond
     the text's own natural width, on top of the padding, making the
     gap wider than declared for short numbers. Fixed right-padding
     (not margin) keeps that slack-free gap part of the number column's
     own declared width — border-box keeps the column's total width
     exactly at --st-num-w-*, padding included, instead of the padding
     adding onto it and drifting the title's start position. */
  text-align: right;
  padding-right: calc(var(--st-gap) * var(--density-scale));
}
.steps-row-parent .steps-row-num { width: var(--st-num-w-parent); }
/* Child numbers are natural-width and left-aligned on EVERY form factor:
   right-aligned digits in a fixed box start further LEFT the longer the
   path gets ("1.3.1" vs "1.3"), visually cancelling the indent. The box
   left edge now IS the digit start, which alignStepIndents() (app.js)
   pins to the parent row's title after each render. */
.steps-row-child .steps-row-num { width: auto; min-width: 0; text-align: left; }
/* Status column's own left/right padding IS the title→status and
   status→actions gap — the column's fixed width (per breakpoint, in
   .steps-grid-card) is sized to exactly fit padding + the status
   button, so there's no slack for those gaps to drift. */
/* Scoped with `.steps-table` purely for specificity: the header's generic
   `.steps-table thead th` rule scores (0,1,2) and would otherwise beat a
   bare `.stcol-*` (0,1,0), imposing its own 18px horizontal padding on the
   status/actions columns. That is exactly what knocked the header's dot
   and ⋮ out of line with every row below — and squeezed the ⋮ flex item
   down to its 5px glyph. At (0,2,1) these win, so header and body cells
   share one definition of where the two icon columns sit. */
.steps-table .stcol-status {
  flex: 0 0 var(--st-status-w);
  min-width: 0;
  box-sizing: border-box;
  padding-left: calc(var(--st-gap-title-status) * var(--density-scale));
  padding-right: calc(var(--st-gap-status-actions) * var(--density-scale));
  display: flex;
  align-items: center;
  justify-content: center;
}
.steps-table .stcol-actions {
  flex: 0 0 var(--st-actions-w);
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding-left: 0;
  padding-right: calc(var(--st-row-pad) * var(--density-scale));
}
.steps-row-parent .stcol-title .steps-title-input { font-weight: 700; font-size: calc(var(--st-font-title-parent) * var(--density-scale)); color: var(--ink); }
.steps-row-child .stcol-title .steps-title-input { font-weight: 500; font-size: calc(var(--st-font-title-child) * var(--density-scale)); color: var(--ink-soft); }

/* A <textarea> rather than an <input> so titles can wrap onto as many
   lines as they actually need instead of being clipped — `rows="1"`
   gives it a single-line starting height, then wireStepsTableRows()
   grows `style.height` to scrollHeight on render and on every input.
   overflow:hidden matters: autosize keeps height == scrollHeight, but a
   1px rounding gap makes Android draw a permanent overlay-scrollbar
   thumb — a grey capsule floating at the end of every row (user-reported).
   (autosize measures scrollHeight, which overflow:hidden doesn't affect.) */
.steps-title-input {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
  background: transparent;
  border: none;
  outline: none;
  resize: none;
  overflow: hidden;
  font: inherit;
  color: inherit;
  padding: calc(6px * var(--density-scale)) 0;
  line-height: 1.35;
  white-space: normal;
  word-wrap: break-word;
  overflow-wrap: break-word;
}
/* The title field is inert until a double-tap opens edit mode. It covers
   almost the whole card, so while it was live it swallowed every tap:
   "select" was only reachable from the narrow number column, and a single
   tap on the text meant "edit". pointer-events:none lets taps fall through
   to the row, which owns single-tap-select vs. double-tap-edit. */
.steps-title-input { pointer-events: none; cursor: default; }
.steps-row-editing .steps-title-input {
  pointer-events: auto;
  cursor: var(--cursor-text, text);
}
/* Hidden everywhere by default — the mobile blocks are the only place it's
   revealed, and only on rows JS has marked .steps-row-truncated. Without
   this base rule the button inherits its default button display and shows
   a stray "…" on every desktop row, where nothing is ever truncated. */
.steps-more-btn { display: none; }
.steps-title-input::placeholder { color: var(--ink-faint); font-weight: 400; }
.steps-title-input:focus { text-decoration: underline; text-decoration-color: var(--accent-soft); text-underline-offset: 3px; }

/* Status is now a plain colored dot (no pill/wording) — the button is
   just a round hit-area around it so the tap target stays reasonable
   even though the visual mark is small. Colors match .steps-status-dot
   (already used in the status popover's option list) so the row dot and
   the menu that changes it use the exact same color per status. */
.steps-status-dot-btn {
  position: relative;
  width: calc(var(--st-pill-h) * var(--density-scale));
  height: calc(var(--st-pill-h) * var(--density-scale));
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  transition: background-color 0.12s, transform 0.1s;
}
/* --st-pill-h shrinks as low as 16-17px in the dense mobile grid — good for
   the visual dot, far too small to reliably tap. An invisible, fixed-size
   hit area keeps the dot's real footprint independent of that variable,
   same "oversized invisible ::before" technique used for the checklist
   checkbox elsewhere in this file. */
.steps-status-dot-btn::before {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  width: 46px; height: 46px;
  transform: translate(-50%, -50%);
}
.steps-status-dot-btn:hover { background: var(--accent-bg); }
.steps-row-parent .steps-status-dot-btn:hover { background: var(--surface); }
.steps-status-dot-btn:active { transform: scale(0.88); }
.steps-status-dot-btn .steps-status-dot { width: 10px; height: 10px; }

.steps-row-menu-btn {
  position: relative;
  width: calc(var(--st-icon-size) + 12px);
  height: calc(var(--st-icon-size) + 12px);
  border-radius: var(--radius-sm);
  background: transparent;
  border: none;
  color: var(--ink-mute);
  font-size: var(--st-icon-size);
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.12s, color 0.12s;
}
.steps-row-menu-btn::before { content: ''; position: absolute; inset: -9px; }
.steps-row-menu-btn:hover { background: var(--bg-elev); color: var(--ink); }
.steps-row-parent .steps-row-menu-btn:hover { background: var(--surface); }

.steps-empty-hint { padding: 28px 16px; text-align: center; font-size: 13px; color: var(--ink-mute); }

/* Status picker + row-menu popovers — same small floating-popover idiom
   already used elsewhere in the app (var-type-pop, folder-color-pop,
   Performance's .perf-colmenu): fresh element per open, viewport-clamped
   inline position, dismiss on outside pointerdown. */
.steps-status-pop, .steps-row-menu-pop {
  position: fixed;
  z-index: 1200;
  background: var(--bg-elev);
  border: var(--card-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 6px;
}
.steps-status-pop { min-width: 150px; }
.steps-row-menu-pop { min-width: 190px; }
.steps-status-pop button, .steps-row-menu-pop button {
  display: flex; align-items: center; gap: 9px; width: 100%;
  border: none; background: transparent; text-align: left;
  padding: 8px 11px; border-radius: var(--radius-sm);
  font-size: 13px; color: var(--ink); cursor: pointer; font-family: inherit;
}
.steps-status-pop button:hover, .steps-row-menu-pop button:hover { background: var(--accent-bg); }
.steps-status-pop button.sel { background: var(--accent-bg-strong); font-weight: 600; }
.steps-row-menu-pop button.danger { color: var(--danger-text); }
.steps-row-menu-pop button.danger:hover { background: var(--danger-bg); }
.steps-row-menu-sep { height: 1px; background: var(--border-soft); margin: 5px 2px; }
.steps-status-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; }
.steps-status-dot.status-not_started { background: var(--accent); }
.steps-status-dot.status-in_progress { background: var(--teal); }
.steps-status-dot.status-done { background: var(--success); }
.steps-status-dot.status-blocked { background: var(--danger); }

/* Rail: History + Attachments (new step-level metadata sections) */
.steps-history-row {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 8px 0; border-bottom: 1px solid var(--border-soft);
  font-size: 12.5px; color: var(--ink-mute);
}
.steps-history-row:last-child { border-bottom: none; }
.steps-history-row strong { color: var(--ink); font-weight: 600; }

.steps-attach-add { margin-bottom: 10px; }
.steps-attach-btn { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; font-size: 12px; padding: 6px 12px; }
.steps-attach-list { display: flex; flex-direction: column; gap: 4px; }
.steps-attach-empty { font-size: 12px; color: var(--ink-faint); font-style: italic; padding: 4px 0; }
.steps-attach-row {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-soft);
  font-size: 12.5px;
}
.steps-attach-icon { color: var(--ink-faint); flex-shrink: 0; display: flex; }
.steps-attach-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
.steps-attach-size { color: var(--ink-faint); font-size: 11px; flex-shrink: 0; }

/* CHECKLIST */
.checklist-items { display: flex; flex-direction: column; gap: 6px; }
.checklist-item {
  /* Centre the single-line case (the common one): the row's height is set
     by the 44px action buttons, so with flex-start a 22px line of text
     sat high — measured 13px of space above it and 35px below, which read
     as "top-aligned in an oversized card". Centre distributes that evenly.
     When a sub-note or due-date is revealed the row grows to two lines and
     the checkbox must sit against the FIRST line, not the middle of the
     block — .has-extra restores flex-start for exactly those items. */
  display: flex; align-items: center; gap: 12px;
  padding: var(--d-cl-pad);
  border-radius: var(--radius-md);
  background: var(--surface);
  border: var(--card-border);
  box-shadow: var(--shadow-card);
  transition: border-color 0.12s;
}
.checklist-item.has-extra { align-items: flex-start; }
.checklist-item.has-extra .checklist-item-checkbox { margin-top: 1px; }
.checklist-item:hover { border-color: var(--accent-soft); }
.checklist-item-checkbox {
  position: relative;
  width: 18px; height: 18px;
  border: 2px solid var(--border);
  border-radius: 5px;
  flex-shrink: 0;
  /* margin-top only for the multi-line (.has-extra) case — see above.
     Centred single-line rows need it flush. */
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: all 0.15s;
  background: var(--surface);
}
.checklist-item-checkbox:hover { border-color: var(--accent); }
.checklist-item-checkbox.checked { background: var(--accent-btn); border-color: var(--accent); color: white; }
.checklist-item-checkbox.checked::after { content: "✓"; font-size: 12px; font-weight: bold; }
.checklist-item-text-wrap { flex: 1; min-width: 0; position: relative; }
/* A textarea so long lines WRAP in place (autosized by JS, same as step
   titles) — textareas don't inherit the body font, so it's set explicitly. */
.checklist-item-text {
  background: transparent;
  border: none;
  outline: none;
  width: 100%;
  font-family: inherit;
  font-size: 13.5px;
  color: var(--ink);
  font-weight: 500;
  line-height: 1.4;
  resize: none;
  overflow: hidden;
  display: block;
  padding: 0;
}
.checklist-item-text.checked { color: var(--ink-mute); text-decoration: line-through; }
.checklist-item-due-wrap { margin-top: 4px; }
.checklist-item-due-input {
  font-size: 11.5px;
  padding: 3px 7px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink-mute);
  cursor: pointer;
  max-width: 100%;
}
.checklist-item-due-wrap.overdue .checklist-item-due-input {
  border-color: var(--danger-text);
  color: var(--danger-text);
  background: var(--danger-bg);
  font-weight: 600;
}
.checklist-item-actions { display: flex; gap: 2px; opacity: 0; transition: opacity 0.15s; }
.checklist-item:hover .checklist-item-actions { opacity: 1; }
.checklist-item-sub-input {
  width: 100%;
  background: transparent;
  border: none;
  outline: none;
  font-size: 12px;
  color: var(--ink-mute);
  margin-top: 4px;
  font-style: italic;
}

/* ── Checklist outline (nested items) ─────────────────────────────
   Design A on both form factors: rows indent per depth, carry a 1 / 1.1
   outline number in the Steps grid's accent-mono gutter, and groups fold
   via the same twist affordance. A group row swaps its checkbox for a
   derived n/m roll-up chip. */
/* Absolute-length cap only: %- and vw-based margins both computed to 0 in
   live verification (the browse map's 45% cap survives because it sits on
   padding). 160px = 8 levels deep, far past practical nesting. */
.checklist-item { margin-left: min(calc(var(--cl-d, 0) * 20px), 160px); }
.cl-num {
  flex-shrink: 0;
  min-width: 22px;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 12px;
  font-weight: 600;
  color: var(--accent-text);
}
/* Only when the list actually has groups: give twist-less leaf rows the
   same leading gutter as group rows (twist 22px + 12px gap), so mixed
   siblings' numbers align. Flat lists skip this and stay tight. */
.checklist-items.cl-has-groups .checklist-item-leaf .cl-num { margin-left: 34px; }
.cl-twist {
  display: flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0;
  border: none; background: none; cursor: pointer;
  color: var(--ink-mute); border-radius: 6px;
  flex-shrink: 0; position: relative;
}
.cl-twist:hover { background: var(--bg-sunk); color: var(--ink); }
.cl-twist svg { transition: transform 0.15s; }
.cl-twist.collapsed svg { transform: rotate(-90deg); }
.cl-agg {
  flex-shrink: 0;
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--bg-sunk);
  color: var(--ink-mute);
}
.cl-agg.complete { background: var(--success-bg); color: var(--success-text); }
/* Mobile-only ⋮ trigger (bottom action sheet) — desktop keeps the four
   hover-revealed inline buttons, which fit fine at pointer widths. */
.checklist-item-more-actions { display: none; font-size: 17px; font-weight: 700; line-height: 1; }
.checklist-item-parent .checklist-item-text { font-weight: 650; }
.checklist-item.has-extra .cl-twist,
.checklist-item.has-extra .cl-agg,
.checklist-item.has-extra .cl-num { margin-top: 1px; }

.snapshots-list { margin-top: 14px; }
.snapshot-item {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 12px 16px;
  margin-bottom: 8px;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  font-size: 12px;
}
.snapshot-info { flex: 1; }
.snapshot-stats { color: var(--ink-soft); }
.snapshot-stats strong { color: var(--ink); font-weight: 600; }
.snapshot-reflection { color: var(--ink-mute); font-style: italic; margin-top: 4px; }
.snapshot-actions { display: flex; gap: 4px; }
.snapshot-detail {
  display: none;
  flex-basis: 100%;
  width: 100%;
  margin-top: 4px;
  padding-top: 10px;
  border-top: 1px solid var(--border-soft);
  color: var(--ink-soft);
  line-height: 1.6;
}
.snapshot-item.expanded .snapshot-detail { display: block; }

/* TEMPLATE */
.template-subject-input {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 12px 16px;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  outline: none;
  margin-bottom: 14px;
  transition: all 0.15s;
}
.template-subject-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }

/* ---- Red {{variable}} placeholders ---- */
/* Body editor (and its table cells): styled via the CSS Custom
   Highlight API — text ranges only, no DOM changes. */
::highlight(tpl-vars) { color: var(--danger-text); }

/* Subject line: a mirror layer renders the text with red tokens while
   the input's own glyphs are transparent (caret + selection intact). */
.subject-wrap {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  margin-bottom: 14px;
  transition: all 0.15s;
}
.subject-wrap:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
.subject-wrap .template-subject-input {
  background: transparent;
  border: none;
  margin-bottom: 0;
  position: relative;
  z-index: 1;
  color: transparent;
  -webkit-text-fill-color: transparent;
}
.subject-wrap .template-subject-input:focus { border-color: transparent; box-shadow: none; }
.subject-wrap .template-subject-input::placeholder {
  color: var(--ink-faint);
  -webkit-text-fill-color: var(--ink-faint);
}
.subject-wrap .template-subject-input::selection { background: var(--accent-ring); }
.subject-mirror {
  position: absolute;
  inset: 0;
  padding: 12px 16px;      /* keep in lockstep with .template-subject-input */
  font-size: 14px;
  font-weight: 600;
  line-height: normal;
  color: var(--ink);
  pointer-events: none;
  overflow: hidden;
  white-space: pre;
  display: flex;
  align-items: center;
  z-index: 0;
}
.subject-mirror-inner { white-space: pre; }
.subject-preview {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 12px 16px;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 14px;
  word-break: break-word;
}
.tpl-var-red { color: #DA3A16; }
[data-theme="dark"] .tpl-var-red { color: #FF6B4A; }
.template-vars-panel { margin-top: 2px; }

/* Each variable is its own card — generous padding, a hairline border, a
   header row (reorder + name + type/category + overflow menu) with the
   value input on its own line below. Replaces the old single dense grid
   row that crowded six separate icon controls into ~280px. Only ever
   rendered inside #tplRail, so no extra narrow-context scoping needed. */
.template-vars-row {
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: 10px 12px 12px;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.template-vars-row:hover { border-color: var(--accent-bg-strong); box-shadow: var(--shadow-sm); }
.template-vars-row + .template-vars-row { margin-top: 8px; }
.template-var-label {
  font-family: var(--font-mono); font-size: 12.5px; color: var(--ink);
  font-weight: 600; cursor: pointer; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; line-height: 1.3;
}
.template-var-label:hover { color: var(--accent-text); text-decoration: underline; text-underline-offset: 2px; }

/* Variable list toolbar (sort modes + add) */
.template-vars-toolbar {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border-soft);
}
.template-vars-toolbar-label { font-size: 11px; color: var(--ink-mute); }
.var-sort-btn {
  font-size: 11px;
  padding: 2px 9px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-mute);
  cursor: pointer;
  transition: all 0.15s;
  line-height: 1.5;
}
.var-sort-btn:hover { border-color: var(--accent); color: var(--ink); }
.var-sort-btn.active { background: var(--accent-btn); border-color: var(--accent); color: #fff; }

/* Category group headers */
.template-vars-group {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-mute);
  margin: 14px 0 8px;
  padding-top: 12px;
  border-top: 1px dashed var(--border-soft);
  line-height: 1.4;
}
.template-vars-group:first-child { margin-top: 0; padding-top: 0; border-top: none; }

/* Card header row: reorder + name + type/category chips + overflow menu */
.var-card-top { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
.var-card-spacer { flex: 1; min-width: 4px; }
.var-drag { display: none; } /* the ▲▼ buttons are the real reorder control in this card layout */
.var-dragging { opacity: 0.4; }
.var-drop-target { outline: 2px dashed var(--accent); outline-offset: 1px; }
.var-move-btns { display: flex; flex-direction: row; gap: 2px; flex-shrink: 0; }
.var-move {
  border: 1px solid var(--border); background: var(--bg-sunk); color: var(--ink-mute);
  border-radius: var(--radius-sm); font-size: 9px; line-height: 1; padding: 3px 5px; cursor: pointer;
}
.var-move:hover { border-color: var(--accent); color: var(--ink); }
.var-cat-chip, .var-type-chip {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10.5px; padding: 2px 8px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--bg-sunk); color: var(--ink-mute); cursor: pointer; white-space: nowrap;
  line-height: 1.6; flex-shrink: 0;
}
.var-cat-chip { max-width: 92px; overflow: hidden; text-overflow: ellipsis; }
.var-cat-chip svg, .var-type-chip svg { flex-shrink: 0; }
.var-cat-chip:hover, .var-type-chip:hover { border-color: var(--accent); color: var(--ink); }
.var-cat-chip:hover svg, .var-type-chip:hover svg { color: var(--accent); }
.var-type-pop {
  position: fixed;
  z-index: 1200;
  min-width: 140px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 6px;
}
.var-type-pop button {
  display: flex; align-items: center; gap: 8px; width: 100%;
  border: none; background: transparent; text-align: left;
  padding: 8px 10px; border-radius: var(--radius-sm);
  font-size: 13px; color: var(--ink); cursor: pointer; font-family: inherit;
}
.var-type-pop button:hover { background: var(--accent-bg); }
.var-type-pop button.sel { background: var(--accent-bg-strong); font-weight: 600; }

/* Rename/delete consolidated into one overflow menu per card — trims the
   old always-crowded row of separate icon buttons down to a single "⋯". */
.var-card-menu-btn {
  width: 24px; height: 24px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border: none; background: transparent; color: var(--ink-faint); cursor: pointer;
  border-radius: var(--radius-sm); font-size: 15px; line-height: 1;
  transition: background 0.12s, color 0.12s;
}
.var-card-menu-btn:hover { background: var(--bg-sunk); color: var(--ink); }
.var-card-menu-pop {
  position: fixed;
  z-index: 1200;
  min-width: 160px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 6px;
}
.var-card-menu-pop button {
  display: flex; align-items: center; gap: 8px; width: 100%;
  border: none; background: transparent; text-align: left;
  padding: 8px 10px; border-radius: var(--radius-sm);
  font-size: 13px; color: var(--ink); cursor: pointer; font-family: inherit;
}
.var-card-menu-pop button:hover { background: var(--accent-bg); }
.var-card-menu-pop button.danger { color: var(--danger-text); }
.var-card-menu-pop button.danger:hover { background: color-mix(in srgb, var(--danger) 12%, transparent); }
.var-act {
  border: none; background: transparent; color: var(--ink-faint); cursor: pointer;
  font-size: 12px; padding: 1px 4px; border-radius: var(--radius-sm);
  opacity: 0; transition: opacity 0.12s;
  line-height: 1.5;
}
.template-vars-row:hover .var-act { opacity: 1; }
.var-act:hover { background: var(--surface); color: var(--ink); }
.var-act-danger:hover { color: var(--danger-text); }
.var-unused .template-var-label { color: var(--ink-mute); }
.var-unused-hint {
  font-family: var(--font-body); font-size: 10.5px; font-weight: 500;
  color: var(--ink-faint); font-style: italic; margin-left: 2px;
}
@keyframes varRowFlash {
  0%, 60% { background: var(--accent-bg-strong); box-shadow: 0 0 0 2px var(--accent); }
  100% { background: transparent; box-shadow: none; }
}
.var-row-flash { animation: varRowFlash 1.8s ease-out; }
.template-var-input {
  display: block;
  width: 100%;
  background: var(--bg-sunk);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  padding: 7px 11px;
  font-size: 13px;
  line-height: 1.45;
  outline: none;
  color: var(--ink);
  transition: border-color 0.15s, background 0.15s;
  min-width: 0;
}
.template-var-input:focus { border-color: var(--accent); background: var(--surface); }
.template-preview-body {
  font-size: 13.5px;
  color: var(--ink-soft);
  line-height: 1.65;
}
.template-preview-body p { margin: 0 0 8px; }
.template-preview-body p:last-child { margin: 0; }
.template-preview-body ul, .template-preview-body ol { padding-left: 22px; margin: 6px 0 10px; }
.template-actions-row { display: flex; gap: 8px; flex-wrap: wrap; }

/* NESTED VARIABLE WINDOW (dbl-click / long-press a {{token}}, or {{…}} btn) */
.var-window { max-width: 460px; }
/* Search sits between the header and the scrolling body, so it stays put
   while results scroll under it and never gets replaced by a re-render. */
.var-window-search {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 16px 10px;
  border-bottom: 1px solid var(--border-soft);
  color: var(--ink-faint);
  flex-shrink: 0;
}
.var-window-search svg { flex-shrink: 0; }
.var-window-search input {
  flex: 1; min-width: 0;
  border: none; outline: none; background: transparent;
  font: inherit; font-size: 13px; color: var(--ink);
  padding: 4px 0;
}
.var-window-search input::placeholder { color: var(--ink-faint); }
.var-window-search input::-webkit-search-cancel-button { cursor: pointer; }
.var-window-body { padding: 14px 16px; overflow-y: auto; flex: 1; }
.var-window-section-title {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--ink-mute);
  margin: 14px 0 6px;
}
.var-window-section-title:first-child { margin-top: 0; }
.var-window-empty { font-size: 12.5px; color: var(--ink-mute); font-style: italic; padding: 4px 2px 8px; }
.var-window-cat { margin-bottom: 2px; }
.var-window-cat-head {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: transparent; border: none; cursor: pointer;
  padding: 6px 6px; border-radius: var(--radius-sm);
  color: var(--ink); font-size: 13px; font-weight: 600; text-align: left;
}
.var-window-cat-head:hover { background: var(--surface); }
.var-window-chev { color: var(--ink-faint); font-size: 10px; width: 12px; }
.var-window-cat-count {
  margin-left: auto; font-size: 11px; color: var(--ink-faint);
  background: var(--surface); border: 1px solid var(--border-soft);
  border-radius: 999px; padding: 1px 7px;
}
.var-window-cat.collapsed .var-window-cat-items { display: none; }
.var-window-cat-items { padding: 2px 0 4px 20px; }
.var-window-item {
  display: flex; align-items: center; gap: 6px;
  padding: 2px 4px; border-radius: var(--radius-sm);
}
.var-window-item:hover { background: var(--accent-bg); }
.var-window-handle {
  cursor: grab; color: var(--ink-faint); font-size: 12px;
  user-select: none; padding: 4px 2px; flex: none;
}
.var-window-handle:active { cursor: grabbing; }
.var-window-main {
  flex: 1; display: flex; align-items: center; gap: 10px; min-width: 0;
  cursor: pointer; padding: 5px 4px; background: transparent; border: none; text-align: left;
}
.var-window-item-std .var-window-main { padding-left: 20px; }
.var-window-acts { display: flex; gap: 2px; opacity: 0; transition: opacity 0.12s; flex: none; }
.var-window-item:hover .var-window-acts { opacity: 1; }
.var-window-acts button {
  border: none; background: transparent; color: var(--ink-faint);
  cursor: pointer; font-size: 12px; padding: 3px 6px; border-radius: var(--radius-sm);
}
.var-window-acts button:hover { background: var(--surface); color: var(--ink); }
.var-window-acts [data-act="del"]:hover { color: var(--danger-text); }

/* Section header row (title + Sort A–Z) */
.var-window-section-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin: 14px 0 6px;
}
.var-window-section-row:first-child { margin-top: 0; }
.var-window-section-row .var-window-section-title { margin: 0; }
.var-window-sort { font-size: 11px; padding: 2px 10px; }

/* Inline edit / add form */
.var-window-form {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 12px;
  margin: 6px 0 10px;
  display: grid;
  gap: 9px;
}
.var-window-field {
  display: grid; gap: 4px;
  font-size: 10.5px; font-weight: 700; color: var(--ink-mute);
  text-transform: uppercase; letter-spacing: 0.05em;
}
.var-window-input {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background-color: var(--bg-elev); color: var(--ink);
  font-size: 13px; padding: 7px 10px; font-family: var(--font-body);
  font-weight: 400; text-transform: none; letter-spacing: normal;
}
.var-window-input:focus { outline: none; border-color: var(--accent); }

/* Optional-text chip: icon-based distinction (no new colours). */
.tpl-opt {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 1px 6px;
  border: 1px solid var(--border);
  background: var(--bg-sunk);
  border-radius: var(--radius-sm);
  font-style: italic;
  vertical-align: baseline;
}
.tpl-opt-ico { font-style: normal; color: var(--ink-mute); }
.tpl-opt-lbl { font-style: normal; color: var(--ink-faint); font-size: 0.9em; }
.tpl-opt-txt { color: var(--ink); font-style: italic; }
.tpl-opt-mirror { background: transparent; border-color: transparent; }

/* Optional-text hover menu — same popover shell as quick-assign. */
.tpl-opt-menu {
  z-index: 340;
  min-width: 200px;
  max-width: 260px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg, 0 8px 24px rgba(0,0,0,0.18));
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.tpl-opt-preview {
  font-size: 12px;
  color: var(--ink);
  font-style: italic;
  margin-bottom: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tpl-opt-act {
  text-align: left;
  border: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--ink);
  border-radius: var(--radius-sm);
  font-size: 12px;
  padding: 5px 8px;
  cursor: pointer;
}
.tpl-opt-act:hover { border-color: var(--accent); color: var(--accent); }

/* Quick-assign popover (Shift+Enter) — reuses existing tokens/components only */
.tpl-quick-assign {
  z-index: 340;
  min-width: 220px;
  max-width: 260px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg, 0 8px 24px rgba(0,0,0,0.18));
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.tpl-qa-token {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--accent);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tpl-qa-input {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elev);
  color: var(--ink);
  font-size: 13px;
  padding: 7px 10px;
  font-family: var(--font-body);
  outline: none;
}
.tpl-qa-input:focus { border-color: var(--accent); }

/* Popover fade — same 0.12s used elsewhere in this file; no new timings. */
.tpl-quick-assign, .tpl-opt-menu { opacity: 0; transition: opacity 0.12s; }
.tpl-quick-assign.tpl-pop-in, .tpl-opt-menu.tpl-pop-in { opacity: 1; }

/* Locked mode: the pointer is in the textbox (or typing) — subtle accent
   border + tint, both existing tokens. */
.tpl-quick-assign.tpl-qa-locked {
  border-color: var(--accent);
  background: var(--accent-bg);
}
.tpl-qa-hint { font-size: 10.5px; color: var(--ink-faint); }

/* Tiny transient caret tooltip */
.tpl-caret-tip {
  z-index: 341;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg, 0 8px 24px rgba(0,0,0,0.18));
  color: var(--ink-mute);
  font-size: 11.5px;
  padding: 5px 10px;
  opacity: 0;
  transition: opacity 0.15s;
  pointer-events: none;
}
.tpl-caret-tip.show { opacity: 1; }
.var-window-form input[data-f="name"] { font-family: var(--font-mono); }
.var-window-form-actions { display: flex; gap: 8px; margin-top: 2px; }
.var-window-form-actions .primary-btn { font-size: 12px; padding: 6px 14px; }
.var-window-form-actions .ghost-btn { font-size: 12px; padding: 6px 12px; }

/* PROCESS IMPORT DIALOG */
.proc-import-window { max-width: 600px; width: min(600px, calc(100vw - 32px)); }
.proc-import-body {
  padding: 14px 16px 6px;
  overflow-y: auto;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 0;
}
.proc-import-help {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 12px;
  color: var(--ink-mute);
}
.proc-import-help-btns { display: flex; gap: 6px; flex: none; }
.proc-import-help-btns .ghost-btn { font-size: 11.5px; padding: 4px 10px; white-space: nowrap; }
.proc-import-text {
  width: 100%;
  min-height: 150px;
  resize: vertical;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elev);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.55;
  padding: 10px 12px;
  outline: none;
}
.proc-import-text:focus { border-color: var(--accent); }
.proc-import-preview {
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  padding: 10px 12px;
  max-height: 220px;
  overflow-y: auto;
  font-size: 12.5px;
}
.proc-import-empty { color: var(--ink-faint); font-style: italic; font-size: 12px; }
.proc-import-error { color: var(--danger-text); font-size: 12px; }
.proc-import-title { font-weight: 700; font-size: 14px; margin-bottom: 2px; }
.proc-import-desc { color: var(--ink-mute); font-size: 12px; margin-bottom: 4px; }
.proc-import-tags { display: flex; gap: 4px; flex-wrap: wrap; margin-bottom: 6px; }
.proc-import-tags span {
  font-size: 10px; padding: 1px 8px; border-radius: 999px;
  background: var(--accent-bg); border: 1px solid var(--accent-bg-strong); color: var(--ink-mute);
}
.proc-import-count {
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--ink-mute); margin: 4px 0 6px; padding-top: 6px;
  border-top: 1px dashed var(--border-soft);
}
.proc-import-step { display: flex; align-items: baseline; gap: 6px; padding: 2px 0; }
.proc-import-step-num { color: var(--ink-faint); font-size: 11.5px; flex: none; }
.proc-import-step-title { font-weight: 600; font-size: 12.5px; }
.proc-import-meta {
  font-size: 10.5px; color: var(--ink-mute); background: var(--accent-bg);
  border-radius: 999px; padding: 0 7px; white-space: nowrap;
}
.proc-import-sub { color: var(--ink-mute); font-size: 11.5px; padding: 1px 0; }
.proc-import-warnings {
  margin-top: 8px; padding-top: 6px; border-top: 1px dashed var(--border-soft);
  font-size: 11px; color: var(--danger-text);
}
.proc-import-footer {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 16px; border-top: 1px solid var(--border-soft);
}
.proc-import-status { font-size: 11px; color: var(--ink-faint); }
.proc-import-footer .primary-btn { font-size: 12.5px; padding: 7px 16px; white-space: nowrap; }

/* "Update from MD" side-by-side preview */
.proc-mdupdate-window { max-width: 680px; width: min(680px, calc(100vw - 32px)); }
.proc-mdupdate-warning {
  margin: 12px 16px 0; padding: 8px 12px; border-radius: var(--radius-sm);
  background: var(--danger-bg); color: var(--danger-text); font-size: 11.5px; line-height: 1.5;
}
.proc-mdupdate-compare {
  display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
  padding: 12px 16px; overflow-y: auto; flex: 1; min-height: 0;
}
@media (max-width: 560px) { .proc-mdupdate-compare { grid-template-columns: 1fr; } }
.proc-mdupdate-col {
  border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
  background: var(--surface); padding: 10px 12px; overflow-y: auto; min-height: 0;
}
.proc-mdupdate-col-head {
  display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
  font-size: 11px; color: var(--ink-mute); font-weight: 600;
}
.proc-mdupdate-col-head .tag {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  padding: 2px 8px; border-radius: 999px;
}
.proc-mdupdate-col-head .tag.current { background: var(--accent-bg); color: var(--ink-mute); }
.proc-mdupdate-col-head .tag.incoming { background: var(--success-bg); color: var(--success-text); }
.proc-mdupdate-list, .proc-mdupdate-list ol { list-style: none; margin: 0; padding: 0; }
.proc-mdupdate-list > li { font-size: 12.5px; font-weight: 600; padding: 3px 0; }
.proc-mdupdate-list > li > ol > li {
  font-size: 11.5px; font-weight: 400; color: var(--ink-mute);
  padding: 2px 0 2px 14px; border-left: 1px dashed var(--border-soft); margin-left: 3px;
}
.proc-mdupdate-empty { color: var(--ink-faint); font-style: italic; font-size: 12px; }
.proc-mdupdate-footer {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  padding: 12px 16px; border-top: 1px solid var(--border-soft);
}
.proc-mdupdate-footer .primary-btn { font-size: 12.5px; padding: 7px 16px; white-space: nowrap; }
.var-window-token {
  font-family: var(--font-mono); font-size: 12px; font-weight: 600;
  color: var(--accent-text); white-space: nowrap;
}
.var-window-val {
  flex: 1; font-size: 12px; color: var(--ink-mute); text-align: right;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.var-window-val em { color: var(--ink-faint); font-size: 11px; }
.var-window-focus { animation: varRowFlash 1.8s ease-out; }
.var-window-footer {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 16px; border-top: 1px solid var(--border-soft);
}
.var-window-footer .ghost-btn { font-size: 12px; padding: 5px 12px; }
.var-window-hint { font-size: 11px; color: var(--ink-faint); text-align: right; }

/* Paragraph-style dropdown in the rich toolbar */
.rich-toolbar .toolbar-block {
  border: 1px solid var(--border);
  background-color: var(--surface);
  color: var(--ink);
  border-radius: var(--radius-sm);
  font-size: 12px;
  padding: 4px 6px;
  cursor: pointer;
  outline: none;
}
.rich-toolbar .toolbar-block:focus { border-color: var(--accent); }
.rich-toolbar .toolbar-font, .rich-toolbar .toolbar-fontsize {
  border: 1px solid var(--border);
  background-color: var(--surface);
  color: var(--ink);
  border-radius: var(--radius-sm);
  font-size: 12px;
  padding: 4px 6px;
  cursor: pointer;
  outline: none;
}
.rich-toolbar .toolbar-font { max-width: 96px; }
.rich-toolbar .toolbar-fontsize { max-width: 62px; }
.rich-toolbar .toolbar-font:focus, .rich-toolbar .toolbar-fontsize:focus { border-color: var(--accent); }

/* Contextual table toolbar (visible while the caret is in a table) */
.tpl-table-bar { margin-top: -6px; border-top: 1px dashed var(--border); }
.tpl-table-bar-label {
  font-size: 11px; font-weight: 700; color: var(--ink-mute);
  text-transform: uppercase; letter-spacing: 0.05em; padding: 0 4px;
  align-self: center;
}
.tpl-table-bar button { font-size: 11.5px; white-space: nowrap; }

/* Insert-table grid picker */
.tpl-table-picker {
  z-index: 300;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg, 0 8px 24px rgba(0,0,0,0.18));
  padding: 10px;
}
.tpl-table-picker-label {
  font-size: 11px; color: var(--ink-mute); margin-bottom: 6px; text-align: center;
}
.tpl-table-picker-grid {
  display: grid;
  grid-template-columns: repeat(6, 22px);
  grid-auto-rows: 22px;
  gap: 3px;
}
.tpl-table-picker-cell {
  border: 1px solid var(--border);
  background: var(--bg-elev);
  border-radius: 3px;
  cursor: pointer;
  padding: 0;
}
.tpl-table-picker-cell.hot { background: var(--accent); border-color: var(--accent); }

/* Highlight colour picker */
.tpl-hilite-picker {
  z-index: 300;
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg, 0 8px 24px rgba(0,0,0,0.18));
  padding: 8px 10px;
}
.tpl-hilite-swatch {
  width: 22px;
  height: 22px;
  border: 1px solid rgba(0,0,0,0.18);
  border-radius: 5px;
  cursor: pointer;
  padding: 0;
  transition: transform 0.1s, box-shadow 0.1s;
}
.tpl-hilite-swatch:hover { transform: scale(1.12); box-shadow: 0 0 0 2px var(--accent); }
.tpl-hilite-none {
  border: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--ink-mute);
  border-radius: 5px;
  font-size: 11px;
  padding: 3px 9px;
  cursor: pointer;
  white-space: nowrap;
}
.tpl-hilite-none:hover { border-color: var(--accent); color: var(--ink); }

/* Tables inside the body editor & preview. Border color is a deliberately
   neutral slate (not the app's purple-tinted --border token) so it doesn't
   clash with colored cells pasted in from Word/Outlook tables. */
.rich-editor table { border-collapse: collapse; margin: 8px 0; max-width: 100%; }
.rich-editor td, .rich-editor th { border: 1px solid var(--border); padding: 6px 8px; vertical-align: top; min-width: 30px; }
.template-preview-body table { border-collapse: collapse; margin: 8px 0; max-width: 100%; }
.template-preview-body td, .template-preview-body th { border: 1px solid var(--border); padding: 6px 8px; vertical-align: top; }
[data-theme="dark"] .rich-editor td, [data-theme="dark"] .rich-editor th,
[data-theme="dark"] .template-preview-body td, [data-theme="dark"] .template-preview-body th {
  border-color: #475569;
}

/* INSIGHTS */
.insight-meta-grid {
  display: grid;
  grid-template-columns: auto 1fr 1fr;
  gap: 14px;
  margin-bottom: 22px;
}
.insight-meta-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.insight-meta-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-mute);
}

/* ===== Insights: Zettelkasten + Goals + Graph ===== */

/* Kind segmented control */
/* Four kinds now (Note / Goal / Challenge / AI Prompt), so the segment wraps
   rather than forcing a horizontal scroll in the narrow meta grid on a phone. */
.ins-kind-seg { display: inline-flex; flex-wrap: wrap; border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; }

/* Challenge kind — four structured prose fields. Plain textareas rather than
   rich editors: these are short structured answers, and keeping them out of the
   contenteditable/wikilink machinery avoids four more toolbars and four more
   selection edge cases for no real gain. They auto-grow, so a long outcome
   isn't read through a 3-row slot. */
.ins-challenge { display: flex; flex-direction: column; }
.ins-ch-field {
  width: 100%;
  min-height: 64px;
  resize: vertical;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 15px;
  line-height: 1.45;
  overflow: hidden;
}
.ins-ch-field::placeholder { color: var(--ink-faint); }
.ins-ch-field:focus { outline: none; border-color: var(--accent); }
.ins-kind-seg button {
  display: inline-flex; align-items: center; gap: 6px;
  border: none;
  background: var(--surface);
  color: var(--ink-mute);
  font-size: 12.5px;
  padding: 7px 12px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.12s, color 0.12s;
}
.ins-kind-seg button + button { border-left: 1px solid var(--border); }
.ins-kind-seg button.active { background: var(--accent-btn); color: #fff; }

/* [[link]] highlight (Custom Highlight API), theme-aware */
::highlight(ins-links) { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.ins-link-hint { margin-left: auto; font-size: 10.5px; color: var(--ink-faint); align-self: center; }

/* [[ autocomplete popup */
.ins-link-suggest {
  position: fixed;
  z-index: 340;
  min-width: 200px;
  max-width: 260px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg, 0 8px 24px rgba(0,0,0,0.18));
  padding: 4px;
  display: flex;
  flex-direction: column;
}
.ins-link-suggest-item {
  text-align: left;
  border: none;
  background: transparent;
  color: var(--ink);
  font-size: 12.5px;
  padding: 6px 9px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ins-link-suggest-item.sel, .ins-link-suggest-item:hover { background: var(--accent-btn); color: #fff; }

/* Links to / Linked from panels */
.ins-links-panels { margin-top: 20px; }
.ins-link-rows { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
.ins-link-row {
  display: flex;
  align-items: center;
  gap: 8px;
  text-align: left;
  border: 1px solid var(--border-soft);
  background: var(--bg-elev);
  border-radius: var(--radius-sm);
  padding: 6px 10px;
  cursor: pointer;
  font-size: 12.5px;
  color: var(--ink);
  transition: border-color 0.12s, background 0.12s;
}
.ins-link-row:hover { border-color: var(--accent); }
.ins-link-row-kind { flex-shrink: 0; }
.ins-link-row-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ins-link-row.ins-link-unresolved { cursor: default; color: var(--ink-mute); border-style: dashed; }
.ins-link-row.ins-link-unresolved:hover { border-color: var(--border); }
.ins-link-row-hint { font-size: 10.5px; color: var(--ink-faint); }
.ins-link-scope-hint { font-size: 11px; color: var(--ink-mute); line-height: 1.4; margin: -2px 0 8px; }
.ins-link-empty { font-size: 12px; color: var(--ink-faint); font-style: italic; padding: 4px 2px; }
.ins-link-row.ins-link-cross [data-unlink] { margin-left: auto; opacity: 0; flex-shrink: 0; }
.ins-link-row.ins-link-cross:hover [data-unlink] { opacity: 1; }
.ins-link-add-btn { width: 100%; justify-content: center; margin-bottom: 18px; font-size: 12.5px; }

/* Store-tab selector inside the cross-tab "Link items" picker */
.linker-store-tabs { display: flex; gap: 6px; margin-bottom: 10px; flex-wrap: wrap; }
.linker-store-tabs button {
  font-size: 12px; padding: 5px 11px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); color: var(--ink-mute); cursor: pointer; transition: all 0.15s;
}
.linker-store-tabs button:hover { border-color: var(--accent); color: var(--ink); }
.linker-store-tabs button.active { background: var(--accent-btn); border-color: var(--accent); color: #fff; }

/* Performance "start blank vs template" first-run choice */
.perf-start-copy { font-size: 13px; color: var(--ink-soft); line-height: 1.55; margin: 0 0 14px; }
.perf-start-options { display: flex; flex-direction: column; gap: 10px; }
.perf-start-option {
  text-align: left; border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface); padding: 14px 16px; cursor: pointer; transition: all 0.15s;
}
.perf-start-option:hover { border-color: var(--accent); background: var(--accent-bg); }
.perf-start-option-title { font-size: 14px; font-weight: 600; color: var(--ink); margin-bottom: 3px; }
.perf-start-option-sub { font-size: 12px; color: var(--ink-mute); line-height: 1.4; }

/* Goal sections */
.goal-list-help { font-size: 11.5px; color: var(--ink-mute); margin: -4px 0 10px; line-height: 1.4; }
.goal-kpis, .goal-questions, .goal-pieces { display: flex; flex-direction: column; gap: 6px; margin-bottom: 20px; }
.goal-kpi-row { display: flex; align-items: center; gap: 6px; }
.goal-kpi-input {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elev);
  color: var(--ink);
  font-size: 12.5px;
  padding: 5px 8px;
  outline: none;
  min-width: 0;
}
.goal-kpi-input:focus { border-color: var(--accent); }
.goal-kpi-name { flex: 1; }
.goal-kpi-num { width: 58px; text-align: center; }
.goal-kpi-unit { width: 62px; }
.goal-kpi-slash { color: var(--ink-faint); }
.goal-kpi-bar { flex: 1.4; height: 6px; background: var(--bg-sunk); border-radius: 999px; overflow: hidden; min-width: 40px; }
.goal-kpi-fill { height: 100%; background: var(--accent); border-radius: 999px; transition: width 0.25s; }
.goal-kpi-pct { width: 38px; text-align: right; font-size: 11.5px; font-variant-numeric: tabular-nums; color: var(--ink-mute); }
.goal-q-row, .goal-piece-row { display: flex; align-items: center; gap: 8px; }
.goal-q-mark { color: var(--ink-mute); font-weight: 700; width: 12px; text-align: center; }
.goal-q-input, .goal-piece-input { flex: 1; }
.goal-piece-check { border: none; background: transparent; font-size: 15px; cursor: pointer; color: var(--ink-mute); padding: 0 2px; line-height: 1; }
.goal-piece-row.done .goal-piece-input { text-decoration: line-through; color: var(--ink-faint); }
.goal-pieces-progress { font-size: 11px; color: var(--ink-mute); margin-bottom: 2px; }
.goal-add-btn { align-self: flex-start; font-size: 12px; padding: 4px 10px; margin-top: 2px; }
.var-act-danger { color: var(--danger-text); }

/* Folder-panel graph button */

/* Knowledge graph modal */
.ins-graph-modal { width: min(1000px, 94vw); height: 86vh; display: flex; flex-direction: column; }
.ins-graph-stats { font-size: 12px; color: var(--ink-mute); margin-left: 10px; }
.ins-graph-modal .modal-header { display: flex; align-items: center; }
.ins-graph-modal .modal-title { margin-right: 0; }
.ins-graph-modal .modal-header .iconbtn { margin-left: auto; }
.ins-graph-body { flex: 1; position: relative; min-height: 0; }
.ins-graph-canvas { display: block; width: 100%; height: 100%; touch-action: none; }
.ins-graph-legend {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 26px;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 12px;
  font-size: 11px;
  color: var(--ink-faint);
  border-top: 1px solid var(--border-soft);
  background: var(--surface);
}
.ins-graph-empty {
  position: absolute;
  inset: 0 0 26px 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 30px;
  color: var(--ink-mute);
  font-size: 13px;
}
.insight-flag-select {
  padding: 9px 14px;
  font-size: 13px;
  font-weight: 500;
}
.insight-flag-row {
  display: flex;
  gap: 6px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.insight-flag-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  border-radius: 20px;
  background: var(--bg-sunk);
  border: 1px solid var(--border);
  font-size: 12px;
  color: var(--ink-soft);
  font-weight: 500;
  cursor: pointer;
  transition: all 0.15s;
}
.insight-flag-btn:hover { background: var(--surface); border-color: var(--ink-faint); }
.insight-flag-btn.active {
  background: var(--accent-bg);
  border-color: var(--accent);
  color: var(--accent-text);
}
.insight-flag-btn .emoji { font-size: 14px; }
.insight-date-input {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 9px 14px;
  font-size: 13px;
  color: var(--ink);
  outline: none;
  font-family: inherit;
  transition: all 0.15s;
}
.insight-date-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
/* The calendar-picker glyph on every native date input is browser chrome,
   not something we draw — color-scheme (set per theme at :root) already
   gets the browser to render it light-on-dark or dark-on-light correctly;
   this just gives it the same hover feedback every other control has. */
input[type="date"]::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: 0.55;
  transition: opacity 0.15s;
  border-radius: 4px;
}
input[type="date"]::-webkit-calendar-picker-indicator:hover { opacity: 1; }
.insight-body-editor {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 16px 18px;
  background: var(--surface);
  font-size: 14px;
  line-height: 1.7;
  color: var(--ink);
  min-height: 220px;
  outline: none;
  margin-bottom: 18px;
  transition: all 0.15s;
}
.insight-body-editor:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
.insight-body-editor:empty::before {
  content: attr(data-placeholder);
  color: var(--ink-faint);
  pointer-events: none;
}

/* BACKREFS */
.backref {
  font-size: 12.5px;
  color: var(--ink-soft);
  padding: 8px 12px;
  background: var(--bg-sunk);
  border-radius: var(--radius-md);
  margin: 4px 0;
  cursor: pointer;
  border: 1px solid var(--border-soft);
  transition: all 0.15s;
  font-weight: 500;
}
.backref:hover { background: var(--accent-bg); color: var(--accent-text); border-color: var(--accent-soft); }

/* MODALS */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(35, 0, 76, 0.5);
  backdrop-filter: blur(8px);
  z-index: 100;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 80px 20px 20px;
  animation: fadeIn 0.18s;
}
[data-theme="dark"] .modal-backdrop { background: rgba(0,0,0,0.7); }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
/* Exit — mirrors the entrance instead of the modal just vanishing when
   `hidden` gets set. Triggered by the .closing class closeModalAnimated()
   adds in app.js right before it. */
@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
@keyframes slideUp { from { transform: translateY(0); opacity: 1; } to { transform: translateY(-16px); opacity: 0; } }
.modal-backdrop.closing { animation: fadeOut var(--duration-base) var(--ease-ios) forwards; }
.modal-backdrop.closing .modal { animation: modalPopOut var(--duration-base) var(--ease-ios) forwards; }
/* Mobile bottom-sheet variants — defined at top level (not nested inside
   the mobile media query) so forced-mobile-mode can also use them at any
   viewport width, not only when the query itself happens to match. */
@keyframes slideUpSheet { from { transform: translateY(100%); } to { transform: translateY(0); } }
@keyframes slideDownSheet { to { transform: translateY(100%); } }
.modal {
  background: var(--bg-elev);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  width: 100%;
  max-width: 560px;
  max-height: calc(100vh - 100px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Bold open: a spring scale-pop (from 0.9 with a slight rise) that overshoots
     1.0 and settles, instead of the flat slide. Mobile overrides this with the
     bottom-sheet slide below. */
  animation: modalPopIn var(--duration-sheet) var(--ease-settle, cubic-bezier(0.22, 0.61, 0.36, 1));
}
@keyframes slideDown { from { transform: translateY(-16px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
@keyframes modalPopIn { from { opacity: 0; transform: translateY(10px) scale(0.9); } to { opacity: 1; transform: none; } }
@keyframes modalPopOut { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(8px) scale(0.94); } }

/* ── Premium minimal transitions ─────────────────────────────────────────
   Content-enter: opening a document or switching sections rises the content
   in gently. One-shot, retriggered by a JS-toggled class (playRise in app.js)
   so it fires on each open, not on the in-place patches. Tick-pop: a delicate
   overshoot when a checkbox / step status is marked done. Both use the shared
   motion tokens and are silenced under prefers-reduced-motion. */
@keyframes contentRise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.anim-rise { animation: contentRise var(--duration-base) var(--ease-ios) both; }
@keyframes contentFade { from { opacity: 0; } to { opacity: 1; } }
.anim-fade { animation: contentFade var(--duration-base) var(--ease-ios) both; }
@keyframes rowRise { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
.anim-row-rise { animation: rowRise var(--duration-fast) var(--ease-ios) both; }
@keyframes tickPop { 0% { transform: scale(1); } 45% { transform: scale(1.28); } 100% { transform: scale(1); } }
.tick-pop { animation: tickPop 0.3s var(--ease-ios); }
/* ── Bolder register ─────────────────────────────────────────────────────
   Directional section slide: switching tabs slides the section in from the
   direction of travel (further-right tab → in from the right), with a touch of
   scale for a modern, page-swiped feel.

   This curve used to be --ease-spring, cubic-bezier(0.34, 1.4, 0.5, 1) —
   the 1.4 is overshoot, a deliberate iOS-style bounce shipped in the
   spring-transitions pass. One UI's motion is the opposite register:
   content decelerates and settles into place, it does not spring past its
   mark and come back. Renamed as well as retimed, because a token called
   "spring" that doesn't spring is worse than no token. Six call sites also
   carried the old curve as an inline fallback literal, so changing only
   the token here would have left the bounce in the modal, the menu, the
   action sheet and the mobile rail sheet. */
:root { --ease-settle: cubic-bezier(0.22, 0.61, 0.36, 1); }
@keyframes sectionSlideR { from { opacity: 0; transform: translateX(42px) scale(0.985); } to { opacity: 1; transform: none; } }
@keyframes sectionSlideL { from { opacity: 0; transform: translateX(-42px) scale(0.985); } to { opacity: 1; transform: none; } }
.anim-slide-r { animation: sectionSlideR var(--duration-sheet) var(--ease-settle) both; }
.anim-slide-l { animation: sectionSlideL var(--duration-sheet) var(--ease-settle) both; }
@media (prefers-reduced-motion: reduce) {
  .anim-rise, .anim-fade, .anim-row-rise, .tick-pop,
  .anim-slide-r, .anim-slide-l,
  /* The bolder window open/close: drop the scale/translate motion (the backdrop
     opacity fade is mild and stays). */
  .modal, .modal-backdrop.closing .modal, .new-menu-pop,
  /* Keyboard-focus tooltip: it still appears, just without the fade. */
  .focus-tip,
  /* Everything below was animating with no guard at all. Found by listing
     every selector in this file that carries an animation declaration and
     diffing that against this block, rather than from memory. The action
     sheet is the primary context menu on a phone and slides up on every
     single "..." tap, and the toast appears after most actions — those two
     alone were the reduced-motion promise quietly not being kept. */
  .action-sheet,
  .toast,

  #mobileRailModal .modal-rail-sheet,
  .var-row-flash,
  .var-window-focus,
  .perf-table td.perf-cell-dup-pulse { animation: none !important; }

  /* Deliberately NOT silenced: .modal-backdrop's own opacity fade. It
     carries no movement, it is what stops a sheet arriving as an abrupt
     flash of full-screen dim, and the setting asks for less MOTION rather
     than for no transition at all. Stated so it reads as a decision. */
}

.modal-wide { max-width: 760px; }
.modal-small { max-width: 440px; }
.modal-search { padding: 0; }
.modal-header {
  padding: 18px 22px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid var(--border-soft);
}
.modal-title { font-size: 17px; font-weight: 700; letter-spacing: -0.02em; }
.modal-body { padding: 22px; overflow-y: auto; flex: 1; }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 22px; }

.search-input-wrap {
  display: flex; align-items: center; gap: 12px;
  padding: 18px 22px;
  border-bottom: 1px solid var(--border-soft);
  color: var(--ink-mute);
}
.search-input-wrap input {
  flex: 1;
  background: transparent;
  border: none;
  outline: none;
  font-size: 16px;
  color: var(--ink);
  font-weight: 500;
}
.search-results { max-height: 60vh; overflow-y: auto; padding: 6px 10px 10px; }
/* Same density language as the map: flat rows on hairline separators under
   a small-caps section label, instead of the iOS grouped cards this list
   used to be (surface + border + radius-lg + a shaded header bar per
   group — the chrome outweighed the rows). Keyboard navigation's .active
   highlight is part of the search contract and stays untouched. */
.search-result-group { margin: 0 0 10px; }
.search-result-group-title {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-mute);
  padding: 8px 4px 4px;
}
.search-result {
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  border-bottom: 1px solid var(--border-soft);
  cursor: pointer;
  transition: background 0.12s;
}
.search-result:last-child { border-bottom: none; }
.search-result:hover, .search-result.active { background: var(--bg-sunk); }
.search-result-title {
  font-weight: 600; color: var(--ink); font-size: 13.5px; letter-spacing: -0.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.search-result-context {
  font-size: 11px; color: var(--ink-mute); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.export-options { display: flex; flex-direction: column; gap: 8px; }
.export-option {
  text-align: left;
  padding: 16px 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  transition: all 0.15s;
}
.export-option:hover { border-color: var(--accent); background: var(--accent-bg); transform: translateY(-1px); box-shadow: var(--shadow-md); }
.export-option-title { font-weight: 700; font-size: 14px; color: var(--ink); margin-bottom: 4px; letter-spacing: -0.01em; }
.export-option-sub { font-size: 12.5px; color: var(--ink-mute); line-height: 1.4; }

.import-preview {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 18px;
  margin-top: 14px;
}
.import-preview-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px 16px;
  margin-bottom: 16px;
  font-size: 13px;
}
.import-preview-stat {
  display: flex;
  justify-content: space-between;
  padding: 8px 0;
  border-bottom: 1px solid var(--border-soft);
}
.import-preview-stat strong { font-weight: 700; }
.import-mode-options { display: flex; flex-direction: column; gap: 6px; margin-top: 14px; }
.import-mode-option {
  text-align: left;
  padding: 12px 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  font-size: 13px;
  transition: all 0.15s;
}
.import-mode-option:hover { border-color: var(--accent); }
.import-mode-option strong { color: var(--ink); display: block; margin-bottom: 3px; font-weight: 600; }
.import-mode-option span { color: var(--ink-mute); font-size: 12px; line-height: 1.4; }

/* Same density pass as the map: the spacing rhythm carried most of the
   slack here (32px between sections, double-spaced titles). The subs stay
   readable — they carry real warnings (the cloud-sync overwrite note), so
   their size and line-height give up less than the whitespace does. */
.settings-section { margin-bottom: 20px; }
.settings-section:last-child { margin-bottom: 0; }
.settings-section-title {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-mute);
  margin-bottom: 8px;
  padding-bottom: 5px;
  border-bottom: 1px solid var(--border-soft);
}
.settings-section-title.danger { color: var(--danger-text); }
.settings-row { margin-bottom: 8px; }
.settings-label { font-weight: 600; font-size: 13px; color: var(--ink); }
.settings-sub { font-size: 11.5px; color: var(--ink-mute); margin-top: 2px; line-height: 1.45; }

kbd {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  background: var(--bg-sunk);
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  color: var(--ink);
  line-height: 1.4;
}
.shortcut-list { display: flex; flex-direction: column; gap: 2px; }
.shortcut-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 4px; border-bottom: 1px solid var(--border-soft);
}
.shortcut-row:last-child { border-bottom: none; }
.shortcut-desc { font-size: 13px; color: var(--ink-soft); }
.shortcut-keys { display: flex; align-items: center; gap: 4px; font-size: 12px; color: var(--ink-mute); flex-shrink: 0; }
/* Group heading inside the shortcut sheet. The sheet listed five global
   keys and nothing else, so per-surface gestures — notably the three ways
   to pan a schedule sideways — were undocumented anywhere in the app. */
.shortcut-group-title {
  margin: 18px 0 2px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* Layout-mode segmented toggle (Auto / Desktop / Mobile) */
.layout-mode-toggle {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  margin-top: 8px;
  padding: 4px;
  background: var(--bg-sunk);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.layout-mode-opt, .text-scale-opt {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 10px 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-mute);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}
.layout-mode-opt svg { flex-shrink: 0; }
.layout-mode-opt:hover, .text-scale-opt:hover { color: var(--ink); background: var(--bg-elev); }
.layout-mode-opt.active, .text-scale-opt.active {
  color: var(--accent-text);
  background: var(--bg-elev);
  border-color: var(--accent-soft);
  box-shadow: var(--shadow-sm);
}

/* Cloud sync segmented toggle (Off / On) — same pattern as .layout-mode-toggle */
.cloud-sync-toggle {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
  margin-top: 8px;
  padding: 4px;
  background: var(--bg-sunk);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.cloud-sync-opt {
  padding: 10px 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-mute);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}
.cloud-sync-opt:hover { color: var(--ink); background: var(--bg-elev); }
.cloud-sync-opt.active {
  color: var(--accent-text);
  background: var(--bg-elev);
  border-color: var(--accent-soft);
  box-shadow: var(--shadow-sm);
}

.auto-backups-list { display: flex; flex-direction: column; gap: 8px; }

.report-types {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}
/* Toggle chips, matching the segmented-pill language used everywhere else
   (layout mode, accent color, sort mode, cloud sync) — these five aren't
   mutually exclusive like those, so each pill toggles independently rather
   than one-of-N. The underlying <input type=checkbox> is unchanged (still
   what generateReport() reads via input[type=checkbox]:checked); it's just
   visually hidden in favor of styling the label as the pill. */
.report-type {
  display: inline-flex;
  align-items: center;
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-sunk);
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-mute);
  cursor: pointer;
  user-select: none;
  transition: all 0.15s;
}
.report-type:hover { border-color: var(--accent); color: var(--ink); }
.report-type:has(input:checked) { background: var(--accent-btn); border-color: var(--accent); color: #fff; }
.report-type:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.report-type input {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}
.report-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.auto-backup-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  font-size: 12px;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.auto-backup-item:hover { border-color: var(--accent-bg-strong); box-shadow: var(--shadow-sm); }
.auto-backup-item span { color: var(--ink-soft); }

.text-input {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 10px 14px;
  font-size: 13px;
  outline: none;
  color: var(--ink);
  margin-bottom: 12px;
  transition: all 0.15s;
}
.text-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
.prompt-message { font-size: 13px; color: var(--ink-soft); line-height: 1.55; margin-bottom: 12px; }
/* Read-only "view full text" popup for a truncated Steps-grid card
   (mobile only, opened by openStepTextPopup in app.js) — plain wrapped
   text, no editing controls here (that stays the row's own inline
   textarea, reachable via this popup's own "Open details" action). */
.step-text-modal-body {
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-word;
  margin-bottom: 16px;
}
.linker-list {
  max-height: 320px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
}
.linker-item {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border-soft);
  cursor: pointer;
  font-size: 13px;
  transition: background 0.12s;
}
.linker-item:last-child { border-bottom: none; }
.linker-item:hover { background: var(--bg-sunk); }
.linker-item input { cursor: pointer; accent-color: var(--accent); }

.ghost-btn.danger-text { color: var(--danger-text); }
.ghost-btn.danger-text:hover { background: var(--danger-bg); color: var(--danger-text); }

.list-item-row {
  display: flex;
  gap: 10px;
  align-items: flex-start;
}
.list-item-main { flex: 1; min-width: 0; }

/* FOLDER PICKER */
.folder-picker-list {
  max-height: 320px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  padding: 4px;
}
.folder-picker-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 13px;
  color: var(--ink-soft);
  transition: background 0.1s;
}
.folder-picker-item:hover { background: var(--bg-sunk); color: var(--ink); }
.folder-picker-item.selected {
  background: var(--accent-bg);
  color: var(--accent-text);
  font-weight: 600;
}
.folder-picker-item .folder-dot { width: 8px; height: 8px; border-radius: 3px; background: var(--ink-faint); flex-shrink: 0; }
.folder-picker-item.depth-1 { padding-left: 22px; }
.folder-picker-item.depth-2 { padding-left: 36px; }
.folder-picker-item.depth-3 { padding-left: 50px; }
.folder-picker-item.depth-4 { padding-left: 64px; }
.folder-picker-empty {
  padding: 24px;
  text-align: center;
  color: var(--ink-mute);
  font-size: 12px;
}

/* FOLDER DRAG (folder onto folder for re-parenting) */
.folder-row.drag-source { opacity: 0.5; box-shadow: var(--shadow-md); background: var(--surface); }
.folder-row.drag-target {
  background: var(--accent-bg) !important;
  outline: 2px dashed var(--accent);
  outline-offset: -2px;
}
.folder-row.drag-insert-before,
.folder-row.drag-insert-after { position: relative; }
.folder-row.drag-insert-before::before,
.folder-row.drag-insert-after::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--accent);
  border-radius: var(--radius-sm);
}
.folder-row.drag-insert-before::before { top: -1px; }
.folder-row.drag-insert-after::after { bottom: -1px; }
.linker-empty {
  padding: 28px; text-align: center;
  color: var(--ink-mute); font-size: 13px;
}

/* RUN MODE */
.run-mode {
  position: fixed;
  inset: 0;
  background: var(--bg);
  z-index: 50;
  display: flex;
  flex-direction: column;
}
.run-mode-header {
  padding: 18px 32px;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--bg-elev);
}
.run-mode-title { font-size: 20px; font-weight: 700; letter-spacing: -0.025em; }
.run-mode-progress {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 32px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-elev);
}
.run-mode-progress-track {
  flex: 1;
  max-width: 760px;
  height: 6px;
  border-radius: 99px;
  background: var(--bg-sunk);
  overflow: hidden;
}
.run-mode-progress-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--accent) 0%, var(--accent-hover) 100%);
  border-radius: 99px;
  transition: width 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
.run-mode-progress-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-mute);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.run-mode-body { flex: 1; overflow-y: auto; padding: 32px; }
.run-step {
  max-width: 760px;
  margin: 0 auto 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}
.run-step-header {
  padding: 16px 22px;
  display: flex; align-items: center; gap: 14px;
  cursor: pointer;
}
.run-step-header:hover { background: var(--bg-sunk); }
.run-step-check {
  width: 24px; height: 24px;
  border: 2px solid var(--border);
  border-radius: 50%;
  flex-shrink: 0;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface);
  transition: all 0.15s;
}
.run-step-check:hover { border-color: var(--accent); }
.run-step-check.checked { background: var(--accent-btn); border-color: var(--accent); }
.run-step-check.checked::after { content: "✓"; color: white; font-size: 13px; font-weight: 700; }
.run-step-title { flex: 1; font-weight: 600; font-size: 15px; letter-spacing: -0.015em; }
.run-step-body {
  padding: 0 22px 20px 60px;
  display: none;
  font-size: 14px;
  color: var(--ink-soft);
  line-height: 1.65;
}
.run-step.expanded .run-step-body { display: block; }
.run-substep-children { padding-left: 12px; }
.run-substep-row {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 8px 6px 16px;
  border-left: 2px solid var(--accent-bg-strong);
  margin: 4px 0;
  cursor: pointer;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  transition: background-color 0.12s;
}
.run-substep-row:hover { background: var(--bg-sunk); }
.run-substep-check {
  width: 18px; height: 18px;
  border: 2px solid var(--border);
  border-radius: 50%;
  flex-shrink: 0;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface);
  transition: all 0.15s;
}
.run-substep-check:hover { border-color: var(--accent); }
.run-substep-check.checked { background: var(--accent-btn); border-color: var(--accent); }
.run-substep-check.checked::after { content: "✓"; color: white; font-size: 10px; font-weight: 700; }
.run-substep-title { flex: 1; font-size: 13.5px; }
.run-substep-title.checked-text { color: var(--ink-faint); text-decoration: line-through; }

/* CONTEXT MENU */
.context-menu {
  position: fixed;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: 5px;
  z-index: 200;
  min-width: 180px;
}
.context-menu button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 14px;
  font-size: 13px;
  border-radius: var(--radius-sm);
  color: var(--ink-soft);
  font-weight: 500;
}
.context-menu button:hover { background: var(--bg-sunk); color: var(--ink); }
.context-menu button.danger { color: var(--danger-text); }
.context-menu button.danger:hover { background: var(--danger-bg); }
.context-menu-sep {
  height: 1px;
  background: var(--border-soft);
  margin: 4px 6px;
}

/* TOASTS */
.toast-container {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}
.toast {
  background: var(--ink);
  color: var(--bg-elev);
  padding: 12px 20px;
  border-radius: var(--radius-md);
  font-size: 13px;
  font-weight: 500;
  box-shadow: var(--shadow-lg);
  animation: toastIn 0.22s cubic-bezier(0.16, 1, 0.3, 1), toastOut 0.2s 2.6s forwards;
  pointer-events: auto;
}
.toast.error { background: var(--danger-solid); color: white; }
.toast.success { background: var(--success-solid); color: white; }
.toast.warning { background: var(--warning); color: white; }
@keyframes toastIn { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
@keyframes toastOut { to { transform: translateY(20px); opacity: 0; } }

.link-graph-process {
  margin-bottom: 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.link-graph-process-header {
  padding: 12px 16px;
  background: var(--bg-sunk);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: -0.01em;
}
.link-graph-process-links { padding: 12px 16px; }
.link-graph-link { font-size: 12.5px; padding: 5px 0; color: var(--ink-soft); }
.link-graph-link strong { color: var(--ink); font-weight: 600; }

/* Same density language as the map: flat rows on hairline separators, not
   the floating cards this list used to be (14/16 padding, radius-lg, 8px
   gaps — a third of the modal was chrome). */
.trash-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding: 7px 4px;
  border-bottom: 1px solid var(--border-soft);
}
.trash-item:hover { background: var(--bg-sunk); }
.trash-item-info { font-size: 13.5px; min-width: 0; }
.trash-item-info > div:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.trash-item-meta { font-size: 11px; color: var(--ink-mute); margin-top: 1px; }
.trash-item-actions { display: flex; gap: 6px; flex: none; }
/* Desktop only — the mobile mirrors keep these at the 44px touch floor,
   since the buttons ARE this row's tap targets. */
.trash-item .ghost-btn, .trash-item .danger-btn {
  padding: 4px 10px;
  font-size: 12px;
  min-height: 28px;
}

.drag-over { background: var(--accent-bg) !important; outline: 2px dashed var(--accent); }
.dragging {
  opacity: 0.9;
  box-shadow: var(--shadow-lg);
  transform: scale(1.02) rotate(-0.5deg);
  cursor: grabbing;
}

/* FAB — a mobile affordance (and the Performance tab's desktop "add row"
   button via .perf-fab); hidden by default so it doesn't float redundantly
   over the sidebar/topbar "New" controls on a normal desktop screen. Shown
   back in by the auto-mobile breakpoint, forced-mobile mode, and .perf-fab
   further down. */
.fab {
  position: fixed;
  bottom: 28px;
  right: 28px;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
  color: white;
  display: none;
  align-items: center;
  justify-content: center;
  /* Plain neutral elevation, not the old --shadow-fab (a soft purple glow
     that bled well past the button's own edge) — a FAB reads as
     "floating" from a normal drop shadow just fine; it doesn't need to
     glow. */
  box-shadow: var(--shadow-md);
  z-index: 40;
  transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
body.perf-fab .fab { display: flex; }
/* Out of the way while scrolling (see initFabScrollHide in app.js) — a
   fixed FAB otherwise sits over the scrolling content column at almost
   every offset, which is what put the "+" and the metadata button on top
   of a step card's "…" affordance. pointer-events:none while faded also
   stops a mis-tap landing on a button the user can barely see. */
body.fabs-scrolling .fab,
body.fabs-scrolling .mobile-meta-fab {
  opacity: 0;
  transform: scale(0.85) translateY(10px);
  pointer-events: none;
}
.fab, .mobile-meta-fab {
  transition: opacity 0.18s var(--ease-ios, cubic-bezier(0.16, 1, 0.3, 1)),
              transform 0.18s var(--ease-ios, cubic-bezier(0.16, 1, 0.3, 1)),
              box-shadow 0.18s;
}
@media (prefers-reduced-motion: reduce) {
  body.fabs-scrolling .fab,
  body.fabs-scrolling .mobile-meta-fab { transform: none; }
}
.fab:hover { transform: scale(1.06) translateY(-2px); box-shadow: var(--shadow-lg); }
.fab:active { transform: scale(0.97); }
/* Add-step / add-sub-step buttons under the Steps grid — the checklist's
   "+ Add item" pattern, on every form factor. */
.steps-add-row { display: flex; gap: 8px; margin-top: 10px; }
.steps-add-row .add-step-btn { flex: 1; margin-top: 0; }
/* The "+" rotates into a "×" while the radial menu is open. */
.fab-plus-icon { transition: transform 0.22s var(--ease-ios, cubic-bezier(0.16,1,0.3,1)); }
.fab.open .fab-plus-icon { transform: rotate(45deg); }

/* ---- Radial add menu ----
   #radialAddChildren is a zero-size anchor positioned (in JS, from
   #fabBtn's own getBoundingClientRect()) at the FAB's center — so it never
   has to duplicate the FAB's several breakpoint-specific bottom/right
   values. Children are absolutely positioned from that point via --tx/--ty,
   computed per child along an up-and-left arc so they can never render
   off the left/top edge from a bottom-right anchor. */
.radial-add-scrim {
  position: fixed;
  inset: 0;
  background: radial-gradient(circle at bottom right, color-mix(in srgb, var(--accent) 14%, transparent), rgba(0, 0, 0, 0.32) 65%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease;
  z-index: 39;
}
.radial-add-scrim.open { opacity: 1; pointer-events: auto; }
.radial-add-children {
  position: fixed;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  z-index: 41;
  pointer-events: none;
}
.radial-add-children.open { pointer-events: auto; }
.radial-add-child {
  position: absolute;
  left: 0;
  top: 0;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 44px;
  padding: 0 16px 0 12px;
  border-radius: 22px;
  border: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  box-shadow: var(--shadow-md);
  transform: translate(var(--anchor-x, -50%), -50%) scale(0.4);
  opacity: 0;
  /* Bolder: an overshoot spring on the transform so each pill pops out with a
     little bounce rather than easing flat. */
  transition: transform 0.34s var(--ease-settle, cubic-bezier(0.22, 0.61, 0.36, 1)), opacity 0.2s ease;
}
.radial-add-child.open {
  transform: translate(calc(var(--anchor-x, -50%) + var(--tx)), calc(-50% + var(--ty))) scale(1);
  opacity: 1;
}
/* Staggered fan-out: children pop out in sequence on open (the delay only
   applies while .open matches, so closing collapses them together). */
.radial-add-child.open:nth-child(2) { transition-delay: 0.03s; }
.radial-add-child.open:nth-child(3) { transition-delay: 0.06s; }
.radial-add-child.open:nth-child(4) { transition-delay: 0.09s; }
.radial-add-child.open:nth-child(5) { transition-delay: 0.12s; }
.radial-add-child.open:nth-child(6) { transition-delay: 0.15s; }
.radial-add-child.open:nth-child(7) { transition-delay: 0.18s; }
.radial-add-child:hover, .radial-add-child:focus-visible { background: var(--accent-bg); border-color: var(--accent-soft); color: var(--accent-text); outline: none; }
.radial-add-child:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.radial-add-child:active { transform: translate(calc(var(--anchor-x, -50%) + var(--tx)), calc(-50% + var(--ty))) scale(0.94); }
.radial-add-child-icon { display: inline-flex; color: var(--accent); flex-shrink: 0; }

/* Reduced motion: children still land at their real, correctly-spaced arc
   position (collapsing them all to the button's own point would make the
   menu unreadable) — only the animated *travel* is removed, via a near-
   instant transition instead of the spring, and the JS skips the stagger
   delay so nothing appears to slide or cascade in. */
@media (prefers-reduced-motion: reduce) {
  .fab-plus-icon, .radial-add-scrim, .radial-add-child { transition-duration: 0.01s; }
  .radial-add-child { transition-timing-function: linear; }
}


/* =========================================================
   ACTION SHEET (mobile bottom sheet for context menus)
   ========================================================= */
.action-sheet-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(35, 0, 76, 0.45);
  backdrop-filter: blur(6px);
  z-index: 250;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  animation: fadeIn 0.18s;
}
.action-sheet {
  width: 100%;
  max-width: 520px;
  background: var(--bg-elev);
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  padding: 8px 16px 24px;
  padding-bottom: max(24px, env(safe-area-inset-bottom));
  box-shadow: var(--shadow-lg);
  animation: sheetUp 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes sheetUp { from { transform: translateY(100%); } to { transform: translateY(0); } }
.action-sheet-handle {
  width: 40px;
  height: 4px;
  background: var(--border);
  border-radius: 2px;
  margin: 4px auto 14px;
}
.action-sheet-title {
  font-weight: 700;
  font-size: 15px;
  color: var(--ink);
  padding: 4px 8px 10px;
  border-bottom: 1px solid var(--border-soft);
  margin-bottom: 8px;
  letter-spacing: -0.01em;
  display: none;
}
.action-sheet-title:not(:empty) { display: block; }
.action-sheet-body { display: flex; flex-direction: column; }
.action-sheet-body button {
  width: 100%;
  text-align: left;
  padding: 14px 12px;
  font-size: 15px;
  font-weight: 500;
  color: var(--ink-soft);
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  gap: 12px;
}
.action-sheet-body button:hover, .action-sheet-body button:active {
  background: var(--bg-sunk);
  color: var(--ink);
}
.action-sheet-body button.danger { color: var(--danger-text); }
.action-sheet-body button.danger:active { background: var(--danger-bg); }
.action-sheet-sep { height: 1px; background: var(--border-soft); margin: 4px 0; }
.action-sheet-cancel {
  width: 100%;
  margin-top: 12px;
  padding: 14px;
  background: var(--bg-sunk);
  color: var(--ink);
  border-radius: var(--radius-md);
  font-weight: 600;
  font-size: 15px;
}
.action-sheet-cancel:active { background: var(--border); }

/* Item "···" menu button — visible on mobile, on-hover on desktop */
.list-item-more {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  color: var(--ink-mute);
  flex-shrink: 0;
  font-size: 18px;
  line-height: 1;
  opacity: 0;
  transition: opacity 0.15s, background 0.15s;
}
.list-item:hover .list-item-more { opacity: 1; }
.list-item-more:hover { background: var(--bg-sunk); color: var(--ink); }
.list-item-more:active { background: var(--accent-bg); }

/* Swipe gesture support */
.list-item {
  position: relative;
  overflow: hidden;
  touch-action: pan-y;
}
.list-item-swipe-actions {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: stretch;
  gap: 0;
  transform: translateX(100%);
  transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
  z-index: 1;
}
.list-item.swiped .list-item-swipe-actions { transform: translateX(0); }
.list-item-swipe-action {
  width: 80px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  color: white;
  font-size: 12px;
  font-weight: 600;
  border: none;
  cursor: pointer;
}
.list-item-swipe-action.move {
  background: var(--accent-btn);
}
.list-item-swipe-action.delete {
  background: var(--danger-solid);
}
.list-item-swipe-action svg { width: 18px; height: 18px; }
.list-item-content-wrap {
  transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.list-item.swiped .list-item-content-wrap {
  transform: translateX(-160px);
}

/* Mobile back buttons (hidden by default; shown via JS on mobile) — still
   shared by #mobileMetaBtn (the metadata-sheet trigger). The standalone
   "< [title]" bar that used to share this class (.detail-mobile-header)
   is gone — the breadcrumb bar now carries the open document's title as
   its own trailing segment (see renderFolderBreadcrumb in app.js). */
.mobile-back-btn {
  display: none;
}

/* =========================================================
   DESKTOP RESPONSIVE TWEAKS
   ========================================================= */
@media (max-width: 1100px) {
  .layout { grid-template-columns: 220px 300px 1fr; }
  .detail-content { padding: 30px 36px 60px; }
}
@media (max-width: 900px) {
  .layout { grid-template-columns: 200px 1fr; }
  .detail-content { padding: 24px 20px 40px; }
}

/* =========================================================
   MOBILE (≤768px) — full overhaul
   Mirrored below by "FORCED MOBILE MODE" (body[data-layout-mode="mobile"]),
   which repeats these same rules unscoped from viewport width. Vanilla CSS
   has no way to share declarations across a real @media condition and an
   attribute-selector-at-any-width condition without a preprocessor, so any
   rule you add/change here must be mirrored there too — a "diff the two
   blocks" pass previously found six rules that had drifted apart (fixed).
   ========================================================= */
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  /* ── The map on a phone ─────────────────────────────────────────────
     Rows to the 44px touch minimum, type one notch UP (this screen is read
     at arm's length), indent one notch tighter for the narrow width. */
  .browse-count { font-size: 13px; min-width: 27px; }
  .browse-view .browse-group > .folder-row {
    font-size: 15.5px;
    padding-top: 5px;
    padding-bottom: 5px;
    min-height: 44px;   /* the strip is the collapse control — touch floor, not the generic 48 */
  }
  .browse-view .folder-row {
    padding-left: min(calc(6px + var(--nest-depth, 0) * 16px), 40%);
  }
  .browse-view .list-item-nested {
    min-height: 44px;
    padding: 2px 12px;
    padding-left: min(calc(21px + var(--nest-depth, 1) * 16px), 45%);
  }
  .browse-view .list-item-nested .list-item-title { font-size: 14.5px; }
  .mp-meta { font-size: 12.5px; }
  /* Trash rows: the buttons ARE the tap targets, so they keep the touch
     floor on a phone regardless of the desktop slimming above. */
  .trash-item .ghost-btn, .trash-item .danger-btn {
    min-height: 44px;
    padding: 10px 12px;
    font-size: 13px;
  }
  /* Search rows are full-width tap targets — touch floor on a phone. */
  .search-result { min-height: 44px; padding: 8px 8px; }
  /* (The chevron fold control is gone — tapping the ROW folds a step with
     sub-steps now; see wireStepsTableRows.) */
  :root {
    /* Mobile chrome thinned (density pass): the two stacked bars ate 108px
       of a ~727px viewport before any content. 48+44 keeps both bars
       comfortably tappable (their controls carry their own ≥44px halos)
       while returning 16px to every screen. */
    --topbar-h: 48px;
    --folder-taskbar-h: 44px;
  }

  /* The page itself scrolls on mobile (panes are in normal flow). */
  html, body { height: auto; min-height: 100%; overflow-x: hidden; overflow-y: auto; }
  
  /* Hide desktop-only elements */
  .desktop-only { display: none !important; }

  /* Topbar: tighter, scrollable tabs */
  .topbar {
    padding: 0 12px;
    gap: 8px;
    position: sticky;
    top: 0;
  }
  .topbar-left {
    gap: 12px;
    flex: 1;
    min-width: 0;
    overflow: hidden;
  }
  .brand-name { display: none; }
  .brand-mark { width: 32px; height: 32px; }
  .tabs {
    flex: 1;
    min-width: 0;
    overflow-x: auto;
    overflow-y: hidden;
    gap: 0;
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  .tabs::-webkit-scrollbar { display: none; }
  .tab {
    padding: 8px 12px;
    font-size: 13px;
    flex-shrink: 0;
    min-height: 36px;
  }
  
  /* Hide secondary topbar icons that have alternatives in the action sheet */
  /* 38px visual, ≥44px hit via halo — a full-size 44px box on five icons
     in a 48px bar read as a wall of buttons. */
  .topbar-right .iconbtn { width: 38px; height: 38px; position: relative; }
  .topbar-right .iconbtn::after { content: ''; position: absolute; inset: -4px; }

  /* The folder task bar and its dropdown are the same component at every
     breakpoint (see the base rules) — only the detail pane needs mobile
     layout handling here. */
  .layout {
    display: block;
    height: auto;
    min-height: calc(100dvh - var(--topbar-h) - var(--folder-taskbar-h));
    position: static;
  }
  .detail-pane {
    position: static;
    width: 100%;
    background: var(--bg-elev);
    overflow: visible;
    height: auto;
  }
  /* The size jump from title to everything else IS the One UI look — 31px
     bold over a 15px body scale. Not sticky and not in the topbar: it
     scrolls away with the content, which is what keeps the chrome above it
     subordinate instead of turning into a permanent 90px header. */
  .screen-title {
    display: block;
    font-family: var(--font-display);
    font-size: 31px;
    font-weight: 700;
    line-height: 1.25;
    letter-spacing: -0.02em;
    color: var(--ink);
    margin: 0;
    /* No gutter of its own: it now sits inside .detail-empty, which already
       supplies the content gutter, so the title lines up with the rows
       below it instead of sitting on its own indent. align-self/text-align
       are for the "nothing here yet" state, where .detail-empty is a
       centering flex column — One UI's title is left-aligned in both. */
    padding: 4px 0 12px;
    text-align: left;
    align-self: stretch;
    text-wrap: balance;
  }
  .folder-taskbar { padding: 0 12px; gap: 8px; }
  /* Folder nav was starved for width here — sort/new-folder/trash + the
     always-present "Saved" indicator ate more than half the row, leaving
     the breadcrumb scroll area only ~180px on a 412px phone even though
     it's the primary content of this row. Collapse the three individual
     buttons into one overflow button (its action sheet just re-triggers
     the same three handlers, so nothing about what they do changes) and
     let the save indicator collapse to zero width when not actively
     showing, instead of reserving space for it permanently. */
  #sortBtn, #newFolderBtn, #trashBtn, #folderTreeBtn { display: none; }
  .folder-taskbar-more-btn { display: flex; }
  .save-indicator {
    max-width: 0;
    padding: 0;
    margin-left: 0;
    border-width: 0;
    overflow: hidden;
    transition: max-width 0.25s var(--ease-ios, ease), opacity 0.25s, padding 0.25s;
  }
  .save-indicator.show { max-width: 70px; padding: 4px 10px; margin-left: 2px; }
  .breadcrumb-segment-label { max-width: 150px; }
  .folder-row {
    padding: 12px 12px;
    font-size: 15px;
    min-height: 48px;
    gap: 10px;
  }
  .ghost-btn { padding: 10px 14px; font-size: 14px; min-height: 44px; }

  .mobile-back-btn {
    display: flex;
    width: 44px;
    height: 44px;
    background: var(--bg-sunk);
    color: var(--accent-text);
    flex-shrink: 0;
  }
  .mobile-back-btn:active { background: var(--accent-bg); }


  /* Metadata sheet trigger — paired with the floating "+" (.fab) rather
     than floating alone top-right: same fixed bottom-right cluster, same
     56px size and bottom-nav clearance, sitting just to its left (.fab is
     right:20px + 56px wide, so 96px clears it with a 20px gap — matches
     .fab's own 20px edge margin, plenty for two independently-tappable
     56px targets). Outline/neutral fill (vs .fab's solid accent gradient)
     keeps them instantly tellable apart at a glance despite matching
     size/position. Starts [hidden] until JS confirms the open tab
     actually has a rail. */
  .mobile-meta-fab {
    position: fixed;
    top: auto;
    /* Secondary to the 56px "+": 44px (still the touch floor) and centered
       on the FAB's own axis, so only ONE full-size circle shades the list. */
    bottom: calc(78px + env(safe-area-inset-bottom));
    right: 86px;
    left: auto;
    width: 44px;
    height: 44px;
    background: var(--bg-elev);
    border: 1.5px solid var(--border);
    color: var(--accent-text);
    z-index: 40;
    border-radius: 50%;
    box-shadow: var(--shadow-md);
    transition: transform 0.15s var(--ease-ios, cubic-bezier(0.16,1,0.3,1)), background-color 0.1s;
  }
  .mobile-meta-fab:active { transform: scale(0.97); background: var(--bg-sunk); }
  /* Metadata content must exist in exactly one place at a time. On mobile
     that's the bottom sheet — #procRail/#clRail/#tplRail (the rails wired
     to wireMobileMetaTrigger) are hidden in their normal stacked spot
     (below .detail-main) and only shown once JS has moved them into
     #mobileRailModalBody. Without this, the same content rendered twice:
     once in-flow at the bottom of the page, once in the sheet. Insights'
     rail has no sheet trigger, so it's untouched here and keeps falling
     into normal stacked flow as before. */
  #procRail, #clRail, #tplRail { display: none; }
  #mobileRailModalBody #procRail,
  #mobileRailModalBody #clRail,
  #mobileRailModalBody #tplRail { display: flex; }
  .select { padding: 8px 12px; font-size: 13px; min-height: 38px; }
  .primary-btn { padding: 8px 14px; font-size: 13px; min-height: 38px; }

  /* .list-item's mobile padding was 14px — a leftover from the flat list
     pane that used to exist, and it out-cascades .list-item-nested's
     compact 5px (same specificity, declared earlier) even though every
     surviving .list-item IS nested. That left a ~54px row wrapped around
     a single 17px line of text once the second line came out: exactly the
     "leftover empty space" the row was meant to lose. 8px vertical wraps
     the 24px "⋯" into a ~40px row — snug to the content, still a
     comfortable touch target. */
  .list-item { padding: 8px 12px; margin-bottom: 2px; min-height: 44px; }
  .list-item-title { font-size: 15px; }
  .list-item-more { opacity: 1; width: 36px; height: 36px; font-size: 20px; }

  /* The comment above aimed for a ~40px row and never got one: the touch
     sweep further down gives .list-item-more `min-height: 44px`, which is
     a LAYOUT box, and it out-cascades the 24px sizing. So a 17px line of
     title sat in a 61px row — the "⋯" was setting the height of every
     file in the app, and a folder of ten documents cost a screen and a
     half of scrolling. The row itself is the thing you tap to open a
     document, so the 44px floor belongs on the row (above); the "⋯" only
     needs 44px of REACH, which a ::before gives it for free — the same
     trick the checklist "…" already uses. Measured 61 -> 44px per row,
     pitch 65 -> 46px, i.e. ~40% more documents per screen. */
  body:not([data-layout-mode="desktop"]) .list-item-nested .list-item-more {
    width: 32px; height: 32px; font-size: 18px;
    min-width: 0; min-height: 0; position: relative;
  }
  body:not([data-layout-mode="desktop"]) .list-item-nested .list-item-more::before {
    content: ''; position: absolute; inset: -9px;
  }

  /* Detail pane — in-flow (no sticky back bar any more — see the
     .mobile-back-btn comment above) */
  .detail-pane {
    position: static;
  }
  .detail-content {
    padding: 12px 14px 96px;
    max-width: 100%;
  }
  .detail-title-input {
    font-size: 24px;
    letter-spacing: -0.02em;
    margin-bottom: 4px; /* equal 4px gaps title/desc/meta, halved (user-annotated) */
  }
  .detail-description { font-size: 14px; margin-bottom: 0; min-height: 0; }
  /* Card rhythm on a phone — 16/16 desktop padding+margin stacked into
     24-32px bands between blocks. The steps card pulls its sides in
     further so the control rail hugs the card edge (user-annotated). */
  .section-card { padding: 12px; margin-bottom: 12px; }
  .section-card.steps-grid-card { padding-left: 5px; padding-right: 5px; }
  .detail-meta-row {
    flex-wrap: wrap;
    font-size: 11.5px;
    gap: 6px;
  }
  /* 36px visual (halo restores 44 hit) — this row is glance-mostly.
     body:not() prefix: must outscore the later blanket
     `body:not(...) .ghost-btn { min-height: 44px }` touch-floor rule. */
  body:not([data-layout-mode="desktop"]) .detail-meta-row .ghost-btn {
    padding: 6px 10px;
    font-size: 12px;
    min-height: 36px;
    position: relative;
  }
  body:not([data-layout-mode="desktop"]) .detail-meta-row .ghost-btn::after { content: ''; position: absolute; inset: -4px; }
  .detail-section-title {
    margin: 14px 0 8px;
    font-size: 11px;
  }

  /* Every small icon button gets a real touch target on mobile, not just
     the step-action ones — 26px was well under the 40x40 minimum used
     everywhere else in this mobile stylesheet. */
  .iconbtn.small { width: 44px; height: 44px; }
  .backup-banner.collapsed .iconbtn.small { width: 34px; height: 34px; }

  /* Rich editor */
  .rich-editor { padding: 12px 14px; font-size: 15px; min-height: 80px; }
  /* M-2: on a phone the wrapping toolbar stacked to 9 rows / 240px — 26% of
     the viewport — burying the editor below the fold. Collapse it to a single
     horizontally-scrollable strip: every control kept, one 44px row, swipe to
     reach the rest. The native mobile-editor pattern (Gmail, Notion), and it
     needs no JS. Applies to all .rich-toolbars (Templates body, Insights,
     step description) and the table bar for consistency. */
  .rich-toolbar {
    padding: 6px;
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    touch-action: pan-x;
    overscroll-behavior-x: contain;
  }
  .rich-toolbar::-webkit-scrollbar { display: none; }
  .rich-toolbar > * { flex: 0 0 auto; }
  .rich-toolbar button { padding: 8px 12px; font-size: 13px; min-height: 44px; }
  /* The strip hides ~2x its width in controls, and with the scrollbar hidden
     there was nothing saying so. Fade whichever edge still has content behind
     it — same "only when it actually scrolls" rule as the folder pill bar,
     driven by .can-scroll-* from updateRichToolbarFades(). */
  .rich-toolbar.can-scroll-right {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
  }
  .rich-toolbar.can-scroll-left {
    -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 28px), transparent);
    mask-image: linear-gradient(to left, #000 calc(100% - 28px), transparent);
  }
  .rich-toolbar.can-scroll-left.can-scroll-right {
    -webkit-mask-image: linear-gradient(to right, transparent, #000 28px, #000 calc(100% - 28px), transparent);
    mask-image: linear-gradient(to right, transparent, #000 28px, #000 calc(100% - 28px), transparent);
  }
  /* "Ctrl+click a link to open it" is a mouse instruction, and it sat at the
     far end of the strip eating 258px of swipe before you reach it. The
     editor placeholder already teaches [[ on touch. */
  .ins-link-hint { display: none; }
  
  /* Notes */
  .notes-section { padding: 14px 14px; }
  .note-add { flex-wrap: wrap; }
  .note-add textarea { font-size: 14px; min-height: 44px; }
  .note-add-btn { padding: 10px 14px; min-height: 40px; font-size: 13px; }
  .note-flag-select { min-width: 56px; min-height: 40px; }
  
  /* Linked cards */
  .linked-card-header { padding: 12px 14px; min-height: 48px; }
  
  /* Checklist — actions (note/delete) are hover-only on desktop, but
     touch has no hover, so they stay visible by default here. The
     checkbox stays a visually-quiet 22px but gets an invisible ::before
     to bring its real tap target up to 40x40. */
  /* Density pass: 62px rows against a 44px floor were mostly padding, and
     the trailing 44px ⋮ + 14px paddings squeezed the text to ~47% of the
     viewport. Tighter box, ⋮ at 32px visual (halo keeps 44 hit). */
  .checklist-item { padding: 6px 10px; gap: 10px; min-height: 44px; box-sizing: border-box; margin-left: min(calc(var(--cl-d, 0) * 14px), 84px); }
  .checklist-items.cl-has-groups .checklist-item-leaf .cl-num { margin-left: 32px; }
  .checklist-item .checklist-item-more-actions {
    width: 32px; height: 32px; min-width: 0; min-height: 0; position: relative;
  }
  .checklist-item .checklist-item-more-actions::before { content: ''; position: absolute; inset: -6px; }
  /* Batch bar clears the bottom nav. */
  .batch-bar { bottom: calc(74px + env(safe-area-inset-bottom)); }
  .checklist-item-checkbox { width: 22px; height: 22px; }
  .checklist-item-checkbox::before { content: ''; position: absolute; inset: -9px; }
  .checklist-item-text { font-size: 15px; min-height: 22px; }
  .checklist-item-actions { opacity: 1; }
  .cl-twist::before { content: ''; position: absolute; inset: -9px; }
  .cl-num { font-size: 12.5px; }
  /* Four always-visible buttons leave a phone-width row no room for the
     text itself — collapse them into one ⋮ opening the bottom sheet. */
  .checklist-item .checklist-item-due-btn,
  .checklist-item .checklist-item-note-btn,
  .checklist-item .checklist-item-addsub-btn,
  .checklist-item .checklist-item-delete-btn { display: none; }
  .checklist-item-more-actions { display: flex; }
  /* Templates */
  .template-subject-input { font-size: 15px; padding: 12px 14px; }
  .subject-mirror { font-size: 15px; padding: 12px 14px; }
  .template-vars-row { padding: 12px 14px 14px; }
  .var-act { font-size: 13px; padding: 4px 6px; }
  .var-cat-chip { font-size: 11px; padding: 4px 9px; }
  .var-type-chip { font-size: 11px; padding: 4px 8px; }
  .template-vars-row .var-move-btns { gap: 5px; }
  .template-vars-row .var-move { font-size: 11px; padding: 6px 8px; min-height: 32px; }
  .var-card-menu-btn { width: 32px; height: 32px; font-size: 16px; }
  .template-var-label { font-size: 12px; }
  .template-var-input { padding: 10px 14px; font-size: 14px; }
  .var-cat-chip, .var-act, .var-type-chip { min-height: 32px; }
  .tpl-table-bar button { min-height: 36px; }
  .var-window-item { min-height: 40px; }
  .var-window-cat-head { min-height: 38px; }
  .var-window-acts { opacity: 1; }
  .var-window-acts button { min-height: 32px; min-width: 32px; }
  .report-type { font-size: 14px; min-height: 40px; }
  .report-type input { width: 20px; height: 20px; }
  .report-actions .ghost-btn { flex: 1; min-height: 44px; }
  .template-actions-row { flex-direction: column; }
  .template-actions-row button { width: 100%; padding: 12px; min-height: 44px; }
  
  /* Insights */
  .insight-meta-grid { grid-template-columns: 1fr; gap: 14px; }
  .insight-body-editor { padding: 14px 16px; min-height: 200px; font-size: 15px; }
  
  /* FAB */
  .fab {
    bottom: max(20px, env(safe-area-inset-bottom));
    right: 20px;
    width: 56px;
    height: 56px;
  }
  
  /* Modals become bottom sheets on mobile */
  .modal-backdrop {
    align-items: flex-end;
    padding: 0;
  }
  .modal {
    max-width: 100%;
    max-height: 92vh;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    animation: sheetUp 0.22s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .modal-search {
    border-radius: 0;
    max-height: 100vh;
    height: 100vh;
  }
  /* Metadata sheet specifically: a fixed-height bottom sheet (not
     content-hugging like other modals) so it reads as a proper pane.
     Body gets a tinted "sunk" background so the white section cards
     actually read as distinct cards rather than blending into it. */
  #mobileRailModal .modal {
    height: 75vh;
    max-height: 80vh;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  }
  #mobileRailModal .modal-body { background: var(--bg-sunk); }
  .modal-header { padding: 16px 20px; }
  .modal-title { font-size: 16px; }
  .modal-body { padding: 20px; padding-bottom: max(20px, env(safe-area-inset-bottom)); }
  .modal-actions { flex-direction: column-reverse; gap: 8px; }
  .modal-actions .primary-btn,
  .modal-actions .ghost-btn,
  .modal-actions .danger-btn { width: 100%; padding: 12px; min-height: 44px; }
  
  /* Search modal stays full-screen on mobile */
  .search-input-wrap { padding: 14px 16px; }
  .search-input-wrap input { font-size: 16px; }
  
  /* Export options */
  .export-option { padding: 14px 16px; min-height: 64px; }
  
  /* Import mode options */
  .import-mode-option { padding: 14px 16px; min-height: 64px; }
  
  /* Folder picker */
  .folder-picker-list { max-height: 50vh; }
  .folder-picker-item { padding: 12px 12px; font-size: 14px; min-height: 44px; }
  
  /* Linker */
  .linker-item { padding: 14px 14px; min-height: 48px; }
  
  /* Tag pills bigger */
  .tag-pill { padding: 6px 12px; font-size: 12px; }
  .tag-add-input { font-size: 14px; padding: 6px 10px; width: 120px; }
  
  /* Run mode */
  .run-mode-header { padding: 14px 18px; }
  .run-mode-progress { padding: 8px 18px; }
  .run-mode-body { padding: 20px 14px; }
  .run-step-header { padding: 14px 16px; min-height: 56px; }
  .run-step-check { width: 28px; height: 28px; }
  .run-substep-row { padding: 8px 8px 8px 14px; min-height: 40px; }
  .run-substep-check { width: 22px; height: 22px; }
  .run-substep-title { font-size: 14px; }

  /* Context menu hidden on mobile — action sheet is used instead */
  .context-menu { display: none !important; }

  /* ---------------------------------------------------------
     FORCED DESKTOP MODE — when the user opts into the desktop
     layout on a narrow screen, undo the mobile overrides above
     and restore the multi-pane grid. Source order places these
     last inside the ≤768px query so they win.
     --------------------------------------------------------- */
  body[data-layout-mode="desktop"] { --topbar-h: 60px; }
  body[data-layout-mode="desktop"] html,
  html:has(body[data-layout-mode="desktop"]),
  body[data-layout-mode="desktop"] { height: 100%; overflow: hidden; }
  body[data-layout-mode="desktop"] .desktop-only { display: flex !important; }
  body[data-layout-mode="desktop"] .brand-name { display: block; }
  body[data-layout-mode="desktop"] .mobile-back-btn { display: none !important; }
  /* .fab is display:none by default now (see base rule) and only shown back
     in by the mobile breakpoint / forced-mobile / .perf-fab rules — no
     desktop-specific override needed here any more. */

  body[data-layout-mode="desktop"] .layout {
    height: calc(100vh - var(--topbar-h) - var(--folder-taskbar-h));
    height: calc(100dvh - var(--topbar-h) - var(--folder-taskbar-h));
  }
  body[data-layout-mode="desktop"] .detail-pane {
    position: relative;
    inset: auto;
    transform: none !important;
    transition: none;
    z-index: auto;
  }

  /* Modals stay centred dialogs (not bottom sheets) in forced desktop */
  body[data-layout-mode="desktop"] .modal-backdrop { align-items: center; padding: 24px; }
  body[data-layout-mode="desktop"] .modal {
    max-width: 560px;
    border-radius: var(--radius-xl);
    max-height: 85vh;
    animation: none;
  }
  body[data-layout-mode="desktop"] .modal-wide { max-width: 760px; }
  body[data-layout-mode="desktop"] .modal-small { max-width: 440px; }

  /* Restore desktop context menu / hide action-sheet reliance */
  body[data-layout-mode="desktop"] .context-menu { display: block; }

  /* Steps grid → stacked rows: row 1 is the #+title (full width, forcing
     a wrap via flex-basis:100%), row 2 is status+actions. Same merged
     #+Title cell as desktop/tablet (see the comment above .steps-table),
     just re-flowed onto two lines since there's no room for four columns
     side by side. td:empty stays hidden so a parent row with sub-steps
     (no status pill) doesn't leave a blank second line. */
  /* Search + filter share a baseline (row 1); sort-seg + the compact
     "+ Add step" pill share the next one (row 2) — flex-wrap does the
     2-per-row split on its own since each pair's combined min-width
     comfortably fits while any 3 together wouldn't. Every control here
     is a dense ~34px, not the ~40-44px "big touch target" sizing used
     for the row-level icon buttons/pills elsewhere — these are compact
     filters, not primary actions. */
  /* Single-row, no-wrap toolbar: search pill flexes/shrinks to absorb
     whatever room the fixed-width filter pill + sort boxes don't need, so
     all of it fits one line without scrolling. Adding steps/sub-steps
     lives in .steps-add-row under the grid, not up here. */
  /* The column-legend header row ("Title" + dot + ⋮) is desktop furniture:
     on a phone it costs a full row before the first step and aligns
     nothing a finger needs. */
  body:not([data-layout-mode="desktop"]) .steps-table thead { display: none; }
  /* The card's own "STEPS / N step(s)" header row goes too (user-annotated)
     — the outline speaks for itself on a phone. */
  body:not([data-layout-mode="desktop"]) .steps-grid-card .detail-section-title { display: none; }
  /* The wrap's overflow-x:auto clipped the ⋮'s right-leaning hit halo to
     its own box — the mobile grid never scrolls horizontally, so let the
     halo actually extend. Border/radius dropped too (user-annotated): the
     inner outlined box inside the shaded card read as double chrome on a
     phone — the shaded card alone is the container. */
  body:not([data-layout-mode="desktop"]) .steps-table-wrap { overflow-x: visible; border: none; border-radius: 0; }
  body:not([data-layout-mode="desktop"]) .steps-toolbar { flex-wrap: nowrap; gap: 6px; }
  body:not([data-layout-mode="desktop"]) .steps-search-wrap { flex: 1 1 auto; min-width: 0; }
  body:not([data-layout-mode="desktop"]) .steps-search-input {
    min-height: 34px; padding: 6px 10px 6px 28px; border-radius: 999px;
  }
  body:not([data-layout-mode="desktop"]) .steps-search-icon { left: 9px; }
  body:not([data-layout-mode="desktop"]) .steps-search-icon svg { width: 12px; height: 12px; }
  /* Icon-only compact box — label text (.pill-label-full) is already
     hidden on mobile via the shared rule every other pill uses. */
  body:not([data-layout-mode="desktop"]) .steps-status-btn {
    flex: 0 0 auto; width: 34px; height: 34px; padding: 0;
    justify-content: center; border-radius: 999px;
  }
  body:not([data-layout-mode="desktop"]) .steps-sort-seg { flex: 0 0 auto; }
  /* The 3-segment sort control becomes one sheet-opening pill on a phone
     (see #stepsSortBtn) — it was taking 132px of a 341px toolbar and
     squeezing the search field to 64px. */
  body:not([data-layout-mode="desktop"]) .steps-sort-seg { display: none; }
  body:not([data-layout-mode="desktop"]) .steps-sort-pill { display: inline-flex; }
  body:not([data-layout-mode="desktop"]) .steps-sort-label { font-size: 11px; font-weight: 700; }
  body:not([data-layout-mode="desktop"]) .steps-sort-seg button {
    flex: 0 0 auto; min-height: 34px; padding: 0 7px; font-size: 11px;
  }
  /* The grid itself no longer restructures on mobile — it stays the same
     single-line # | Title | Status | Actions row as desktop/tablet, just
     smaller (see the --st-* tokens on .steps-grid-card and the ≤900/480/360
     breakpoints above .steps-toolbar). */
  body:not([data-layout-mode="desktop"]) .steps-title-input { min-height: 24px; }

  /* Titles WRAP on mobile (user-requested revision of the earlier fixed
     single-line + "…" tap-to-expand card): rows keep a floor height but
     grow to fit the wrapped title, and the "…" cue is gone — the whole
     text is simply visible. autosizeStepTitle() (app.js) grows the
     textarea to its scrollHeight, so no max-height may clamp it here. */
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row-parent,
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row-child {
    min-height: calc(var(--st-row-h-parent) * var(--density-scale));
  }
  body:not([data-layout-mode="desktop"]) .stcol-title { position: relative; }
  body:not([data-layout-mode="desktop"]) .steps-more-btn { display: none; }
  /* Indent geometry fix (verified against a live phone screenshot where
     sub-step numbers sat LEFT of their parent's): every row reserves one
     26px twist slot in its padding, and a row that actually has a twist
     pulls it back into that slot with a negative margin. Numbers then
     line up purely by depth — a parent's inline twist can no longer push
     its own number right of its children's. */
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row-parent .stcol-title {
    padding-left: calc(var(--st-row-pad) * var(--density-scale));
  }
  /* Per-depth offsets instead of a flat per-level increment: the number
     string widens every level ("1 -" ≈ 30px, "1.1 -" ≈ 46, "1.1.1 -" ≈ 61
     with its trailing margin), and the alignment rule is "a child's number
     starts under its parent's TITLE" — so each level's indent must grow by
     the PARENT's number width, not by a constant. Measured, then pinned.
     Depth 4+ decays to +30/level so a pathological tree can't eat the row. */
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row-child .stcol-title {
    padding-left: calc((var(--st-row-pad) + var(--st-indent)) * var(--density-scale));
  }
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row-child[data-depth="2"] .stcol-title {
    padding-left: calc((var(--st-row-pad) + 65px) * var(--density-scale));
  }
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row-child[data-depth="3"] .stcol-title {
    padding-left: calc((var(--st-row-pad) + 126px) * var(--density-scale));
  }
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row-child[data-depth="4"] .stcol-title {
    padding-left: calc((var(--st-row-pad) + 156px) * var(--density-scale));
  }
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row-child[data-depth="5"] .stcol-title {
    padding-left: calc((var(--st-row-pad) + 186px) * var(--density-scale));
  }
}

/* =========================================================
   TABLET (below the 1280px right-rail threshold, above the
   mobile single-screen threshold) — the existing 3-pane grid
   already renders correctly here, so the only gap is the
   metadata rail: with no right-column grid AND no mobile sheet
   trigger, it fell into plain stacked flow, fully visible,
   below the step content. Give it a slide-in side sheet instead,
   triggered by the same #mobileMetaBtn used on mobile, reusing
   the same "move the rail into #mobileRailModalBody" JS.
   Scoped to match the inverse of isMobile()'s own touch-ceiling
   logic (768/950 split by pointer type) so this range never
   overlaps with the mobile media query below and double-triggers
   both a bottom sheet AND a side sheet at the same width. */
@media (min-width: 769px) and (max-width: 1279px) and (pointer: fine),
       (min-width: 951px) and (max-width: 1279px) and (any-pointer: coarse) {
  /* Pre-existing gap found while testing this range: the topbar has no
     responsive handling between the mobile query (≤768px) and where it
     naturally has enough room (~1024px+), so brand+tabs+icons overflowed
     the viewport with no scrollbar. Same shrink-and-scroll technique the
     mobile topbar already uses. */
  .topbar { padding: 0 16px; gap: 12px; }
  .topbar-left { gap: 16px; flex: 1; min-width: 0; overflow: hidden; }
  .tabs { min-width: 0; overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none; }
  .tabs::-webkit-scrollbar { display: none; }
  .brand-name { display: none; }

  #procRail, #clRail, #tplRail { display: none; }
  #mobileRailModalBody #procRail,
  #mobileRailModalBody #clRail,
  #mobileRailModalBody #tplRail { display: flex; }

  .mobile-meta-fab {
    display: flex;
    position: fixed;
    top: 16px;
    right: 16px;
    z-index: 12;
    border-radius: 50%;
    box-shadow: var(--shadow-sm);
  }

  #mobileRailModal {
    align-items: stretch;
    justify-content: flex-end;
    padding: 0;
  }
  #mobileRailModal .modal-rail-sheet {
    width: 45%;
    min-width: 340px;
    max-width: 480px;
    max-height: 100vh;
    height: 100vh;
    border-radius: 0;
    animation: railSideSheetIn 200ms cubic-bezier(0.16, 1, 0.3, 1);
  }

  /* Steps grid sizing at this tier now comes entirely from the --st-*
     tokens on .steps-grid-card (see the @media (max-width: 900px) block
     above), which this range also falls within. */
}
@keyframes railSideSheetIn {
  from { transform: translateX(100%); }
  to { transform: translateX(0); }
}

/* =========================================================
   VERY NARROW (≤480px) — extra polish for phones
   ========================================================= */
@media (max-width: 480px) {
  .tab { padding: 7px 10px; font-size: 12.5px; }
  /* Topbar icon buttons deliberately stay 40px here — shrinking touch
     targets as the screen gets smaller is backwards from normal scaling.
     What was actually oversized was the chrome AROUND them: full-width
     padding/gaps sized for a mouse cursor, and 18px glyphs that made
     already-big buttons look even heavier. Tighten those — the tap
     target box is untouched, only its padding and the icon inside it
     shrink — plus the folder pills and action-bar pills next to it,
     which had no phone-specific sizing at all before this. */
  .topbar { padding: 0 12px; gap: 8px; }
  .topbar-left { gap: 12px; }
  .topbar-right { gap: 8px; }
  .topbar-right .iconbtn svg { width: 16px; height: 16px; }
  .folder-taskbar { padding: 0 10px; gap: 6px; }
  .breadcrumb-segment { padding: 5px 7px; font-size: 12px; gap: 5px; }
  .breadcrumb-segment-icon svg { width: 13px; height: 13px; }
  /* Was 110px — with the taskbar's actions collapsed to one overflow
     button (see the ≤768px block above), there's enough width back to
     let names run longer before ellipsis kicks in, even on a small phone. */
  .breadcrumb-segment-label { max-width: 135px; }
  .detail-content { padding: 18px 14px 100px; }
  .detail-title-input { font-size: 21px; }
  /* Same reasoning as the ≤768px block: a single-line title + "⋯" doesn't
     need 12px of vertical air on either side. */
  .list-item { padding: 8px 12px; }

  /* Checklist items — tighter padding, shadow-only, slightly smaller text */
  .checklist-item { padding: 6px 10px; border-color: transparent; }
  .checklist-item-text { font-size: 14px; }

  /* Action bar — a single non-wrapping scrollable row, short labels,
     40x40 minimum tap target (taking priority over the smaller cosmetic
     pill height used on wider screens) — but the horizontal padding and
     row gap around that fixed height are fair game, so the row doesn't
     eat more width than the tap target itself needs. */
  .detail-action-bar {
    flex-wrap: nowrap;
    overflow-x: auto;
    white-space: nowrap;
    scrollbar-width: none;
    -ms-overflow-style: none;
    /* 7 pills at a fixed 44px each need every spare pixel at the
       narrower end of "common" widths — measured at 360px specifically
       (7*44 + 6*gap must fit ~332px of real row width there), so 4px is
       the minimum gap, not a guess. justify-content: space-between grows
       it evenly on wider screens where there's slack, so spacing stays
       visually equal everywhere rather than just at the tightest width. */
    gap: 6px;
    /* 3px of vertical padding so the pills’ ±3px hit halos live INSIDE
       this bar. It scrolls horizontally, and a scroll container clips on
       both axes — the halos were being cut off, leaving 38px targets
       where the code claimed 44 (measured). */
    padding: 4px 0;
    margin-bottom: 7px;
    justify-content: space-between;
  }
  .detail-action-bar::-webkit-scrollbar { display: none; }
  /* 38px visual, ≥44px hit via halo (halos meet exactly at the 6px gap):
     a row of seven full 44px circles was the heaviest block in the header. */
  .action-pill { height: 38px; padding: 0 9px; gap: 5px; font-size: 12.5px; position: relative; }
  .action-pill::after { content: ''; position: absolute; inset: -4px; border-radius: 24px; }
  .action-pill svg { width: 15px; height: 15px; }
  .pill-label-full { display: none; }
  .pill-label-short { display: inline; }
  /* All 7 pills (category + Run/Dup/Imp/MD/Upd MD/Del) are uniform 44x44
     squares here — same footprint, same gap between every one of them
     (the row's own `gap`, nothing pill-specific layered on top). Earlier
     rounds kept Run labeled and gave Delete extra breathing room; both
     were deliberately undone for this pass, since "no pill visually
     larger than another" and "equal spacing across the entire row" both
     rule those out directly. Every icon here already reads fine alone
     (▶ Run, folder-color-tinted folder icon, trash for Delete, etc.) so
     nothing is lost dropping the labels — aria-label/title still carry
     the name for accessibility. Fixed width (not just icon-only padding)
     is what actually guarantees the uniform footprint regardless of each
     icon's own intrinsic size. */
  #procRunBtn, #procFolderBtn, #procDupBtn, #procImportBtn, #procExportMdBtn, #procUpdateMdBtn, #procDeleteBtn {
    width: 44px;
    padding: 0;
    justify-content: center;
    gap: 0;
  }
  #procRunBtn .pill-label-short,
  #procDupBtn .pill-label-short, #procImportBtn .pill-label-short,
  #procExportMdBtn .pill-label-short, #procUpdateMdBtn .pill-label-short,
  #procDeleteBtn .pill-label-short { display: none; }
}

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 5px; }
::-webkit-scrollbar-thumb:hover { background: var(--ink-faint); }


/* =========================================================
   FORCED MOBILE MODE — same rules as the ≤768px block above,
   but triggered by body[data-layout-mode="mobile"] at ANY width.
   Lets the user opt into the single-screen mobile layout on a
   desktop via Settings → Display → Layout mode.
   KEEP THIS IN SYNC WITH THE ≤768px BLOCK ABOVE — see the note there.
   ========================================================= */
body[data-layout-mode="mobile"] {
    --topbar-h: 48px;
    --folder-taskbar-h: 44px;
    height: auto;
    min-height: 100%;
    overflow-x: hidden;
    overflow-y: auto;
  }

  
  /* Hide desktop-only elements */
body[data-layout-mode="mobile"] .desktop-only { display: none !important; }


  /* Topbar: tighter, scrollable tabs */
body[data-layout-mode="mobile"] .topbar {
    padding: 0 12px;
    gap: 8px;
    position: sticky;
    top: 0;
  }
body[data-layout-mode="mobile"] .topbar-left {
    gap: 12px;
    flex: 1;
    min-width: 0;
    overflow: hidden;
  }
body[data-layout-mode="mobile"] .brand-name { display: none; }
body[data-layout-mode="mobile"] .brand-mark { width: 32px; height: 32px; }
body[data-layout-mode="mobile"] .tabs {
    flex: 1;
    min-width: 0;
    overflow-x: auto;
    overflow-y: hidden;
    gap: 0;
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
body[data-layout-mode="mobile"] .tabs::-webkit-scrollbar { display: none; }
body[data-layout-mode="mobile"] .tab {
    padding: 8px 12px;
    font-size: 13px;
    flex-shrink: 0;
    min-height: 36px;
  }

  
  /* Hide secondary topbar icons that have alternatives in the action sheet */
body[data-layout-mode="mobile"] .topbar-right .iconbtn { width: 38px; height: 38px; position: relative; }
body[data-layout-mode="mobile"] .topbar-right .iconbtn::after { content: ''; position: absolute; inset: -4px; }

  /* The folder task bar + dropdown are the same component at every
     breakpoint (see the base rules) — only the detail pane needs
     forced-mobile layout handling here. */
body[data-layout-mode="mobile"] .layout {
    display: block;
    height: auto;
    min-height: calc(100dvh - var(--topbar-h) - var(--folder-taskbar-h));
    position: static;
  }
body[data-layout-mode="mobile"] .detail-pane {
    position: static;
    width: 100%;
    background: var(--bg-elev);
    overflow: visible;
    height: auto;
    opacity: 1 !important;
    pointer-events: auto !important;
  }
/* Mirrors the .screen-title block in the max-width block above. */
body[data-layout-mode="mobile"] .screen-title {
  display: block;
  font-family: var(--font-display);
  font-size: 31px;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: 0;
  padding: 4px 0 12px;
  text-align: left;
  align-self: stretch;
  text-wrap: balance;
}
body[data-layout-mode="mobile"] .folder-taskbar { padding: 0 12px; gap: 8px; }
/* Mirrors the ≤768px block above — see the comment there. */
body[data-layout-mode="mobile"] #sortBtn,
body[data-layout-mode="mobile"] #newFolderBtn,
body[data-layout-mode="mobile"] #trashBtn,
body[data-layout-mode="mobile"] #folderTreeBtn { display: none; }
body[data-layout-mode="mobile"] .folder-taskbar-more-btn { display: flex; }
body[data-layout-mode="mobile"] .save-indicator {
    max-width: 0;
    padding: 0;
    margin-left: 0;
    border-width: 0;
    overflow: hidden;
    transition: max-width 0.25s var(--ease-ios, ease), opacity 0.25s, padding 0.25s;
  }
body[data-layout-mode="mobile"] .save-indicator.show { max-width: 70px; padding: 4px 10px; margin-left: 2px; }
body[data-layout-mode="mobile"] .breadcrumb-segment-label { max-width: 150px; }
body[data-layout-mode="mobile"] .folder-row {
    padding: 12px 12px;
    font-size: 15px;
    min-height: 48px;
    gap: 10px;
  }
body[data-layout-mode="mobile"] .ghost-btn { padding: 10px 14px; font-size: 14px; min-height: 44px; }

body[data-layout-mode="mobile"] .mobile-back-btn {
    display: flex;
    width: 44px;
    height: 44px;
    background: var(--bg-sunk);
    color: var(--accent-text);
    flex-shrink: 0;
  }
body[data-layout-mode="mobile"] .mobile-back-btn:active { background: var(--accent-bg); }

body[data-layout-mode="mobile"] .mobile-meta-fab {
    position: fixed;
    top: auto;
    bottom: calc(78px + env(safe-area-inset-bottom));
    right: 86px;
    left: auto;
    width: 44px;
    height: 44px;
    background: var(--bg-elev);
    border: 1.5px solid var(--border);
    color: var(--accent-text);
    z-index: 40;
    border-radius: 50%;
    box-shadow: var(--shadow-md);
    transition: transform 0.15s var(--ease-ios, cubic-bezier(0.16,1,0.3,1)), background-color 0.1s;
  }
body[data-layout-mode="mobile"] .mobile-meta-fab:active { transform: scale(0.97); background: var(--bg-sunk); }
body[data-layout-mode="mobile"] #procRail,
body[data-layout-mode="mobile"] #clRail,
body[data-layout-mode="mobile"] #tplRail { display: none; }
body[data-layout-mode="mobile"] #mobileRailModalBody #procRail,
body[data-layout-mode="mobile"] #mobileRailModalBody #clRail,
body[data-layout-mode="mobile"] #mobileRailModalBody #tplRail { display: flex; }
body[data-layout-mode="mobile"] .select { padding: 8px 12px; font-size: 13px; min-height: 38px; }
body[data-layout-mode="mobile"] .primary-btn { padding: 8px 14px; font-size: 13px; min-height: 38px; }
/* Mirrors the .list-item padding note in the max-width block above. */
  /* ── The map on a phone — forced-mobile mirror of the block above. */
  body[data-layout-mode="mobile"] .browse-count { font-size: 13px; min-width: 27px; }
  body[data-layout-mode="mobile"] .browse-view .browse-group > .folder-row {
    font-size: 15.5px;
    padding-top: 5px;
    padding-bottom: 5px;
    min-height: 44px;
  }
  body[data-layout-mode="mobile"] .browse-view .folder-row {
    padding-left: min(calc(6px + var(--nest-depth, 0) * 16px), 40%);
  }
  body[data-layout-mode="mobile"] .browse-view .list-item-nested {
    min-height: 44px;
    margin: 0;
    padding: 2px 12px;
    padding-left: min(calc(21px + var(--nest-depth, 1) * 16px), 45%);
  }
  body[data-layout-mode="mobile"] .browse-view .list-item-nested .list-item-title { font-size: 14.5px; }
  body[data-layout-mode="mobile"] .mp-meta { font-size: 12.5px; }
  /* Trash rows: the buttons ARE the tap targets, so they keep the touch
     floor on a phone regardless of the desktop slimming above. */
  body[data-layout-mode="mobile"] .trash-item .ghost-btn, body[data-layout-mode="mobile"] .trash-item .danger-btn {
    min-height: 44px;
    padding: 10px 12px;
    font-size: 13px;
  }
body[data-layout-mode="mobile"] .list-item { padding: 8px 12px; margin-bottom: 2px; min-height: 44px; }
  /* Search rows are full-width tap targets — touch floor on a phone. */
  body[data-layout-mode="mobile"] .search-result { min-height: 44px; padding: 8px 8px; }
body[data-layout-mode="mobile"] .list-item-title { font-size: 15px; }
body[data-layout-mode="mobile"] .list-item-more { opacity: 1; width: 36px; height: 36px; font-size: 20px; }
/* Mirrors the "⋯ was setting every row's height" note in the max-width
   block above — 44px of reach for the button, 44px of height for the row. */
body[data-layout-mode="mobile"] .list-item-nested .list-item-more {
  width: 32px; height: 32px; font-size: 18px;
  min-width: 0; min-height: 0; position: relative;
}
body[data-layout-mode="mobile"] .list-item-nested .list-item-more::before {
  content: ''; position: absolute; inset: -9px;
}

  /* Detail pane — in-flow (no sticky back bar any more) */
body[data-layout-mode="mobile"] .detail-content {
    padding: 12px 14px 96px;
    max-width: 100%;
  }
body[data-layout-mode="mobile"] .detail-title-input {
    font-size: 24px;
    letter-spacing: -0.02em;
    margin-bottom: 4px;
  }
body[data-layout-mode="mobile"] .detail-description { font-size: 14px; margin-bottom: 0; min-height: 0; }
body[data-layout-mode="mobile"] .section-card { padding: 12px; margin-bottom: 12px; }
body[data-layout-mode="mobile"] .section-card.steps-grid-card { padding-left: 5px; padding-right: 5px; }
body[data-layout-mode="mobile"] .detail-meta-row {
    flex-wrap: wrap;
    font-size: 11.5px;
    gap: 6px;
  }
body[data-layout-mode="mobile"] .detail-meta-row .ghost-btn {
    padding: 6px 10px;
    font-size: 12px;
    min-height: 36px;
    position: relative;
  }
body[data-layout-mode="mobile"] .detail-meta-row .ghost-btn::after { content: ''; position: absolute; inset: -4px; }
body[data-layout-mode="mobile"] .detail-section-title {
    margin: 14px 0 8px;
    font-size: 11px;
  }

body[data-layout-mode="mobile"] .iconbtn.small { width: 44px; height: 44px; }


  /* Rich editor */
body[data-layout-mode="mobile"] .rich-editor { padding: 12px 14px; font-size: 15px; min-height: 80px; }
/* M-2: single horizontally-scrollable toolbar strip — forced-mobile mirror. */
body[data-layout-mode="mobile"] .rich-toolbar {
  padding: 6px;
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  touch-action: pan-x;
  overscroll-behavior-x: contain;
}
body[data-layout-mode="mobile"] .rich-toolbar::-webkit-scrollbar { display: none; }
body[data-layout-mode="mobile"] .rich-toolbar > * { flex: 0 0 auto; }
body[data-layout-mode="mobile"] .rich-toolbar button { padding: 8px 12px; font-size: 13px; min-height: 44px; }
/* Edge-fade scroll affordance + mouse-only hint — forced-mobile mirror. */
body[data-layout-mode="mobile"] .rich-toolbar.can-scroll-right {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
}
body[data-layout-mode="mobile"] .rich-toolbar.can-scroll-left {
  -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 28px), transparent);
  mask-image: linear-gradient(to left, #000 calc(100% - 28px), transparent);
}
body[data-layout-mode="mobile"] .rich-toolbar.can-scroll-left.can-scroll-right {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 28px, #000 calc(100% - 28px), transparent);
  mask-image: linear-gradient(to right, transparent, #000 28px, #000 calc(100% - 28px), transparent);
}
body[data-layout-mode="mobile"] .ins-link-hint { display: none; }

  
  /* Notes */
body[data-layout-mode="mobile"] .notes-section { padding: 14px 14px; }
body[data-layout-mode="mobile"] .note-add { flex-wrap: wrap; }
body[data-layout-mode="mobile"] .note-add textarea { font-size: 14px; min-height: 44px; }
body[data-layout-mode="mobile"] .note-add-btn { padding: 10px 14px; min-height: 40px; font-size: 13px; }
body[data-layout-mode="mobile"] .note-flag-select { min-width: 56px; min-height: 40px; }

  
  /* Linked cards */
body[data-layout-mode="mobile"] .linked-card-header { padding: 12px 14px; min-height: 48px; }

  
  /* Checklist — actions (note/delete) are hover-only on desktop, but
     touch has no hover, so they stay visible by default here. The
     checkbox stays a visually-quiet 22px but gets an invisible ::before
     to bring its real tap target up to 40x40. */
body[data-layout-mode="mobile"] .checklist-item { padding: 6px 10px; gap: 10px; min-height: 44px; box-sizing: border-box; margin-left: min(calc(var(--cl-d, 0) * 14px), 84px); }
body[data-layout-mode="mobile"] .checklist-items.cl-has-groups .checklist-item-leaf .cl-num { margin-left: 32px; }
body[data-layout-mode="mobile"] .checklist-item .checklist-item-more-actions {
  width: 32px; height: 32px; min-width: 0; min-height: 0; position: relative;
}
body[data-layout-mode="mobile"] .checklist-item .checklist-item-more-actions::before { content: ''; position: absolute; inset: -6px; }
body[data-layout-mode="mobile"] .batch-bar { bottom: calc(74px + env(safe-area-inset-bottom)); }
body[data-layout-mode="mobile"] .checklist-item-checkbox { width: 22px; height: 22px; }
body[data-layout-mode="mobile"] .checklist-item-checkbox::before { content: ''; position: absolute; inset: -9px; }
body[data-layout-mode="mobile"] .checklist-item-text { font-size: 15px; min-height: 22px; }
body[data-layout-mode="mobile"] .checklist-item-actions { opacity: 1; }
body[data-layout-mode="mobile"] .cl-twist::before { content: ''; position: absolute; inset: -9px; }
body[data-layout-mode="mobile"] .cl-num { font-size: 12.5px; }
body[data-layout-mode="mobile"] .checklist-item .checklist-item-due-btn,
body[data-layout-mode="mobile"] .checklist-item .checklist-item-note-btn,
body[data-layout-mode="mobile"] .checklist-item .checklist-item-addsub-btn,
body[data-layout-mode="mobile"] .checklist-item .checklist-item-delete-btn { display: none; }
body[data-layout-mode="mobile"] .checklist-item-more-actions { display: flex; }

  /* Templates */
body[data-layout-mode="mobile"] .template-subject-input { font-size: 15px; padding: 12px 14px; }
body[data-layout-mode="mobile"] .subject-mirror { font-size: 15px; padding: 12px 14px; }
body[data-layout-mode="mobile"] .template-vars-row { padding: 12px 14px 14px; }
body[data-layout-mode="mobile"] .var-act { font-size: 13px; padding: 4px 6px; }
body[data-layout-mode="mobile"] .var-cat-chip { font-size: 11px; padding: 4px 9px; }
body[data-layout-mode="mobile"] .var-type-chip { font-size: 11px; padding: 4px 8px; }
body[data-layout-mode="mobile"] .template-vars-row .var-move-btns { gap: 5px; }
body[data-layout-mode="mobile"] .template-vars-row .var-move { font-size: 11px; padding: 6px 8px; min-height: 32px; }
body[data-layout-mode="mobile"] .var-card-menu-btn { width: 32px; height: 32px; font-size: 16px; }
body[data-layout-mode="mobile"] .template-var-label { font-size: 12px; }
body[data-layout-mode="mobile"] .template-var-input { padding: 10px 14px; font-size: 14px; }
body[data-layout-mode="mobile"] .var-cat-chip,
body[data-layout-mode="mobile"] .var-act, body[data-layout-mode="mobile"] .var-type-chip { min-height: 32px; }
/* Radius has to scale with the control. --radius-sm (10px) is sized for
   the 26-32px chrome these controls are at desktop widths, but mobile
   inflates every one of them to the 44px touch floor without touching
   their corners — leaving rows of 44px controls reading noticeably
   squarer than the 16px rows and buttons around them. Measured across
   Checklists, Templates, Insights, the Steps grid and the Gantt. */
body[data-layout-mode="mobile"] .iconbtn.small,
body[data-layout-mode="mobile"] .list-item-more,
body[data-layout-mode="mobile"] .toolbar-block,
body[data-layout-mode="mobile"] .toolbar-font,
body[data-layout-mode="mobile"] .toolbar-fontsize,
body[data-layout-mode="mobile"] .rich-toolbar button,
body[data-layout-mode="mobile"] .gt-btn,
body[data-layout-mode="mobile"] .steps-row-menu-btn { border-radius: var(--radius-md); }
body[data-layout-mode="mobile"] .tpl-table-bar button { min-height: 36px; }
body[data-layout-mode="mobile"] .var-window-item { min-height: 40px; }
body[data-layout-mode="mobile"] .var-window-cat-head { min-height: 38px; }
body[data-layout-mode="mobile"] .var-window-acts { opacity: 1; }
body[data-layout-mode="mobile"] .var-window-acts button { min-height: 32px; min-width: 32px; }
body[data-layout-mode="mobile"] .report-type { font-size: 14px; min-height: 40px; }
body[data-layout-mode="mobile"] .report-type input { width: 20px; height: 20px; }
body[data-layout-mode="mobile"] .report-actions .ghost-btn { flex: 1; min-height: 44px; }
body[data-layout-mode="mobile"] .template-actions-row { flex-direction: column; }
body[data-layout-mode="mobile"] .template-actions-row button { width: 100%; padding: 12px; min-height: 44px; }

  
  /* Insights */
body[data-layout-mode="mobile"] .insight-meta-grid { grid-template-columns: 1fr; gap: 14px; }
body[data-layout-mode="mobile"] .insight-body-editor { padding: 14px 16px; min-height: 200px; font-size: 15px; }

  
  /* FAB */
body[data-layout-mode="mobile"] .fab {
    bottom: max(20px, env(safe-area-inset-bottom));
    right: 20px;
    width: 56px;
    height: 56px;
  }

  
  /* Modals become bottom sheets on mobile */
body[data-layout-mode="mobile"] .modal-backdrop {
    align-items: flex-end;
    padding: 0;
  }
body[data-layout-mode="mobile"] .modal {
    max-width: 100%;
    max-height: 92vh;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    animation: sheetUp 0.22s cubic-bezier(0.16, 1, 0.3, 1);
  }
body[data-layout-mode="mobile"] .modal-search {
    border-radius: 0;
    max-height: 100vh;
    height: 100vh;
  }
body[data-layout-mode="mobile"] #mobileRailModal .modal {
    height: 75vh;
    max-height: 80vh;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  }
body[data-layout-mode="mobile"] #mobileRailModal .modal-body { background: var(--bg-sunk); }
body[data-layout-mode="mobile"] .modal-header { padding: 16px 20px; }
body[data-layout-mode="mobile"] .modal-title { font-size: 16px; }
body[data-layout-mode="mobile"] .modal-body { padding: 20px; padding-bottom: max(20px, env(safe-area-inset-bottom)); }
body[data-layout-mode="mobile"] .modal-actions { flex-direction: column-reverse; gap: 8px; }
body[data-layout-mode="mobile"] .modal-actions .primary-btn,
body[data-layout-mode="mobile"] .modal-actions .ghost-btn,
body[data-layout-mode="mobile"] .modal-actions .danger-btn { width: 100%; padding: 12px; min-height: 44px; }

  
  /* Search modal stays full-screen on mobile */
body[data-layout-mode="mobile"] .search-input-wrap { padding: 14px 16px; }
body[data-layout-mode="mobile"] .search-input-wrap input { font-size: 16px; }

  
  /* Export options */
body[data-layout-mode="mobile"] .export-option { padding: 14px 16px; min-height: 64px; }

  
  /* Import mode options */
body[data-layout-mode="mobile"] .import-mode-option { padding: 14px 16px; min-height: 64px; }

  
  /* Folder picker */
body[data-layout-mode="mobile"] .folder-picker-list { max-height: 50vh; }
body[data-layout-mode="mobile"] .folder-picker-item { padding: 12px 12px; font-size: 14px; min-height: 44px; }

  
  /* Linker */
body[data-layout-mode="mobile"] .linker-item { padding: 14px 14px; min-height: 48px; }

  
  /* Tag pills bigger */
body[data-layout-mode="mobile"] .tag-pill { padding: 6px 12px; font-size: 12px; }
body[data-layout-mode="mobile"] .tag-add-input { font-size: 14px; padding: 6px 10px; width: 120px; }

  
  /* Run mode */
body[data-layout-mode="mobile"] .run-mode-header { padding: 14px 18px; }
body[data-layout-mode="mobile"] .run-mode-progress { padding: 8px 18px; }
body[data-layout-mode="mobile"] .run-mode-body { padding: 20px 14px; }
body[data-layout-mode="mobile"] .run-step-header { padding: 14px 16px; min-height: 56px; }
body[data-layout-mode="mobile"] .run-step-check { width: 28px; height: 28px; }
body[data-layout-mode="mobile"] .run-substep-row { padding: 8px 8px 8px 14px; min-height: 40px; }
body[data-layout-mode="mobile"] .run-substep-check { width: 22px; height: 22px; }
body[data-layout-mode="mobile"] .run-substep-title { font-size: 14px; }

  
  /* Context menu hidden on mobile — action sheet is used instead */
body[data-layout-mode="mobile"] .context-menu { display: none !important; }

  /* Gantt toolbar: 11 labelled buttons don't fit a phone width, so the
     less-frequent ones move into a "more" overflow menu. */
body[data-layout-mode="mobile"] .gantt-toolbar .gt-secondary { display: none; }
/* Mirror of the media block: the forced-mobile copy was missing the
   scroller rules entirely, so this half of the convention had drifted. */
body[data-layout-mode="mobile"] .gantt-toolbar { overflow-x: auto; flex-wrap: nowrap; }
body[data-layout-mode="mobile"] .gantt-toolbar .gantt-summary { display: none; }
/* The overflow menu is where the Gantt's less-frequent actions live on a
   phone — and its own toggle was riding off the end of the scroller.
   Measured at 360px: the 44px button sat at x 348-392, so 12px of it was
   on screen and elementFromPoint at its centre returned nothing. It was
   reachable only by first scrolling the toolbar sideways, with nothing
   saying so. Sticky pins it to the visible right edge while the rest of
   the toolbar scrolls underneath — the same split the folder taskbar
   already uses (scrolling pills, fixed actions). It keeps a positioning
   context, so the absolutely-positioned menu still anchors to it. */
body[data-layout-mode="mobile"] .gantt-toolbar .gt-more-wrap {
  display: inline-flex;
  position: sticky;
  right: 0;
  margin-left: auto;
  z-index: 5;
  background: var(--g-panel);
  padding-left: 6px;
}
body[data-layout-mode="mobile"] .gantt-toolbar .gt-btn { height: 36px; }

  /* Table-view row actions are hover-only and 18px on desktop — invisible
     and too small to tap on a phone. */
body[data-layout-mode="mobile"] .g-row-actions { opacity: 1; }
body[data-layout-mode="mobile"] .g-row-add, body[data-layout-mode="mobile"] .g-row-del { width: 28px; height: 28px; font-size: 15px; }

  /* Collapsed backup-banner dismiss shrinks to 22px — bump back up for touch. */
body[data-layout-mode="mobile"] .backup-banner.collapsed .iconbtn.small { width: 34px; height: 34px; }

  /* Steps grid stays the same single-line row as every other breakpoint;
     sizing comes from the --st-* tokens (body[data-layout-mode="mobile"]
     .steps-grid-card override, above). Toolbar: search+filter share a
     baseline, sort-seg+add-step share the next (mirrors the ≤480px
     block above). */
body[data-layout-mode="mobile"] .steps-table thead { display: none; }
body[data-layout-mode="mobile"] .steps-grid-card .detail-section-title { display: none; }
body[data-layout-mode="mobile"] .steps-table-wrap { overflow-x: visible; border: none; border-radius: 0; }
body[data-layout-mode="mobile"] .steps-toolbar { flex-wrap: nowrap; gap: 6px; }
body[data-layout-mode="mobile"] .steps-search-wrap { flex: 1 1 auto; min-width: 0; }
body[data-layout-mode="mobile"] .steps-search-input {
  min-height: 34px; padding: 6px 10px 6px 28px; border-radius: 999px;
}
body[data-layout-mode="mobile"] .steps-search-icon { left: 9px; }
body[data-layout-mode="mobile"] .steps-search-icon svg { width: 12px; height: 12px; }
body[data-layout-mode="mobile"] .steps-status-btn {
  flex: 0 0 auto; width: 34px; height: 34px; padding: 0;
  justify-content: center; border-radius: 999px;
}
body[data-layout-mode="mobile"] .steps-sort-seg { flex: 0 0 auto; }
body[data-layout-mode="mobile"] .steps-sort-seg { display: none; }
body[data-layout-mode="mobile"] .steps-sort-pill { display: inline-flex; }
body[data-layout-mode="mobile"] .steps-sort-label { font-size: 11px; font-weight: 700; }
body[data-layout-mode="mobile"] .steps-sort-seg button {
  flex: 0 0 auto; min-height: 34px; padding: 0 7px; font-size: 11px;
}
body[data-layout-mode="mobile"] .steps-title-input { min-height: 24px; }

/* Wrapping rows + twist-slot indent — mirrors the
   body:not([data-layout-mode="desktop"]) block above. If you touch one,
   touch the other. */
body[data-layout-mode="mobile"] .steps-table tr.steps-row-parent,
body[data-layout-mode="mobile"] .steps-table tr.steps-row-child {
  min-height: calc(var(--st-row-h-parent) * var(--density-scale));
}
body[data-layout-mode="mobile"] .stcol-title { position: relative; }
body[data-layout-mode="mobile"] .steps-more-btn { display: none; }
body[data-layout-mode="mobile"] .steps-table tr.steps-row-parent .stcol-title {
  padding-left: calc(var(--st-row-pad) * var(--density-scale));
}
body[data-layout-mode="mobile"] .steps-table tr.steps-row-child .stcol-title {
  padding-left: calc((var(--st-row-pad) + var(--st-indent)) * var(--density-scale));
}
body[data-layout-mode="mobile"] .steps-table tr.steps-row-child[data-depth="2"] .stcol-title {
  padding-left: calc((var(--st-row-pad) + 65px) * var(--density-scale));
}
body[data-layout-mode="mobile"] .steps-table tr.steps-row-child[data-depth="3"] .stcol-title {
  padding-left: calc((var(--st-row-pad) + 126px) * var(--density-scale));
}
body[data-layout-mode="mobile"] .steps-table tr.steps-row-child[data-depth="4"] .stcol-title {
  padding-left: calc((var(--st-row-pad) + 156px) * var(--density-scale));
}
body[data-layout-mode="mobile"] .steps-table tr.steps-row-child[data-depth="5"] .stcol-title {
  padding-left: calc((var(--st-row-pad) + 186px) * var(--density-scale));
}

/* Mirrors the "VERY NARROW (≤480px)" block above — kept in sync manually
   since forced-mobile mode isn't width-gated. If you touch the ≤480px
   block above, mirror the change here too. */
@media (max-width: 480px) {
  body[data-layout-mode="mobile"] .tab { padding: 7px 10px; font-size: 12.5px; }
  body[data-layout-mode="mobile"] .topbar { padding: 0 12px; gap: 8px; }
  body[data-layout-mode="mobile"] .topbar-left { gap: 12px; }
  body[data-layout-mode="mobile"] .topbar-right { gap: 8px; }
  body[data-layout-mode="mobile"] .topbar-right .iconbtn svg { width: 16px; height: 16px; }
  body[data-layout-mode="mobile"] .folder-taskbar { padding: 0 10px; gap: 6px; }
  body[data-layout-mode="mobile"] .breadcrumb-segment { padding: 5px 7px; font-size: 12px; gap: 5px; }
  body[data-layout-mode="mobile"] .breadcrumb-segment-icon svg { width: 13px; height: 13px; }
  body[data-layout-mode="mobile"] .breadcrumb-segment-label { max-width: 135px; }
  /* Mirrors the ≤480px block above — see the comment there. */
  body[data-layout-mode="mobile"] .detail-content { padding: 18px 14px 100px; }
  body[data-layout-mode="mobile"] .detail-title-input { font-size: 21px; }
  body[data-layout-mode="mobile"] .list-item { padding: 8px 12px; }

  body[data-layout-mode="mobile"] .checklist-item { padding: 6px 10px; border-color: transparent; }
  body[data-layout-mode="mobile"] .checklist-item-text { font-size: 14px; }

  body[data-layout-mode="mobile"] .detail-action-bar {
    flex-wrap: nowrap;
    overflow-x: auto;
    white-space: nowrap;
    scrollbar-width: none;
    -ms-overflow-style: none;
    gap: 6px;
    padding: 4px 0;
    margin-bottom: 7px;
    justify-content: space-between;
  }
  body[data-layout-mode="mobile"] .detail-action-bar::-webkit-scrollbar { display: none; }
  body[data-layout-mode="mobile"] .action-pill { height: 38px; padding: 0 9px; gap: 5px; font-size: 12.5px; position: relative; }
  body[data-layout-mode="mobile"] .action-pill::after { content: ''; position: absolute; inset: -4px; border-radius: 24px; }
  body[data-layout-mode="mobile"] .action-pill svg { width: 15px; height: 15px; }
  body[data-layout-mode="mobile"] .pill-label-full { display: none; }
  body[data-layout-mode="mobile"] .pill-label-short { display: inline; }
  /* Mirrors the ≤480px block above — see the comment there. */
  body[data-layout-mode="mobile"] #procRunBtn,
  body[data-layout-mode="mobile"] #procFolderBtn,
  body[data-layout-mode="mobile"] #procDupBtn,
  body[data-layout-mode="mobile"] #procImportBtn,
  body[data-layout-mode="mobile"] #procExportMdBtn,
  body[data-layout-mode="mobile"] #procUpdateMdBtn,
  body[data-layout-mode="mobile"] #procDeleteBtn {
    width: 44px;
    padding: 0;
    justify-content: center;
    gap: 0;
  }
  body[data-layout-mode="mobile"] #procRunBtn .pill-label-short,
  body[data-layout-mode="mobile"] #procDupBtn .pill-label-short,
  body[data-layout-mode="mobile"] #procImportBtn .pill-label-short,
  body[data-layout-mode="mobile"] #procExportMdBtn .pill-label-short,
  body[data-layout-mode="mobile"] #procUpdateMdBtn .pill-label-short,
  body[data-layout-mode="mobile"] #procDeleteBtn .pill-label-short { display: none; }
}


/* =========================================================
   MOBILE REFINEMENTS — close the remaining "desktop-feel"
   gaps. Placed last so these win on source order. Applies to
   the ≤768px auto breakpoint AND forced mobile mode.
   ========================================================= */
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  /* iOS Safari zooms the page when a focused field is < 16px.
     Force every editable control to 16px so tapping never zooms.
     Visual size stays close because padding is unchanged. */
  input, textarea, select,
  .text-input,
  .template-subject-input,
  .rich-editor, .insight-body-editor, .tag-add-input,
  .checklist-item-text, .note-add textarea, .search-input-wrap input {
    font-size: 16px !important;
  }
  /* .detail-title-input is a real <input> (so it's still caught by the bare
     `input` rule above and needs its own override) but it's the page TITLE,
     styled at 32px on desktop — collapsing it to the same 16px as a normal
     field lost all visual hierarchy and shrank its tap/type area to ~19px
     tall. 22px is still comfortably >16px (no iOS zoom) while keeping it
     reading as a title, not a form field. */
  .detail-title-input { font-size: 21px !important; }

  /* Momentum scrolling + no rubber-band chaining inside panes */
  .folder-tree, .detail-content,
  .modal-body, .search-results, .run-mode-body {
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
  }

  /* Kill the grey tap flash on iOS/Android for interactive bits */
  button, .iconbtn, .tab, .list-item, .folder-row,
  .export-option, .linker-item, .folder-picker-item,
  .action-sheet button, .context-menu button {
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
  }

  /* Topbar respects the notch / status bar safe area */
  .topbar {
    padding-top: env(safe-area-inset-top);
    height: calc(var(--topbar-h) + env(safe-area-inset-top));
  }

  /* Insight Note/Goal segmented toggle — 29px on its own, too short to
     tap reliably; bump the real box without touching its desktop size. */
  .ins-kind-seg button { min-height: 44px; }

  /* Bottom-sheet presentation — on mobile, a dialog box floating near the
     top of the screen reads as a web modal, not a native one. iOS presents
     this kind of content as a sheet that slides up from the bottom edge.
     Desktop keeps its existing centered-dialog behavior untouched. */
  .modal-backdrop {
    align-items: flex-end;
    padding: 0;
  }
  .modal {
    max-width: 100%;
    max-height: calc(92vh - env(safe-area-inset-bottom));
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    padding-bottom: env(safe-area-inset-bottom);
    animation: slideUpSheet var(--duration-sheet) var(--ease-settle, cubic-bezier(0.22, 0.61, 0.36, 1));
  }
  .modal-backdrop.closing .modal {
    animation: slideDownSheet var(--duration-sheet) var(--ease-ios) forwards;
  }
}

/* Same refinements for forced-mobile mode at any width */
body[data-layout-mode="mobile"] input,
body[data-layout-mode="mobile"] textarea,
body[data-layout-mode="mobile"] select,
body[data-layout-mode="mobile"] .rich-editor,
body[data-layout-mode="mobile"] .insight-body-editor {
  font-size: 16px !important;
}
body[data-layout-mode="mobile"] .detail-title-input { font-size: 21px !important; }
body[data-layout-mode="mobile"] button,
body[data-layout-mode="mobile"] .iconbtn,
body[data-layout-mode="mobile"] .tab,
body[data-layout-mode="mobile"] .list-item,
body[data-layout-mode="mobile"] .folder-row {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
body[data-layout-mode="mobile"] .ins-kind-seg button { min-height: 44px; }
body[data-layout-mode="mobile"] .modal-backdrop { align-items: flex-end; padding: 0; }
body[data-layout-mode="mobile"] .modal {
  max-width: 100%;
  max-height: calc(92vh - env(safe-area-inset-bottom));
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  padding-bottom: env(safe-area-inset-bottom);
  animation: slideUpSheet var(--duration-sheet) var(--ease-settle, cubic-bezier(0.22, 0.61, 0.36, 1));
}
body[data-layout-mode="mobile"] .modal-backdrop.closing .modal {
  animation: slideDownSheet var(--duration-sheet) var(--ease-ios) forwards;
}

/* Tablet gap: a small DESKTOP window (mouse) in the 769–1024px range
   showed a cramped 2-column grid. Give it more room. Restricted to
   fine-pointer devices so touch phones in this width band fall through
   to the mobile layout instead. */
@media (min-width: 769px) and (max-width: 1024px) and (pointer: fine) {
  body:not([data-layout-mode="desktop"]):not([data-layout-mode="mobile"]) .layout {
    grid-template-columns: 180px 1fr;
  }
  body:not([data-layout-mode="desktop"]):not([data-layout-mode="mobile"]) .detail-content {
    /* 80px here reserved clearance for the floating "+", which this
       fine-pointer band does not render — measured 59% of the viewport
       as chrome before the first step row at 1024. */
    padding: 28px 32px 40px;
  }
}


/* =========================================================
   MOBILE BOTTOM TAB BAR — primary section nav at the thumb.
   Hidden on desktop; shown in the ≤768px auto breakpoint and
   in forced-mobile mode. Top #tabs is hidden in those contexts.
   ========================================================= */
.bottom-nav { display: none; }

/* Shared appearance (applied when shown by the contexts below) */
.bottom-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 45;
  grid-template-columns: repeat(6, 1fr);
  align-items: stretch;
  background: var(--bg-elev);
  border-top: 1px solid var(--border);
  box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.06);
  padding-bottom: env(safe-area-inset-bottom);
  /* Shift clear of the on-screen keyboard instead of ending up hidden
     behind it — see initKeyboardAvoidance() in app.js. */
  transform: translateY(calc(-1 * var(--keyboard-inset)));
  transition: transform var(--duration-fast) var(--ease-ios);
  /* Deliberately NO backdrop-filter. It was saturate(180%) blur(8px), but
     --bg-elev is fully opaque in both themes (measured: rgb(255,255,255) light,
     rgb(23,20,29) dark, alpha 1), so the blur had nothing to show through and
     was invisible — while still forcing the compositor to keep a backdrop root
     and re-filter behind this always-visible fixed bar on every scroll frame.
     Pure cost, no pixels. The translucent modal/action-sheet scrims keep
     theirs, where the blur actually shows. */
}
.bottom-nav-tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 8px 4px;
  min-height: 56px;
  background: none;
  border: none;
  color: var(--ink-mute);
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
  position: relative;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  transition: color 0.18s ease;
}
.bottom-nav-tab svg { display: block; opacity: 0.9; transition: transform 0.18s cubic-bezier(0.16,1,0.3,1); }
.bottom-nav-tab span {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 6 tabs: tighter labels + smaller icons so nothing truncates or wraps
   to a second row on small phones (was tuned for 5 tabs; the FAB and
   .save-indicator's mobile offset assume this bar stays a single row). */
.bottom-nav { font-size: 9.5px; }
.bottom-nav-tab { padding-left: 1px; padding-right: 1px; gap: 2px; }
.bottom-nav-tab svg { width: 19px; height: 19px; }
.bottom-nav-tab.active { color: var(--accent); }
.bottom-nav-tab.active svg { transform: translateY(-1px) scale(1.05); }
.bottom-nav-tab.active::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 26px;
  height: 3px;
  border-radius: 0 0 3px 3px;
  background: var(--accent);
}
.bottom-nav-tab:active svg { transform: scale(0.92); }

/* ---- Auto breakpoint (≤768px) ---- */
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  body:not([data-layout-mode="desktop"]) .bottom-nav { display: grid; }
  body:not([data-layout-mode="desktop"]) .tabs { display: none; }
  /* The FAB already covers "new item" on mobile (same #createItem handler),
     so drop the two duplicates — the topbar icon and the desktop "+"
     button — rather than showing three "new" affordances at once. */
  body:not([data-layout-mode="desktop"]) #newBtn { display: none; }
  body:not([data-layout-mode="desktop"]) #newMenuBtn { display: none; }
  /* Show the FAB on mobile widths and lift it above the bar, clear of the safe area */
  body:not([data-layout-mode="desktop"]) .fab {
    display: flex;
    bottom: calc(72px + env(safe-area-inset-bottom));
  }

  /* Reserve room so the floating buttons never cover content at rest.
     80px only cleared the bottom NAV, not the two 56px FABs that sit
     72px above it — so at full scroll the last card ran under them and
     its "…" truncation button collided with the metadata FAB (measured:
     a 10px overlap band). --fab-clearance is derived from the FABs' own
     geometry (72px offset + 56px height + 20px breathing room) rather
     than being another hand-tuned number that can drift out of sync with
     them. */
  body:not([data-layout-mode="desktop"]) .detail-content {
    padding-bottom: calc(var(--fab-clearance) + env(safe-area-inset-bottom));
  }
}

/* ---- Forced mobile mode at any width ---- */
body[data-layout-mode="mobile"] .bottom-nav { display: grid; }
body[data-layout-mode="mobile"] .tabs { display: none; }
body[data-layout-mode="mobile"] #newBtn { display: none; }
body[data-layout-mode="mobile"] #newMenuBtn { display: none; }
body[data-layout-mode="mobile"] .fab { display: flex; bottom: calc(72px + env(safe-area-inset-bottom)); }
body[data-layout-mode="mobile"] .detail-content { padding-bottom: calc(var(--fab-clearance) + env(safe-area-inset-bottom)); }

/* =========================================================
   GANTT / SCHEDULE  (ported from the Mindmap tool)
   Variable mapping → ProcDocs tokens:
     --panel        → var(--bg-elev)
     --panel-2      → var(--bg-sunk)
     --panel-border → var(--border)
     --bg-grid      → var(--border-soft)
     --accent-2     → var(--gold)       (critical-path / today accent)
     --shadow       → rgba(0,0,0,.18)
   ========================================================= */
.gantt-wrap {
  --g-panel: var(--bg-elev);
  --g-panel-2: var(--bg-sunk);
  --g-panel-border: var(--border);
  --g-bg-grid: var(--border-soft);
  --g-accent-2: var(--gold);
  --g-shadow: rgba(0,0,0,.18);
  position: relative;
  display: flex;
  flex-direction: column;
  flex: 1 1 0;
  min-height: 0;
  min-width: 0;
  height: 100%;
  background: var(--g-panel-2);
  border-radius: var(--radius-lg);
  border: 1px solid var(--g-panel-border);
  overflow: hidden;
}

/* compact edit bar */
.gantt-wrap .gantt-edit {
  flex: 0 0 auto; display: none; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 14px; background: var(--g-panel); border-bottom: 1px solid var(--g-panel-border);
  font-size: 12px; color: var(--ink); position: relative; z-index: 6;
}
.gantt-wrap .gantt-edit.show { display: flex; }
.gantt-wrap .ge-field { display: flex; flex-direction: column; gap: 2px; }
.gantt-wrap .ge-lab { font-size: 9px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-mute); }
.gantt-wrap .gantt-edit input, .gantt-wrap .gantt-edit select {
  border: 1.5px solid var(--g-panel-border); border-radius: 7px; background: var(--bg); color: var(--ink);
  font-family: var(--font-body); font-size: 12px; padding: 5px 8px; outline: none; height: 30px;
}
.gantt-wrap .gantt-edit input:focus, .gantt-wrap .gantt-edit select:focus { border-color: var(--accent); }
.gantt-wrap .gantt-edit input[type=range] { padding: 0; border: none; background: transparent; height: auto; }
.gantt-wrap .ge-btn {
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--g-panel-border); background: var(--g-panel-2); color: var(--ink);
  border-radius: 7px; padding: 6px 10px; cursor: pointer; font-size: 11.5px; height: 30px; white-space: nowrap;
}
.gantt-wrap .ge-btn svg { flex-shrink: 0; }
.gantt-wrap .ge-btn:hover { background: var(--g-bg-grid); }
.gantt-wrap .ge-btn.danger:hover { background: var(--danger-solid); color: #fff; border-color: var(--danger-text); }
.gantt-wrap .ge-crit { font-size: 11px; font-weight: 700; color: var(--g-accent-2); white-space: nowrap; }
.gantt-wrap .ge-spacer { flex: 1 1 auto; }
.gantt-wrap .ge-close { border: none; background: transparent; color: var(--ink-mute); font-size: 20px; cursor: pointer; width: 28px; height: 28px; border-radius: 7px; }
.gantt-wrap .ge-close:hover { background: var(--g-bg-grid); color: var(--ink); }
.gantt-wrap .ge-toggle.on { background: var(--accent-bg); border-color: var(--accent); color: var(--ink); }
.gantt-wrap .ge-toggle.ge-conflict { background: var(--danger-bg); border-color: var(--danger-text); color: var(--danger-text); font-weight: 700; }
.gantt-wrap .ge-autopill {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px; color: var(--ink-mute); background: var(--g-bg-grid); border-radius: 7px; padding: 5px 9px; white-space: nowrap;
}

/* gantt toolbar (chart actions) */
.gantt-toolbar { flex: 0 0 auto; display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
  padding: 8px 12px; background: var(--g-panel); border-bottom: 1px solid var(--g-panel-border); }
.gantt-toolbar .gt-btn { display: inline-flex; align-items: center; gap: 5px; border: 1px solid var(--g-panel-border);
  background: var(--g-panel-2); color: var(--ink-soft); border-radius: var(--radius-sm); height: 30px; padding: 0 9px;
  cursor: pointer; font-size: 12px; font-family: var(--font-body); white-space: nowrap; }
.gantt-toolbar .gt-btn:hover { background: var(--g-bg-grid); color: var(--ink); }
.gantt-toolbar .gt-btn.active { background: var(--accent-bg); border-color: var(--accent); color: var(--accent-text); }
.gantt-toolbar .gt-btn svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2; }
.gantt-toolbar .gt-sep { width: 1px; align-self: stretch; background: var(--g-panel-border); margin: 2px 4px; }
.gantt-toolbar .gantt-summary { font-size: 12px; color: var(--ink-mute); margin-left: auto; white-space: nowrap; }

/* Overflow menu for less-frequent toolbar actions — collapsed into on
   mobile only (see the two mirrored mobile blocks below/further down). */
.gantt-toolbar .gt-more-wrap { display: none; position: relative; }
.gantt-toolbar .gt-more-menu {
  display: none;
  flex-direction: column;
  gap: 3px;
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 20;
  background: var(--g-panel);
  border: 1px solid var(--g-panel-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: 6px;
  min-width: 168px;
}
.gantt-toolbar .gt-more-menu.open { display: flex; }
.gantt-toolbar .gt-more-menu .gt-btn { width: 100%; justify-content: flex-start; height: 36px; }

/* scroll + inner */
.gantt-scroll { flex: 1 1 auto; min-height: 0; min-width: 0; overflow: auto; overflow-x: auto; overflow-y: auto; position: relative; background: var(--g-panel-2); scrollbar-gutter: stable;
  /* Touch horizontal-scroll hardening: allow both pan axes explicitly, keep
     momentum scrolling, and contain the horizontal overscroll so a right/left
     swipe scrolls the timeline instead of triggering the browser's back/forward
     navigation gesture (the likely cause of "can't scroll right" on Android). */
  touch-action: pan-x pan-y; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; }
/* Always-visible, grabbable scrollbars so it's obvious the chart pans horizontally */
.gantt-scroll::-webkit-scrollbar { width: 12px; height: 12px; }
.gantt-scroll::-webkit-scrollbar-track { background: var(--g-panel-2); border-radius: var(--radius-md); }
.gantt-scroll::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--ink) 28%, transparent); border-radius: var(--radius-md); border: 3px solid var(--g-panel-2); background-clip: padding-box; }
.gantt-scroll::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--ink) 45%, transparent); border: 3px solid var(--g-panel-2); background-clip: padding-box; }
.gantt-scroll::-webkit-scrollbar-corner { background: var(--g-panel-2); }
.gantt-scroll { scrollbar-width: auto; scrollbar-color: color-mix(in srgb, var(--ink) 28%, transparent) var(--g-panel-2); }
.gantt-inner { position: relative; }
/* Custom always-visible horizontal scrollbar (OS overlay bars are unreliable) */
.gantt-hscroll { display: none; flex: 0 0 auto; height: 14px; position: relative;
  background: var(--g-panel); border-top: 1px solid var(--g-panel-border);
  cursor: pointer; user-select: none; }
.gantt-hscroll.show { display: block; }
.gantt-hscroll-thumb { position: absolute; top: 3px; height: 8px; left: 0; min-width: 40px;
  background: color-mix(in srgb, var(--ink) 32%, transparent); border-radius: var(--radius-sm);
  cursor: grab; will-change: transform; transition: background .12s; }
.gantt-hscroll-thumb:hover { background: color-mix(in srgb, var(--ink) 48%, transparent); }
.gantt-hscroll-thumb:active { cursor: grabbing; background: var(--accent); }
/* The plotting area (right of the frozen panels) gets its own subtle backdrop
   so it reads as a distinct surface from the TASK / DEPENDS ON columns. */
.g-chart-area { position: absolute; top: 0; bottom: 0; background: var(--bg); z-index: 0; cursor: grab; }
/* Tooltip shown on KEYBOARD focus only (see initFocusTooltips). Hover keeps the
   native title bubble; this exists so keyboard users are not the only ones
   without labels on icon-only controls. */
.focus-tip {
  position: fixed;
  z-index: 9999;
  max-width: 260px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  background: var(--ink);
  color: var(--bg-elev);
  font-size: 12.5px;
  line-height: 1.35;
  box-shadow: var(--shadow-md);
  pointer-events: none;
  animation: contentFade var(--duration-fast) var(--ease-ios);
}

/* Agenda view — tasks grouped by the week they start. A Gantt answers "what
   shape is this project"; on a phone the commoner question is "what is
   happening, and when", and that is a list, on the axis a phone scrolls. */
.gantt-agenda { display: none; flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 4px 0 8px; }
.gantt-agenda.show { display: block; }
.gantt-agenda-empty { padding: 24px 16px; color: var(--ink-mute); font-size: 14px; text-align: center; }
.gantt-agenda-week {
  position: sticky; top: 0; z-index: 1;
  background: var(--bg-elev);
  padding: 10px 12px 6px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-faint);
}
.gantt-agenda-week.current { color: var(--accent-text); }
.gantt-agenda-row {
  display: flex; align-items: center; gap: 10px;
  min-height: 52px; padding: 8px 12px;
  border-bottom: 1px solid var(--g-bg-grid);
  cursor: pointer;
}
.gantt-agenda-row:active { background: var(--accent-bg); }
.gantt-agenda-row.sel { background: var(--accent-bg); }
.gantt-agenda-row .ga-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.gantt-agenda-row .ga-dot.late { box-shadow: 0 0 0 3px var(--danger-bg, rgba(220,38,38,.18)); }
.gantt-agenda-row .ga-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.gantt-agenda-row .ga-name { font-size: 15px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gantt-agenda-row .ga-meta { font-size: 12px; color: var(--ink-mute); font-variant-numeric: tabular-nums; }
.gantt-agenda-row .ga-pct { font-size: 12px; color: var(--ink-faint); font-variant-numeric: tabular-nums; flex: none; }

/* Timeline zoom presets. Shown on EVERY viewport, not just phones.
   ganttZoom is stored on the schedule document, so a Fit chosen on a phone
   also applies on the desktop — and at desktop width fit-to-width means the
   timeline exactly fills the scroller, i.e. scrollWidth === clientWidth and
   there is nothing left to scroll horizontally. While this control was
   phone-only, that state was invisible and unreachable from a desktop: the
   wheel did nothing and the only way out was to guess that +/- clears the
   preset. The control that sets a state has to live where the state applies.
   Measured at 1024/1280/1440/1920: the toolbar has room, 0 overflow. */
.gt-zoom-seg { display: inline-flex; align-items: center; gap: 2px; padding: 2px; background: var(--bg-sunk); border-radius: var(--radius-md); flex: none; }
.gt-zoom-seg .gt-zoom-opt { font-size: 12px; font-weight: 600; padding: 0 10px; border-radius: var(--radius-sm); color: var(--ink-mute); background: none; white-space: nowrap; }
.gt-zoom-seg .gt-zoom-opt.active { background: var(--surface); color: var(--accent-text); box-shadow: var(--shadow-sm); }
.gt-zoom-seg .gt-zoom-opt:active { transform: scale(0.96); }

.gantt-scroll.panning { cursor: grabbing; }
.gantt-scroll.panning * { cursor: grabbing !important; }
.g-chart-divider { position: absolute; top: 0; bottom: 0; width: 0; border-left: 2px solid var(--g-panel-border); z-index: 7; }
.g-col-resize { position: absolute; top: 0; width: 7px; cursor: col-resize; z-index: 11; will-change: transform; }
.g-col-resize::after { content: ""; position: absolute; left: 3px; top: 0; bottom: 0; width: 1px; background: transparent; transition: background .12s; }
.g-col-resize:hover::after { background: var(--accent); width: 2px; left: 2px; }
.g-grid-line { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--g-bg-grid); }
.g-weekend { position: absolute; top: 0; bottom: 0; background: color-mix(in srgb, var(--ink) 5%, transparent); }
.g-today { position: absolute; top: 0; bottom: 0; width: 0; border-left: 2px dashed var(--g-accent-2); z-index: 6; }
.g-today::before { content: "Today"; position: absolute; top: 2px; left: 50%; transform: translateX(-50%);
  background: var(--g-accent-2); color: #fff; font-size: 9px; font-weight: 700; letter-spacing: .03em;
  padding: 2px 6px; border-radius: 7px; white-space: nowrap; }
.g-month { position: absolute; top: 0; height: 26px; display: flex; align-items: center; padding-left: 7px;
  font-size: 11px; font-weight: 700; color: var(--ink-mute); border-left: 1px solid var(--g-panel-border); white-space: nowrap; }
.g-day { position: absolute; top: 26px; height: 30px; display: flex; flex-direction: column; align-items: center; justify-content: center;
  font-size: 10px; color: var(--ink-mute); border-left: 1px solid var(--g-bg-grid); }
.g-day .g-dow { font-size: 8px; opacity: .7; }
/* Date ruler — frozen to the top on vertical scroll */
.g-ruler { position: absolute; z-index: 6; will-change: transform;
  background: var(--g-panel); border-bottom: 1px solid var(--g-panel-border); }
/* Frozen corner headers (TASK / DEPENDS ON) — stay put both directions, above the ruler */
.g-corner-head { display: flex; align-items: center; padding: 0 10px; font-weight: 700; font-size: 12px;
  color: var(--ink-mute); border-bottom: 1px solid var(--g-panel-border); position: relative; z-index: 12;
  background: inherit; will-change: transform; }
.g-dephead.g-corner-head { font-size: 11px; letter-spacing: .04em; }

.g-rowlabels { position: absolute; left: 0; top: 0; background: var(--g-panel); border-right: 1px solid var(--g-panel-border); z-index: 8; will-change: transform; }
.g-rowdeps { position: absolute; left: 0; top: 0; background: color-mix(in srgb, var(--g-panel-2) 70%, var(--ink) 4%); border-right: 2px solid var(--g-panel-border); z-index: 8; will-change: transform; }
.g-rowlabel { display: flex; align-items: center; gap: 7px; padding: 0 10px; border-bottom: 1px solid var(--g-bg-grid);
  font-size: 13px; cursor: pointer; overflow: hidden; }
.g-rowlabel:hover { background: var(--g-bg-grid); }
.g-rowlabel.sel { background: var(--accent-bg); }
.g-rowlabel.summary { font-weight: 700; }
.g-rl-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; }
.g-rl-twirl { width: 14px; text-align: center; color: var(--ink-mute); flex: none; cursor: pointer; }
.g-rl-prog { font-size: 10px; color: var(--ink-mute); flex: none; }
.g-rl-badge { font-size: 10px; color: var(--ink-mute); flex: none; margin-left: 2px; }
/* gap 2 -> 8: the add/delete halos below are 26px, and at a 2px gap
   they overlapped, leaving each button 22px of reachable width. */
.g-row-actions { display: flex; gap: 8px; flex: none; margin-left: 4px; opacity: 0; transition: opacity .12s; }
.g-rowlabel:hover .g-row-actions { opacity: 1; }
.g-row-add, .g-row-del { width: 18px; height: 18px; border-radius: 5px; border: 1px solid var(--g-panel-border);
  background: var(--g-panel-2); color: var(--ink-mute); cursor: pointer; font-size: 13px; line-height: 1; padding: 0;
  display: flex; align-items: center; justify-content: center; font-family: var(--font-body); }
.g-row-add:hover { background: var(--accent-btn); color: #fff; border-color: var(--accent); }
.g-row-del:hover { background: var(--danger-solid); color: #fff; border-color: var(--danger-text); }
.g-dephead { display: flex; align-items: center; padding: 0 10px; font-weight: 700; font-size: 11px;
  letter-spacing: .04em; color: var(--ink-mute); border-bottom: 1px solid var(--g-panel-border); }
.g-deprow { display: flex; align-items: center; gap: 4px; padding: 0 8px; border-bottom: 1px solid var(--g-bg-grid);
  font-size: 11px; cursor: pointer; flex-wrap: wrap; overflow: hidden; }
.g-deprow:hover { background: var(--g-bg-grid); }
.g-deprow.sel { background: var(--accent-bg); }
.g-dep-chip { display: inline-flex; align-items: center; gap: 3px; background: var(--g-bg-grid); border-radius: var(--radius-sm); padding: 2px 6px; white-space: nowrap; max-width: 100%; overflow: hidden; }
.g-dep-chip .gd-name { overflow: hidden; text-overflow: ellipsis; max-width: 80px; }
.g-dep-chip .gd-type { font-weight: 700; font-size: 10px; }
.g-dep-chip .gd-lag { font-size: 9px; font-weight: 700; color: var(--ink-mute); }
.g-dep-chip.dt-FS .gd-type { color: var(--teal-text); }
.g-dep-chip.dt-SS .gd-type { color: var(--success-text); }
.g-dep-chip.dt-FF .gd-type { color: var(--danger-text); }
.g-dep-chip.dt-SF .gd-type { color: #A81093; }
[data-theme="dark"] .g-dep-chip.dt-SF .gd-type { color: #F04FD9; }
.g-dep-none { color: var(--ink-mute); }
.g-dep-add { border: 1px dashed var(--g-panel-border); background: transparent; border-radius: var(--radius-sm); color: var(--ink-mute); cursor: pointer; font-size: 11px; padding: 2px 7px; }
.g-dep-add:hover { border-color: var(--accent); color: var(--accent); }
.g-rl-status { flex: none; width: 9px; height: 9px; border-radius: 50%; display: inline-block; margin-right: 4px; background: var(--ink-mute); }
.g-rl-owner { width: 20px; height: 20px; border-radius: 50%; color: #fff; font-size: 9px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center; flex: none; }
.st-notstarted { background: #B7ADC7; } .st-inprogress { background: var(--teal); } .st-blocked { background: var(--danger); } .st-done { background: var(--success); }

.g-bar-owner { position: absolute; right: -24px; top: 50%; transform: translateY(-50%); width: 20px; height: 20px; border-radius: 50%;
  color: #fff; font-size: 9px; font-weight: 700; display: flex; align-items: center; justify-content: center; z-index: 4; }
.g-baseline { position: absolute; height: 5px; border-radius: 3px; background: repeating-linear-gradient(90deg, var(--ink-mute), var(--ink-mute) 4px, transparent 4px, transparent 7px); opacity: .55; z-index: 1; }

.g-rowband { position: absolute; left: 0; right: 0; border-bottom: 1px solid var(--g-bg-grid); }
.g-rowband.sel { background: color-mix(in srgb, var(--accent-bg) 55%, transparent); }
.g-bar { position: absolute; border-radius: 7px; cursor: grab; box-shadow: 0 1px 3px rgba(0,0,0,.08);
  display: flex; align-items: center; overflow: visible; user-select: none; transition: box-shadow .12s, transform .08s; }
.g-bar:hover { box-shadow: 0 3px 10px rgba(0,0,0,.13); }
.g-bar:active { cursor: grabbing; }
.g-bar .g-fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: var(--radius-sm); opacity: .92; }
.g-bar .g-label { position: absolute; left: calc(100% + 8px); white-space: nowrap; font-size: 11px; color: var(--ink-mute); pointer-events: none; }
.g-bar .g-label.inside { left: 9px; color: var(--ink); font-weight: 600; text-shadow: 0 1px 2px rgba(255,255,255,.5); }
/* date/duration caption sitting just beneath the bar, aligned to its left edge */
.g-bar .g-label.below { left: 1px; top: calc(100% + 2px); font-size: 10.5px; line-height: 1.1; color: var(--ink-mute); font-weight: 500; }
[data-theme="dark"] .g-bar .g-label.inside { text-shadow: 0 1px 2px rgba(0,0,0,.5); }
.g-bar.crit { border-color: var(--g-accent-2) !important; box-shadow: inset 3px 0 0 var(--g-accent-2), 0 1px 3px rgba(0,0,0,.08); }
.g-bar.conflict { outline: 2px dashed var(--danger); outline-offset: 1px; }
.g-warn { color: var(--danger-text); font-weight: 700; }
#gantt-inner.dep-focus .g-bar, .gantt-inner.dep-focus .g-bar,
.gantt-inner.dep-focus .g-milestone { opacity: .28; transition: opacity .12s; }
.gantt-inner.dep-focus .g-bar.dep-lit, .gantt-inner.dep-focus .g-milestone.dep-lit { opacity: 1; }
.gantt-inner.dep-focus .gantt-dep-svg path { opacity: .12; }
.gantt-inner.dep-focus .gantt-dep-svg path.dep-lit { opacity: 1; stroke-width: 2.4; }
.g-bar.summary { height: 6px !important; border-radius: 2px; border: none !important; box-shadow: none; opacity: 0.92; }
.g-bar.summary .g-fill { display: none; }
.g-bar.summary::before, .g-bar.summary::after { content:""; position:absolute; top:0; width:0; height:0;
  border-left:5px solid transparent; border-right:5px solid transparent;
  border-top:7px solid var(--g-summary-cap, color-mix(in srgb, var(--ink) 45%, transparent)); }
.g-bar.summary::before { left:0; } .g-bar.summary::after { right:0; }
.g-handle { position: absolute; top: 0; bottom: 0; width: 8px; cursor: ew-resize; z-index: 3; opacity: 0; transition: opacity .12s; }
.g-bar:hover .g-handle { opacity: 1; }
.g-handle.l { left: -2px; } .g-handle.r { right: -2px; }
.g-prog-handle { position: absolute; top: 50%; transform: translate(-50%,-50%); width: 12px; height: 12px;
  border-radius: 50%; background: var(--g-panel); border: 2px solid var(--ink); cursor: ew-resize; z-index: 4; }
.g-link-dot { position: absolute; top: 50%; right: -7px; transform: translateY(-50%); width: 13px; height: 13px;
  border-radius: 50%; background: var(--accent); border: 2px solid var(--g-panel); cursor: crosshair; z-index: 5; display: none; }
.g-bar:hover .g-link-dot { display: block; }
.g-milestone { position: absolute; width: 20px; height: 20px;
  background: linear-gradient(135deg, color-mix(in srgb, var(--g-accent-2) 100%, #fff 18%), var(--g-accent-2));
  transform: rotate(45deg); border-radius: 4px;
  border: 2px solid var(--g-panel);
  box-shadow: 0 2px 5px var(--g-shadow), inset 0 1px 1px rgba(255,255,255,.35);
  cursor: grab; z-index: 3; transition: transform .1s, box-shadow .12s; }
.g-milestone:hover { box-shadow: 0 3px 9px var(--g-shadow), inset 0 1px 1px rgba(255,255,255,.45); }
.g-milestone:active { cursor: grabbing; }
.g-milestone .g-label { position: absolute; left: 24px; top: 50%; transform: rotate(-45deg) translateY(-50%);
  transform-origin: left center; white-space: nowrap; font-size: 11px; font-weight: 600; color: var(--ink-soft); }
.g-milestone.crit { box-shadow: 0 0 0 2px var(--g-accent-2), 0 2px 6px var(--g-shadow), inset 0 1px 1px rgba(255,255,255,.4); }
.gantt-dep-svg { position: absolute; top: 0; left: 0; overflow: visible; pointer-events: none; z-index: 2; }
.gantt-dep-svg path { stroke: color-mix(in srgb, var(--ink-mute) 70%, transparent); stroke-width: 1.5; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.gantt-dep-svg path.crit { stroke: var(--g-accent-2); stroke-width: 2; }
.g-droplink { position: absolute; pointer-events: none; z-index: 20; stroke: var(--accent); stroke-width: 2; stroke-dasharray: 5 4; fill: none; }

/* table view */
.gantt-table { display: none; flex: 1 1 auto; overflow: auto; padding: 0; background: var(--bg);
  /* Same touch-scroll hardening as the chart: the table is the MOBILE DEFAULT
     view and is wider than the phone (7 columns), so its horizontal scroll is
     the one a phone user hits first. */
  touch-action: pan-x pan-y; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; }
.gantt-table.show { display: block; }
/* Empty schedule prompt (no tasks) — fills the body where the chart/table would be. */
.gantt-empty-state {
  flex: 1 1 auto; min-height: 180px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; padding: 40px 24px; text-align: center;
}
.gantt-empty-title { font-size: 16px; font-weight: 700; color: var(--ink); }
.gantt-empty-sub { font-size: 13px; color: var(--ink-mute); max-width: 34ch; }
.gantt-empty-cta {
  margin-top: 10px; padding: 10px 18px; min-height: 44px;
  border-radius: var(--radius-md); border: none; cursor: pointer;
  background: var(--accent-btn); color: #fff; font-weight: 600; font-size: 14px;
}
.gantt-empty-cta:active { filter: brightness(0.94); }
.gt-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.gt-table th { position: sticky; top: 0; background: var(--g-panel-2); text-align: left; padding: 9px 12px; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-mute); border-bottom: 1px solid var(--g-panel-border); z-index: 2; }
.gt-table td { padding: 7px 12px; border-bottom: 1px solid var(--g-bg-grid); white-space: nowrap; }
.gt-table tr { cursor: pointer; }
.gt-table tr:hover td { background: var(--g-bg-grid); }
.gt-table tr.sel td { background: var(--accent-bg); }
.gt-kind { font-size: 10px; color: var(--ink-mute); border: 1px solid var(--g-panel-border); border-radius: 5px; padding: 1px 5px; margin-right: 4px; }
.gt-name { font-weight: 500; }
.gt-muted { color: var(--ink-mute); }
.gt-deps { font-size: 11px; color: var(--ink-mute); max-width: 260px; overflow: hidden; text-overflow: ellipsis; }
.gt-rename-input { font-size: 13px; font-family: var(--font-body); border: 1.5px solid var(--accent); border-radius: 5px; padding: 2px 5px; outline: none; }

/* legend + popovers */
.gantt-legend { display: none; position: absolute; right: 14px; top: 60px; z-index: 28; width: 290px;
  background: var(--g-panel); border: 1px solid var(--g-panel-border); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); padding: 12px; }
.gantt-legend.show { display: block; }
.gantt-legend .lg-h { display: flex; justify-content: space-between; align-items: center; font-weight: 700; font-size: 12px; margin-bottom: 10px; }
.gantt-legend .lg-h button { border: none; background: transparent; font-size: 18px; cursor: pointer; color: var(--ink-mute); }
.gantt-legend .lg-row { display: flex; align-items: center; gap: 9px; font-size: 12px; margin-bottom: 8px; }
.gantt-legend .lg-bar { width: 34px; height: 14px; border-radius: 4px; flex: none; }
.gantt-legend .lg-bar.lg-summary { height: 7px; background: var(--ink-mute); border-radius: 2px; }
.gantt-legend .lg-dia { width: 14px; height: 14px; background: var(--g-accent-2); transform: rotate(45deg); border-radius: 2px; flex: none; }
.gantt-legend .lg-fill { width: 34px; height: 14px; border-radius: 4px; flex: none; background: linear-gradient(90deg,var(--teal) 55%, #BEE4EF 55%); }
.gantt-legend .lg-base { width: 34px; height: 6px; flex: none; background: repeating-linear-gradient(90deg, var(--ink-mute), var(--ink-mute) 4px, transparent 4px, transparent 7px); opacity:.6; }
.ge-pop { position: absolute; top: 60px; right: 14px; z-index: 30; width: 280px;
  background: var(--g-panel); border: 1px solid var(--g-panel-border); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); padding: 12px; }
.ge-pop-h { display: flex; justify-content: space-between; align-items: center; font-weight: 700; font-size: 12px; margin-bottom: 8px; }
.ge-pop-h button { border: none; background: transparent; font-size: 18px; cursor: pointer; color: var(--ink-mute); }
.ge-pop-wide { width: 360px; }
.ge-pop .cl-txt { width: 100%; border: 1px solid var(--g-panel-border); border-radius: var(--radius-sm); padding: 4px 6px; font-size: 12px; background-color: var(--bg); color: var(--ink); }
.ge-pop .dp-row { display:flex; gap:5px; align-items:center; margin-bottom:5px; }
.ge-pop .dp-name { flex:1; font-size:12px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ge-pop .dp-row select { height: 26px; padding: 2px 4px; font-size: 11px; border:1px solid var(--g-panel-border); border-radius:var(--radius-sm); background-color:var(--bg); color:var(--ink); }
.ge-pop .dp-row input { height: 26px; width: 46px; padding: 2px 4px; font-size: 11px; border:1px solid var(--g-panel-border); border-radius:var(--radius-sm); background:var(--bg); color:var(--ink); }
.ge-pop .dp-row button { border: none; background: transparent; cursor: pointer; color: var(--ink-mute); font-size: 13px; }
.ge-pop .dp-add { width:100%; margin-top:6px; border:1px solid var(--g-panel-border); border-radius:var(--radius-sm); background-color:var(--bg); color:var(--ink); height:30px; padding:0 6px; }

/* gantt fills the detail pane for this tab */
.detail-pane.gantt-pane { overflow: hidden; }
.detail-content.gantt-detail {
  display: flex; flex-direction: column;
  padding: 0; height: 100%; min-height: 0; min-width: 0;
  max-width: none; margin: 0; width: 100%; overflow: hidden;
}
.gantt-detail .gantt-head { flex: 0 0 auto; padding: 12px 20px 8px; }
.gantt-detail .gantt-head .detail-meta-row { margin-bottom: 8px; }
.gantt-detail .gantt-head .detail-title-input { margin: 0; font-size: 22px; line-height: 1.15; }
.gantt-detail .gantt-head .detail-description { margin: 2px 0 0; min-height: 0; font-size: 13px; line-height: 1.4; }
.gantt-detail .gantt-head .detail-tags { margin: 6px 0 0; }
.gantt-detail .gantt-body { flex: 1 1 auto; min-height: 0; min-width: 0; display: flex; padding: 0 12px 12px; overflow: hidden; }
/* Forced-mobile mirror of the touch-scroll axis routing (see the
   @media (max-width:768px) rule below for the full rationale). */
body[data-layout-mode="mobile"] .gantt-scroll { touch-action: pan-x pan-y; }
body[data-layout-mode="mobile"] .gantt-table { touch-action: pan-x; }
/* Zoom presets — forced-mobile mirror of the @media rules below. */
body[data-layout-mode="mobile"] .gantt-toolbar [data-g="zoomin"],
body[data-layout-mode="mobile"] .gantt-toolbar [data-g="zoomout"] { display: none; }
/* M1/M2 mirror: compact header + reserve bottom-nav height. */
body[data-layout-mode="mobile"] .gantt-detail .gantt-head { padding: 8px 14px 4px; }
body[data-layout-mode="mobile"] .gantt-detail .gantt-head .detail-meta-row { margin-bottom: 4px; }
body[data-layout-mode="mobile"] .gantt-detail .gantt-head .detail-title-input { font-size: 18px !important; }
body[data-layout-mode="mobile"] .gantt-detail .gantt-head .detail-description { font-size: 12.5px; margin-top: 1px; }
body[data-layout-mode="mobile"] .gantt-detail .gantt-head .detail-tags { margin-top: 4px; }
/* Height pin — forced-mobile mirror of the @media rule below. */
body[data-layout-mode="mobile"] .detail-pane.gantt-pane {
  height: calc(100dvh - var(--topbar-h) - var(--folder-taskbar-h) - 56px - env(safe-area-inset-bottom));
  min-height: 0;
}
body[data-layout-mode="mobile"] .gantt-detail .gantt-body { padding-bottom: 8px; }
/* Table column diet + row-button hit expander — forced-mobile mirrors of the
   @media rules below. */
body[data-layout-mode="mobile"] .gt-table th:nth-child(2),
body[data-layout-mode="mobile"] .gt-table td:nth-child(2),
body[data-layout-mode="mobile"] .gt-table th:nth-child(6),
body[data-layout-mode="mobile"] .gt-table td:nth-child(6),
body[data-layout-mode="mobile"] .gt-table th:nth-child(8),
body[data-layout-mode="mobile"] .gt-table td:nth-child(8) { display: none; }
body[data-layout-mode="mobile"] .gt-table { width: 100%; }
body[data-layout-mode="mobile"] .gt-table th:first-child,
body[data-layout-mode="mobile"] .gt-table td:first-child {
  max-width: 120px; overflow: hidden; text-overflow: ellipsis;
}
body[data-layout-mode="mobile"] .gt-table .gt-kind { display: none; }
body[data-layout-mode="mobile"] .gt-table td { padding: 14px 8px; }
body[data-layout-mode="mobile"] .gt-table th { padding: 9px 8px; }
body[data-layout-mode="mobile"] .g-row-add,
body[data-layout-mode="mobile"] .g-row-del { position: relative; }
body[data-layout-mode="mobile"] .g-row-add::before,
body[data-layout-mode="mobile"] .g-row-del::before { content: ''; position: absolute; inset: -8px 0; }

/* mobile: keep the chart usable when a user opts into it from Table view.
   Table is now the default on mobile (see mount() in app.js), but the drag
   handles below still need to work for anyone who switches to the chart. */
@media (max-width: 768px) {
  /* Table view is the mobile default, and all 8 columns came to 789px inside a
     386px viewport — you could see about half a schedule without scrolling
     sideways. Drop the three that carry least at a glance (Owner, Duration,
     Depends on); tapping any row still opens the editor with the full record,
     so nothing becomes unreachable. */
  .gt-table th:nth-child(2), .gt-table td:nth-child(2),   /* Owner */
  .gt-table th:nth-child(6), .gt-table td:nth-child(6),   /* Dur */
  .gt-table th:nth-child(8), .gt-table td:nth-child(8) { display: none; } /* Depends on */
  /* Dropping columns got it to 494px; the Task column's natural 230px was the
     rest of the gap. Cap it and ellipsize so the whole table fits with no
     sideways scrolling at all, and drop the Stage/Task/Milestone label — the
     indentation already carries hierarchy and the name needs the room. */
  .gt-table { width: 100%; }
  .gt-table th:first-child, .gt-table td:first-child {
    max-width: 120px; overflow: hidden; text-overflow: ellipsis;
  }
  .gt-table .gt-kind { display: none; }
  /* Every row opens the editor on tap, but they were 31px. Tighter side
     padding pays for the taller rows, so the table still fits. */
  .gt-table td { padding: 14px 8px; }
  .gt-table th { padding: 9px 8px; }

  /* The per-row add/delete buttons are 28px in a 52px row. Widening them would
     collide with each other in the frozen label column, so expand the hit box
     vertically only — 28 + 8 top + 8 bottom clears the 44px floor. */
  .g-row-add, .g-row-del { position: relative; }
  .g-row-add::before, .g-row-del::before { content: ''; position: absolute; inset: -8px 0; }

  /* Touch scrolling on BOTH axes is the browser's job. This was pan-y, with a
     JS pointer-drag handler doing horizontal, on the belief that native
     horizontal scroll broke the frozen-panel pinning. That belief was wrong —
     pinPanels() listens for the scroll event and compensates correctly under
     native scrolling (measured: scrollLeft 0->400 keeps the label column at the
     same on-screen x). Meanwhile pan-y actively broke real fingers: a swipe
     that is even slightly diagonal gets claimed as a vertical scroll and the
     pointer stream is cancelled, so the timeline never moved. Native scrolling
     also brings momentum and overscroll for free. */
  .gantt-scroll { touch-action: pan-x pan-y; }
  .gantt-table { touch-action: pan-x; }
  /* Phase 2: on a phone, +/- is replaced by explicit Fit / Month / Week. A
     preset is discoverable and reversible; incremental zoom is neither, and it
     is how you end up looking at 15% of a schedule with no way back. */
  .gantt-toolbar [data-g="zoomin"],
  .gantt-toolbar [data-g="zoomout"] { display: none; }

  .gantt-toolbar { overflow-x: auto; flex-wrap: nowrap; }
  .gantt-toolbar .gantt-summary { display: none; }
  .gantt-toolbar .gt-secondary { display: none; }
  /* The overflow menu is where the Gantt's less-frequent actions live on a
     phone — and its own toggle was riding off the end of the scroller.
     Measured at 360px: the 44px button sat at x 348-392, so 12px of it was
     on screen and elementFromPoint at its centre returned nothing. It was
     reachable only by first scrolling the toolbar sideways, with nothing
     saying so. Sticky pins it to the visible right edge while the rest of
     the toolbar scrolls underneath — the same split the folder taskbar
     already uses (scrolling pills, fixed actions). It keeps a positioning
     context, so the absolutely-positioned menu still anchors to it. */
  .gantt-toolbar .gt-more-wrap {
    display: inline-flex;
    position: sticky;
    right: 0;
    margin-left: auto;
    z-index: 5;
    background: var(--g-panel);
    padding-left: 6px;
  }
  .gantt-toolbar .gt-btn { height: 36px; }

  /* M1: the header ate ~219px (24–27% of a phone) before any schedule showed.
     Compact it so more of the timeline/table is above the fold. */
  .gantt-detail .gantt-head { padding: 8px 14px 4px; }
  .gantt-detail .gantt-head .detail-meta-row { margin-bottom: 4px; }
  .gantt-detail .gantt-head .detail-title-input { font-size: 18px !important; }
  .gantt-detail .gantt-head .detail-description { font-size: 12.5px; margin-top: 1px; }
  .gantt-detail .gantt-head .detail-tags { margin-top: 4px; }
  /* M2: the content is auto-height and page-scrolls on mobile; reserve the fixed
     bottom-nav's height so the last rows aren't trapped behind it. */
  /* Mobile sets .layout/.detail-pane to height:auto so ordinary sections
     scroll as one long document. The Gantt is not a document — it is an
     app-like surface with its own internal scrollers, and auto height left
     .gantt-scroll sized to its own content (scrollHeight === clientHeight, so
     nothing to scroll vertically) inside a pane 407px taller than the phone
     that clips via .detail-pane.gantt-pane{overflow:hidden}. The bottom of a
     schedule was simply unreachable and NEITHER axis scrolled. Pin the pane to
     the viewport so the scrollers get a bounded height and overflow again. */
  .detail-pane.gantt-pane {
    height: calc(100dvh - var(--topbar-h) - var(--folder-taskbar-h) - 56px - env(safe-area-inset-bottom));
    min-height: 0;
  }
  /* The pane height now excludes the bottom nav, so reserving 58px here too
     would double-count it. */
  .gantt-detail .gantt-body { padding-bottom: 8px; }

  /* These were opacity:0 / display:none until :hover — meaningless on a
     touch screen, since there's no hover state to reveal them. Make them
     permanently visible (semi-transparent so they don't compete with the
     bar's own label) and widen the hit area for a fingertip. */
  /* M3: bigger grab targets. The visible handle stays slim, but an invisible
     ::before pads the tap area out to a fingertip. */
  .g-handle { width: 18px; opacity: .5; }
  .g-handle::before { content: ''; position: absolute; inset: -8px -6px; }
  .g-handle.l { left: -7px; } .g-handle.r { right: -7px; }
  .g-prog-handle { width: 20px; height: 20px; }
  .g-prog-handle::before { content: ''; position: absolute; inset: -8px; }
  .g-link-dot { width: 22px; height: 22px; right: -11px; display: block; opacity: .55; }
  .g-link-dot::before { content: ''; position: absolute; inset: -8px; }
  .g-bar:active .g-handle,
  .g-bar:active .g-link-dot { opacity: 1; }

  /* Same problem in table view: row add/delete are hover-only and 18px —
     invisible and too small to hit with a fingertip. Table is the mobile
     default view, so this is the row action most mobile users need. */
  .g-row-actions { opacity: 1; }
  .g-row-add, .g-row-del { width: 28px; height: 28px; font-size: 15px; }
}

/* linked checklists / templates for the selected task */
.gantt-linked { display: none; flex: 0 0 auto; max-height: 34%; overflow-y: auto;
  background: var(--g-panel); border-top: 1px solid var(--g-panel-border); padding: 10px 14px; }
.gantt-linked.show { display: block; }
.gantt-linked .gl-head { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-mute); margin-bottom: 8px; }
.gantt-linked .gl-cards { display: flex; flex-direction: column; gap: 6px; }
.gantt-linked .gl-card { border: 1px solid var(--g-panel-border); border-radius: var(--radius-sm); background: var(--g-panel-2); overflow: hidden; }
.gantt-linked .gl-card-h { display: flex; align-items: center; gap: 8px; padding: 7px 10px; cursor: pointer; }
.gantt-linked .gl-card-h:hover { background: var(--g-bg-grid); }
.gantt-linked .gl-ic { flex: none; font-size: 13px; }
.gantt-linked .gl-t { flex: 1; font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gantt-linked .gl-unlink { flex: none; border: none; background: transparent; color: var(--ink-mute); cursor: pointer; font-size: 11px; text-decoration: underline; }
.gantt-linked .gl-unlink:hover { color: var(--danger-text); }
.gantt-linked .gl-body { display: none; padding: 8px 10px 10px; font-size: 12px; border-top: 1px solid var(--g-bg-grid); }
.gantt-linked .gl-card.open .gl-body { display: block; }
.g-rl-badge { font-size: 9px; font-weight: 700; color: var(--ink-mute); flex: none; margin-left: 2px;
  background: var(--g-bg-grid); border-radius: 5px; padding: 1px 4px; }

/* =========================================================
   PERFORMANCE TAB — career metrics table + dashboard
   Full-width takeover of the .layout area.
   ========================================================= */

/* When the performance tab is active, hide the standard detail pane —
   .performance-view (its sibling inside .layout) takes over full-width. */
.layout.performance-active > .detail-pane {
  display: none !important;
}

.performance-view {
  display: flex;
  flex-direction: column;
  height: calc(100vh - var(--topbar-h) - var(--folder-taskbar-h));
  overflow-y: auto;
  overflow-x: hidden;
  background: var(--bg);
}
.performance-view[hidden] { display: none; }

/* ----- Header / toolbar ----- */
.perf-header {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 18px 28px 14px;
  border-bottom: 1px solid var(--border-soft);
  background: var(--bg-elev);
  flex-wrap: wrap;
}
.perf-title-block { display: flex; flex-direction: column; gap: 2px; margin-right: auto; }
.perf-title { font-size: 18px; font-weight: 700; color: var(--ink); letter-spacing: -0.01em; }
.perf-subtitle { font-size: 12px; color: var(--ink-mute); }
.perf-toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* Generate-report button — deliberately dark rather than accent, so the
   report action reads as separate from the purple primary action next to
   it. The values are tokens (--btn-dark / --btn-dark-hover) rather than
   inline hex so this stays the ONE documented exception to the accent
   system instead of looking like a stray colour. */
.perf-report-btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 14px;
  border-radius: var(--radius-md);
  background: var(--btn-dark);
  color: #ffffff;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
  transition: all 0.15s;
  box-shadow: 0 1px 2px rgba(35, 0, 76, 0.18);
}
.perf-report-btn:hover { background: var(--btn-dark-hover); box-shadow: 0 4px 12px rgba(35, 0, 76, 0.28); transform: translateY(-1px); }
.perf-report-btn:active { transform: translateY(0); }
.perf-report-btn svg { flex: none; }


/* ----- Dashboard cards ----- */
.perf-dashboard {
  padding: 18px 28px;
  border-bottom: 1px solid var(--border-soft);
  background: var(--bg-elev);
  overflow-x: auto;
}
.perf-dash-filters {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  flex-wrap: wrap;
}
.perf-dash-filters .perf-filter-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
}
.perf-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 12px;
}
.perf-card {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.perf-card.perf-card-total {
  background: var(--accent-bg);
  border-color: var(--accent-bg-strong);
}
.perf-card-value {
  font-size: 26px;
  font-weight: 800;
  color: var(--ink);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.perf-card.perf-card-total .perf-card-value { color: var(--accent-text); }
.perf-card-label {
  font-size: 12px;
  color: var(--ink-mute);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.perf-dash-empty { font-size: 13px; color: var(--ink-mute); padding: 4px 0; }

/* ----- Table region ----- */
.perf-table-region {
  flex: 1;
  min-height: 460px; /* never collapses under a tall dashboard */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0 28px 16px;
}
.perf-table-scroll {
  margin-top: 14px;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  overflow: auto;
  background: var(--bg-elev);
  flex: 1;
  min-height: 0;
}
.perf-table {
  border-collapse: separate;
  border-spacing: 0;
  table-layout: fixed;
  width: max-content;
  min-width: 100%;
  font-size: 12.5px;
}
.perf-table th, .perf-table td {
  border-right: 1px solid var(--border-soft);
  border-bottom: 1px solid var(--border-soft);
  padding: 0;
  position: relative;
  vertical-align: middle;
  overflow: hidden;
}
.perf-table th:last-child, .perf-table td:last-child { border-right: none; }

/* Header row */
.perf-table thead th {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--bg-sunk);
  font-weight: 600;
  color: var(--ink-soft);
  text-align: left;
  user-select: none;
  height: 36px;
  vertical-align: middle;
  box-shadow: 0 1px 0 var(--border-soft); /* keeps a crisp edge while frozen */
}
.perf-th-inner {
  display: flex;
  align-items: center;
  /* gap 4 -> 9: the filter and menu halos below are 26px, and at 4px they
     overlapped — each button kept only 21px of reachable width. */
  gap: 9px;
  padding: 3px 8px;
  height: 100%;
  cursor: grab;
}
.perf-th-inner.dragging { cursor: grabbing; }
.perf-th-label {
  flex: 1;
  min-width: 0;
  /* allow up to two lines for multi-word labels, then ellipsis */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  white-space: normal;
  line-height: 1.18;
  font-size: 11.5px;
  outline: none;
  border-radius: 3px;
  padding: 2px 3px;
}
.perf-th-label:focus { background: var(--bg-elev); box-shadow: 0 0 0 2px var(--accent-ring); white-space: normal; }
.perf-th-filter {
  border: none;
  background: transparent;
  color: var(--ink-faint);
  font-size: 9px;
  line-height: 1;
  padding: 2px 3px;
  cursor: pointer;
  border-radius: 4px;
  position: relative;
  opacity: 0;
  transition: opacity 0.12s, color 0.12s;
}
.perf-table th:hover .perf-th-filter { opacity: 0.7; }
.perf-th-filter:hover { opacity: 1; color: var(--accent); background: var(--bg-sunk); }
.perf-th-filter.active { opacity: 1; color: var(--accent); }
.perf-th-filter.active::after {
  content: "";
  position: absolute;
  top: -1px; right: -1px;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--accent);
}
.perf-clear-filters { color: var(--accent); }

/* Density segmented control (toolbar) */
.perf-density-seg { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.perf-density-seg button {
  border: none;
  background: var(--surface);
  color: var(--ink-mute);
  font-size: 11px;
  padding: 4px 8px;
  cursor: pointer;
  line-height: 1;
}
.perf-density-seg button + button { border-left: 1px solid var(--border); }
.perf-density-seg button.sel { background: var(--accent-btn); color: #fff; }

/* Column analytics footer */
.perf-table tfoot td {
  position: sticky;
  bottom: 0;
  background: var(--bg-sunk);
  border-top: 1px solid var(--border);
  font-size: 11px;
  color: var(--ink-mute);
  padding: 5px 8px;
  vertical-align: middle;
  z-index: 4;
}
.perf-foot-stat { display: flex; flex-direction: column; gap: 1px; line-height: 1.25; }
.perf-foot-stat b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.perf-foot-label { font-size: 9px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-faint); }

/* Filter popover */
.perf-filter-pop { min-width: 210px; padding: 8px; }
.perf-filter-modes { display: flex; gap: 3px; margin-bottom: 7px; }
.perf-filter-mode {
  flex: 1;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink-mute);
  font-size: 10.5px;
  padding: 3px 4px;
  border-radius: 5px;
  cursor: pointer;
}
.perf-filter-mode.sel { background: var(--accent-btn); border-color: var(--accent); color: #fff; }
.perf-filter-input, .perf-filter-searchset { width: 100%; margin-bottom: 7px; }
.perf-filter-list { max-height: 180px; overflow-y: auto; margin-bottom: 7px; }
.perf-filter-check {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 4px 6px;
  border-radius: 5px;
  cursor: pointer;
  font-size: 12.5px;
  color: var(--ink);
}
.perf-filter-check:hover { background: var(--bg-sunk); }
.perf-filter-check input { accent-color: var(--accent); cursor: pointer; }
.perf-filter-range { display: flex; gap: 8px; margin-bottom: 7px; }
.perf-filter-range label { flex: 1; font-size: 11px; color: var(--ink-mute); display: flex; flex-direction: column; gap: 3px; }
.perf-filter-range input {
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg-elev);
  color: var(--ink);
  font-size: 12.5px;
  padding: 4px 7px;
  outline: none;
  font-variant-numeric: tabular-nums;
}
.perf-filter-range input:focus { border-color: var(--accent); }
.perf-filter-presets { display: flex; gap: 4px; margin-bottom: 7px; }
.perf-filter-presets button {
  flex: 1;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink-mute);
  font-size: 10.5px;
  padding: 3px 4px;
  border-radius: 5px;
  cursor: pointer;
  white-space: nowrap;
}
.perf-filter-presets button:hover { border-color: var(--accent); color: var(--accent); }
.perf-filter-foot { display: flex; justify-content: flex-end; }
.perf-filter-clear {
  border: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--ink-mute);
  font-size: 11px;
  padding: 3px 10px;
  border-radius: 5px;
  cursor: pointer;
}
.perf-filter-clear:hover { border-color: var(--accent); color: var(--ink); }
.perf-filter-empty {
  text-align: center;
  color: var(--ink-mute);
  padding: 20px;
  font-size: 13px;
}
.perf-filter-empty-clear { color: var(--accent); cursor: pointer; text-decoration: underline; }
.perf-th-menu {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  border: none;
  background: transparent;
  color: var(--ink-faint);
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
.perf-th-menu:hover { background: var(--border); color: var(--ink); }

/* Column resize handle */
.perf-col-resize {
  position: absolute;
  top: 0;
  right: -3px;
  width: 7px;
  height: 100%;
  cursor: col-resize;
  z-index: 6;
}
.perf-col-resize::after {
  content: "";
  position: absolute;
  top: 8px;
  bottom: 8px;
  left: 3px;
  width: 1px;
  background: transparent;
  transition: background 0.12s;
}
.perf-col-resize:hover::after,
.perf-col-resize.resizing::after { background: var(--accent); }

/* Drop indicator when reordering columns */
.perf-table th.perf-drop-left { box-shadow: inset 3px 0 0 var(--accent); }
.perf-table th.perf-drop-right { box-shadow: inset -3px 0 0 var(--accent); }

/* Body cells */
.perf-table tbody tr:hover td { background: var(--bg); }
.perf-table tbody td { background: var(--bg-elev); height: var(--perf-row-h, 29px); }
.perf-cell {
  display: flex;
  align-items: center;
  width: 100%;
  height: 100%;
  min-height: var(--perf-row-h, 29px);
}
/* Density modes — only change row height/padding; every colour, radius and
   token is inherited unchanged. Compact is the existing default. */
.performance-view { --perf-row-h: 29px; }
.performance-view.perf-density-comfortable { --perf-row-h: 48px; }
.performance-view.perf-density-roomy { --perf-row-h: 64px; }
.performance-view.perf-density-comfortable .perf-cell-value { padding: 8px; }
.performance-view.perf-density-roomy .perf-cell-value { padding: 12px 8px; }
.perf-cell-value {
  flex: 1;
  padding: 4px 8px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink);
  cursor: text;
  min-width: 0;
  line-height: 1.45;
}
/* One-line by default; expand to full (wrapping) text while the cell is
   active/focused, then collapse back to one line on blur. Expanded cells
   overlay neighbours so long values stay readable without shifting layout. */
.perf-cell-value.perf-cell-expanded {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  position: relative;
  z-index: 6;
  background: var(--bg-elev);
  box-shadow: 0 2px 8px rgba(0,0,0,0.12);
  border-radius: var(--radius-sm);
}
.perf-cell-value:empty::before {
  content: "—";
  color: var(--ink-faint);
}
.perf-cell-value[contenteditable="true"] {
  white-space: normal;
  overflow: visible;
  cursor: text;
}
.perf-cell-value:focus {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--accent-ring);
  background: var(--bg-elev);
}
/* Compact "+" (top-right of the deliverable-type cell): Duplicate & Transform */
.perf-plus-btn {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 17px;
  height: 17px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink-mute);
  font-size: 12px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s, background 0.12s, border-color 0.12s, color 0.12s;
  z-index: 2;
}
.perf-table tr:hover .perf-plus-btn { opacity: 1; }
.perf-plus-btn:hover {
  background: var(--accent-btn);
  border-color: var(--accent);
  color: #fff;
}
.perf-plus-btn:active { transform: translateY(1px); }
[data-theme="dark"] .perf-plus-btn { background: var(--bg-elev); }
/* The universal transform button is a touch wider — keep it tidy */
.perf-transform-btn { font-weight: 600; }

/* Transform popup: grouped, searchable list of all Level-2 types */
.perf-transform-pop { min-width: 280px; max-width: 340px; }
.perf-transform-head {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding: 9px 12px 6px;
}
.perf-dd-group {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-mute);
  padding: 8px 12px 3px;
  position: sticky;
  top: 0;
  background: var(--surface);
}
[data-theme="dark"] .perf-dd-group { background: var(--bg-elev); }

/* Picker cells (dropdown / date) — show pointer, hint they're clickable */
.perf-table td.perf-cell-picker .perf-cell { cursor: pointer; }
.perf-table td.perf-cell-picker .perf-cell-value { cursor: pointer; }
.perf-table td.perf-cell-picker:hover { background: var(--bg-sunk); }

/* Date picker popover */
.perf-datepicker { padding: 10px; display: flex; flex-direction: column; gap: 8px; min-width: 200px; }
.perf-date-input {
  font: inherit; padding: 8px 10px; border: 1px solid var(--border);
  border-radius: var(--radius-md); background: var(--surface); color: var(--ink);
}
.perf-date-clear {
  font-size: 12px; color: var(--ink-soft); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 6px 10px; cursor: pointer;
}

/* ----- Duration picker (Time Spent column) ----- */
.perf-durpicker { width: 196px; padding: 10px; }
.perf-dur-input {
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elev);
  color: var(--ink);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  padding: 5px 8px;
  outline: none;
  margin-bottom: 8px;
}
.perf-dur-input:focus { border-color: var(--accent); }
.perf-dur-wheels {
  display: flex;
  align-items: stretch;
  justify-content: center;
  gap: 4px;
}
.perf-dur-col { flex: 1; text-align: center; min-width: 0; }
.perf-dur-label {
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
  margin-bottom: 3px;
}
.perf-dur-sep { align-self: center; padding-top: 12px; color: var(--ink-faint); font-weight: 700; }
.perf-dur-wheel {
  height: 78px;                 /* 3 items visible, selected row centred */
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  overscroll-behavior: contain;
  padding: 26px 0;
  background: var(--bg-sunk);
  border-radius: var(--radius-sm);
  scrollbar-width: none;
  -webkit-mask-image: linear-gradient(transparent, #000 32%, #000 68%, transparent);
  mask-image: linear-gradient(transparent, #000 32%, #000 68%, transparent);
}
.perf-dur-wheel::-webkit-scrollbar { display: none; }
.perf-dur-item {
  height: 26px;
  line-height: 26px;
  scroll-snap-align: center;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-mute);
  cursor: pointer;
  user-select: none;
}
.perf-dur-item.sel { color: var(--ink); font-weight: 700; }
.perf-dur-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 9px;
}
.perf-dur-done {
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #fff;
  border-radius: var(--radius-sm);
  font-size: 11.5px;
  font-weight: 600;
  padding: 4px 14px;
  cursor: pointer;
}
.perf-dur-done:hover { filter: brightness(1.08); }
.perf-date-clear:hover { background: var(--bg-sunk); color: var(--ink); }

/* Row drag handle + drop indicators */
.perf-rownum-inner { display: flex; align-items: center; justify-content: center; gap: 3px; }
.perf-row-grip {
  cursor: grab; color: var(--ink-soft); opacity: 0; font-size: 12px;
  line-height: 1; user-select: none; transition: opacity 0.12s;
}
.perf-table tbody tr:hover .perf-row-grip { opacity: 0.7; }
.perf-row-grip:hover { opacity: 1; color: var(--accent); }
.perf-row-grip:active { cursor: grabbing; }
.perf-row-dragging { opacity: 0.5; }
.perf-row-drop-above td { box-shadow: inset 0 2px 0 0 var(--accent); }
.perf-row-drop-below td { box-shadow: inset 0 -2px 0 0 var(--accent); }

/* Row number / handle column */
.perf-rownum {
  width: 44px;
  text-align: center;
  color: var(--ink-faint);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  background: var(--bg-sunk) !important;
  user-select: none;
  position: sticky;
  left: 0;
  z-index: 3;
}
.perf-table thead .perf-rownum { z-index: 7; }
.perf-row-del {
  border: none;
  background: transparent;
  color: var(--ink-faint);
  cursor: pointer;
  font-size: 13px;
  padding: 6px;
  border-radius: 4px;
  opacity: 0;
}
.perf-table tbody tr:hover .perf-row-del { opacity: 1; }
.perf-row-del:hover { color: var(--danger-text); background: var(--danger-bg); }

/* Add-column header button */
.perf-addcol-th {
  width: 46px;
  background: var(--bg-sunk);
  text-align: center;
}
.perf-addcol-btn, .perf-addrow-btn {
  border: 1px dashed var(--border);
  background: transparent;
  color: var(--ink-mute);
  cursor: pointer;
  border-radius: var(--radius-sm);
  font-size: 13px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.perf-addcol-btn { width: 28px; height: 28px; font-size: 16px; }
.perf-addcol-btn:hover, .perf-addrow-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
  border-style: solid;
}
.perf-addrow-btn {
  padding: 5px 12px;
  font-weight: 600;
  font-size: 12.5px;
  border-style: solid;
  border-color: var(--accent);
  color: var(--accent);
}
.perf-addrow-btn:hover { background: var(--accent-btn); color: #fff; }

.perf-empty-state {
  text-align: center;
  padding: 48px 20px;
  color: var(--ink-mute);
}
.perf-empty-state .perf-empty-mark { font-size: 30px; opacity: 0.4; margin-bottom: 10px; }

/* ----- Dropdown popover (cell option picker) ----- */
.perf-dropdown {
  position: fixed;
  z-index: 1200;
  min-width: 200px;
  max-width: 300px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 6px;
  max-height: 320px;
  overflow-y: auto;
}
.perf-dropdown[hidden] { display: none; }
.perf-dd-search {
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 7px 9px;
  font-size: 13px;
  margin-bottom: 6px;
  background: var(--bg);
  color: var(--ink);
  font-family: inherit;
}
.perf-dd-search:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
.perf-dd-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 13px;
  color: var(--ink);
}
.perf-dd-option:hover, .perf-dd-option.active { background: var(--accent-bg); }
.perf-dd-option.selected { font-weight: 600; }
.perf-dd-option .perf-dd-check { width: 14px; color: var(--accent); flex-shrink: 0; }
.perf-dd-option .perf-dd-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Rename / delete actions — hover-revealed, minimal */
.perf-dd-acts {
  display: flex;
  align-items: center;
  gap: 2px;
  opacity: 0;
  transition: opacity 0.12s;
  flex-shrink: 0;
}
.perf-dd-option:hover .perf-dd-acts { opacity: 1; }
.perf-dd-act {
  border: none;
  background: transparent;
  color: var(--ink-mute);
  font-size: 12px;
  line-height: 1;
  padding: 3px 4px;
  border-radius: 4px;
  cursor: pointer;
}
.perf-dd-act:hover { background: var(--bg-sunk); color: var(--ink); }
.perf-dd-act-del:hover { color: var(--danger-text); }
.perf-dd-rename-input {
  flex: 1;
  min-width: 0;
  border: 1px solid var(--accent);
  border-radius: 4px;
  background: var(--bg-elev);
  color: var(--ink);
  font-size: 13px;
  padding: 2px 6px;
  outline: none;
}
.perf-dd-warn {
  font-size: 11px;
  color: var(--ink-mute);
  padding: 5px 10px 2px;
  opacity: 0;
  max-height: 0;
  overflow: hidden;
  transition: opacity 0.15s, max-height 0.15s;
}
.perf-dd-warn.show { opacity: 1; max-height: 40px; }

/* Orphaned dropdown value (option was deleted) — subtle amber dot */
.perf-cell-orphan .perf-cell-value { color: var(--ink); }
.perf-orphan-dot {
  flex-shrink: 0;
  color: var(--gold-text);
  font-size: 9px;
  margin-right: 4px;
  cursor: help;
}
[data-theme="dark"] .perf-orphan-dot { color: #ffcf4d; }
.perf-dd-option .perf-dd-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.perf-dd-option-del {
  border: none; background: transparent; color: var(--ink-faint);
  cursor: pointer; font-size: 13px; padding: 2px 5px; border-radius: 4px; opacity: 0;
}
.perf-dd-option:hover .perf-dd-option-del { opacity: 1; }
.perf-dd-option-del:hover { color: var(--danger-text); background: var(--danger-bg); }
.perf-dd-sep { height: 1px; background: var(--border-soft); margin: 5px 2px; }
.perf-dd-add {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; border-radius: var(--radius-sm);
  cursor: pointer; font-size: 13px; color: var(--accent); font-weight: 600;
}
.perf-dd-add:hover { background: var(--accent-bg); }
.perf-dd-clear {
  padding: 7px 10px; border-radius: var(--radius-sm);
  cursor: pointer; font-size: 12px; color: var(--ink-mute);
}
.perf-dd-clear:hover { background: var(--bg-sunk); color: var(--ink); }
.perf-dd-empty { padding: 10px; font-size: 12px; color: var(--ink-faint); text-align: center; }

/* Column header menu popover */
.perf-colmenu {
  position: fixed;
  z-index: 1200;
  min-width: 180px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 6px;
}
.perf-colmenu[hidden] { display: none; }
.perf-colmenu button {
  display: flex; align-items: center; gap: 9px; width: 100%;
  border: none; background: transparent; text-align: left;
  padding: 9px 11px; border-radius: var(--radius-sm);
  font-size: 13px; color: var(--ink); cursor: pointer; font-family: inherit;
}
.perf-colmenu button:hover { background: var(--accent-bg); }
.perf-colmenu button.danger { color: var(--danger-text); }
.perf-colmenu button.danger:hover { background: var(--danger-bg); }
.perf-colmenu .perf-colmenu-sep { height: 1px; background: var(--border-soft); margin: 5px 2px; }
/* M-3: the stacked-card note is desktop-hidden; the @media block below reveals
   it exactly where the <thead> (filter funnels + column tools) is hidden. */
.perf-mobile-tools-note { display: none; }

/* ----- Mobile adaptations for performance view ----- */
@media (max-width: 760px) {
  .perf-header { padding: 14px 16px 12px; }
  .perf-dashboard { padding: 14px 16px; }
  .perf-table-region { padding: 0 16px 80px; }
  /* M-3: the honest-absence note, revealed only where the <thead> (and its
     filter funnels / column tools) is hidden by the stacked-card rule below. */
  .perf-mobile-tools-note {
    display: flex; align-items: center; gap: 8px;
    margin: 12px 2px 2px;
    padding: 10px 12px;
    font-size: 12.5px; line-height: 1.4;
    color: var(--ink-mute);
    background: var(--bg-sunk);
    border-radius: var(--radius-md);
  }
  .perf-mobile-tools-note svg { flex: 0 0 auto; color: var(--accent); }
  .perf-cards { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
  .perf-card-value { font-size: 22px; }
  /* On mobile the layout becomes display:block; force the perf view to own the screen. */
  body.m-perf .layout > .detail-pane { display: none !important; }
  .performance-view { height: auto; min-height: calc(100dvh - var(--topbar-h) - var(--folder-taskbar-h)); }

  /* The density toggle exists so desktop users can trade row height for
     visible rows — on a phone, compact is the only spacing that makes
     sense, so the control is hidden and every density is forced to it. */
  .perf-density-seg { display: none; }
  .performance-view.perf-density-comfortable,
  .performance-view.perf-density-roomy { --perf-row-h: 29px; }
  .performance-view.perf-density-comfortable .perf-cell-value,
  .performance-view.perf-density-roomy .perf-cell-value { padding: 4px 8px; }

  /* ---------------------------------------------------------------
     Stacked-card table. This is a pure CSS reflow of the exact same
     DOM the desktop grid uses (see renderTable() in app.js) — every
     row stays a real <tr>/<td> with its editing, filtering, and
     drag-reorder handlers intact. Only display/layout changes; column
     drag-reorder, resize, and per-column filter triggers live in the
     now-hidden <thead> and are desktop-only, same as any spreadsheet
     app collapsing to a single-column mobile view.
     --------------------------------------------------------------- */
  .perf-table-scroll { overflow-x: hidden; }
  .perf-table { display: block; width: 100%; min-width: 0; table-layout: auto; }
  .perf-table colgroup, .perf-table thead, .perf-table tfoot { display: none; }
  .perf-table tbody { display: block; }
  .perf-table tbody tr {
    display: block;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--bg-elev);
    margin-bottom: 10px;
    padding: 2px 12px;
  }
  .perf-table tbody tr:hover td { background: transparent; }
  .perf-table tbody td {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    width: 100%;
    height: auto;
    min-height: 0;
    background: transparent !important;
    border: none;
    border-top: 1px solid var(--border-soft);
    padding: 9px 0;
  }
  .perf-table tbody td:first-child { border-top: none; }
  .perf-table tbody td:last-child { display: none; } /* the empty filler under desktop's "add column" th */
  .perf-table td[data-label]::before {
    content: attr(data-label);
    flex-shrink: 0;
    width: 38%;
    max-width: 130px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ink-mute);
    padding-top: 6px;
  }
  .perf-table td[data-label] .perf-cell,
  .perf-table td[data-label] .perf-cell-value { min-height: 0; flex: 1; }
  .perf-table td[data-label] .perf-cell-value { white-space: normal; }
  .perf-rownum-inner { justify-content: flex-start; }
  .perf-row-del, .perf-plus-btn { opacity: 1; } /* both were :hover-only, meaningless on touch */
}

/* ----- Performance: duplicate short-title detection ----- */
.perf-table td.perf-cell-dup {
  background: color-mix(in srgb, var(--gold) 12%, var(--bg-elev)) !important;
  box-shadow: inset 0 0 0 1.5px var(--gold);
}
[data-theme="dark"] .perf-table td.perf-cell-dup {
  background: color-mix(in srgb, var(--gold) 16%, var(--bg-elev)) !important;
}
.perf-table td.perf-cell-dup .perf-cell-value { color: var(--gold-text); font-weight: 600; }
.perf-dup-flag {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  color: var(--gold-text);
  font-size: 12px;
  cursor: default;
}
.perf-table td.perf-cell-dup-pulse {
  animation: perfDupPulse 1.2s ease;
}
@keyframes perfDupPulse {
  0%, 100% { box-shadow: inset 0 0 0 1.5px var(--gold); }
  30%      { box-shadow: inset 0 0 0 3px var(--gold), 0 0 0 3px color-mix(in srgb, var(--gold) 25%, transparent); }
}

/* Toolbar duplicate badge */
.perf-dup-badge {
  border: 1px solid var(--gold);
  background: color-mix(in srgb, var(--gold) 10%, var(--bg-elev));
  color: var(--gold-text);
  font-weight: 600;
  font-size: 12px;
  padding: 6px 12px;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, transform 0.1s;
}
.perf-dup-badge:hover { background: color-mix(in srgb, var(--gold) 20%, var(--bg-elev)); }
.perf-dup-badge:active { transform: scale(0.97); }
.perf-dup-badge[hidden] { display: none; }

/* =========================================================
   MOBILE TYPOGRAPHY — bigger numbering, smaller item text
   =========================================================
   Placed last, after the "iOS Safari zooms a focused field < 16px" block
   above, because that block sets `font-size: 16px !important` on every
   input/textarea. Two of the four sections put their item text in a real
   field (.steps-title-input, .checklist-item-text), so their size is
   unreachable without matching that !important — hence the ones below.
   The iOS guarantee is preserved rather than discarded: the 16px floor is
   restored on :focus, and zoom only ever triggers on a FOCUSED field, so
   the smaller resting size is safe.

   --mobile-text-shrink is one shared knob (0.7 = the requested 30% cut)
   applied to all four sections' item text, so the numbering-to-text ratio
   stays identical across Processes, Checklists, Templates and Insights.
   It multiplies whatever each element's current size is rather than
   replacing it, so the Steps grid's own --density-scale still applies on
   top: Compact/Comfortable/Roomy each shrink by 30% from THEIR size, not
   to one flat value. Desktop is untouched — every rule here is inside a
   mobile scope. */
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  /* --- Processes: step / sub-step rows --- */
  /* Base sizes are stated as literals (16/15px) because that IS what these
     currently render at on mobile — the !important rule above flattens the
     --st-font-title-* tokens, so those tokens are not the "current size"
     the 30% is measured from. */
  body:not([data-layout-mode="desktop"]) .steps-row-parent .stcol-title .steps-title-input {
    font-size: calc(16px * var(--density-scale, 1) * var(--mobile-text-shrink)) !important;
  }
  body:not([data-layout-mode="desktop"]) .steps-row-child .stcol-title .steps-title-input {
    font-size: calc(15px * var(--density-scale, 1) * var(--mobile-text-shrink)) !important;
  }
  /* Back to 16px while focused → iOS still never zooms on tap-to-edit.
     Must repeat the full .steps-row-* .stcol-title chain: the sizing rules
     above score (0,4,1), so a shorter `.steps-title-input:focus` selector
     (0,3,1) LOSES to them even with !important on both, and the field
     silently stayed sub-16px while focused. Measured, not assumed. */
  body:not([data-layout-mode="desktop"]) .steps-row-parent .stcol-title .steps-title-input:focus,
  body:not([data-layout-mode="desktop"]) .steps-row-child .stcol-title .steps-title-input:focus {
    font-size: 16px !important;
  }

  /* --- Checklists: item text (also a real <input>) --- */
  body:not([data-layout-mode="desktop"]) .checklist-item-text {
    font-size: calc(15px * var(--mobile-text-shrink)) !important;
  }
  body:not([data-layout-mode="desktop"]) .checklist-item-text:focus { font-size: 16px !important; }

  /* --- Shared item cards — these are what Templates and Insights (and
         every other section) list their documents with, so one rule
         covers the remaining two sections. --- */
  body:not([data-layout-mode="desktop"]) .list-item-title {
    font-size: calc(15px * var(--mobile-text-shrink));
  }
}
/* Forced-mobile mirror — this repo's convention: mobile rules exist twice,
   width-gated and mode-gated. If you touch one, touch the other. */
body[data-layout-mode="mobile"] .steps-row-parent .stcol-title .steps-title-input {
  font-size: calc(16px * var(--density-scale, 1) * var(--mobile-text-shrink)) !important;
}
body[data-layout-mode="mobile"] .steps-row-child .stcol-title .steps-title-input {
  font-size: calc(15px * var(--density-scale, 1) * var(--mobile-text-shrink)) !important;
}
body[data-layout-mode="mobile"] .steps-row-parent .stcol-title .steps-title-input:focus,
body[data-layout-mode="mobile"] .steps-row-child .stcol-title .steps-title-input:focus {
  font-size: 16px !important;
}
body[data-layout-mode="mobile"] .checklist-item-text {
  font-size: calc(15px * var(--mobile-text-shrink)) !important;
}
body[data-layout-mode="mobile"] .checklist-item-text:focus { font-size: 16px !important; }
body[data-layout-mode="mobile"] .list-item-title {
  font-size: calc(15px * var(--mobile-text-shrink));
}

/* ===========================================================
   TOUCH-TARGET FLOOR (mobile)
   ===========================================================
   Audit finding H-5. A 44px floor is not a style preference — below it
   mis-tap rates climb sharply, and the worst offenders here were the two
   controls used most often in the app's core loop (the step status dot at
   17x17 and the step row's ⋮ at 29x29).

   Two things had to change, not one. Several of these controls ALREADY
   carried an invisible oversized ::before expander — the status dot's is
   a full 44x44 — but `.steps-table tr.steps-row` sets `overflow: hidden`
   (it needs to, to clip the row's rounded corners and the title
   textarea), so the expander was being cropped back to the row's height
   and the column's width. Probing the real hit box with elementFromPoint
   rather than trusting getBoundingClientRect showed 31x34, not 44x44.
   So the fix is to make the CELL the target: widen the two action columns
   to 44px on mobile and let the buttons fill their cell in both axes.

   Density stays a typography/spacing control, not a target-size control —
   the same call Apple's own compact modes make. Compact still tightens
   text and padding; it no longer shrinks anything you have to hit.
   --------------------------------------------------------- */
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  /* Declared on .steps-grid-card, NOT body: the per-breakpoint blocks
     above set these same tokens on .steps-grid-card itself, and a
     declaration on a descendant always beats one inherited from body. */
  /* Density pass revision: the cells no longer widen to 44px (two 44px
     icon columns cost the title ~23% of a phone's width). The buttons
     fill their narrow cell and full row height, and asymmetric halos
     rebuild the 44px hit width — leaning AWAY from each other (status
     toward the title, ⋮ toward the row edge) so the two hit areas don't
     collide in the gap between them. Rows have no overflow clipping any
     more (flat WBS rows), so the halos genuinely extend the hit box —
     the reason the cells were widened in the first place no longer holds. */
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row-parent {
    min-height: max(44px, calc(var(--st-row-h-parent) * var(--density-scale)));
  }
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row-child {
    min-height: max(34px, calc(var(--st-row-h-child) * var(--density-scale)));
  }
  body:not([data-layout-mode="desktop"]) .steps-table .stcol-status,
  body:not([data-layout-mode="desktop"]) .steps-table .stcol-actions { align-self: stretch; padding: 0; }
  /* User-requested (annotated screenshot): the status dot and ⋮ live to
     the LEFT of the number on mobile — [dot][⋮][number][title] — so the
     title runs clean to the row's right edge. Cell order only; the DOM
     (and desktop) keep Title|Status|Actions. An empty status cell (non-
     leaf parents) keeps its width so the two control columns stay
     vertically gridded across parent and child rows. */
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row .stcol-status { order: -2; }
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row .stcol-actions { order: -1; }
  /* "1 - Title" number format (user-annotated): the number hugs its own
     width with a dash separator instead of right-aligning in a fixed
     column — the fixed boxes existed for the desktop grid's alignment,
     which the phone outline doesn't need. */
  body:not([data-layout-mode="desktop"]) .steps-row-num {
    width: auto; min-width: 0; text-align: left; padding-right: 0; margin-right: 7px;
  }
  body:not([data-layout-mode="desktop"]) .steps-row-num::after { content: ' -'; }
  /* Group rhythm (user-annotated): sub-steps pack tight (no extra gaps),
     the parent keeps a slightly LARGER gap to its first sub-step, and a
     clear band separates one step group from the next. */
  body:not([data-layout-mode="desktop"]) .steps-table tbody { gap: calc(1.9px * var(--density-scale)); }
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row-parent { margin-top: 4.5px; margin-bottom: 1px; }
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row-parent:first-child { margin-top: 0; }
  /* Selection reads from the row tint alone on mobile (user-annotated) —
     the inset accent bar added a stripe left of the dot rail. */
  body:not([data-layout-mode="desktop"]) .steps-table tr.steps-row.selected { box-shadow: none; }
  body:not([data-layout-mode="desktop"]) .steps-status-dot-btn,
  body:not([data-layout-mode="desktop"]) .steps-row-menu-btn {
    width: 100%; height: 100%; position: relative;
  }
  body:not([data-layout-mode="desktop"]) .steps-status-dot-btn::before {
    content: ''; position: absolute; inset: 0 -5px 0 -13px;
  }
  body:not([data-layout-mode="desktop"]) .steps-row-menu-btn::before {
    content: ''; position: absolute; inset: 0 -12px 0 -2px;
  }

  /* Section chrome. The breadcrumb Home segment is the ONLY control that
     opens the folder tree, and it was 30x30. */
  body:not([data-layout-mode="desktop"]) .breadcrumb-home,
  body:not([data-layout-mode="desktop"]) .breadcrumb-home { min-width: 44px; min-height: 44px; }
  body:not([data-layout-mode="desktop"]) .breadcrumb-segment { min-height: 44px; }
  body:not([data-layout-mode="desktop"]) .folder-taskbar .iconbtn.small { width: 44px; height: 44px; }

  /* Toolbars and inline actions that sat in the 30-38px band. */
  body:not([data-layout-mode="desktop"]) .ghost-btn,
  body:not([data-layout-mode="desktop"]) .steps-status-btn,
  body:not([data-layout-mode="desktop"]) .steps-sort-seg button,
  body:not([data-layout-mode="desktop"]) .var-sort-btn,
  body:not([data-layout-mode="desktop"]) .ins-kind-seg button,
  body:not([data-layout-mode="desktop"]) .gt-btn,
  body:not([data-layout-mode="desktop"]) .perf-density-seg button,
  body:not([data-layout-mode="desktop"]) .linker-store-tabs button,
  body:not([data-layout-mode="desktop"]) select,
  body:not([data-layout-mode="desktop"]) .toolbar-font,
  body:not([data-layout-mode="desktop"]) .primary-btn,
  body:not([data-layout-mode="desktop"]) .danger-btn,
  body:not([data-layout-mode="desktop"]) .detail-empty-cta,
  body:not([data-layout-mode="desktop"]) .toolbar-fontsize { min-height: 44px; }
  body:not([data-layout-mode="desktop"]) .steps-status-btn,
  body:not([data-layout-mode="desktop"]) .steps-sort-seg button,
  body:not([data-layout-mode="desktop"]) .gt-btn { min-width: 44px; }
  /* The rich-text toolbar already had min-height:44px but ran 28-43px
     wide. It wraps, so widening costs a row, not a scroll. */
  body:not([data-layout-mode="desktop"]) .rich-toolbar button { min-width: 44px; }

  /* Destructive inline actions — the goal-section ✕ measured 9x13, the
     smallest target in the app and the one you least want to mis-hit. */

  /* Radius has to scale with the control. --radius-sm (10px) is sized for
     the 26-32px chrome these controls are at desktop widths, but mobile
     inflates every one of them to the 44px touch floor without touching
     their corners — leaving rows of 44px controls reading noticeably
     squarer than the 16px rows and buttons around them. Measured across
     Checklists, Templates, Insights, the Steps grid and the Gantt. */
  body:not([data-layout-mode="desktop"]) .iconbtn.small,
  body:not([data-layout-mode="desktop"]) .list-item-more,
  body:not([data-layout-mode="desktop"]) .toolbar-block,
  body:not([data-layout-mode="desktop"]) .toolbar-font,
  body:not([data-layout-mode="desktop"]) .toolbar-fontsize,
  body:not([data-layout-mode="desktop"]) .rich-toolbar button,
  body:not([data-layout-mode="desktop"]) .gt-btn,
  body:not([data-layout-mode="desktop"]) .steps-row-menu-btn { border-radius: var(--radius-md); }
  body:not([data-layout-mode="desktop"]) .var-act,
  body:not([data-layout-mode="desktop"]) .list-item-more,
  body:not([data-layout-mode="desktop"]) .goal-piece-check { min-width: 44px; min-height: 44px; }
}

/* Forced-mobile mirror — this repo's convention: every mobile rule exists
   twice. If you touch one, touch the other. */
body[data-layout-mode="mobile"] .steps-table tr.steps-row-parent {
  min-height: max(44px, calc(var(--st-row-h-parent) * var(--density-scale)));
}
body[data-layout-mode="mobile"] .steps-table tr.steps-row-child {
  min-height: max(34px, calc(var(--st-row-h-child) * var(--density-scale)));
}
body[data-layout-mode="mobile"] .steps-table .stcol-status,
body[data-layout-mode="mobile"] .steps-table .stcol-actions { align-self: stretch; padding: 0; }
body[data-layout-mode="mobile"] .steps-table tr.steps-row .stcol-status { order: -2; }
body[data-layout-mode="mobile"] .steps-table tr.steps-row .stcol-actions { order: -1; }
body[data-layout-mode="mobile"] .steps-row-num {
  width: auto; min-width: 0; text-align: left; padding-right: 0; margin-right: 7px;
}
body[data-layout-mode="mobile"] .steps-row-num::after { content: ' -'; }
body[data-layout-mode="mobile"] .steps-table tbody { gap: calc(1.9px * var(--density-scale)); }
body[data-layout-mode="mobile"] .steps-table tr.steps-row-parent { margin-top: 4.5px; margin-bottom: 1px; }
body[data-layout-mode="mobile"] .steps-table tr.steps-row-parent:first-child { margin-top: 0; }
body[data-layout-mode="mobile"] .steps-table tr.steps-row.selected { box-shadow: none; }
body[data-layout-mode="mobile"] .steps-status-dot-btn,
body[data-layout-mode="mobile"] .steps-row-menu-btn {
  width: 100%; height: 100%; position: relative;
}
body[data-layout-mode="mobile"] .steps-status-dot-btn::before {
  content: ''; position: absolute; inset: 0 -5px 0 -13px;
}
body[data-layout-mode="mobile"] .steps-row-menu-btn::before {
  content: ''; position: absolute; inset: 0 -12px 0 -2px;
}
body[data-layout-mode="mobile"] .breadcrumb-home,
body[data-layout-mode="mobile"] .breadcrumb-home { min-width: 44px; min-height: 44px; }
body[data-layout-mode="mobile"] .breadcrumb-segment { min-height: 44px; }
body[data-layout-mode="mobile"] .folder-taskbar .iconbtn.small { width: 44px; height: 44px; }
body[data-layout-mode="mobile"] .ghost-btn,
body[data-layout-mode="mobile"] .steps-status-btn,
body[data-layout-mode="mobile"] .steps-sort-seg button,
body[data-layout-mode="mobile"] .var-sort-btn,
body[data-layout-mode="mobile"] .ins-kind-seg button,
body[data-layout-mode="mobile"] .gt-btn,
body[data-layout-mode="mobile"] .perf-density-seg button,
body[data-layout-mode="mobile"] .linker-store-tabs button,
body[data-layout-mode="mobile"] select,
body[data-layout-mode="mobile"] .toolbar-font,
body[data-layout-mode="mobile"] .primary-btn,
body[data-layout-mode="mobile"] .danger-btn,
body[data-layout-mode="mobile"] .detail-empty-cta,
body[data-layout-mode="mobile"] .toolbar-fontsize { min-height: 44px; }
body[data-layout-mode="mobile"] .steps-status-btn,
body[data-layout-mode="mobile"] .steps-sort-seg button,
body[data-layout-mode="mobile"] .gt-btn { min-width: 44px; }
body[data-layout-mode="mobile"] .rich-toolbar button { min-width: 44px; }
body[data-layout-mode="mobile"] .var-act,
body[data-layout-mode="mobile"] .list-item-more,
body[data-layout-mode="mobile"] .goal-piece-check { min-width: 44px; min-height: 44px; }

/* ---- FAB dock-left (finding H-1) ----------------------------------
   Toggled by syncRadialAddButton() only while a Process document is open
   at mobile widths — the one screen whose rows put an interactive ⋮ hard
   against the right edge. Everywhere else the FAB stays bottom-right.
   Set on <body> rather than per-breakpoint so it wins over every .fab
   position rule (base, ≤768, ≤480, forced-mobile, .perf-fab). */
/* Both floating buttons move, not just the "+". The metadata FAB normally
   sits at right:96px — 20px to the left of the "+" — so mirroring it to
   left:96px keeps the same pair spacing and the same reading order, and
   leaves the row's 44px action column clear of both. Moving only the "+"
   just handed the collision to the metadata button (measured: it took over
   covering row 6's ⋮). */
body.fab-dock-left .fab { right: auto; left: 20px; }
body.fab-dock-left .mobile-meta-fab { right: auto; left: 96px; }

/* The goal sections' "+ Piece" button sat at y749-793 with the bottom nav
   starting at 755 — .detail-content's --fab-clearance padding stops at the
   card, and the last section's own trailing button fell past it. */
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  body:not([data-layout-mode="desktop"]) .goal-pieces { margin-bottom: calc(var(--fab-clearance) * 0.4); }
}
body[data-layout-mode="mobile"] .goal-pieces { margin-bottom: calc(var(--fab-clearance) * 0.4); }

/* (The old "cells widened to 44px, halos disabled" scheme is gone — the
   density pass returned the cells to their narrow tokens and rebuilt the
   44px hit width with ASYMMETRIC halos that lean away from each other,
   so the earlier steal-the-neighbour's-hit-area failure can't recur.) */

/* ---- Performance takeover (finding H-4) ----------------------------
   Performance is a full-width view with no folders, no items store and no
   sort — so the folder taskbar that sits above every other section has
   nothing to act on here. It used to stay visible and fully interactive
   because selectTab() returns early for this tab. Hidden outright rather
   than disabled: a row of dead controls reads worse than no row. */
body.perf-takeover { --folder-taskbar-h: 0px; }  /* reclaim the 52px in every height calc that reads this */
body.perf-takeover .folder-taskbar { display: none; }

/* ---- Menu / action-sheet icons (findings M-4, L-6) ------------------
   The folder context menu and the step action sheet were the last
   unadorned text lists in an otherwise icon-led app — and on mobile the
   action sheet is the PRIMARY step-management surface, so it's the one
   that most needed the scannability. */
.context-menu button { display: flex; align-items: center; gap: 9px; }
.context-menu .menu-icon,
.action-sheet-body button svg {
  flex: none;
  display: inline-flex;
  color: var(--ink-mute);
}
.context-menu button.danger .menu-icon,
.action-sheet-body button.danger svg { color: var(--danger-text); }
.action-sheet-body button { display: flex; align-items: center; gap: 11px; }
/* The label span already exists in openActionSheet's markup; let it take
   the remaining width so icons stay in a straight column. */
.action-sheet-body button > span { flex: 1; min-width: 0; text-align: left; }

/* ===========================================================
   ARCHIVE
   ===========================================================
   Borrows the Trash view's row treatment deliberately — the two are peers
   and should read as the same KIND of place, differing in tone (neutral
   here, destructive there) rather than in layout. */
.archive-loading { padding: 24px; text-align: center; color: var(--ink-mute); font-size: 13px; }
.archive-group { margin-bottom: 18px; }
.archive-group:last-child { margin-bottom: 0; }
.archive-group-title {
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-faint); margin-bottom: 8px;
}
.archive-row {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px;
  background: var(--surface);
  border: var(--card-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  margin-bottom: 6px;
  cursor: pointer;
  transition: background var(--duration-fast), transform var(--duration-fast);
}
.archive-row:hover { background: var(--accent-bg); }
.archive-row:active { transform: scale(0.995); }
.archive-row-icon { flex: none; display: inline-flex; color: var(--ink-faint); }
.archive-row-title {
  flex: 1; min-width: 0;
  font-size: 13.5px; font-weight: 500; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.archive-row-date { flex: none; font-size: 11.5px; color: var(--ink-mute); font-variant-numeric: tabular-nums; }
.archive-restore { flex: none; display: inline-flex; align-items: center; gap: 5px; }
.archive-empty { text-align: center; padding: 40px 20px; }
.archive-empty-mark { color: var(--ink-faint); opacity: 0.55; margin-bottom: 10px; display: flex; justify-content: center; }
.archive-empty-title { font-size: 15px; font-weight: 600; color: var(--ink); margin-bottom: 6px; }
.archive-empty-sub { font-size: 12.5px; color: var(--ink-mute); max-width: 380px; margin: 0 auto; line-height: 1.5; }

/* Toast with an inline undo (see toast()'s `action` argument). */
.toast-with-action { display: flex; align-items: center; gap: 14px; }
.toast-msg { flex: 1; min-width: 0; }
.toast-action {
  flex: none;
  font-weight: 700;
  font-size: 12.5px;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  color: inherit;
  opacity: 0.85;
  background: transparent;
  padding: 4px 2px;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.toast-action:hover { opacity: 1; }

@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  /* Date is the first thing to go when width is tight — the title and the
     restore action are what the row is for. */
  body:not([data-layout-mode="desktop"]) .archive-row-date { display: none; }
  body:not([data-layout-mode="desktop"]) .archive-restore { min-height: 44px; }
  body:not([data-layout-mode="desktop"]) .toast-action { min-height: 44px; display: inline-flex; align-items: center; }
}
body[data-layout-mode="mobile"] .archive-row-date { display: none; }
body[data-layout-mode="mobile"] .archive-restore { min-height: 44px; }
body[data-layout-mode="mobile"] .toast-action { min-height: 44px; display: inline-flex; align-items: center; }

/* Gantt dependency chips — the remove button measured 9x13, the smallest
   target left in the app after the touch-target pass, because it sits in
   a dense popover the sweep only reaches with a schedule open. */
.dp-del, .dp-x { display: inline-flex; align-items: center; justify-content: center; flex: none; color: var(--ink-mute); }
.dp-del:hover, .dp-x:hover { color: var(--danger-text); }
.dp-del svg, .dp-x svg { pointer-events: none; }
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  body:not([data-layout-mode="desktop"]) .dp-del,
  body:not([data-layout-mode="desktop"]) .dp-x { min-width: 44px; min-height: 44px; }
}
body[data-layout-mode="mobile"] .dp-del,
body[data-layout-mode="mobile"] .dp-x { min-width: 44px; min-height: 44px; }

/* Final touch-target sweep leftovers: the tag pill's remove button (28x30)
   and the Performance report button (81x30). */
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  body:not([data-layout-mode="desktop"]) .tag-pill button,
  body:not([data-layout-mode="desktop"]) .perf-report-btn { min-width: 44px; min-height: 44px; }
  body:not([data-layout-mode="desktop"]) .tag-pill button { display: inline-flex; align-items: center; justify-content: center; }
}
body[data-layout-mode="mobile"] .tag-pill button,
body[data-layout-mode="mobile"] .perf-report-btn { min-width: 44px; min-height: 44px; }
body[data-layout-mode="mobile"] .tag-pill button { display: inline-flex; align-items: center; justify-content: center; }

/* ===========================================================
   KEYBOARD INSET — full coverage (keyboard × layers pass)
   ===========================================================
   --keyboard-inset (set by initKeyboardAvoidance via visualViewport) was
   consumed by exactly ONE rule: .bottom-nav. Measured with a simulated
   300px keyboard at 412x915: promptDialog's INPUT (y 722-764) and its
   OK/Cancel row (y 798-894) both sat behind the keyboard (top edge 615) —
   the user typed blind and couldn't reach OK. Same for the wipe confirm's
   type-to-confirm, the bottom third of the metadata sheet, and the action
   sheet. The search modal was clear only because its input is pinned to
   the top by design.

   One mechanism for all of them, matching what .bottom-nav already does:
   the fixed backdrop gets bottom padding equal to the inset, so the
   content lays out inside the VISIBLE viewport instead of the layout
   viewport. Padding rather than transform, because several of these
   containers are also the modal's scroll parent — translating them up
   would clip their top edge off-screen; shrinking the box keeps
   everything reachable by scrolling.

   Decision recorded (Phase C4): the viewport meta stays `resizes-visual`
   (no interactive-widget change). Rationale: the inset mechanism now
   covers every fixed surface and is scoped to exactly the elements that
   need it; switching the whole layout viewport to resizes-content would
   re-lay-out EVERYTHING on every keyboard show (including the Steps grid
   mid-edit) and cannot be measured in this preview — it goes on the
   on-device checklist as an experiment, not shipped blind. */
.modal-backdrop {
  padding-bottom: var(--keyboard-inset, 0px);
  transition: padding-bottom var(--duration-fast) var(--ease-ios);
}
.action-sheet-backdrop { padding-bottom: var(--keyboard-inset, 0px); }
.action-sheet { margin-bottom: var(--keyboard-inset, 0px); }
/* The metadata bottom sheet is height-capped; shrink it so its lower
   fields stay reachable (it scrolls internally). */
#mobileRailModal .modal { max-height: calc(92dvh - var(--keyboard-inset, 0px)); }
/* Popovers positioned against an anchor can end up under the keyboard;
   cap them to the visible area so they scroll rather than hide. */
.perf-dropdown, .steps-menu-pop {
  max-height: calc(100dvh - var(--topbar-h) - var(--keyboard-inset, 0px) - 24px);
  overflow-y: auto;
}

/* ===========================================================
   ANDROID TOUCH FEEL (native-Android pass, C3)
   =========================================================== */
/* touch-action was present on list/folder rows and nav tabs but MISSING on
   the rapid-tap surfaces where the 300ms double-tap-zoom delay actually
   bites: the Steps status dot is tapped repeatedly to cycle status, and
   checklist checkboxes are the tick-tick-tick case. `manipulation` keeps
   pinch-zoom available; it only removes the double-tap gesture. */
.steps-status-dot-btn, .checklist-item-checkbox, .run-step-check, .run-substep-check,
.iconbtn, .ghost-btn, .primary-btn, .danger-btn, .action-pill,
.gt-btn, .perf-table td, .steps-row-menu-btn, .steps-more-btn,
.tag-pill button, .var-act, .goal-piece-check {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* Chrome's font boosting can silently inflate text in narrow containers;
   pin it. (This also covers Samsung Internet.) */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

/* Pull-to-refresh and scroll chaining fight sheets and drawers: reaching
   the top of a modal body would start refreshing the whole PWA. Contain
   the scrollables that sit in layers. */
.modal-body, .action-sheet-body, .detail-content,
.run-mode-body, .perf-table-scroll, .gantt-scroll {
  overscroll-behavior-y: contain;
}

/* ---- iOS-only zoom guard (C4) --------------------------------------
   The mobile typography pass shrinks field text at rest
   (--mobile-text-shrink) and snaps it back to 16px on :focus so iOS
   doesn't zoom the viewport. Android Chrome NEVER zooms on focus, so
   there the snap-back was pure cost: a visible font-size jump and a
   reflow on every single focus, bought for nothing. The 16px :focus
   restores below re-state the originals inside an iOS-only gate
   (@supports -webkit-touch-callout matches iOS Safari alone), and the
   un-gated originals are neutralised by the Android reset that follows.
   Net: iOS behaviour unchanged; Android fields keep their resting size
   while editing — no jump, no reflow. */
@supports not (-webkit-touch-callout: none) {
  /* Android/desktop: cancel the focus snap-back. Same selectors and
     specificity as the originals, declared later, so they win. */
  @media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
    body:not([data-layout-mode="desktop"]) .steps-row-parent .stcol-title .steps-title-input:focus {
      font-size: calc(16px * var(--density-scale, 1) * var(--mobile-text-shrink)) !important;
    }
    body:not([data-layout-mode="desktop"]) .steps-row-child .stcol-title .steps-title-input:focus {
      font-size: calc(15px * var(--density-scale, 1) * var(--mobile-text-shrink)) !important;
    }
    body:not([data-layout-mode="desktop"]) .checklist-item-text:focus {
      font-size: calc(15px * var(--mobile-text-shrink)) !important;
    }
  }
  body[data-layout-mode="mobile"] .steps-row-parent .stcol-title .steps-title-input:focus {
    font-size: calc(16px * var(--density-scale, 1) * var(--mobile-text-shrink)) !important;
  }
  body[data-layout-mode="mobile"] .steps-row-child .stcol-title .steps-title-input:focus {
    font-size: calc(15px * var(--density-scale, 1) * var(--mobile-text-shrink)) !important;
  }
  body[data-layout-mode="mobile"] .checklist-item-text:focus {
    font-size: calc(15px * var(--mobile-text-shrink)) !important;
  }
}


/* =========================================================
   DESKTOP COSMETICS (pointer-first sizing)
   =========================================================
   The visual system was tuned for One UI on a phone — 16px radii on
   32px buttons, 44px touch floors, phone-scale rhythm — and desktop
   inherited all of it, which read as an oversized mobile app in a
   window. This layer retunes size, radius and rhythm for a mouse.

   Everything routes through tokens whose :root DEFAULTS ARE TODAY'S
   VALUES, so any surface that isn't in the desktop scope below is
   provably unchanged. Only the two desktop scopes override them:
   the media query (identical to app.js's isMobile() rule: >950px, or
   >768px with no coarse pointer) and forced-desktop mode. */
:root {
  --d-iconbtn: 36px;
  --d-tab-pad: 8px 16px;
  --d-crumb-pad: 7px 9px;
  --d-pill-h: 34px;
  --d-pill-pad-x: 13px;
  --d-btn-min-h: 44px;
  --d-btn-pad: 10px 16px;
  --d-cl-pad: 8px 14px;
  --d-detail-pad: 40px 56px 100px;
  --d-title-mb: 12px;
  --d-desc-mb: 22px;
  --d-tags-mb: 28px;
  --d-sect-mt: 32px;
  --d-sect-mb: 14px;
  --d-list-title-fs: 14px;
  --d-card-pad: 16px;
  --d-search-pad: 8px 12px 8px 30px;
  --d-desc-min-h: 46px;
}
@media (min-width: 951px), (min-width: 769px) and (not (any-pointer: coarse)) {
  body:not([data-layout-mode="mobile"]) {
    /* Radii: a 16px corner on a 32px control is a phone proportion; at
       pointer sizes it reads as a lozenge. Halved-ish across the scale —
       cards, popovers, sheets and icon buttons all follow. Controls that
       are MEANT to be pills (tags, action pills, segmented tracks) use
       literal 20/50/999px radii and are untouched by this. */
    --radius-sm: 8px;
    --radius-md: 10px;
    --radius-lg: 14px;
    --radius-xl: 18px;
    /* Touch floors are pointer overhead: a mouse hits a 30px target as
       easily as a 44px one, and the 44s made every secondary control
       shout. */
    --d-iconbtn: 32px;
    --d-tab-pad: 6px 14px;
    --d-crumb-pad: 5px 8px;
    --d-pill-h: 30px;
    --d-pill-pad-x: 11px;
    --d-btn-min-h: 32px;
    --d-btn-pad: 7px 14px;
    --d-cl-pad: 5px 11px;
    /* 100px of bottom padding existed to clear the floating "+", which
       desktop doesn't have. */
    --d-detail-pad: 24px 32px 40px;
    --d-title-mb: 8px;
    --d-desc-mb: 14px;
    --d-tags-mb: 18px;
    --d-sect-mt: 18px;
    --d-sect-mb: 8px;
    --d-card-pad: 14px;
    --d-search-pad: 6px 12px 6px 30px;
    /* No forced two-line reserve: the field autosizes, so an empty
       description costs one line instead of two. */
    --d-desc-min-h: 0px;
  }
}
body[data-layout-mode="desktop"] {
  --radius-sm: 8px;
  --radius-md: 10px;
  --radius-lg: 14px;
  --radius-xl: 18px;
  --d-iconbtn: 32px;
  --d-tab-pad: 6px 14px;
  --d-crumb-pad: 5px 8px;
  --d-pill-h: 30px;
  --d-pill-pad-x: 11px;
  --d-btn-min-h: 32px;
  --d-btn-pad: 7px 14px;
  --d-cl-pad: 5px 11px;
  --d-detail-pad: 24px 32px 40px;
  --d-title-mb: 8px;
  --d-desc-mb: 14px;
  --d-tags-mb: 18px;
  --d-sect-mt: 18px;
  --d-sect-mb: 8px;
  --d-card-pad: 14px;
  --d-search-pad: 6px 12px 6px 30px;
  --d-desc-min-h: 0px;
}
/* Consumers live with their ORIGINAL rules, earlier in this file —
   putting them here would place them after the mobile mirrors and win
   the cascade at equal specificity, quietly resizing the phone UI. */

/* =========================================================
   DESKTOP SHELL (Tier 1): navigator sidebar, Home pane,
   command palette, window-controls-overlay
   =========================================================
   Same containment strategy as the cosmetics layer above: the sidebar
   and its grid exist ONLY inside the two desktop scopes; the base rules
   here keep every new element display:none, so phones cannot see any of
   this even by accident. */
.desk-nav, .desk-nav-expand { display: none; }

/* Home pane + palette pieces carry their own classes, which only desktop
   code paths ever add - safe to style unscoped. */
.detail-empty.desk-home {
  align-items: flex-start;
  justify-content: flex-start;
  text-align: left;
  gap: 10px;
  padding: 36px 48px;
}
.desk-home .screen-title { margin-bottom: 6px; }
.desk-home-label {
  font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink-mute);
}
.desk-home-recents { display: flex; flex-direction: column; gap: 2px; width: min(520px, 100%); }
.desk-home-recent {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  width: 100%;
  padding: 7px 10px;
  border: none; background: transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font: inherit; text-align: left;
  color: var(--ink);
  transition: background-color 0.1s;
}
.desk-home-recent:hover { background: var(--bg-sunk); }
.desk-home-recent-title {
  font-size: 13.5px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.desk-home-recent-when { font-size: 11.5px; color: var(--ink-faint); flex-shrink: 0; }
.desk-home .detail-empty-cta { margin-top: 8px; }
.desk-home-hint { font-size: 11.5px; color: var(--ink-faint); margin-top: 18px; }

/* Command palette rows inside the search modal. */
.search-cmd { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.search-cmd .search-result-title { display: flex; align-items: center; gap: 8px; }
.search-cmd-mark { color: var(--accent); font-weight: 700; }
.search-cmd-kbd {
  font-family: 'JetBrains Mono', monospace;
  font-size: 10.5px;
  color: var(--ink-mute);
  background: var(--bg-sunk);
  border: 1px solid var(--border-soft);
  border-radius: 5px;
  padding: 1px 6px;
  flex-shrink: 0;
}
.desk-nav-empty { padding: 18px 12px; font-size: 12.5px; color: var(--ink-mute); line-height: 1.5; }

/* The desktop scopes proper. */
@media (min-width: 951px), (min-width: 769px) and (not (any-pointer: coarse)) {
  body:not([data-layout-mode="mobile"]) .layout:not(.performance-active) {
    display: grid;
    grid-template-columns: var(--desknav-w, 288px) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
  }
  body:not([data-layout-mode="mobile"]) .desk-nav {
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--bg);
    border-right: 1px solid var(--border-soft);
  }
  body:not([data-layout-mode="mobile"]) .desk-nav-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 10px 8px 8px 16px;
    flex-shrink: 0;
  }
  body:not([data-layout-mode="mobile"]) .desk-nav-title {
    font-size: 11px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--ink-mute);
  }
  body:not([data-layout-mode="mobile"]) .desk-nav-tree {
    flex: 1; min-height: 0;
    overflow-y: auto;
    padding: 0 8px 28px;
  }
  /* Compact the tree for a 288px pane: the browse rows were sized for the
     full-width main pane. */
  body:not([data-layout-mode="mobile"]) .desk-nav .folder-row { font-size: 13px; }
  body:not([data-layout-mode="mobile"]) .desk-nav .list-item { min-height: 28px; }
  body:not([data-layout-mode="mobile"]) .desk-nav .list-item-title { font-size: 12.5px; }
  body:not([data-layout-mode="mobile"]) .desk-nav .browse-count { font-size: 11px; padding: 1px 7px; }
  body:not([data-layout-mode="mobile"]) .desk-nav .mp-meta { font-size: 10.5px; }
  /* Collapsed: single column, slim floating re-open handle. */
  body:not([data-layout-mode="mobile"]).desknav-collapsed .layout:not(.performance-active) {
    grid-template-columns: minmax(0, 1fr);
  }
  body:not([data-layout-mode="mobile"]).desknav-collapsed .desk-nav { display: none; }
  body:not([data-layout-mode="mobile"]) .desk-nav-expand[hidden] { display: none; }
  body:not([data-layout-mode="mobile"]).desknav-collapsed .desk-nav-expand:not([hidden]) {
    display: flex; align-items: center; justify-content: center;
    position: absolute;
    left: 8px; top: 10px;
    z-index: 30;
    width: 26px; height: 26px;
    border: 1px solid var(--border-soft);
    border-radius: var(--radius-sm);
    background: var(--bg-elev);
    color: var(--ink-mute);
    cursor: pointer;
  }
  body:not([data-layout-mode="mobile"]).desknav-collapsed .desk-nav-expand:hover { color: var(--ink); background: var(--bg-sunk); }
}
body[data-layout-mode="desktop"] .layout:not(.performance-active) {
  display: grid;
  grid-template-columns: var(--desknav-w, 288px) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
}
body[data-layout-mode="desktop"] .desk-nav {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--bg);
  border-right: 1px solid var(--border-soft);
}
body[data-layout-mode="desktop"] .desk-nav-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 8px 8px 16px;
  flex-shrink: 0;
}
body[data-layout-mode="desktop"] .desk-nav-title {
  font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink-mute);
}
body[data-layout-mode="desktop"] .desk-nav-tree {
  flex: 1; min-height: 0;
  overflow-y: auto;
  padding: 0 8px 28px;
}
body[data-layout-mode="desktop"] .desk-nav .folder-row { font-size: 13px; }
body[data-layout-mode="desktop"] .desk-nav .list-item { min-height: 28px; }
body[data-layout-mode="desktop"] .desk-nav .list-item-title { font-size: 12.5px; }
body[data-layout-mode="desktop"] .desk-nav .browse-count { font-size: 11px; padding: 1px 7px; }
body[data-layout-mode="desktop"] .desk-nav .mp-meta { font-size: 10.5px; }
body[data-layout-mode="desktop"].desknav-collapsed .layout:not(.performance-active) {
  grid-template-columns: minmax(0, 1fr);
}
body[data-layout-mode="desktop"].desknav-collapsed .desk-nav { display: none; }
body[data-layout-mode="desktop"] .desk-nav-expand[hidden] { display: none; }
body[data-layout-mode="desktop"].desknav-collapsed .desk-nav-expand:not([hidden]) {
  display: flex; align-items: center; justify-content: center;
  position: absolute;
  left: 8px; top: 10px;
  z-index: 30;
  width: 26px; height: 26px;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  background: var(--bg-elev);
  color: var(--ink-mute);
  cursor: pointer;
}
body[data-layout-mode="desktop"].desknav-collapsed .desk-nav-expand:hover { color: var(--ink); background: var(--bg-sunk); }

/* Window-controls-overlay: only live when the INSTALLED app opts in via
   the manifest; a browser tab never matches this media query. The topbar
   becomes the drag region (that is what a title bar is), interactive
   children opt back out, and env() keeps content clear of the OS caption
   buttons. */
@media (display-mode: window-controls-overlay) {
  .topbar {
    -webkit-app-region: drag;
    app-region: drag;
    padding-left: max(14px, env(titlebar-area-x, 0px));
    width: env(titlebar-area-width, 100%);
    min-height: env(titlebar-area-height, 48px);
  }
  .topbar .iconbtn, .topbar .tab, .topbar input {
    -webkit-app-region: no-drag;
    app-region: no-drag;
  }
}

/* Performance is a full-width takeover on every layout — the navigator has
   no folders or documents to show there. Specificity (0,3,1) outranks both
   desktop scopes' display:flex without needing !important. */
.layout.performance-active .desk-nav { display: none; }
/* The expand handle lives inside .layout too; its show-rule carries
   (0,4,1) specificity, so the hide here matches it and wins on order. */
body.desknav-collapsed .layout.performance-active .desk-nav-expand:not([hidden]) { display: none; }

/* Shortcut hints inside context menus — desktop discovers keys where the
   mouse already is. Reuses the palette kbd look. */
.steps-row-menu-pop .menu-kbd {
  float: right;
  margin-left: 14px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px;
  color: var(--ink-faint);
  background: var(--bg-sunk);
  border: 1px solid var(--border-soft);
  border-radius: 4px;
  padding: 0 5px;
  line-height: 16px;
}

/* =========================================================
   DESKTOP SHELL Tier 3: dividers, tooltips, drop target
   ========================================================= */
/* Pane dividers. Hidden with their panes on mobile (base display:none for
   the sidebar one; the rail one only renders on desktop rails). The grab
   strip is 5px wide but sits over the pane edge, so it steals no layout. */
.desk-nav-resizer { display: none; }
@media (min-width: 951px), (min-width: 769px) and (not (any-pointer: coarse)) {
  body:not([data-layout-mode="mobile"]) .desk-nav { position: relative; }
  body:not([data-layout-mode="mobile"]) .desk-nav-resizer {
    display: block;
    position: absolute;
    top: 0; right: -3px; bottom: 0;
    width: 6px;
    cursor: col-resize;
    z-index: 20;
  }
}
body[data-layout-mode="desktop"] .desk-nav { position: relative; }
body[data-layout-mode="desktop"] .desk-nav-resizer {
  display: block;
  position: absolute;
  top: 0; right: -3px; bottom: 0;
  width: 6px;
  cursor: col-resize;
  z-index: 20;
}
.desk-nav-resizer:hover, .desk-nav-resizer.dragging { background: var(--accent-bg-strong); }
.rail-resizer {
  position: absolute;
  top: 0; left: -3px; bottom: 0;
  width: 6px;
  cursor: col-resize;
  z-index: 20;
}
.rail-resizer:hover, .rail-resizer.dragging { background: var(--accent-bg-strong); }
.detail-rail.collapsed .rail-resizer { display: none; }
body.pane-resizing { cursor: col-resize; user-select: none; }
body.pane-resizing * { cursor: col-resize !important; }

/* Styled tooltip — replaces the native title bubble on desktop (custom
   delay, theme colors, readable size). One element, repositioned. */
.app-tooltip {
  position: fixed;
  z-index: 4000;
  max-width: 280px;
  padding: 5px 10px;
  border-radius: 7px;
  background: var(--ink);
  color: var(--bg-elev);
  font-size: 11.5px;
  line-height: 1.45;
  pointer-events: none;
  opacity: 0;
  transform: translateY(2px);
  transition: opacity 0.12s, transform 0.12s;
  box-shadow: 0 4px 14px rgba(0,0,0,0.18);
}
.app-tooltip.on { opacity: 1; transform: none; }

/* Whole-window file drop target. */
.drop-overlay {
  position: fixed; inset: 0;
  z-index: 5000;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(2px);
  pointer-events: none;   /* the window handles the drop; this only shows */
}
.drop-overlay[hidden] { display: none; }
.drop-overlay-card {
  border: 2px dashed var(--accent);
  border-radius: var(--radius-lg);
  background: var(--bg-elev);
  color: var(--ink);
  padding: 26px 40px;
  font-size: 14.5px;
  font-weight: 600;
  text-align: center;
}
.drop-overlay-sub { font-size: 12px; font-weight: 400; color: var(--ink-mute); margin-top: 6px; }
/* Containing block for the rail's edge divider. Static otherwise, so this
   promotes nothing that was relying on a farther ancestor. */
.detail-rail { position: relative; }

/* =========================================================
   MOBILE Tier 1: action strip, browse gestures
   =========================================================
   The strip only ever SHOWS on mobile (syncMobileActionStrip gates on
   isMobile() before unhiding), so these base rules never touch desktop. */
.mobile-action-strip {
  position: fixed;
  /* Opposite side from the (dockable) FAB pair — same rule pattern as
     body.fab-dock-left below the FABs themselves. */
  left: 16px;
  bottom: calc(72px + env(safe-area-inset-bottom));
  z-index: 44;   /* under sheets/modals, over content — same band as FABs */
  display: flex;
  align-items: center;
  gap: 8px;
}
.mobile-action-strip[hidden] { display: none; }
/* FABs docked LEFT (this app defaults there) push the strip RIGHT, and
   vice versa — the two clusters may never share a corner. */
body.fab-dock-left .mobile-action-strip { left: auto; right: 16px; }
body:not(.fab-dock-left) .mobile-action-strip { left: 16px; right: auto; }
/* Selection mode borrows the bottom edge for the batch bar. */
body.batch-on .mobile-action-strip { display: none; }
/* The strip replaces the top action bar — showing both would duplicate
   every action on one screen. */
body.has-action-strip #detailContent .detail-action-bar { display: none; }
.mas-primary {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 46px;
  padding: 0 18px;
  border: none;
  border-radius: 23px;
  background: var(--accent-btn);
  color: #fff;
  font: inherit;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.22);
}
.mas-primary svg { flex-shrink: 0; }
.mas-more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--bg-elev);
  color: var(--ink-soft);
  cursor: pointer;
  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.16);
}
/* Ride the same scroll-hide as the FABs. */
body.fabs-scrolling .mobile-action-strip {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s var(--ease-ios, cubic-bezier(0.16, 1, 0.3, 1));
}
.mobile-action-strip { transition: opacity 0.18s var(--ease-ios, cubic-bezier(0.16, 1, 0.3, 1)); }

/* Pull-to-search hint — a small pill that rides the drag under the
   taskbar and arms past the release threshold. */
.pull-search-hint {
  position: fixed;
  top: calc(var(--topbar-h) + var(--folder-taskbar-h) + 6px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px;
  border-radius: 999px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  color: var(--ink-mute);
  font-size: 12px;
  font-weight: 600;
  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.14);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s;
}
.pull-search-hint.on { opacity: 1; }
.pull-search-hint.armed { color: var(--accent-text); border-color: var(--accent-soft); }

/* =========================================================
   MOBILE Tier 2: checklist swipe, sheet dismiss
   ========================================================= */
/* Swipe state only ever appears on mobile (attachChecklistSwipe gates on
   isMobile() at touchstart), so these are safe unscoped. pan-y lets the
   browser keep vertical scrolling while horizontal drags reach JS. */
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  body:not([data-layout-mode="desktop"]) .checklist-item { touch-action: pan-y; }
}
body[data-layout-mode="mobile"] .checklist-item { touch-action: pan-y; }
.checklist-item.cl-swiping { position: relative; z-index: 2; }
/* The commit discs ride the row's own transform in from the edges:
   ::before (done) enters from the left on a right-swipe, ::after (delete)
   from the right on a left-swipe. */
.checklist-item.cl-swiping::before,
.checklist-item.cl-swiping::after {
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 50%;
  margin-top: -15px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  color: #fff;
  font-size: 15px;
  font-weight: 800;
  opacity: 0.45;
  transition: opacity 0.1s, transform 0.1s;
}
.checklist-item.cl-swiping::before {
  content: '✓';
  left: -46px;
  background: var(--success);
}
.checklist-item.cl-swiping::after {
  content: '✕';
  right: -46px;
  background: var(--danger);
}
.checklist-item.cl-swipe-arm-r::before,
.checklist-item.cl-swipe-arm-l::after {
  opacity: 1;
  transform: scale(1.15);
}

/* The metadata bottom sheet gets the same grabber the action sheet has —
   both now answer the drag-down they visually invite. */
.mobile-rail-sheet-backdrop .modal .action-sheet-handle { margin: 8px auto 2px; }

/* =========================================================
   STEPS GRID VIEW (Prototype A rev 2)
   =========================================================
   A wide data grid alternative to the outline: the status dot is the
   only pinned column; step, owner, estimate, tags, material and
   description scroll horizontally. Works on every layout — the wrap owns
   the horizontal scroll, so phones pan it like the Performance table. */
.steps-dgrid-wrap { overflow-x: auto; }
.steps-dgrid-wrap[hidden] { display: none; }
.steps-dgrid { border-collapse: separate; border-spacing: 0; min-width: 980px; width: 100%; font-size: 13px; }
.steps-dgrid th, .steps-dgrid td {
  padding: 6px 12px;
  border-bottom: 1px solid var(--border-soft);
  white-space: nowrap;
  text-align: left;
  vertical-align: middle;
}
.steps-dgrid thead th {
  font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em;
  color: var(--ink-mute);
}
/* The one pinned column. Solid card background so rows sliding under it
   don't show through; the shadow says "there's more behind me". */
.steps-dgrid .dg-fix {
  position: sticky; left: 0; z-index: 2;
  width: 46px; min-width: 46px;
  text-align: center;
  background: var(--bg-sunk);
  box-shadow: 6px 0 8px -6px rgba(0, 0, 0, 0.14);
}
.steps-dgrid-row { cursor: pointer; }
.steps-dgrid-row:hover td { background: var(--bg); }
.steps-dgrid-row:hover .dg-fix { background: var(--bg); }
.steps-dgrid-row.selected td { background: var(--accent-bg); }
.steps-dgrid-row.selected .dg-fix { background: var(--accent-bg); }
/* Flex lives on an INNER span — display:flex directly on a td takes it
   out of the table-cell algorithm and collapses the whole column. */
.dg-step { min-width: 220px; }
.dg-stepin { display: flex; align-items: center; gap: 8px; padding-left: calc(var(--dg-d, 0) * 22px); }
.dg-num {
  font-family: var(--font-mono, 'JetBrains Mono', monospace);
  font-size: 12px; font-weight: 700;
  color: var(--accent-text);
  flex-shrink: 0;
}
.dg-title {
  flex: 1; min-width: 0;
  background: transparent; border: none; outline: none;
  font: inherit; font-size: 13.5px; font-weight: 500;
  color: var(--ink-soft);
  padding: 2px 0;
}
.dg-title-top { font-weight: 700; color: var(--ink); }
.dg-title::placeholder { color: var(--ink-faint); }
.dg-title:focus { box-shadow: 0 1px 0 var(--accent-soft); }

/* Grid view: adjustable columns. Fixed table layout so the colgroup
   widths GOVERN (auto layout treats them as suggestions); cells clip
   with ellipsis instead of spilling into their neighbours. */
/* Two columns now: 46px dot + the step taking ALL remaining width, so
   the table fills its card again (the exact-pixel-widths concern from
   the resizable-columns era is gone with the columns themselves). */
.steps-dgrid { table-layout: fixed; min-width: 0; width: 100%; }
.steps-dgrid td { overflow: hidden; text-overflow: ellipsis; }


/* Owner & estimate rail fields — small labeled inputs, rail idiom. */
.rail-own-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.rail-own-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.rail-own-field span {
  font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em;
  color: var(--ink-mute);
}
.rail-own-field input {
  width: 100%;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 6px 9px;
  font: inherit; font-size: 12.5px;
  color: var(--ink);
  outline: none;
}
.rail-own-field input:focus { border-color: var(--accent-soft); }
.rail-own-field input::placeholder { color: var(--ink-faint); }

/* Grid fold: caret for parents, spacer keeps leaf rows aligned. */
.dg-caret {
  flex-shrink: 0;
  width: 18px; height: 18px;
  border: none; background: none; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-mute);
  cursor: pointer;
  border-radius: 5px;
  transition: transform 0.12s ease;
  position: relative;
}
.dg-caret::before { content: ''; position: absolute; inset: -6px; }  /* thumb halo */
.dg-caret:hover { color: var(--ink); background: var(--bg); }
.dg-caret.collapsed { transform: rotate(-90deg); }
.dg-caret-spacer { flex-shrink: 0; width: 18px; }
.dg-fold-count {
  flex-shrink: 0;
  font-size: 10.5px; font-weight: 700;
  color: var(--accent-text);
  background: var(--accent-bg);
  border-radius: 999px;
  padding: 1px 8px;
  margin-left: 6px;
}

/* =========================================================
   MOBILE TYPE RAMP
   =========================================================
   An audit of the phone layout found the supporting layer sitting 1–2px
   under One UI's norms — meta rows 11.5, section labels 11, breadcrumb /
   toolbar pills / rail labels / tag pills 12 — with nothing between a
   21px document title and 11.5px meta. Worse, ~20 scattered literals
   meant no single place to tune it.

   These five tokens are that place. They carry MOBILE values and are
   consumed only inside the mobile scopes below, so desktop's own
   (separately tuned) sizes are untouched. --text-scale multiplies all of
   them, which is what makes the Settings → Text size control possible:
   a PWA never receives Android's system font-size setting, so the app
   has to offer its own. */
:root {
  --text-scale: 1;
  --fs-doc-title: 24px;   /* document titles — was 21, below the 31px browse title above it */
  --fs-row-title: 15.5px; /* document rows in the browse map — was 14.5/400 */
  --fs-secondary: 13px;   /* breadcrumb, toolbar pills, rail labels, grid numbers */
  --fs-label: 12.5px;     /* meta rows, section labels, tag pills */
  --fs-chip: 12px;        /* counts and small chips — the floor */
}

/* Consumers. Inside the mobile scopes and at the END of the stylesheet,
   so they beat the older per-element mirrors at equal specificity by
   document order rather than by escalating !important everywhere. */
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  body:not([data-layout-mode="desktop"]) .detail-title-input {
    font-size: calc(var(--fs-doc-title) * var(--text-scale)) !important;
    line-height: 1.2;
  }
  body:not([data-layout-mode="desktop"]) .detail-meta-row,
  body:not([data-layout-mode="desktop"]) .detail-section-title,
  body:not([data-layout-mode="desktop"]) .tag-pill {
    font-size: calc(var(--fs-label) * var(--text-scale));
  }
  body:not([data-layout-mode="desktop"]) .breadcrumb-segment,
  body:not([data-layout-mode="desktop"]) .steps-status-btn,
  body:not([data-layout-mode="desktop"]) .rail-section-title,
  body:not([data-layout-mode="desktop"]) .rail-header-title,
  body:not([data-layout-mode="desktop"]) .dg-num {
    font-size: calc(var(--fs-secondary) * var(--text-scale));
  }
  body:not([data-layout-mode="desktop"]) .dg-fold-count,
  body:not([data-layout-mode="desktop"]) .mp-meta,
  body:not([data-layout-mode="desktop"]) .browse-count {
    font-size: calc(var(--fs-chip) * var(--text-scale));
  }
  /* Document rows are the most-tapped surface in the app; 14.5px at
     weight 400 read as secondary text next to their own folder rows. */
  body:not([data-layout-mode="desktop"]) .list-item-title-text {
    font-size: calc(var(--fs-row-title) * var(--text-scale));
    font-weight: 500;
  }
  /* The 16px inputs (step titles, checklist lines, description, search)
     are already at the platform floor AND load-bearing for iOS zoom —
     they scale UP with the setting but never below 16. */
  body:not([data-layout-mode="desktop"]) .dg-title,
  body:not([data-layout-mode="desktop"]) .checklist-item-text,
  body:not([data-layout-mode="desktop"]) .detail-description {
    font-size: max(16px, calc(16px * var(--text-scale))) !important;
  }
}
body[data-layout-mode="mobile"] .detail-title-input {
  font-size: calc(var(--fs-doc-title) * var(--text-scale)) !important;
  line-height: 1.2;
}
body[data-layout-mode="mobile"] .detail-meta-row,
body[data-layout-mode="mobile"] .detail-section-title,
body[data-layout-mode="mobile"] .tag-pill {
  font-size: calc(var(--fs-label) * var(--text-scale));
}
body[data-layout-mode="mobile"] .breadcrumb-segment,
body[data-layout-mode="mobile"] .steps-status-btn,
body[data-layout-mode="mobile"] .rail-section-title,
body[data-layout-mode="mobile"] .rail-header-title,
body[data-layout-mode="mobile"] .dg-num {
  font-size: calc(var(--fs-secondary) * var(--text-scale));
}
body[data-layout-mode="mobile"] .dg-fold-count,
body[data-layout-mode="mobile"] .mp-meta,
body[data-layout-mode="mobile"] .browse-count {
  font-size: calc(var(--fs-chip) * var(--text-scale));
}
body[data-layout-mode="mobile"] .list-item-title-text {
  font-size: calc(var(--fs-row-title) * var(--text-scale));
  font-weight: 500;
}
body[data-layout-mode="mobile"] .dg-title,
body[data-layout-mode="mobile"] .checklist-item-text,
body[data-layout-mode="mobile"] .detail-description {
  font-size: max(16px, calc(16px * var(--text-scale))) !important;
}

/* =========================================================
   GRID VIEW — MOBILE FIT (Tier 2)
   =========================================================
   The grid became the default steps view but was designed desktop-first.
   Measured on a 393px phone: step titles CLIPPED at 243px (a single-line
   input where the outline wraps), the status button a 10px-tall target,
   the pinned dot column taking 12% of the screen at its desktop 46px, a
   27px "Step" header the outline hides on phones, and a flat 38px row
   rhythm with no parent/child distinction. */

/* Titles wrap on every form factor now (the element is a textarea) —
   clipping a step's name is worse than a taller row anywhere. */
.dg-title {
  resize: none;
  overflow: hidden;
  line-height: 1.35;
  font-family: inherit;
}

@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  /* Reclaim 8px from the pinned column for the title. */
  body:not([data-layout-mode="desktop"]) .steps-dgrid .dg-fix { width: 38px; min-width: 38px; }
  /* The status button carried no touch halo in the grid — the outline's
     dot has one. 10x34 measured; this takes it past the floor without
     changing the dot's visual size. */
  body:not([data-layout-mode="desktop"]) .steps-dgrid .steps-status-dot-btn { position: relative; }
  body:not([data-layout-mode="desktop"]) .steps-dgrid .steps-status-dot-btn::before {
    content: '';
    position: absolute;
    left: 50%; top: 50%;
    width: 46px; height: 46px;
    transform: translate(-50%, -50%);
  }
  /* A one-word header ("Step") plus a dot glyph is pure chrome on a
     phone — the outline hides its thead here for the same reason. */
  body:not([data-layout-mode="desktop"]) .steps-dgrid thead { display: none; }
  /* Rhythm: parents get the touch-height row, children sit tighter —
     the outline's 44/34 relationship, in the grid's proportions. */
  body:not([data-layout-mode="desktop"]) .steps-dgrid-row[data-depth="0"] .dg-step { min-height: 44px; }
  body:not([data-layout-mode="desktop"]) .steps-dgrid-row .dg-step { min-height: 38px; padding-top: 4px; padding-bottom: 4px; }
  body:not([data-layout-mode="desktop"]) .steps-dgrid .dg-stepin { align-items: flex-start; }
  body:not([data-layout-mode="desktop"]) .steps-dgrid .dg-num { padding-top: 2px; }
  body:not([data-layout-mode="desktop"]) .steps-dgrid .dg-caret { margin-top: 1px; }
}
body[data-layout-mode="mobile"] .steps-dgrid .dg-fix { width: 38px; min-width: 38px; }
body[data-layout-mode="mobile"] .steps-dgrid .steps-status-dot-btn { position: relative; }
body[data-layout-mode="mobile"] .steps-dgrid .steps-status-dot-btn::before {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 46px; height: 46px;
  transform: translate(-50%, -50%);
}
body[data-layout-mode="mobile"] .steps-dgrid thead { display: none; }
body[data-layout-mode="mobile"] .steps-dgrid-row[data-depth="0"] .dg-step { min-height: 44px; }
body[data-layout-mode="mobile"] .steps-dgrid-row .dg-step { min-height: 38px; padding-top: 4px; padding-bottom: 4px; }
body[data-layout-mode="mobile"] .steps-dgrid .dg-stepin { align-items: flex-start; }
body[data-layout-mode="mobile"] .steps-dgrid .dg-num { padding-top: 2px; }
body[data-layout-mode="mobile"] .steps-dgrid .dg-caret { margin-top: 1px; }

/* =========================================================
   DESKTOP AUDIT FIXES
   =========================================================
   Measured across 1024 / 1440 / 2560 in both themes. Contrast came back
   clean (the one "failure" was my probe walking past a gradient-painted
   button to the pane behind it). These are the real findings. */

/* The grid's ⋮ — its absence made the row menu (add sub-step, duplicate,
   move, promote/demote, export, delete) UNREACHABLE on touch, where
   right-click doesn't exist and long-press is drag-reorder. Pointer keeps
   the outline's convention of revealing it on row hover; touch shows it
   always, since there is no hover to reveal it with. */
/* Scoped past the outline's mobile rule, which stretches
   .steps-row-menu-btn to width:100% — correct inside its own fixed-width
   cell, but in the grid's flex row it absorbed the whole row (measured
   293px). */
.steps-dgrid .steps-row-menu-btn.dg-menu {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  padding: 0;
  margin-left: auto;
  font-size: 17px;
  line-height: 1;
  opacity: 0;
  transition: opacity 0.12s;
}
.steps-dgrid-row:hover .steps-row-menu-btn.dg-menu,
.steps-dgrid-row.selected .steps-row-menu-btn.dg-menu,
.steps-dgrid .steps-row-menu-btn.dg-menu:focus-visible { opacity: 1; }
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  body:not([data-layout-mode="desktop"]) .steps-dgrid .steps-row-menu-btn.dg-menu { opacity: 1; }
}
body[data-layout-mode="mobile"] .steps-dgrid .steps-row-menu-btn.dg-menu { opacity: 1; }
/* Batch mode replaces per-row actions with selection. */
.steps-dgrid.batch-mode .dg-menu { display: none; }
/* Touch target: the button is 28px so it sits quietly in a pointer row,
   but a thumb needs 44. The outline's own ⋮ halo is horizontal-only
   (it lives in a full-height cell); the grid's sits inside a flex row,
   so it needs its own vertical reach. */
.steps-dgrid .steps-row-menu-btn.dg-menu { position: relative; }
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  body:not([data-layout-mode="desktop"]) .steps-dgrid .steps-row-menu-btn.dg-menu::before {
    content: ''; position: absolute; inset: -8px -6px;
  }
}
body[data-layout-mode="mobile"] .steps-dgrid .steps-row-menu-btn.dg-menu::before {
  content: ''; position: absolute; inset: -8px -6px;
}

/* Sidebar document rows are the navigator's primary content but rendered
   at 12.5px/400 — lighter and smaller than the folder rows above them,
   the same inversion the phone audit found in the browse map. */
.desk-nav .list-item-title-text { font-size: 13px; font-weight: 500; }

/* WCAG 2.5.8 asks 24x24 of a pointer target; the status dot measured
   22x22 with no halo on desktop (mobile got one in the grid pass). */
.steps-dgrid .steps-status-dot-btn { position: relative; }
.steps-dgrid .steps-status-dot-btn::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 28px; height: 28px;
  transform: translate(-50%, -50%);
}

/* =========================================================
   CHECKLIST + TEMPLATE MOBILE AUDIT FIXES
   =========================================================
   Measured on a 393px phone, both themes. Contrast came back clean in
   both sections (26 measurements); these are the layout, target and
   type findings. */

/* The template's actions live BELOW the body editor, so on a phone you
   scrolled past an entire email to copy it. The bottom strip now mirrors
   that row (syncMobileActionStrip reads it), so showing both would be
   duplication — same rule the process/checklist top bar follows. */
body.has-action-strip #detailContent .template-actions-row { display: none; }

@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  /* Preview toggle measured 18px tall — a switch you can see but barely
     hit. The track keeps its size; the label grows the target. */
  body:not([data-layout-mode="desktop"]) .tpl-preview-switch {
    min-height: 44px;
    padding: 0 4px;
  }
  /* Checkbox measured 35x35 effective: the row is huge but the control
     that actually ticks the line was under the floor. */
  body:not([data-layout-mode="desktop"]) .checklist-item-checkbox { position: relative; }
  body:not([data-layout-mode="desktop"]) .checklist-item-checkbox::after {
    content: '';
    position: absolute;
    left: 50%; top: 50%;
    width: 44px; height: 44px;
    transform: translate(-50%, -50%);
  }
  /* Three labels sat at 11px, under the type ramp's 12px floor. */
  body:not([data-layout-mode="desktop"]) .cl-agg,
  body:not([data-layout-mode="desktop"]) #clCount,
  body:not([data-layout-mode="desktop"]) .tpl-switch-label {
    font-size: calc(var(--fs-chip) * var(--text-scale));
  }
}
body[data-layout-mode="mobile"] .tpl-preview-switch { min-height: 44px; padding: 0 4px; }
body[data-layout-mode="mobile"] .checklist-item-checkbox { position: relative; }
body[data-layout-mode="mobile"] .checklist-item-checkbox::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 44px; height: 44px;
  transform: translate(-50%, -50%);
}
body[data-layout-mode="mobile"] .cl-agg,
body[data-layout-mode="mobile"] #clCount,
body[data-layout-mode="mobile"] .tpl-switch-label {
  font-size: calc(var(--fs-chip) * var(--text-scale));
}

/* =========================================================
   BROWSE QUICK-ADD (+)
   =========================================================
   On an Insights kind header it creates that kind; on any folder row it
   creates that section's item IN that folder — the context the browse
   view has at the moment you click, and the generic "+" cannot. */
.browse-add-btn {
  flex-shrink: 0;
  margin-left: 8px;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-soft);
  border-radius: 50%;
  background: var(--bg-elev);
  color: var(--ink-mute);
  cursor: pointer;
  padding: 0;
  position: relative;
  /* Visible at rest, but quiet: fully hidden until hover (the grid ⋮
     convention) made the button undiscoverable in a narrow sidebar —
     you had to already know it was there to find it. At 0.45 the row
     reads as offering something without competing with the folder
     name; hover and keyboard focus take it to full strength. */
  opacity: 0.45;
  transition: opacity 0.12s, color 0.12s, background-color 0.12s;
}
.browse-add-btn:hover { color: var(--accent-text); border-color: var(--accent-bg-strong); background: var(--accent-bg); }
/* Hovering the row, the button itself, or focusing it by keyboard brings
   it to full strength. Touch has no hover, so it is solid there (below). */
.kind-group-label:hover .browse-add-btn,
.folder-row:hover .browse-add-btn,
.browse-add-btn:hover,
.browse-add-btn:focus-visible { opacity: 1; }
/* The count chip should sit next to the label, not get pushed by the +. */
.kind-group-label .browse-count { margin-right: auto; }
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  body:not([data-layout-mode="desktop"]) .browse-add-btn {
    opacity: 1;
    width: 30px; height: 30px;
  }
  /* Thumb reach without growing the visible control past the row rhythm. */
  body:not([data-layout-mode="desktop"]) .browse-add-btn::after {
    content: '';
    position: absolute;
    left: 50%; top: 50%;
    width: 44px; height: 44px;
    transform: translate(-50%, -50%);
  }
}
body[data-layout-mode="mobile"] .browse-add-btn { opacity: 1; width: 30px; height: 30px; }
body[data-layout-mode="mobile"] .browse-add-btn::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 44px; height: 44px;
  transform: translate(-50%, -50%);
}

/* =========================================================
   CHECKLIST + TEMPLATE DESKTOP AUDIT FIXES
   =========================================================
   Measured at 1024 / 1440 / 2560 in both themes. Contrast came back
   clean (104 measurements); no overflow at any width. What failed was
   pointer target size — WCAG 2.5.8 asks 24x24, and three controls that
   got touch halos in the mobile pass were left bare for a mouse. */

/* The checkbox that actually ticks a line measured 18x18. */
.checklist-item-checkbox { position: relative; }
.checklist-item-checkbox::before {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 28px; height: 28px;
  transform: translate(-50%, -50%);
}
/* Fold caret: 22x22. */
.cl-twist { position: relative; }
.cl-twist::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 26px; height: 26px;
  transform: translate(-50%, -50%);
}
/* The template's Preview switch measured 18px tall on every desktop
   width — the mobile pass grew it, the pointer one never did. */
.tpl-preview-switch { min-height: 26px; }

/* =========================================================
   SCHEDULES AUDIT FIXES
   =========================================================
   Measured on a phone and at 1024 / 1440 in both themes. Mobile came
   back clean — every toolbar target 44x44, contrast fine, no overflow —
   and mobile had already grown these row controls to 28px. Desktop kept
   the bare 18px, the same one-platform-only pattern the checklist
   checkbox and template switch showed.

   The controls stay visually small (they sit in a dense timeline), so
   the halo grows the TARGET only. It fits: rows are 44px tall (GEOM.rowH),
   so a 26px halo clears .g-rowlabel's overflow:hidden vertically. */
.g-rl-twirl, .g-row-add, .g-row-del { position: relative; }
.g-rl-twirl::after, .g-row-add::after, .g-row-del::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 26px; height: 26px;
  transform: translate(-50%, -50%);
}

/* =========================================================
   INSIGHTS AUDIT FIXES
   =========================================================
   Desktop came back clean at 1024 / 1440 (no target under 24, contrast
   clean in both themes). These are the phone's three sub-44 targets. */
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  /* The outline bullet folds a row's subtree — measured 33x33, because
     its halo was sized before the bullet itself shrank. */
  body:not([data-layout-mode="desktop"]) .ins-ol-bullet::before { inset: -14px; }
  /* Meta-row ghosts (Share / Duplicate / Delete) measured 43x41 — the
     halo added in the mobile pass was a shade too tight, and rounding
     kept landing it just under. */
  body:not([data-layout-mode="desktop"]) .detail-meta-row .ghost-btn { position: relative; }
  body:not([data-layout-mode="desktop"]) .detail-meta-row .ghost-btn::after {
    content: ''; position: absolute; inset: -6px -7px;
  }
  /* Quick-add + measured 43 tall: a 44px halo on a 30px control rounds
     down. 46 clears it. */
  body:not([data-layout-mode="desktop"]) .browse-add-btn::after { width: 46px; height: 46px; }
}
body[data-layout-mode="mobile"] .ins-ol-bullet::before { inset: -14px; }
body[data-layout-mode="mobile"] .detail-meta-row .ghost-btn { position: relative; }
body[data-layout-mode="mobile"] .detail-meta-row .ghost-btn::after { content: ''; position: absolute; inset: -6px -7px; }
body[data-layout-mode="mobile"] .browse-add-btn::after { width: 46px; height: 46px; }

/* =========================================================
   FULL-REGRESSION AUDIT: touch targets at their row ceiling
   =========================================================
   Measured rows: the grid's are 37px tall and the checklist's 46px. A
   halo larger than its row bleeds into the neighbours and they start
   stealing each other's taps — worse than a small target. So these grow
   to the ceiling and stop:
     grid caret        30 -> 36 (row 37)
     checklist twist   39 -> 44 (row 46)
   Left at their ceiling deliberately: the grid ⋮ (39, already over its
   row), the checklist checkbox (43 of 46) and the status dot, whose
   WIDTH is capped by the 38px pinned column it lives in. All clear
   WCAG 2.5.8's 24px; 44 is this app's own bar, met where there is room. */
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  body:not([data-layout-mode="desktop"]) .dg-caret::before { inset: -9px; }
  body:not([data-layout-mode="desktop"]) .cl-twist { position: relative; }
  body:not([data-layout-mode="desktop"]) .cl-twist::after {
    content: ''; position: absolute; left: 50%; top: 50%;
    width: 44px; height: 44px; transform: translate(-50%, -50%);
  }
}
body[data-layout-mode="mobile"] .dg-caret::before { inset: -9px; }
body[data-layout-mode="mobile"] .cl-twist { position: relative; }
body[data-layout-mode="mobile"] .cl-twist::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%);
}

/* =========================================================
   PERFORMANCE AUDIT FIXES
   =========================================================
   Contrast came back clean in both themes at every width, and nothing
   overflows (the table's own horizontal scroll at 1024 is the point of a
   data table). Targets were the gap — this section had the most WCAG
   2.5.8 failures of any: the column ⋯ at 20x20, its filter chevron at
   13x15, the toolbar buttons 19px tall, and a 7px resize handle.

   The controls stay visually small — a dense data grid is why you use
   this view — so halos grow the TARGET and leave the drawing alone. */
.perf-th-menu, .perf-th-filter { position: relative; }
.perf-th-menu::after, .perf-th-filter::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 26px; height: 26px;
  transform: translate(-50%, -50%);
}
/* Header cells are the containing block for these halos; without this the
   ::after positions against a farther ancestor and lands off-target. */
.perf-table th { position: relative; }
/* A 7px grab strip is thin even for a mouse. 11px stays invisible (it sits
   over the column edge) and is over 50% easier to catch. It stops there
   rather than reaching 24: a column-boundary handle has to sit ON the
   boundary, and a 24px one would swallow clicks meant for the header
   cells either side — WCAG 2.5.8 exempts a target whose presentation is
   essential to its function, which this is. */
.perf-col-resize { width: 11px; right: -5px; }
/* Toolbar buttons measured 19px tall. */
.perf-toolbar button { min-height: 26px; }
/* Cell text was 11px — the smallest body text in the app, in the view
   whose entire job is reading values. */
.perf-table td { font-size: 12px; }

@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  /* Mobile renders rows as cards, so there is room for a real target. */
  body:not([data-layout-mode="desktop"]) .perf-row-del { position: relative; }
  body:not([data-layout-mode="desktop"]) .perf-row-del::after {
    content: ''; position: absolute; left: 50%; top: 50%;
    width: 44px; height: 44px; transform: translate(-50%, -50%);
  }
  body:not([data-layout-mode="desktop"]) .perf-table td { font-size: calc(var(--fs-label) * var(--text-scale)); }
  body:not([data-layout-mode="desktop"]) .perf-toolbar button { min-height: 44px; }
}
body[data-layout-mode="mobile"] .perf-row-del { position: relative; }
body[data-layout-mode="mobile"] .perf-row-del::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%);
}
body[data-layout-mode="mobile"] .perf-table td { font-size: calc(var(--fs-label) * var(--text-scale)); }
body[data-layout-mode="mobile"] .perf-toolbar button { min-height: 44px; }

/* =========================================================
   SETTINGS MODAL AUDIT FIXES
   =========================================================
   Contrast clean in both themes at every width (73 measurements), the
   modal fits without offscreen edges, its body scrolls, and the danger
   zone is reachable. Two real findings, both on the phone. */
@media (max-width: 768px), (any-pointer: coarse) and (max-width: 950px) {
  /* Segmented options measured 39-43px. They are wide and easy to hit,
     but this is a settings sheet with room to spare — no reason to sit
     under the bar. */
  body:not([data-layout-mode="desktop"]) .layout-mode-opt,
  body:not([data-layout-mode="desktop"]) .text-scale-opt,
  body:not([data-layout-mode="desktop"]) .cloud-sync-opt { min-height: 44px; }
  /* Settings labels predate the mobile type ramp and sat at 11-11.5px,
     under its 12px floor: section titles, the sub-line under each label,
     the storage/export stats and the shortcut keys. */
  body:not([data-layout-mode="desktop"]) .settings-section-title,
  body:not([data-layout-mode="desktop"]) .settings-sub,
  body:not([data-layout-mode="desktop"]) #dbStats,
  body:not([data-layout-mode="desktop"]) #lastExportStat,
  body:not([data-layout-mode="desktop"]) #settingsModal kbd,
  body:not([data-layout-mode="desktop"]) #shortcutSheetModal kbd {
    font-size: calc(var(--fs-chip) * var(--text-scale));
  }
  /* The report-type checkboxes are visually-hidden inputs behind their own
     labels — the label is the real target, and it measured 40px tall. */
  body:not([data-layout-mode="desktop"]) .report-type {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
}
body[data-layout-mode="mobile"] .layout-mode-opt,
body[data-layout-mode="mobile"] .text-scale-opt,
body[data-layout-mode="mobile"] .cloud-sync-opt { min-height: 44px; }
body[data-layout-mode="mobile"] .settings-section-title,
body[data-layout-mode="mobile"] .settings-sub,
body[data-layout-mode="mobile"] #dbStats,
body[data-layout-mode="mobile"] #lastExportStat,
body[data-layout-mode="mobile"] #settingsModal kbd,
body[data-layout-mode="mobile"] #shortcutSheetModal kbd {
  font-size: calc(var(--fs-chip) * var(--text-scale));
}
body[data-layout-mode="mobile"] .report-type {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

/* Desktop half of the same finding: the settings labels sat at 11-11.5px,
   under the 12px floor the Performance audit set for desktop secondary
   text. Contrast already passed at every width — this is legibility. */
@media (min-width: 951px), (min-width: 769px) and (not (any-pointer: coarse)) {
  body:not([data-layout-mode="mobile"]) .settings-section-title,
  body:not([data-layout-mode="mobile"]) .settings-sub,
  body:not([data-layout-mode="mobile"]) #dbStats,
  body:not([data-layout-mode="mobile"]) #lastExportStat,
  body:not([data-layout-mode="mobile"]) #settingsModal kbd,
  body:not([data-layout-mode="mobile"]) #shortcutSheetModal kbd { font-size: 12px; }
}
body[data-layout-mode="desktop"] .settings-section-title,
body[data-layout-mode="desktop"] .settings-sub,
body[data-layout-mode="desktop"] #dbStats,
body[data-layout-mode="desktop"] #lastExportStat,
body[data-layout-mode="desktop"] #settingsModal kbd,
body[data-layout-mode="desktop"] #shortcutSheetModal kbd { font-size: 12px; }

/* =========================================================
   DESKTOP DENSITY PASS 1 — the grid obeys the density toggle
   =========================================================
   Measured: .steps-dgrid rows were 41px in compact, comfortable AND
   roomy — --density-scale only ever fed the outline .steps-table, so in
   the default grid view the toggle was a silent no-op. Desktop-scoped:
   the mobile grid keeps its audited 37px rows untouched. */
@media (min-width: 951px), (min-width: 769px) and (not (any-pointer: coarse)) {
  body:not([data-layout-mode="mobile"]) .steps-grid-card[data-density="compact"] .steps-dgrid td {
    padding-top: 2px; padding-bottom: 2px;      /* row ~33px */
  }
  body:not([data-layout-mode="mobile"]) .steps-grid-card[data-density="roomy"] .steps-dgrid td {
    padding-top: 10px; padding-bottom: 10px;    /* row ~49px */
  }
}
body[data-layout-mode="desktop"] .steps-grid-card[data-density="compact"] .steps-dgrid td {
  padding-top: 2px; padding-bottom: 2px;
}
body[data-layout-mode="desktop"] .steps-grid-card[data-density="roomy"] .steps-dgrid td {
  padding-top: 10px; padding-bottom: 10px;
}

/* =========================================================
   DESKTOP DENSITY PASS 2 — detail column, type scale, rail
   =========================================================
   Measured before: doc title 32px (mobile-scale hero; the phone itself
   uses 24), rail header 55px tall for one line (pad 16/14), section
   labels 11px (under the 12px desktop floor the perf audit set). */
@media (min-width: 951px), (min-width: 769px) and (not (any-pointer: coarse)) {
  body:not([data-layout-mode="mobile"]) .detail-title-input { font-size: 26px; }
  body:not([data-layout-mode="mobile"]) .detail-rail .rail-header { padding: 10px 0 8px; }
  body:not([data-layout-mode="mobile"]) .detail-section-title,
  body:not([data-layout-mode="mobile"]) .rail-section-title { font-size: 12px; }
}
body[data-layout-mode="desktop"] .detail-title-input { font-size: 26px; }
body[data-layout-mode="desktop"] .detail-rail .rail-header { padding: 10px 0 8px; }
body[data-layout-mode="desktop"] .detail-section-title,
body[data-layout-mode="desktop"] .rail-section-title { font-size: 12px; }

/* =========================================================
   DESKTOP DENSITY PASS 3 — checklist rows as a flat list
   =========================================================
   Measured: every desktop row was its own bordered, shadowed card
   (38px tall, 1px border, r10, 6px gaps) — a mobile card idiom that
   reads as visual noise in a mouse UI. Native Windows lists are flat:
   separation by hover, not by chrome. Rows drop to ~32px and the
   per-row border/shadow goes; batch-select and checked states keep
   their own backgrounds. Mobile keeps its audited 46px cards. */
@media (min-width: 951px), (min-width: 769px) and (not (any-pointer: coarse)) {
  body:not([data-layout-mode="mobile"]) .checklist-item {
    background: transparent;
    border-color: transparent;
    box-shadow: none;
    padding: 3px 9px;
    border-radius: 8px;
  }
  body:not([data-layout-mode="mobile"]) .checklist-item:hover {
    background: var(--bg);
    border-color: transparent;
  }
  body:not([data-layout-mode="mobile"]) .checklist-items { gap: 2px; }
}
body[data-layout-mode="desktop"] .checklist-item {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  padding: 3px 9px;
  border-radius: 8px;
}
body[data-layout-mode="desktop"] .checklist-item:hover {
  background: var(--bg);
  border-color: transparent;
}
body[data-layout-mode="desktop"] .checklist-items { gap: 2px; }

/* =========================================================
   DESKTOP DENSITY PASS 4 — shell chrome
   =========================================================
   Measured: the breadcrumb bar was 52px tall holding 30px of content,
   the navigator header 44px (pad 10/8), tree folder rows 39px (pad 6).
   Mouse chrome doesn't need that slack; the layout's height calcs all
   read --folder-taskbar-h, so everything below the bar follows. */
@media (min-width: 951px), (min-width: 769px) and (not (any-pointer: coarse)) {
  body:not([data-layout-mode="mobile"]) { --folder-taskbar-h: 44px; }
  body:not([data-layout-mode="mobile"]) .desk-nav-head { padding: 6px 8px 6px 16px; }
  body:not([data-layout-mode="mobile"]) .browse-view .browse-group > .folder-row {
    padding-top: 4px; padding-bottom: 4px;
  }
}
body[data-layout-mode="desktop"] { --folder-taskbar-h: 44px; }
body[data-layout-mode="desktop"] .desk-nav-head { padding: 6px 8px 6px 16px; }
body[data-layout-mode="desktop"] .browse-view .browse-group > .folder-row {
  padding-top: 4px; padding-bottom: 4px;
}

/* =========================================================
   TABLET DENSITY PASS (coarse pointer, 769-950px)
   =========================================================
   A touch tablet in this band gets the phone layout stretched wide.
   Measured at 820/912: detail content unbounded (max-width 100%) so
   running text hit 99-110 chars per line, and every modal opened as a
   full-width bottom sheet — an 820-912px wall. The browse view already
   caps at 840px; the detail column now matches it, the description
   keeps a readable measure, and modals return to the centered dialog
   the base layer already defines. Touch idioms stay: bottom nav, FAB,
   44px rows, the metadata sheet, and the full-screen search modal. */
@media (min-width: 769px) and (max-width: 950px) and (any-pointer: coarse) {
  body:not([data-layout-mode="desktop"]) .detail-content {
    max-width: 840px;
    margin-left: auto;
    margin-right: auto;
  }
  body:not([data-layout-mode="desktop"]) .detail-description { max-width: 68ch; }
  body:not([data-layout-mode="desktop"]) .modal-backdrop {
    align-items: center;
    padding: 80px 20px 20px;
  }
  body:not([data-layout-mode="desktop"]) .modal {
    max-width: 560px;
    max-height: calc(100vh - 100px);
    border-radius: var(--radius-lg);
    animation: modalPopIn var(--duration-sheet) var(--ease-settle, cubic-bezier(0.22, 0.61, 0.36, 1));
  }
  /* Search stays the full-screen takeover it is on every touch layout. */
  body:not([data-layout-mode="desktop"]) .modal-search {
    max-width: 100%; height: 100vh; max-height: 100vh; border-radius: 0;
  }
}
/* Forced-mobile mode on a tablet-or-wider window: same corrections. */
@media (min-width: 769px) {
  body[data-layout-mode="mobile"] .detail-content {
    max-width: 840px;
    margin-left: auto;
    margin-right: auto;
  }
  body[data-layout-mode="mobile"] .detail-description { max-width: 68ch; }
  body[data-layout-mode="mobile"] .modal-backdrop {
    align-items: center;
    padding: 80px 20px 20px;
  }
  body[data-layout-mode="mobile"] .modal {
    max-width: 560px;
    max-height: calc(100vh - 100px);
    border-radius: var(--radius-lg);
    animation: modalPopIn var(--duration-sheet) var(--ease-settle, cubic-bezier(0.22, 0.61, 0.36, 1));
  }
  body[data-layout-mode="mobile"] .modal-search {
    max-width: 100%; height: 100vh; max-height: 100vh; border-radius: 0;
  }
}

/* The metadata sheet stays a bottom sheet on tablets — it's a pane
   idiom (grabber, 75vh), not a dialog; only its backdrop needed
   re-anchoring after the centered-dialog rules above. */
@media (min-width: 769px) and (max-width: 950px) and (any-pointer: coarse) {
  body:not([data-layout-mode="desktop"]) #mobileRailModal { align-items: flex-end; padding: 0; }
  body:not([data-layout-mode="desktop"]) #mobileRailModal .modal {
    max-width: 100%;
    max-height: 80vh;
    animation: sheetUp 0.22s cubic-bezier(0.16, 1, 0.3, 1);
  }
}
@media (min-width: 769px) {
  body[data-layout-mode="mobile"] #mobileRailModal { align-items: flex-end; padding: 0; }
  body[data-layout-mode="mobile"] #mobileRailModal .modal {
    max-width: 100%;
    max-height: 80vh;
    animation: sheetUp 0.22s cubic-bezier(0.16, 1, 0.3, 1);
  }
}

/* =========================================================
   DESKTOP UPLIFT PASS — type-scale stragglers
   =========================================================
   The uplift audit measured the type roles across the shell and detail:
   three values sat off the desktop scale. The navigator title rendered
   at 11px (under the 12px caption floor), the description at 15px with
   a 1.55 line-height (arm's-length body on a desk), and the grid's
   column headers at 10.5px. Radii (8/10/14/20 by class), icon-to-label
   alignment (0.0px deltas) and the palette (every chrome colour a
   Schema-1 hue or tint) all measured clean — left alone. */
@media (min-width: 951px), (min-width: 769px) and (not (any-pointer: coarse)) {
  body:not([data-layout-mode="mobile"]) .desk-nav-title { font-size: 12px; }
  body:not([data-layout-mode="mobile"]) .detail-description {
    font-size: 14px;
    line-height: 1.45;
  }
  body:not([data-layout-mode="mobile"]) .steps-dgrid thead th { font-size: 11px; }
}
body[data-layout-mode="desktop"] .desk-nav-title { font-size: 12px; }
body[data-layout-mode="desktop"] .detail-description {
  font-size: 14px;
  line-height: 1.45;
}
body[data-layout-mode="desktop"] .steps-dgrid thead th { font-size: 11px; }

/* =========================================================
   DESKTOP MENUS PASS — sheet-idiom stragglers + gantt micro-type
   =========================================================
   Measured: the "+" dropdown and folder context menu already sit at
   Windows scale (33px items, 13px labels, one 15px icon size) — left
   alone. Two misses: action sheets (the sort menu) still rose as
   phone bottom sheets from the base of a desktop monitor, and the
   gantt ruler's day-of-week letters rendered at 8px. The sheet keeps
   its DOM and dismiss stack — only the presentation changes; the
   grabber hides because there is nothing to drag with a mouse. */
@media (min-width: 951px), (min-width: 769px) and (not (any-pointer: coarse)) {
  body:not([data-layout-mode="mobile"]) .action-sheet-backdrop {
    align-items: center;
    padding: 20px;
  }
  body:not([data-layout-mode="mobile"]) .action-sheet {
    max-width: 360px;
    border-radius: 14px;
    padding: 10px 12px 12px;
    animation: modalPopIn var(--duration-sheet) var(--ease-settle, cubic-bezier(0.22, 0.61, 0.36, 1));
  }
  body:not([data-layout-mode="mobile"]) .action-sheet-handle { display: none; }
  body:not([data-layout-mode="mobile"]) .g-day .g-dow { font-size: 9.5px; }
}
body[data-layout-mode="desktop"] .action-sheet-backdrop {
  align-items: center;
  padding: 20px;
}
body[data-layout-mode="desktop"] .action-sheet {
  max-width: 360px;
  border-radius: 14px;
  padding: 10px 12px 12px;
  animation: modalPopIn var(--duration-sheet) var(--ease-settle, cubic-bezier(0.22, 0.61, 0.36, 1));
}
body[data-layout-mode="desktop"] .action-sheet-handle { display: none; }
body[data-layout-mode="desktop"] .g-day .g-dow { font-size: 9.5px; }

/* Desktop action-sheet rows: 14px vertical padding + 15px labels made
   48px touch rows inside what is now a mouse dialog. 8px/13px matches
   the "+" dropdown and context menu (33px rows, 13px labels). */
@media (min-width: 951px), (min-width: 769px) and (not (any-pointer: coarse)) {
  body:not([data-layout-mode="mobile"]) .action-sheet-body button {
    padding: 8px 10px;
    font-size: 13px;
    border-radius: 8px;
  }
}
body[data-layout-mode="desktop"] .action-sheet-body button {
  padding: 8px 10px;
  font-size: 13px;
  border-radius: 8px;
}

/* =========================================================
   ACCOUNT GATE — the login wall (multi-user deploys only)
   =========================================================
   Rendered above everything, outside the LayerStack (Back/Esc must not
   dismiss it). Tokens carry both themes. Only shown when the deploy has
   the auth API and no session exists — a static host never sees it. */
.auth-gate {
  position: fixed;
  inset: 0;
  z-index: 500;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  padding-bottom: max(24px, env(safe-area-inset-bottom));
}
.auth-card {
  width: 100%;
  max-width: 360px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
  padding: 28px 24px 22px;
}
.auth-brand {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--accent-text);
  margin-bottom: 4px;
}
.auth-sub {
  font-size: 13px;
  color: var(--ink-mute);
  margin-bottom: 18px;
}
.auth-label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-soft);
  margin: 12px 0 4px;
}
.auth-input {
  width: 100%;
  box-sizing: border-box;
  min-height: 44px;
  padding: 10px 12px;
  /* 16px floor: below it, mobile Safari/Chrome zoom the page on focus. */
  font-size: 16px;
  font-family: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.auth-input:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.auth-error {
  margin-top: 12px;
  font-size: 13px;
  color: var(--danger-text);
}
.auth-submit {
  width: 100%;
  margin-top: 16px;
  min-height: 44px;
  border-radius: 10px;
  font-size: 14px;
}
.auth-submit[disabled] { opacity: 0.6; cursor: default; }
.auth-switch {
  display: block;
  width: 100%;
  margin-top: 14px;
  font-size: 13px;
  color: var(--accent-text);
  text-align: center;
  padding: 8px;
  border-radius: 8px;
}
.auth-switch:hover { background: var(--accent-bg); }

/* Import preview: the captured description's first line, under its
   step/sub-step title — proof pre-import that descriptions were read. */
.proc-import-descline {
  font-size: 11.5px;
  color: var(--ink-mute);
  margin: 1px 0 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* =========================================================
   INSTALLED-APP TITLE BAR (window-controls-overlay)
   =========================================================
   In the installed Windows app, the OS window buttons float over the
   page's top-right corner (manifest display_override). Un-styled, they
   sat in a bare strip clashing with the header and could cover the
   topbar's own icons — and the window had no drag handle at all, since
   WCO apps must declare their own. Scoped to the installed mode only;
   browser tabs and phones never match this media query. */
@media (display-mode: window-controls-overlay) {
  header.topbar {
    -webkit-app-region: drag;
    app-region: drag;
    /* Reserve the corner the OS buttons occupy. */
    padding-right: max(24px, calc(100vw - env(titlebar-area-x, 0px) - env(titlebar-area-width, 100vw)));
  }
  header.topbar button,
  header.topbar input,
  header.topbar a {
    -webkit-app-region: no-drag;
    app-region: no-drag;
  }
}
