/* =============================================================================
 * Phase 2 · silhouettes v2 helper classes (Pass 3 § 03 + § 05).
 *
 * Selection ring inherits currentColor from .nshape parent. Freshness halo
 * pulses 3 s when fresh (≤ 6 h); steady stroke otherwise. Stale (> 30 d)
 * desaturation is inline opacity via the silhouettes module — no CSS here.
 * ============================================================================= */
.es-sel-ring { color: var(--hot); pointer-events: none; }
.es-halo { pointer-events: none; }
/* Sprint 4.3 Issue 1 — the freshness PULSE animation is removed. The user
   explicitly does not want a perpetually-running animation, and ~88 nodes
   pulsing keeps the iOS compositor awake (battery + repaint) even at rest.
   Freshness now reads as a STATIC halo: halo.js sets stroke-opacity by age
   (fresh ≈ 0.55, fading with age). The .es-halo-pulse class halo.js still
   emits is now inert — no @keyframes, no motion anywhere on the canvas. */
.es-halo-pulse { animation: none; }

/* =============================================================================
 * Sprint 4.2 · fx suppression — the real Phase-4 perf lever.
 *
 * The HUD proved the bottleneck is PAINT, not transform: commit ~2.4ms but
 * worst ~200ms during pan. The cost is the per-node freshness halo (a stroked
 * circle whose `es-halo-pulse` is an always-on CSS animation → the iOS layer
 * re-rasters every frame) plus the selection drop-shadows / edge glow. SVG
 * filters and animated strokes are NOT GPU-composited on iOS Safari.
 *
 * Two routes funnel here, both default OFF:
 *   · body.no-fx        — --no-fx: kill fx always (Issue 2 diagnostic).
 *   · body.fx-suppressed — --fx-motion: kill fx only WHILE the canvas is in
 *                          motion (Issue 3 Path A); the static view keeps full
 *                          fidelity, so the user only loses the halo while
 *                          actively panning — when they can't perceive it anyway.
 *
 * We remove the halo geometry outright (display:none → no paint, no animation)
 * and neutralise every CSS/SVG filter inside the two canvas layers. Shapes,
 * fills, paths and colours are untouched. Recalc happens once per class toggle
 * (gesture start / settle), never per frame. */
body.no-fx .es-halo,
body.fx-suppressed .es-halo { display: none !important; }
body.no-fx svg#cv *, body.no-fx #canvasOverlay *,
body.fx-suppressed svg#cv *, body.fx-suppressed #canvasOverlay * { filter: none !important; }

/* =============================================================================
 * Phase 2 · edge legend (Pass 3 § 06).
 *
 * Auto-docked bottom-right when canvas has ≥ 3 distinct edge types in use.
 * DOM element (NOT SVG) so it stays at native pixel size at any zoom.
 * Dismissible per type-signature (re-opens when a new type is added).
 * ============================================================================= */
#edgeLegend {
  position: fixed;
  inset-inline-end: var(--s-3);
  bottom: var(--s-8);
  z-index: 28;
  background: var(--srf-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-3);
  box-shadow: var(--el-3);
  font-family: var(--font-sans);
  font-size: 12px;
  min-width: 180px;
  max-width: 220px;
  color: var(--ink);
  -webkit-user-select: none; user-select: none;
}
#edgeLegend .el-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 6px 10px;
  border-bottom: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-4);
  text-transform: uppercase;
  letter-spacing: 0.12em;
}
#edgeLegend .el-x {
  cursor: pointer; padding: 0 4px; color: var(--ink-4);
}
#edgeLegend .el-x:hover { color: var(--ink); }
#edgeLegend .el-body { padding: 4px 0; }
#edgeLegend .el-row {
  display: flex; align-items: center; gap: var(--s-2);
  padding: 4px 10px;
  color: var(--ink-2);
  cursor: default;
  transition: background var(--t-instant) var(--e-out);
}
#edgeLegend .el-row:hover {
  background: rgba(255,255,255,0.04);
  color: var(--ink);
}
#edgeLegend .el-row svg { flex-shrink: 0; }

/* =============================================================================
 * Sprint 3 preamble · Edge selection + controls panel.
 *
 * Sprint 3.1 Issue 2 — each edge renders TWO <path> elements:
 *   path.e2-hit   (or .edge-hit)  20-px transparent stroke, pointer target
 *   path.e2       (or .edge)      thin visible stroke, pointer-events:none
 * The hit-path catches all taps cleanly even on iPad. The visible path keeps
 * its existing styling. On selection we add .e2-sel to the visible path.
 * ============================================================================= */
path.e2-hit, path.edge-hit {
  pointer-events: stroke;
  cursor: pointer;
  /* Defensive: even though stroke="transparent", make sure nothing renders. */
  fill: none;
}
path.e2, path.edge { cursor: pointer; }
path.e2-sel,
path.edge.e2-sel { stroke-width: 3px !important; filter: drop-shadow(0 0 4px var(--hot-ring)); }
#edgeControls {
  background: var(--srf-3);
  border: 1px solid var(--line-3);
  border-radius: var(--r-3);
  box-shadow: var(--el-3);
  padding: 4px;
  font-family: var(--font-sans);
  -webkit-user-select: none; user-select: none;
}
#edgeControls .ec-row {
  display: flex; align-items: center; gap: 6px;
}
#edgeControls .ec-type-chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 28px; padding: 0 10px;
  background: var(--srf-2); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-pill);
  font-family: var(--font-mono); font-size: 11px; cursor: pointer;
}
#edgeControls .ec-type-chip:hover { background: var(--srf-4); }
#edgeControls .ec-type-sw { width: 14px; height: 3px; border-radius: 2px; display: inline-block; }
.ec-type-sw.ec-type-feeds      { background: var(--hot); }
.ec-type-sw.ec-type-blocker    { background: var(--st-blocked); }
.ec-type-sw.ec-type-derived    { background: var(--st-progress); }
.ec-type-sw.ec-type-example    { background: var(--st-done); }
.ec-type-sw.ec-type-proof      { background: var(--st-done); border: 1px dashed var(--st-done); height: 2px; }
.ec-type-sw.ec-type-related    { background: var(--ink-3); }
.ec-type-sw.ec-type-idea-from  { background: var(--st-idea); }
.ec-type-sw.ec-type-tentative  { background: var(--st-pending); opacity: 0.55; }
#edgeControls .ec-label-input {
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: var(--r-2);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 12px;
  padding: 0 8px;
  height: 28px;
  width: 140px;
  outline: none;
}
#edgeControls .ec-label-input:focus { border-color: var(--hot); box-shadow: 0 0 0 2px var(--hot-ring); }
#edgeControls .ec-trash {
  background: transparent;
  border: 1px solid var(--line-2);
  border-radius: var(--r-2);
  color: var(--st-blocked);
  font-size: 16px; line-height: 1;
  height: 28px; width: 28px;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
#edgeControls .ec-trash:hover { background: rgba(248,113,113,0.10); border-color: rgba(248,113,113,0.40); }

/* =============================================================================
 * Sprint 3.1 Issue 3 · Edge-draw handles (touch-friendly link gesture).
 *
 * A 24-px touch target with a 12-px visible dot in the centre. The dot uses
 * the hot accent so it reads as a live grab affordance. Halo ring around
 * the dot lifts it off whatever the selected node is sitting on.
 * ============================================================================= */
#edgeDrawHandles { /* fixed-position layer set inline by edge-draw.js */ }
.edge-handle {
  /* sizing/positioning is inline (per-handle); this rule is for visual style */
  border-radius: 50%;
  transition: transform var(--t-fast, 120ms) var(--e-out, ease-out);
}
.edge-handle:hover { transform: scale(1.18); }
.edge-handle:active { transform: scale(0.92); }
.edge-handle-dot {
  display: inline-block;
  width: 12px; height: 12px;
  background: var(--hot, #FF7A45);
  border: 2px solid var(--hot-ring, rgba(255,122,69,0.45));
  border-radius: 50%;
  box-shadow:
    0 0 0 2px var(--bg, #08090C),
    0 0 6px 0 var(--hot-ring, rgba(255,122,69,0.45));
  pointer-events: none;
}
/* Hide during active drag/pan to reduce visual noise. */
body.dragging .edge-handle { opacity: 0; pointer-events: none; }

/* =============================================================================
 * Sprint 3.1 Issue 4 · Orange selection-glow residue fix.
 *
 * `.nslice.sel` in src/app.css carries a drop-shadow filter for the orange
 * selection glow. During a drag, the fast-path sets `.nslice` inline
 * `style.transform = translate(dx, dy)` every pointermove (skipping
 * render() to avoid the ghost-trail). On iOS WebKit, drop-shadow + inline
 * transform = the blur backing store gets stuck at the pre-drag position,
 * leaving a faint orange smudge at the old node spot until the next paint.
 *
 * Fix: drop the filter during `body.dragging`. The SVG `<circle class="ring">`
 * inside the slice (single element per node, slice-attached, no compositor
 * caching) keeps the user's selection unambiguous during motion. Glow
 * returns instantly on pointerup.
 *
 * Belt-and-suspenders: `will-change: transform` promotes selected slices
 * to their own compositor layer up-front, so when the drag begins the
 * layer is already isolated and the filter buffer would lose its source
 * cleanly even if the !important rule somehow loses cascade priority.
 * ============================================================================= */
.nslice.sel { will-change: transform; }
/* Sprint 3.2 Issue 1 — the same WebKit drop-shadow / inline-transform
   compositor bug also applies to `.nslice.holding` (press-and-hold
   visual feedback in src/app.css:409), which is added to ANY node the
   user grabs — not just selected ones. Sprint 3.1's override only
   covered .nslice.sel. Extend to cover EVERY .nslice during body.dragging
   or body.holding (the slice may have neither .sel nor .holding when the
   user did a quick tap-and-drag without the hold gate).
   The SVG g.node.holding > .th already provides an orange tint inside
   the cv SVG (which slides cleanly via SVG-transform, no residue), so
   no visual is lost. */
body.dragging .nslice,
body.holding .nslice,
.nslice.holding {
  filter: none !important;
  transition: none !important;
}

/* =============================================================================
 * Sprint 3 · Workspace spine + canvas drawer (Pass 4 § 01).
 *
 * Both anchored to the leading-script edge. Mirror under body[dir="rtl"].
 * z-index higher than #tabs (15) so they sit above the bottom tab strip
 * when both are active during the transition week.
 * ============================================================================= */
#spine {
  position: fixed;
  inset-block: 0;
  inset-inline-start: 0;
  width: 56px;
  z-index: 35;
  background: linear-gradient(180deg, #13171D 0%, #0E1116 100%);
  border-inline-end: 1px solid var(--line);
  padding: 16px 0 16px 0;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  -webkit-user-select: none; user-select: none;
}
#spine .spine-top { display: flex; flex-direction: column; align-items: center; gap: 10px; flex: 1; overflow-y: auto; width: 100%; }
#spine .spine-bot { display: flex; flex-direction: column; align-items: center; gap: 8px; padding-bottom: 8px; }
#spine .ws-chip {
  /* Sprint 3.2 Issue 2 — slightly bigger (36px) + visible 1px border so
     the chip reads against the dark spine even when its background hue
     is dim. Outer shadow keeps the soft halo. */
  width: 36px; height: 36px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: 12px; font-weight: 700; color: white;
  position: relative; cursor: pointer;
  transition: transform var(--t-quick) var(--e-out), box-shadow var(--t-quick) var(--e-out);
  box-shadow:
    0 0 0 1px rgba(255,255,255,0.12),
    0 1px 0 rgba(255,255,255,0.22) inset,
    0 4px 12px -4px rgba(0,0,0,0.55);
  border: 0;
  padding: 0;
}
#spine .ws-chip:hover { transform: scale(1.06); box-shadow:
    0 0 0 1px rgba(255,255,255,0.32),
    0 1px 0 rgba(255,255,255,0.28) inset,
    0 6px 14px -4px rgba(0,0,0,0.6); }
#spine .ws-chip.active {
  /* Reinforce: bright outline ring + slight scale so the active chip
     stands out even before the eye registers the leading-edge rail. */
  box-shadow:
    0 0 0 2px var(--ink, #F0EBE5),
    0 1px 0 rgba(255,255,255,0.28) inset,
    0 6px 14px -4px rgba(0,0,0,0.6);
}
#spine .ws-chip.active::before {
  /* Was at inset-inline-start: -16px which put the rail at viewport x=-4
     (cropped off-screen). With 36px chips + spine padding the chip's
     leading edge sits at viewport x=9.5; offset -6 puts the rail at
     viewport x=3.5–7.5, fully visible in the spine's leading gutter. */
  content: ""; position: absolute;
  inset-inline-start: -6px;
  top: 7px; bottom: 7px;
  width: 4px;
  border-radius: 2px;
  background: var(--ink, #F0EBE5);
  box-shadow: 0 0 6px var(--ink, #F0EBE5);
}
#spine .ws-chip.add {
  background: none !important; color: var(--ink-4) !important;
  border: 1px dashed var(--line-3);
  box-shadow: none;
}
#spine .sb {
  /* Sprint 3.2 Issue 7 — bigger + bordered + brighter so the hamburger
     reads as an actionable button instead of a faint glyph.
     Sprint 3.5 Issue 2 — `touch-action: manipulation` eliminates iOS
     Safari's 300 ms tap delay AND prevents iOS from interpreting a
     1-px finger micro-movement as a drag; this was the most likely
     cause of the "gear chip doesn't open Tools" report. */
  width: 36px; height: 36px; border-radius: 10px;
  background: var(--srf-2); border: 1px solid var(--line-2); color: var(--ink-2); cursor: pointer;
  font-size: 18px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
#spine .sb:hover { background: var(--srf-3); color: var(--ink); border-color: var(--line-3); }
/* Sprint 3.5 Issue 2 — softened :active scale (was 0.95) so the touch's
   sub-pixel movement during the scale doesn't trip iOS's drag detection. */
#spine .sb:active { transform: scale(0.97); }
/* Same touch-action for workspace chips, which also sit in the spine. */
#spine .ws-chip { touch-action: manipulation; -webkit-tap-highlight-color: transparent; }

/* =============================================================================
 * Sprint 4 · Phase 4 (Pass 5 §03) · canvas tile layer.
 *
 * #tiles is a 2D-context HTMLCanvasElement that rasterises the dot-grid +
 * zone fills at the current zoom tier. Sized to the viewport at
 * devicePixelRatio so the raster is crisp on Retina. Pan/pinch within a
 * tier translate this element via CSS transform — no redraw.
 *
 * Sits BELOW the SVG (`#cv` z-index 0 was the default; here we say
 * `#tiles` z = 0 and shift `#cv` up via the body class set by tile-cache).
 * When --canvas-tiles is OFF, tile-cache.js never paints into #tiles and
 * the canvas stays transparent, so SVG paints on top of empty pixels.
 * ============================================================================= */
#canvas-root {
  /* Layout-transparent semantic wrapper. Children are position:fixed. */
  position: static;
  display: contents;
}
#tiles {
  position: fixed;
  top: 0; left: 0;
  width: 100vw; height: 100vh;
  height: 100dvh;
  z-index: 0;
  pointer-events: none;
  /* Same transform origin as #canvasOverlay so the same view math
     produces the same on-screen position. */
  transform-origin: 0 0;
  -webkit-transform-origin: 0 0;
  will-change: transform;
  /* Sub-pixel rendering hint for the dot-grid */
  image-rendering: auto;
}
/* When tiles is active, lift #cv above it. The existing #cv z-index:0 in
   src/app.css means tiles + cv would fight; this body class ensures
   tiles paints under cv. */
body.canvas-tiles-on svg#cv { z-index: 1; }
body.canvas-tiles-on #canvasOverlay { z-index: 2; }
/* Sprint 3.3 Issue 7 — Tools (gear) chip distinguished from the drawer
   hamburger by a faint hot tint, since it opens a meaningfully different
   surface (a tools menu vs a layout toggle). */
#spine .sb.tools { color: var(--ink-2); }
#spine .sb.tools:hover { color: var(--hot, #FF7A45); border-color: var(--hot-ring, rgba(255,122,69,0.45)); }

/* Sprint 3.4 Issue 4 — promoted spine chips. The bot section now stacks
   Drawer / Search / Undo / Import / Lang / Tools / + (top-to-bottom).
   A <label> chip is used for Import so the iOS user-gesture chain
   forwards to the file input cleanly. */
#spine .sb[data-act="search"] { color: var(--ink); }
#spine .sb[data-act="search"]:hover { color: var(--hot, #FF7A45); border-color: var(--hot-ring, rgba(255,122,69,0.45)); }
#spine .sb.disabled,
#spine .sb[disabled] {
  opacity: 0.4;
  pointer-events: none;
  cursor: default;
}
/* The Import label-chip — same dimensions as other .sb buttons. */
#spine label.sb {
  display: inline-flex; /* the rest is inherited from .sb */
}

/* =============================================================================
 * Sprint 3.3 Issue 7 · Tools panel (replaces #tb top toolbar).
 *
 * Slides out from the trailing edge of the spine. Same 224 px width as
 * the drawer for visual symmetry. Lives at z-index 33 — below the
 * spine (35) and palette (200) but above the canvas chrome.
 * ============================================================================= */
#toolsPanel {
  position: fixed;
  inset-block-start: 0;
  inset-block-end: 0;
  inset-inline-start: 56px;
  width: 264px; max-width: 80vw;
  /* Sprint 3.3 Issue 7 — sits above the drawer (z-index 34) so it can
     overlay when both happen to be open in the same session; below the
     palette (200) and edge controls. */
  z-index: 36;
  background: var(--srf-2);
  border-inline-end: 1px solid var(--line);
  padding: 16px 12px;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--ink);
  -webkit-user-select: none; user-select: none;
  transform: translateX(-100%);
  opacity: 0; visibility: hidden;
  transition: transform var(--t-base, 220ms) var(--e-out, ease-out),
              opacity var(--t-quick, 140ms) var(--e-out, ease-out),
              visibility 0ms linear var(--t-base, 220ms);
  overflow-y: auto;
}
#toolsPanel.on {
  transform: translateX(0);
  opacity: 1; visibility: visible;
  transition: transform var(--t-base, 220ms) var(--e-out, ease-out),
              opacity var(--t-quick, 140ms) var(--e-out, ease-out),
              visibility 0ms linear 0ms;
}
body[dir="rtl"] #toolsPanel {
  inset-inline-start: auto; inset-inline-end: 56px;
  border-inline-end: 0; border-inline-start: 1px solid var(--line);
  transform: translateX(100%);
}
body[dir="rtl"] #toolsPanel.on { transform: translateX(0); }

#toolsPanel .tp-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 4px 12px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 8px;
}
#toolsPanel .tp-title {
  font-size: 14px; font-weight: 600; color: var(--ink);
}
#toolsPanel .tp-close {
  width: 32px; height: 32px;
  background: transparent;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  color: var(--ink-2);
  font-size: 20px; line-height: 1;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
#toolsPanel .tp-close:hover { background: var(--srf-3); color: var(--ink); }
#toolsPanel .tp-section { margin: 12px 0 16px; }
#toolsPanel .tp-section-head {
  font-family: var(--font-mono); font-size: 10px;
  color: var(--ink-4); letter-spacing: 0.12em; text-transform: uppercase;
  padding: 4px 6px 8px;
}
#toolsPanel .tp-row {
  display: grid; grid-template-columns: 28px 1fr auto; align-items: center;
  width: 100%; background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 8px 10px;
  cursor: pointer;
  color: var(--ink);
  font-family: inherit; font-size: 13px;
  text-align: start;
}
#toolsPanel .tp-row:hover { background: var(--srf-3); border-color: var(--line-2); }
#toolsPanel .tp-ic { font-size: 14px; color: var(--ink-2); justify-self: start; }
#toolsPanel .tp-name { font-weight: 500; }
#toolsPanel .tp-sub  { font-size: 11px; color: var(--ink-4); }

@media (pointer: coarse) {
  #toolsPanel .tp-row { padding: 12px 10px; }
  #toolsPanel .tp-close { width: 44px; height: 44px; font-size: 24px; }
}

/* =============================================================================
 * Sprint 3.3 Issue 6 · Retire the OLD bottom canvas-tab strip when the
 * new spine is active. Without nav-v2 the strip stays — emergency
 * rollback. With nav-v2 ON, the canvas reclaims that 48 px.
 * ============================================================================= */
body.nav-v2-on #tabs { display: none !important; }
/* Sprint 3.4 Issue 1 — the top-right breadcrumb (#bc) showed
   Workspace → Parent → Current canvas lineage. The drawer now shows the
   same information persistently. Hidden under nav-v2; left visible when
   the flag is OFF for rollback. */
body.nav-v2-on #bc { display: none !important; }
/* Sprint 3.4 Issue 3 — the bottom-left "Nodes/Edges/Zones" sidebar (#sb)
   showed the current canvas's contents grouped by zone. Drawer Issue 2
   now exposes the same canvas → zone → node hierarchy persistently in
   the spine area, so the sidebar is redundant. Hidden under nav-v2;
   left visible when flag is OFF for rollback. */
body.nav-v2-on #sb { display: none !important; }
/* Also hide its toggle button #sbBtn if present, since clicking it now
   has no effect. */
body.nav-v2-on #sbBtn { display: none !important; }

/* =============================================================================
 * Sprint 3.3 Issue 7 · Retire the OLD top toolbar when the user opts in
 * to the migrated Tools panel. Flag-gated so the user can roll back
 * mid-session if the Tools panel falls short. The search pill stays in
 * place regardless — it's not part of #tb.
 * ============================================================================= */
body.toolbar-migrated-on #tb { display: none !important; }
body[dir="rtl"] #spine { inset-inline-start: auto; inset-inline-end: 0; border-inline-end: 0; border-inline-start: 1px solid var(--line); }

#drawer {
  position: fixed;
  inset-block-start: 0;
  /* Sprint 3.3 Issue 6 — #tabs is now hidden under nav-v2 (see rule
     `body.nav-v2-on #tabs { display: none }`), so the drawer can
     extend to the viewport bottom. */
  inset-block-end: 0;
  inset-inline-start: 56px;
  width: 224px;
  z-index: 34;
  background: var(--srf-1);
  border-inline-end: 1px solid var(--line);
  padding: 16px 14px;
  font-size: 13px;
  font-family: var(--font-sans);
  overflow-y: auto;
  -webkit-user-select: none; user-select: none;
  transition: width var(--t-base) var(--e-out);
  /* Sprint 4 Issue 4 — custom scrollbar so the desktop default Chrome
     chrome doesn't break the visual system. Tokens come from
     css/tokens.css; ink-3 is the same muted ink used for secondary
     drawer text so the thumb fades into the panel ink ramp. */
  scrollbar-width: thin;
  scrollbar-color: var(--ink-3) transparent;
}
#drawer::-webkit-scrollbar { width: 6px; height: 6px; }
#drawer::-webkit-scrollbar-track { background: transparent; }
#drawer::-webkit-scrollbar-thumb { background: var(--ink-3); border-radius: 3px; }
#drawer::-webkit-scrollbar-thumb:hover { background: var(--ink-2); }
/* Sprint 3.5 Issue 3 — collapsed drawer must be ZERO pixels visible.
   The previous 40-px vertical strip with a chevron was: (a) a redundant
   second entry point alongside the hamburger chip on the spine, (b)
   clipping the bottom-left "shift+drag = connect" help legend and the
   trailing edge of Kanban columns. Now the entire drawer is fully
   hidden when collapsed; the spine's hamburger is the only way to
   reopen it.

   Sprint 3.6 Issue 6 — close animation symmetry. The previous
   `visibility: hidden !important` short-circuited the width transition
   on close (instantly invisible → no anim). Now we delay the visibility
   hide via `transition: visibility 0ms linear var(--t-base)`, which
   sets visibility:hidden only AFTER the width transition completes.
   The expand path uses `visibility: visible` with 0 delay so the
   content paints in immediately at width:0 and then animates outward.
   Easing and duration match for symmetry. */
#drawer {
  transition:
    width var(--t-base, 220ms) var(--e-out, ease-out),
    padding var(--t-base, 220ms) var(--e-out, ease-out),
    visibility 0ms linear 0ms;
}
#drawer.collapsed {
  width: 0 !important;
  padding: 0 !important;
  border: 0 !important;
  overflow: hidden !important;
  visibility: hidden;
  transition:
    width var(--t-base, 220ms) var(--e-out, ease-out),
    padding var(--t-base, 220ms) var(--e-out, ease-out),
    visibility 0ms linear var(--t-base, 220ms);
}
#drawer.collapsed > * { display: none; }
#drawer .dr-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 14px; gap: 8px;
}
#drawer .ws-name { font-size: 14px; font-weight: 500; color: var(--ink); display: flex; align-items: center; gap: 8px; min-width: 0; }
#drawer .ws-name .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
#drawer .ws-name span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#drawer .ws-count { font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-4); flex-shrink: 0; }
#drawer .dr-toggle {
  /* Sprint 3.2 Issue 3 — was 10px font with 4px padding = ~18px target,
     unusable on touch. Now 32px hit target with a 16px icon. */
  background: transparent; border: 1px solid transparent; color: var(--ink-2); cursor: pointer;
  font-size: 16px; line-height: 1;
  width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 8px;
}
#drawer .dr-toggle:hover { color: var(--ink); background: var(--srf-2); border-color: var(--line-2); }
#drawer.collapsed .dr-toggle { width: 32px; height: 32px; margin: 0 auto; }
#drawer .dr-tree { display: flex; flex-direction: column; gap: 2px; }
#drawer .dr-row {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 6px 5px 6px;
  border-radius: 6px; cursor: pointer;
  color: var(--ink-2);
  font-size: 12.5px;
  min-height: 28px;
}
#drawer .dr-row:hover { background: rgba(255,255,255,0.03); color: var(--ink); }
#drawer .dr-row.active { background: rgba(255,122,69,0.10); color: var(--hot); }
#drawer .dr-row .caret {
  /* Sprint 3.2 Issue 3 — caret was a 10px font in a 12px-wide box, too
     tiny to tap reliably on iPad. Now a 24x24 inline-flex hit target. */
  width: 24px; height: 24px;
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  font-family: var(--font-mono); font-size: 14px; color: var(--ink-3);
  border-radius: 4px;
}
#drawer .dr-row .caret:hover { background: var(--srf-2); color: var(--ink); }
@media (pointer: coarse) {
  /* iPad / touch: 44x44 minimum per Apple HIG. */
  #drawer .dr-row .caret { width: 32px; height: 32px; font-size: 16px; }
  #drawer .dr-toggle { width: 44px; height: 44px; font-size: 18px; }
}
#drawer .dr-row .child-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; opacity: 0.7; }
#drawer .dr-row .dr-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#drawer .dr-row .count { font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-4); flex-shrink: 0; }

/* Sprint 3.4 Issue 2 — zone rows + node rows in the drawer. */
#drawer .dr-row .zone-dot {
  width: 10px; height: 10px; border-radius: 2px;
  flex-shrink: 0;
  border: 1px solid rgba(255,255,255,0.18);
}
#drawer .dr-row .zone-dot-empty {
  background: transparent !important;
  border: 1px dashed var(--line-3);
}
#drawer .dr-row.dr-zone-row {
  color: var(--ink);
  font-size: 12px;
}
#drawer .dr-row.dr-zone-row:hover { background: rgba(255,255,255,0.04); }
#drawer .dr-row.dr-node-row {
  color: var(--ink-3);
  font-size: 11.5px;
  min-height: 22px;
}
#drawer .dr-row.dr-node-row:hover { color: var(--ink); background: rgba(255,255,255,0.03); }
#drawer .dr-row .node-glyph {
  font-family: var(--font-mono); font-size: 14px; color: var(--ink-4);
  flex-shrink: 0; width: 12px; text-align: center;
}

/* Sprint 3.2 Issue 5 — drawer-row context menu (Set as default, etc). */
#drawerRowMenu button {
  display: block; width: 100%; text-align: start;
  background: transparent; border: 0;
  color: var(--ink, #F0EBE5);
  font: inherit;
  padding: 8px 12px;
  border-radius: 6px;
  cursor: pointer;
}
#drawerRowMenu button:hover { background: var(--srf-4, #2A3038); }
#drawerRowMenu button.danger { color: var(--st-blocked, #F87171); }
#drawerRowMenu button.danger:hover { background: rgba(248,113,113,0.10); }
#drawer .dr-section-head {
  font-family: var(--font-mono); font-size: 10px; color: var(--ink-4);
  letter-spacing: 0.12em; text-transform: uppercase;
  margin: 18px 6px 8px; display: flex; justify-content: space-between; align-items: center;
}
body[dir="rtl"] #drawer { inset-inline-start: auto; inset-inline-end: 56px; border-inline-end: 0; border-inline-start: 1px solid var(--line); }

/* When --nav-v2 is on, the existing toolbar + sidebar shift right to make
   room for the spine + drawer (56 + 224 = 280px) so the canvas isn't
   occluded. Old #tabs bottom strip stays visible during the transition. */
body.nav-v2-on #tb { inset-inline-start: calc(280px + 12px); max-width: calc(100vw - 280px - 24px); }
body.nav-v2-on #sb { inset-inline-start: calc(280px + 12px); }
body.nav-v2-on #fl { inset-inline-start: calc(280px + 12px); }
/* Sprint 3.5 Issue 3 — drawer collapsed is now zero pixels (was 40 px),
   so canvas chrome only needs to clear the 56-px spine, not 96 px. */
body.nav-v2-on.drawer-collapsed #tb { inset-inline-start: calc(56px + 12px); max-width: calc(100vw - 56px - 24px); }
body.nav-v2-on.drawer-collapsed #sb { inset-inline-start: calc(56px + 12px); }
body.nav-v2-on.drawer-collapsed #fl { inset-inline-start: calc(56px + 12px); }

/* =============================================================================
 * Sprint 3 · ⌘K command palette (Pass 4 § 02).
 *
 * Full-viewport backdrop + centred 580px box, top-anchored at ~60px so the
 * input is comfortably reachable without covering anything important.
 * ============================================================================= */
#palette {
  position: fixed; inset: 0; z-index: 200;
  visibility: hidden; opacity: 0;
  transition: opacity var(--t-quick) var(--e-out), visibility 0ms linear var(--t-quick);
}
#palette.on { visibility: visible; opacity: 1; transition: opacity var(--t-base) var(--e-out), visibility 0ms linear 0ms; }

/* Sprint 3.2 Issue 4 — always-on "Search anywhere" pill in the top
   center of the viewport. Independent of --nav-v2 (this is the palette's
   own visible entry point so users don't need to know the Cmd+K / iPad
   swipe gestures). Hides when the palette itself is open. */
#paletteOpenPill {
  position: fixed;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 36;
  display: inline-flex; align-items: center; gap: 8px;
  height: 32px; padding: 0 12px;
  background: var(--srf-2, #181C24);
  color: var(--ink-2, #C8C5BE);
  border: 1px solid var(--line-2, #2A2F3A);
  border-radius: 999px;
  font-family: var(--font-sans, Inter);
  font-size: 12.5px;
  cursor: pointer;
  box-shadow: 0 4px 12px -4px rgba(0,0,0,0.5);
  transition: background var(--t-quick) var(--e-out), color var(--t-quick) var(--e-out), border-color var(--t-quick) var(--e-out);
}
#paletteOpenPill:hover { background: var(--srf-3, #1F242E); color: var(--ink, #F0EBE5); border-color: var(--hot-ring, rgba(255,122,69,0.45)); }
#paletteOpenPill .pp-ic { font-size: 13px; color: var(--ink-3, #7C828E); }
#paletteOpenPill .pp-lbl { font-weight: 500; }
#paletteOpenPill .pp-kbd {
  font-family: var(--font-mono, monospace); font-size: 10.5px;
  color: var(--ink-3, #7C828E);
  border: 1px solid var(--line-2, #2A2F3A);
  border-radius: 4px; padding: 1px 5px;
  direction: ltr; /* keep Cmd+K LTR even in RTL mode */
}
#palette.on ~ #paletteOpenPill,
body:has(#palette.on) #paletteOpenPill { opacity: 0; pointer-events: none; }
/* When --nav-v2 is on the spine occupies the leading edge — shift the
   pill so it stays visually centred over the canvas, not over the spine. */
body.nav-v2-on #paletteOpenPill { left: calc(50% + 28px); }
body[dir="rtl"].nav-v2-on #paletteOpenPill { left: calc(50% - 28px); }
#palette .palette-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.55); -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); }
#palette .palette-box {
  position: absolute; top: 80px; left: 50%; transform: translateX(-50%);
  width: 580px; max-width: calc(100vw - 32px);
  background: var(--srf-3);
  border: 1px solid var(--line-3);
  border-radius: var(--r-4);
  box-shadow: var(--el-4), 0 0 0 1px var(--hl-3) inset;
  font-family: var(--font-sans);
  -webkit-user-select: none; user-select: none;
}
#palette .palette-input-row {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
}
#palette .palette-ic {
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-3);
  border: 1px solid var(--line-2); border-radius: 4px; padding: 2px 6px;
  /* Keep the keyboard chip LTR even inside an RTL palette */
  direction: ltr;
}
#palette .palette-input-row input {
  flex: 1; background: transparent; border: 0; outline: 0;
  color: var(--ink);
  font-family: var(--font-sans); font-size: 16px; letter-spacing: -0.005em;
  -webkit-user-select: text; user-select: text;
}
#palette .palette-esc-chip {
  font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-3);
  padding: 2px 6px; border: 1px solid var(--line-2); border-radius: 4px;
  direction: ltr;
}
/* Sprint 3.3 Issue 3 — visible × close button for touch users. */
#palette .palette-close {
  width: 32px; height: 32px;
  background: transparent;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  color: var(--ink-2);
  font-size: 20px; line-height: 1;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
#palette .palette-close:hover { background: var(--srf-2); color: var(--ink); border-color: var(--line-3); }
@media (pointer: coarse) {
  #palette .palette-close { width: 44px; height: 44px; font-size: 24px; }
}
#palette .palette-results {
  max-height: min(380px, 50vh); overflow-y: auto; padding: 4px 0 8px;
  -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
}
#palette .palette-res-head {
  font-family: var(--font-mono); font-size: 10px; color: var(--ink-4);
  letter-spacing: 0.12em; text-transform: uppercase;
  padding: 10px 18px 4px;
  display: flex; justify-content: space-between;
}
#palette .palette-res-head .count { color: var(--ink-3); }
#palette .palette-res {
  display: grid; grid-template-columns: 24px 1fr auto auto; gap: 12px; align-items: center;
  padding: 7px 18px; cursor: pointer; color: var(--ink-2);
}
#palette .palette-res:hover,
#palette .palette-res.sel { background: var(--hot-wash); color: var(--ink); }
#palette .palette-res-ic { color: var(--ink-3); text-align: center; font-size: 13px; }
#palette .palette-res-name { color: var(--ink); font-size: 14px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#palette .palette-res-name mark { background: rgba(255,122,69,0.30); color: var(--hot-soft); padding: 0 1px; border-radius: 2px; }
#palette .palette-res-sub { font-family: var(--font-mono); font-size: 11px; color: var(--ink-4); }
#palette .palette-kbd { font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-3); border: 1px solid var(--line-2); border-radius: 4px; padding: 1px 6px; direction: ltr; }
#palette .palette-empty { padding: 24px 18px; color: var(--ink-4); font-size: 13px; text-align: center; }
#palette .palette-foot {
  display: flex; gap: 18px; padding: 10px 18px; border-top: 1px solid var(--line);
  font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-4);
}
#palette .palette-foot span b { color: var(--ink-3); font-weight: 500; }

/* =============================================================================
 * Sprint 3 · View tabs + view stage (Pass 4 § 03).
 * ============================================================================= */
#viewTabs {
  position: fixed;
  top: 12px;
  inset-inline-end: 64px;            /* clear of #lgBtn at top-right */
  z-index: 32;
  display: inline-flex;
  background: var(--srf-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  padding: 3px;
  font-family: var(--font-sans);
  -webkit-user-select: none; user-select: none;
}
#viewTabs .vt-tab {
  background: transparent; border: 0;
  color: var(--ink-3); cursor: pointer;
  font-family: var(--font-mono); font-size: 11px;
  padding: 5px 12px; border-radius: var(--r-pill);
  display: inline-flex; align-items: center; gap: 6px;
  letter-spacing: 0.05em;
  transition: background var(--t-quick), color var(--t-quick);
}
#viewTabs .vt-tab.active { background: var(--hot-wash); color: var(--hot); border: 1px solid var(--hot-ring); padding: 4px 11px; }
#viewTabs .vt-ic { font-size: 13px; }
@media (max-width: 760px) { #viewTabs .vt-lbl { display: none; } }
body[dir="rtl"] #viewTabs { inset-inline-end: 64px; }

#viewStage {
  position: fixed;
  inset: 0;
  z-index: 31;
  background: var(--bg);
  overflow-y: auto;
  display: none;
  padding: 56px var(--s-5) var(--s-5);    /* leave room for toolbar at top */
  font-family: var(--font-sans);
  color: var(--ink);
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
#viewStage.on { display: block; }

/* Sprint 3.6 Issue 2 — viewStage was `inset: 0` (full viewport), which
   in RTL put the rightmost Kanban column ("Idea") under the spine that
   sits on the right edge. In LTR the leftmost column ("Done"… in
   English order, "Idea" in RTL) was occluded by the spine on the left
   edge. Clear the spine's 56-px column, plus the drawer's 224-px
   column when it's expanded. */
body.nav-v2-on #viewStage {
  inset-inline-start: calc(56px + 224px);  /* spine + open drawer */
}
body.nav-v2-on.drawer-collapsed #viewStage {
  inset-inline-start: 56px;                /* just the spine */
}
body[dir="rtl"].nav-v2-on #viewStage {
  inset-inline-start: 0;
  inset-inline-end: calc(56px + 224px);
}
body[dir="rtl"].nav-v2-on.drawer-collapsed #viewStage {
  inset-inline-end: 56px;
}
body.non-canvas-view svg#cv,
body.non-canvas-view #canvasOverlay,
body.non-canvas-view #emptyState { visibility: hidden; pointer-events: none; }
#viewStage .vs-empty { padding: 64px; text-align: center; color: var(--ink-3); font-size: 14px; }

/* List view */
#viewStage .vs-list { max-width: 960px; margin: 0 auto; }
#viewStage .vs-list table { width: 100%; border-collapse: collapse; }
#viewStage .vs-list th {
  font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-3);
  text-transform: uppercase; letter-spacing: 0.12em;
  text-align: start; padding: 8px 12px;
  border-bottom: 1px solid var(--line); cursor: pointer;
}
#viewStage .vs-list th:hover { color: var(--ink); }
#viewStage .vs-list td { padding: 8px 12px; border-bottom: 1px solid var(--line); font-size: 13px; }
#viewStage .vs-list tr { cursor: pointer; }
#viewStage .vs-list tr:hover td { background: rgba(255,255,255,0.03); }
#viewStage .vs-glyph { width: 24px; text-align: center; color: var(--ink-3); }
#viewStage .vs-title { color: var(--ink); font-weight: 500; }
#viewStage .vs-type { color: var(--ink-3); font-family: var(--font-mono); font-size: 11px; }
#viewStage .vs-chip { display: inline-block; padding: 1px 8px; border: 1px solid var(--line-2); border-radius: var(--r-pill); font-size: 11px; font-weight: 500; }
#viewStage .vs-age { font-family: var(--font-mono); font-size: 11px; color: var(--ink-4); }

/* Kanban view
   Sprint 3.4 Issue 5 — column sizing rewritten so all 5 columns fit on
   iPad Pro landscape (1366 × 1024) without horizontal scroll.
   Approach:
     · grid-template-columns: repeat(5, minmax(180px, 1fr))
       → each col gets an equal share of the available width but never
         shrinks below 180 px (the minimum that keeps card titles
         legible). 5 × 180 = 900 px minimum before overflow.
     · overflow-x: auto ONLY when the 900 px minimum exceeds the
       container — typically only iPad Pro portrait (1024) WITH the
       drawer + spine eating ~280 px of width.
     · max-width removed so the grid expands to fill the view stage.
     · Each col body becomes its own vertical scroll container so a
       column with many cards doesn't push the column header off-screen.
     · RTL: container `direction: rtl` reverses grid order
       (Done leftmost, Idea rightmost) per Hebrew reading flow. */
#viewStage .vs-kanban {
  display: grid; grid-template-columns: repeat(5, minmax(180px, 1fr));
  gap: var(--s-3);
  margin: 0 auto;
  min-height: 0;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  height: 100%;
}
body[dir="rtl"] #viewStage .vs-kanban { direction: rtl; }
#viewStage .vs-col {
  background: var(--srf-1); border: 1px solid var(--line); border-radius: var(--r-3);
  display: flex; flex-direction: column;
  min-height: 240px;
  /* Cap column height so #viewStage's scroll stays on the inner column,
     not the whole grid (vertical). 100vh - some chrome ≈ 80vh works. */
  max-height: calc(100vh - 120px);
}
#viewStage .vs-col-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 12px; border-bottom: 1px solid var(--line);
  border-top: 3px solid;
  border-radius: var(--r-3) var(--r-3) 0 0;
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-2);
  text-transform: uppercase; letter-spacing: 0.08em;
}
#viewStage .vs-col-count { color: var(--ink-4); }
#viewStage .vs-col-body {
  padding: 8px;
  display: flex; flex-direction: column; gap: 8px;
  min-height: 100px;
  /* Sprint 3.4 Issue 5 — vertical scroll lives inside each column body,
     not the whole grid. Allows many cards in one column without pushing
     other columns out of view. */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  flex: 1;
}
#viewStage .vs-col-body.drop-target { background: var(--hot-wash); border-radius: 0 0 var(--r-3) var(--r-3); }
#viewStage .vs-card {
  background: var(--srf-3); border: 1px solid var(--line-2); border-radius: var(--r-2);
  padding: 8px 10px; cursor: grab;
  transition: transform var(--t-quick), border-color var(--t-quick);
}
#viewStage .vs-card:hover { border-color: var(--hot-ring); }
#viewStage .vs-card.dragging { opacity: 0.6; cursor: grabbing; }
#viewStage .vs-card-head { display: flex; align-items: center; gap: 6px; font-weight: 500; color: var(--ink); font-size: 13px; }
#viewStage .vs-card-glyph { color: var(--ink-3); flex-shrink: 0; }
#viewStage .vs-card-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#viewStage .vs-card-body { margin-top: 4px; font-size: 11px; color: var(--ink-3); line-height: 1.4; }

/* Sprint 5 Issue 7 — contain card content so a long unbroken token (URL, code,
   math) in any column (the Pending column was the reported case) can't escape
   its card and create sideways scroll. Columns scroll only vertically. */
#viewStage .vs-col-body { overflow-x: hidden; }
#viewStage .vs-card { min-width: 0; }
#viewStage .vs-card-body { overflow-wrap: anywhere; word-break: break-word; }

/* Sprint 5 Issue 8 — the lightning/filter entry point is hidden by default.
   A "Show filter button" toggle in Workspace settings adds body.show-filter to
   reveal it. The filter itself is unchanged — only its chrome visibility. */
body:not(.show-filter) #fl { display: none !important; }

/* Timeline view */
#viewStage .vs-timeline { max-width: 720px; margin: 0 auto; }
#viewStage .vs-tl-group { margin-bottom: 28px; }
#viewStage .vs-tl-head {
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-3);
  text-transform: uppercase; letter-spacing: 0.12em; margin-bottom: 8px;
}
#viewStage .vs-tl-rail { border-inline-start: 1px solid var(--line); padding-inline-start: 18px; display: flex; flex-direction: column; gap: 6px; }
#viewStage .vs-tl-row { position: relative; padding: 6px 8px; cursor: pointer; border-radius: var(--r-2); font-size: 13px; color: var(--ink-2); }
#viewStage .vs-tl-row:hover { background: rgba(255,255,255,0.03); color: var(--ink); }
#viewStage .vs-tl-dot {
  position: absolute; inset-inline-start: -22px; top: 12px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--hot);
}
#viewStage .vs-tl-verb { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); }
#viewStage .vs-tl-title { color: var(--ink); font-weight: 500; }
#viewStage .vs-tl-where { font-family: var(--font-mono); font-size: 11px; color: var(--ink-4); }

/* Weak-spot view */
#viewStage .vs-weakspot { max-width: 800px; margin: 0 auto; }
#viewStage .vs-ws-head {
  /* Sprint 3.3 Issue 5 — head now flexes to fit the Reset action. */
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); text-transform: uppercase; letter-spacing: 0.12em; margin-bottom: 12px;
}
#viewStage .vs-ws-reset {
  background: transparent; color: var(--ink-3);
  border: 1px solid var(--line-2); border-radius: var(--r-pill);
  padding: 4px 10px; cursor: pointer;
  font-family: var(--font-sans); font-size: 11px; text-transform: none; letter-spacing: 0;
}
#viewStage .vs-ws-reset:hover { background: var(--srf-2); color: var(--ink); border-color: var(--line-3); }
#viewStage .vs-ws-adj { display: flex; gap: 6px; margin-top: 6px; }
#viewStage .vs-ws-adj-btn {
  background: var(--srf-2); color: var(--ink-2);
  border: 1px solid var(--line-2); border-radius: var(--r-pill);
  padding: 3px 10px; cursor: pointer;
  font-family: var(--font-sans); font-size: 11px;
}
#viewStage .vs-ws-adj-btn:hover { background: var(--srf-3); color: var(--ink); border-color: var(--hot-ring); }
#viewStage .vs-ws-adj-btn[data-act="harder"]:hover { color: var(--st-blocked, #F87171); border-color: rgba(248,113,113,0.5); }
#viewStage .vs-ws-adj-btn[data-act="easier"]:hover { color: var(--st-done, #4FD18B); border-color: rgba(79,209,139,0.5); }
#viewStage .vs-ws-list { display: flex; flex-direction: column; gap: 8px; }
#viewStage .vs-ws-row {
  display: grid; grid-template-columns: auto 1fr auto; gap: 12px; align-items: center;
  background: var(--srf-1); border: 1px solid var(--line); border-radius: var(--r-3);
  padding: 12px 14px;
}
#viewStage .vs-ws-chip {
  font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
  padding: 3px 10px; border-radius: var(--r-pill);
}
#viewStage .vs-ws-chip.tier-hot  { background: rgba(248,113,113,0.18); color: #F87171; }
#viewStage .vs-ws-chip.tier-warm { background: rgba(242,196,98,0.18);  color: #F2C462; }
#viewStage .vs-ws-chip.tier-cool { background: rgba(111,168,255,0.18); color: #6FA8FF; }
#viewStage .vs-ws-topic { color: var(--ink); font-weight: 500; font-size: 14px; }
#viewStage .vs-ws-why   { color: var(--ink-2); font-size: 12px; margin-top: 2px; }
#viewStage .vs-ws-sigs  { color: var(--ink-4); font-family: var(--font-mono); font-size: 10.5px; margin-top: 4px; }
#viewStage .vs-ws-go {
  background: var(--srf-3); color: var(--ink); border: 1px solid var(--line-2);
  border-radius: var(--r-2); padding: 6px 12px; cursor: pointer;
  font-family: var(--font-sans); font-size: 12px;
}
#viewStage .vs-ws-go:hover { background: var(--srf-4); border-color: var(--hot-ring); color: var(--hot); }

/* =============================================================================
 * Phase 2.5 R3 — kill iOS tap-highlight residue on draggable surfaces.
 *
 * The 6%-opacity zone fills (zones-v2) amplified WebKit's default orange
 * tap-highlight when dragging across them, leaving a visible smear that only
 * cleared on the next gesture. Setting tap-highlight to transparent on every
 * draggable / dragged-over surface removes the WebKit default entirely.
 * outline:none guards against focus rings during pointer drag.
 * ============================================================================= */
.zone, .zone-v2,
svg#cv .node,
.nslice,
g.node,
g.zone,
.zr,
body.dragging,
body.dragging .nslice,
body.dragging g.node,
body.holding,
body.holding .nslice,
body.holding g.node {
  -webkit-tap-highlight-color: transparent;
  outline: none;
}
/* Belt-and-suspenders: the SVG canvas itself + the HTML overlay layer */
svg#cv, #canvasOverlay, #zoneChips {
  -webkit-tap-highlight-color: transparent;
}

/* =============================================================================
 * Phase 2.5 R5 — make scrollable sheets fully scrollable on iOS Safari.
 *
 * #pn (property panel / detail sheet) had overflow-y:auto + max-height
 * already, but missed -webkit-overflow-scrolling:touch — without it, iOS
 * still bounces on pull-down and doesn't reach the bottom. overscroll-
 * behavior:contain stops the scroll from chaining into the canvas.
 * touch-action:pan-y is already set in app.css for #pn; we re-affirm here.
 * Same prophylactic for #modal .mc which also holds long-form content.
 * ============================================================================= */
#pn,
#modal .mc,
#dlg .dc,
#more,
#sb .sbbody {
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  touch-action: pan-y;
}

/* =============================================================================
 * Phase 2 · zone sticky chip (Pass 3 § 08).
 *
 * Floats outside the zone's top edge as a pill. DOM (not SVG) so size
 * stays native-pixel regardless of canvas zoom. The chip's `top` is
 * computed by ZonesV2.reposition() so it pins to the visible top of the
 * zone when the zone scrolls partly off-screen.
 * ============================================================================= */
.zone-chip {
  position: fixed;
  display: none;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 11px;
  letter-spacing: 0.04em;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  border: 1px solid;
  background: rgba(255,255,255,0.04);
  color: var(--ink-2);
  white-space: nowrap;
  pointer-events: auto;
  -webkit-user-select: none; user-select: none;
  box-shadow: var(--el-1);
  z-index: 14;
}

/* =============================================================================
 * Three migrated component groups (Task 2.1).
 *
 * Each rule below uses ONLY canonical Pass 2 tokens. Specificity is higher
 * than the legacy declarations in app.css so these win without need for
 * !important. Touch only buttons, inputs, and the More menu — every other
 * surface stays on v1 declarations until a later task migrates it.
 * ============================================================================= */

/* ── BUTTONS ── (Pass 2 § 02 components) */
.btn,
#tb button,
#tb .tbLabel,
#more button,
#more label,
.brow button,
#dlg button {
  display: inline-flex; align-items: center; gap: var(--s-2);
  border-radius: var(--r-2);
  padding: 0 var(--s-3); height: 32px;
  font-family: var(--font-sans); font-weight: 500; font-size: 13.5px;
  letter-spacing: -0.005em;
  cursor: pointer;
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink);
  transition: background var(--t-quick) var(--e-out),
              border-color var(--t-quick) var(--e-out),
              transform var(--t-quick) var(--e-out);
  white-space: nowrap;
}
.btn:active,
#tb button:active,
#tb .tbLabel:active,
#more button:active,
#more label:active {
  transform: translateY(0.5px);
}

.btn-primary,
#tb button.pr,
#tb .ac,
.brow button.pr,
#dlg button.pr {
  background: linear-gradient(180deg, #FF9870, var(--hot) 50%, var(--hot-deep));
  color: #1A0A04;
  border: 1px solid transparent;
  box-shadow: 0 1px 0 rgba(255,255,255,0.30) inset,
              0 6px 14px -4px rgba(255,122,69,0.45);
}
.btn-primary:hover,
#tb button.pr:hover,
#tb .ac:hover,
.brow button.pr:hover,
#dlg button.pr:hover {
  filter: brightness(1.07);
}

.btn-secondary {
  background: var(--srf-3);
  border-color: var(--line-2);
  color: var(--ink);
  box-shadow: 0 1px 0 var(--hl-2) inset;
}
.btn-secondary:hover { background: var(--srf-4); border-color: var(--line-3); }

.btn-ghost { color: var(--ink-2); }
.btn-ghost:hover { color: var(--ink); background: var(--srf-2); }

.btn-danger,
#tb button.dn,
#dlg button.dn,
#more button[style*="--block"] {
  color: var(--st-blocked);
  border-color: rgba(248,113,113,0.32);
  background: rgba(248,113,113,0.06);
}
.btn-danger:hover,
#tb button.dn:hover,
#dlg button.dn:hover {
  background: rgba(248,113,113,0.12);
  border-color: rgba(248,113,113,0.50);
}

.btn-lg { height: 40px; padding: 0 18px; font-size: 14.5px; border-radius: var(--r-3); }
.btn-sm { height: 26px; padding: 0 10px; font-size: 12px;   border-radius: var(--r-1); }

/* ── INPUTS ── */
.input,
#tb input,
#sr,
#sb input,
#pn input,
#pn textarea,
#pn select,
#dlg input[type="text"],
#dlg textarea,
#modal textarea,
#modal input {
  height: auto;
  background: var(--bg);
  color: var(--ink);
  border: 1px solid var(--line-2);
  border-radius: var(--r-2);
  padding: 0 var(--s-3);
  font-family: var(--font-sans);
  font-size: 13.5px;
  outline: none;
  transition: border-color var(--t-quick) var(--e-out),
              box-shadow   var(--t-quick) var(--e-out);
}
/* Inputs that need fixed heights restore their existing measurements */
#tb input, #sr { height: 32px; min-width: 130px; }
.input { height: 32px; min-width: 200px; }

.input:focus,
#tb input:focus,
#sr:focus,
#sb input:focus,
#pn input:focus,
#pn textarea:focus,
#pn select:focus,
#dlg input[type="text"]:focus,
#dlg textarea:focus,
#modal textarea:focus,
#modal input:focus {
  border-color: var(--hot);
  box-shadow: 0 0 0 3px var(--hot-ring);
}
.input::placeholder,
#tb input::placeholder,
#sr::placeholder { color: var(--ink-4); }

/* Pill search variant */
.search {
  display: inline-flex; align-items: center; gap: var(--s-2);
  height: 32px;
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  padding: 0 var(--s-3);
  color: var(--ink-3);
  font-family: var(--font-mono);
  font-size: 12px;
  min-width: 280px;
}
.search .kbd {
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--ink-3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-1);
  padding: 1px 6px;
}

/* ── MORE MENU ── */
#more {
  background: var(--srf-3);
  border: 1px solid var(--line-3);
  border-radius: var(--r-4);
  box-shadow: var(--el-4);
  padding: 6px 0;
  font-family: var(--font-sans);
  font-size: 13.5px;
}
#more .mh {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-4);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: var(--s-3) var(--s-4) 6px;
}
#more button,
#more label {
  display: flex; align-items: center; gap: var(--s-2);
  padding: 7px var(--s-4);
  color: var(--ink);
  cursor: pointer;
  border-radius: 0;
  text-align: start;
  background: transparent;
  border: 0;
  height: auto;
  transition: background var(--t-instant) var(--e-out);
}
#more button:hover,
#more label:hover {
  background: rgba(255,255,255,0.04);
  color: var(--ink);
}
#more .msep {
  height: 1px;
  background: var(--line);
  margin: 6px 12px;
}
