/* Graph editor CSS styles — Graphden brand palette (docs/brand).
 * Five brand values: Graphden Blue #0066CC (--accent), Blue Deep #0047A3
 * (--blue-deep), Ink #0D1117 (--fg / borders / dark ground), Soft Tint
 * #EEF4FF (--soft-tint / tinted grounds), White. #4D94FF is Blue's
 * light-on-dark tint (the dark-theme --accent). New brand values are
 * added as tokens (stylelint strict-value forbids bare hex in rule bodies).
 * Theme variables live on `:root`; `body.theme-dark` overrides. The
 * full sidebar respects `--sidebar-width` so the resize handle and
 * the collapse button can change one number to retheme + relayout. */
:root {
  --bg: #fff;
  --fg: #0d1117;
  --muted-fg: #57606a;
  --light-fg: #8b949e;
  --border: #0d1117;
  --light-border: #d0d7de;
  --input-border: #c9d1d9;
  --hover-bg: #eef4ff;
  --selected-bg: #dbe7ff;
  --sidebar-bg: #f5f8ff;
  --header-bg: #eef4ff;
  --header-fg: #0d1117;
  /* Legacy accent token — retargeted to the redesign's one-accent
     "flow" teal (was Graphden-Blue #0066cc) so pre-redesign surfaces
     (execute popover, lens active chip, focus rings) stop reading as
     a foreign blue island. Keep in sync with --gd-flow below. */
  --accent: #137c74;
  --soft-tint: #eef4ff;
  /* Graph node ("card") palette — keeps the same visual hierarchy in both
     themes (slightly-raised body + distinct header band + visible outline)
     instead of inverting to bright gray. The focused/root card wears a
     Graphden-Blue title band so the active node reads in brand colour. */
  /* Redesign 2026-08: node cards read from the --gd-* palette so the canvas
     matches the shell. The focused/root title band is a fixed deep teal
     (#106b63) — white text clears AA on it in both themes. */
  --card-bg: var(--gd-panel);
  --card-fg: var(--gd-ink);
  --card-border: var(--gd-line-2);
  --card-header-bg: #106b63;
  --card-header-fg: #fff;
  --card-row-highlight: var(--gd-flow-wash);
  /* HOF-captured arg strip — subtle blue tint that survives both themes. */
  --hof-fg: #557;
  --hof-bg: #f4f6fc;
  --hof-border: #b8c0e0;
  /* Description tooltip — high-contrast bubble that sits over both
     overlay rows and the cy canvas, so it must read against any bg. */
  --tooltip-bg: rgb(0 0 0 / 0.88);
  --tooltip-fg: #fff;
  /* Shadow + scrim tokens — same alpha curves as Material Design's
     elevation set; centralised here so any new floating element picks
     consistent depth instead of inventing a new opacity. */
  --shadow-sm: 0 2px 6px rgb(0 0 0 / 0.18);
  --shadow-md: 0 2px 6px rgb(0 0 0 / 0.25);
  --shadow-lg: 0 4px 12px rgb(0 0 0 / 0.25);
  --shadow-xl: 0 4px 14px rgb(0 0 0 / 0.30);
  --overlay-hover: rgb(0 0 0 / 0.08);
  --overlay-pressed: rgb(127 127 127 / 0.20);
  --modal-backdrop-bg: rgb(0 0 0 / 0.4);
  --sidebar-width: 280px;
  /* Touch-target sizing — Apple HIG / WCAG 2.5.5. The visible glyph
     stays small on desktop (15px etc.); these tokens are the
     min hit-area enforced under `@media (hover:none)`. Two values
     so non-critical affordances (e.g. an inline `+` near big
     siblings) can use `--touch-target-min` without bloating the
     UI on tightly-packed rows. */
  --touch-target: 44px;
  --touch-target-min: 28px;
  /* Below this viewport width the sidebar auto-collapses on first
     visit (iPad portrait = 768; we leave headroom for split-view
     1/2 and 1/3 multitasking on iPad). */
  --sidebar-narrow-breakpoint: 900px;
  /* Right-edge action icons are sized + pinned via these vars so
     `@media (hover:none)` can grow them for touch without
     touching the JS that builds the icons. `applyActionIconBox`
     reads --icon-size; the pinning helpers use --icon-pin-r-1
     (closest to edge) and --icon-pin-r-2 (one slot in).
     In rem so the whole badge (box, glyph and the pin offsets that
     space the boxes apart) scales together with the user's browser
     font-size — a px box around a rem glyph overflows at 150%. */
  --icon-size: 0.9375rem;
  --icon-font-size: 0.625rem;
  --icon-pin-r-1: 0.375rem;
  --icon-pin-r-2: 1.5rem;
  --icon-pin-r-3: 2.625rem;
  /* Monospace font stack — shared between the layout-affecting JS
     pinning helpers (none yet) and editor's :code-shaped surfaces. */
  --mono: ui-monospace, monospace;
  /* Status semantic colours — used by edit-form validation, picker
     compatibility hints, error tooltips. Same hue across themes so
     "green = ok / red = problem" is unambiguous. */
  --success-fg: #4ade80;
  --error-fg: #ff6b6b;
  /* WCAG-AA-safe error TEXT on the light surface (#ff6b6b is 2.6:1
     on the light bg — fine as a glow/border accent, too low for
     small text). Use for small error-colored text nodes. */
  --error-fg-strong: #c0392b;
  --warning-fg: #c47b00;
  /* Saturated fills: `--on-accent` is legible text over Graphden Blue / any
     status fill; `--danger-bg` backs the fatal-load banner. */
  --on-accent: #fff;
  --danger-bg: #c0392b;
  /* Effect-chip categorical palette — encodes effect KIND (db / network
     / time / …) so the chip background must be the same colour in both
     themes. Chip text is fixed dark since every bg here is bright. */
  --effect-chip-fg: #0d1117;
  --effect-chip-db: #5eaaff;
  --effect-chip-env: #c084fc;
  --effect-chip-io: #4ade80;
  --effect-chip-network: #ff9a3c;
  --effect-chip-time: #facc15;
  --effect-chip-misc: #ccc;
  --effect-chip-random: #f0abfc;
  --effect-chip-process: #94a3b8;
  --effect-chip-raw-sql: #f87171;

  /* ========================================================================
   * REDESIGN 2026-08 — the "drafting board / graph paper" design system.
   * A parallel `--gd-*` token set introduced alongside the legacy tokens so
   * the shell can be rebuilt surface-by-surface without breaking the running
   * editor at any commit. Migrated components read `--gd-*`; un-migrated ones
   * keep the legacy tokens above until they're replaced. Light values here;
   * every colour is mirrored under `body.theme-dark`. See memory
   * project_editor_ui_redesign_2026_08_08.
   * ===================================================================== */
  /* type roles */
  --gd-ui-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --gd-mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
  /* grounds — cool porcelain, deliberately not cream */
  --gd-paper: #eff1f0;
  --gd-paper-2: #f6f7f6;
  --gd-panel: #fbfcfb;
  /* ink ramp */
  --gd-ink: #181b1c;
  --gd-ink-2: #4a5254;
  --gd-ink-3: #626c6e; /* darkened from #7c8688 to clear WCAG AA (4.5:1) as muted text — on the PAPER bg too (4.76:1), not just the white panels */
  --gd-ink-4: #a6afb0;
  /* structural lines + the dot-grid substrate (the signature) */
  --gd-line: #dce0df;
  --gd-line-2: #cad0cf;
  --gd-grid: #d6dbda;
  /* the one accent — "flow" (edges, active, primary) */
  --gd-flow: #137c74;
  --gd-flow-ink: #0c5b55;
  --gd-flow-wash: #ddecea;
  --gd-flow-2: #1fa093;
  /* the 3-way binding chip language: literal / reference / free */
  --gd-lit: #525b5e;
  --gd-lit-wash: #e6e9e8;
  --gd-ref: #137c74;
  --gd-ref-wash: #ddecea;
  --gd-free: #b0791e;
  --gd-free-wash: #f2e9d6;
  /* semantic (separate from the accent hue) */
  --gd-ok: #2e7d57;
  --gd-warn: #b0791e;
  --gd-crit: #c0453c;
  --gd-crit-wash: #f4e0de;
  /* shell geometry (theme-independent) */
  --gd-ctxbar-h: 44px;
  --gd-explorer-w: 260px;
  --gd-inspector-w: 344px;
  --gd-r: 9px;
  --gd-r-sm: 6px;
  --gd-shadow: 0 1px 2px rgb(20 30 28 / 0.06), 0 6px 18px rgb(20 30 28 / 0.06);
  --gd-shadow-pop: 0 8px 30px rgb(20 30 28 / 0.16);
}
body.theme-dark {
  /* Ink #0D1117 is the brand's dark ground. */
  --bg: #0d1117;
  --fg: #e6edf3;
  --muted-fg: #9aa5b1;
  --light-fg: #7d8695;
  --border: #30363d;
  --light-border: #21262d;
  --input-border: #30363d;
  --hover-bg: #182130;
  --selected-bg: #22304a;
  --sidebar-bg: #10151c;
  --header-bg: #161b22;
  --header-fg: #e6edf3;
  /* Legacy accent — dark-theme flow teal (see the light-theme note). */
  --accent: #37c9bb;
  --soft-tint: #182130;
  /* Card body slightly raised vs Ink body; the focused/root card keeps the
     Graphden-Blue title band (brand colour reads on dark too); outline
     subtle so the node frame isn't a glaring box. */
  /* Redesign 2026-08: dark cards read from the --gd-* dark palette. */
  --card-bg: var(--gd-panel);
  --card-fg: var(--gd-ink);
  --card-border: var(--gd-line-2);
  --card-header-bg: #106b63;
  --card-header-fg: #fff;
  --card-row-highlight: var(--gd-flow-wash);
  --hof-fg: #aab8e8;
  --hof-bg: #1e2030;
  --hof-border: #3a4070;
  --tooltip-bg: rgb(232 232 232 / 0.92);
  --tooltip-fg: #1a1a1a;
  /* Dark theme — slightly stronger shadows since they're on a darker
     base, scrim flipped to white on dark. */
  --shadow-sm: 0 2px 6px rgb(0 0 0 / 0.40);
  --shadow-md: 0 2px 6px rgb(0 0 0 / 0.50);
  --shadow-lg: 0 4px 12px rgb(0 0 0 / 0.50);
  --shadow-xl: 0 4px 14px rgb(0 0 0 / 0.55);
  --overlay-hover: rgb(255 255 255 / 0.08);
  --overlay-pressed: rgb(255 255 255 / 0.16);
  --modal-backdrop-bg: rgb(0 0 0 / 0.65);
  /* Status colours — slight desaturation against the dark background
     so they don't vibrate. Hue still maps green=ok / red=error. */
  --success-fg: #5be08a;
  --error-fg: #f77;
  /* On the dark surface the base red already clears AA for text. */
  --error-fg-strong: #f77;
  --warning-fg: #e0a94a;
  --on-accent: #fff;
  --danger-bg: #e5484d;
  /* Effect-chip palette stays identical in dark theme — colour encodes
     category, not surface. Chip fg flips to white since dark-theme chips
     could otherwise look washed out against page bg in screenshots. */

  /* REDESIGN 2026-08 — dark mirror of the `--gd-*` system (deep slate). */
  --gd-paper: #0f1315;
  --gd-paper-2: #141a1c;
  --gd-panel: #171e20;
  --gd-ink: #e8edec;
  --gd-ink-2: #aeb9b8;
  --gd-ink-3: #7b8786;
  --gd-ink-4: #586261;
  --gd-line: #252e30;
  --gd-line-2: #313b3d;
  --gd-grid: #1c2426;
  --gd-flow: #37c9bb;
  --gd-flow-ink: #7fe3d9;
  --gd-flow-wash: #123230;
  --gd-flow-2: #4fe0d2;
  --gd-lit: #9aa4a5;
  --gd-lit-wash: #20282a;
  --gd-ref: #37c9bb;
  --gd-ref-wash: #123230;
  --gd-free: #d9a84e;
  --gd-free-wash: #2a2417;
  --gd-ok: #4fb983;
  --gd-warn: #d9a84e;
  --gd-crit: #e8756b;
  --gd-crit-wash: #2e1a18;
  --gd-shadow: 0 1px 2px rgb(0 0 0 / 0.4), 0 6px 20px rgb(0 0 0 / 0.35);
  --gd-shadow-pop: 0 10px 34px rgb(0 0 0 / 0.55);
}
* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }

/* Screen-reader-only utility — text is invisible but readable by
   assistive tech. Standard recipe (clip + 1px box) survives
   high-contrast mode and defeats accidental tab-focus. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%);
  white-space: nowrap; border: 0;
}

/* Skip link (WCAG 2.4.1) — the first tab stop on the page, parked
   off-screen until it takes focus, then pinned over the top bar. Above
   every z-index in the file so nothing can cover it while showing. */
.gd-skip-link {
  position: absolute;
  top: 0; left: 0;
  transform: translateY(-120%);
  z-index: 10000;
  padding: 8px 14px;
  background: var(--accent); color: var(--on-accent);
  font-size: 0.8125rem; text-decoration: none;
  border-radius: 0 0 4px;
}
.gd-skip-link:focus { transform: translateY(0); }

/* The skip link's target takes focus programmatically (tabindex="-1");
   never draw a ring around the whole canvas for it. */
#graph-container:focus { outline: none; }

body { font-family: 'SF Mono', Monaco, Inconsolata, monospace;
  background: var(--bg); color: var(--fg); }

/* ID-based layout (matches editor-body hiccup structure).
 *
 * The sidebar is absolute-positioned ON TOP of a full-viewport
 * graph-container. Collapse / expand is a `transform: translateX(...)`
 * slide — graph-container never reflows, cytoscape never resizes,
 * edges never get recomputed. That keeps the animation perfectly
 * smooth (the only thing moving is the sidebar layer, GPU-composited
 * via transform) and frees us from juggling a pan compensation in
 * lockstep with the CSS transition.
 *
 * Trade-off: when the sidebar is open it covers the leftmost slice of
 * the graph. We compensate at fit time (fitInVisibleArea in
 * editor-render.js) by panning the freshly-fitted graph half the
 * sidebar width to the right so the visible portion stays centered. */
#app { height: 100vh; }
#main-container { position: relative; height: 100%; }
#side-menu {
  position: absolute; top: 0; left: 0; height: 100%;
  width: var(--sidebar-width); background: var(--sidebar-bg);
  border-right: 2px solid var(--border); overflow-y: auto;
  z-index: 200;
  transform: translateX(0);
  transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
  will-change: transform;
}
body.resizing-sidebar #side-menu { transition: none; user-select: none; }
body.resizing-sidebar { user-select: none; cursor: col-resize; }
/* Collapsed: slide the sidebar one full width to the left so it sits
   off-screen. Width stays the same in the underlying layout, but
   nothing reflows since the sidebar is absolute-positioned anyway.
   The expand affordance is a separate floating button — see
   `.sidebar-expand-floating`. */
body.sidebar-collapsed #side-menu {
  transform: translateX(calc(-1 * var(--sidebar-width) - 2px));
}
body.sidebar-collapsed #sidebar-resizer { display: none; }
#graph-container {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  display: flex; flex-direction: column; overflow: hidden;
}
#graph-surface { width: 100%; height: 100%; }

/* The overlay layer. Carries the viewport transform for every node / edge-label
   overlay at once, so panning and zooming is a single style write rather than a
   loop over ~200 overlays. Overlays inside it are positioned in graph
   coordinates. Zero-sized and pointer-transparent: its children are absolutely
   positioned, overflow it, and opt back into pointer events themselves. */
#graph-layer {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  z-index: 10;
  transform-origin: 0 0;
  pointer-events: none;
}

/* Edge layer — inside #graph-layer, so it rides the same viewport transform and
   its paths are emitted in graph coordinates. `overflow: visible` because graph
   coordinates run negative in both axes and the paths spill far outside this
   box. It carries no z-index, so it paints under the overlays (which do).

   The 1px size is load-bearing: per SVG, a zero width or height on the root
   `<svg>` disables rendering of the element entirely — the paths keep their
   geometry (getBoundingClientRect still reports the right box) but nothing is
   ever painted. It cannot be `0` like its parent. */
#edge-layer {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  overflow: visible;
}

/* Stroke widths are set on #edge-lines / #edge-hits per zoom, in graph units,
   and inherited by the paths. */
.edge-line {
  fill: none;
  stroke: var(--fg);
}

/* Unified arg edges — provenance is a gradation, not a different UI.
   Unset edges are dashed; optional/deep grades lighten them; a λ edge
   is a ghost: the enclosing HOF supplies that argument per call. */
.edge-line.edge-unset { stroke-dasharray: 5 4; }
.edge-line.edge-optional { opacity: 0.62; }
.edge-line.edge-deep { opacity: 0.55; stroke-dasharray: 2 5; }
.edge-line.edge-optional.edge-deep { opacity: 0.42; }
.edge-line.edge-lambda { opacity: 0.38; stroke-dasharray: 1 5; }
.placeholder-binder.is-optional { opacity: 0.72; }
.placeholder-binder.is-deep { border-style: dotted; }
.lambda-arg-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%;
  border: 1px dashed var(--hof-border);
  background: var(--hof-bg); color: var(--hof-fg);
  font-size: 0.75rem; font-style: italic; cursor: help;
}

/* Hovering an edge lights the whole visual bundle it belongs to. Markers use
   `fill: context-stroke`, so the arrowheads follow without a rule of their own. */
.edge-line.edge-hovered {
  stroke: var(--accent);
}

/* The grab target: same path, fat transparent stroke. `pointer-events: stroke`
   hit-tests the stroke area, which is what lets `elementsFromPoint` return
   every edge whose vertical run passes under the cursor. */
.edge-hit {
  fill: none;
  stroke: transparent;
  pointer-events: stroke;
}

/* Floating expand button — visible only when sidebar is collapsed. Sits
   in viewport top-left, fades in once the shrink animation has cleared
   space for it so the icon doesn't appear over a still-shrinking
   sidebar. */
.sidebar-expand-floating {
  /* The Explorer's EXPAND affordance while collapsed: a slim tab flush against
     the viewport's left edge (where the Explorer reappears), pointing right
     (›). Shown ONLY on Build + collapsed (rule below) — the Explorer doesn't
     exist on the overlay surfaces, so neither does its toggle. On a
     NON-default branch it also carries the branch-name badge (child span
     .gd-edge-branch, synced by editor-branches.js) so the write-context stays
     visible with the chrome collapsed. */
  position: fixed;
  top: calc(var(--gd-ctxbar-h, 44px) + 10px);
  left: 0;
  z-index: 320;
  width: 18px; min-height: 46px; padding: 0;
  background: var(--gd-panel); color: var(--gd-ink-2);
  border: 1px solid var(--gd-line); border-left: none;
  border-radius: 0 7px 7px 0;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none;
  transition: opacity 0.18s ease;
}
/* Build-only: gated on the active surface so it never floats over Operate /
   Settings / Workspaces (which have no Explorer). */
body[data-surface="build"].sidebar-collapsed .sidebar-expand-floating {
  opacity: 1; pointer-events: auto;
  transition: opacity 0.18s ease 0.2s; /* wait for the shrink to clear */
}
.sidebar-expand-floating:hover {
  background: var(--gd-flow-wash); color: var(--gd-flow); border-color: var(--gd-flow);
}

/* Right-edge resize handle. 4 px wide, centred on the sidebar's
   trailing border so the cursor lands on it before the user has to
   nail the 2 px border itself. */
#sidebar-resizer {
  position: absolute; top: 0; right: -2px; width: 6px; height: 100%;
  cursor: col-resize; z-index: 250; user-select: none;
}
#sidebar-resizer:hover { background: var(--overlay-hover); }

/* Search bar — input + clear on row 1, "Only services" toggle wraps
 * to row 2 below. Wrap-flex avoids competing with the input for
 * horizontal real estate on narrow sidebars. */
#search-bar { display: flex; padding: 6px 8px;
  border-bottom: 1px solid var(--light-border); gap: 4px;
  flex-wrap: wrap; }
/* The LENS — kind chips with focus semantics ("show only this kind").
   aria-pressed=true = in the lens ("All" is pressed when the lens is
   empty); synced from localStorage by syncKindFilterBar. The services /
   apps chips carry a count; the apps chip is hidden (JS) when the
   deployment has no app routing. */
#kind-filters {
  flex: 1 1 100%;
  /* Wrap when the chips outgrow the fixed panel width. The earlier
     one-line-always rule (overflow → hidden-scrollbar scroll) was
     re-evaluated when the 7th chip (tests) landed: at the standard
     panel width the tail chips sat INVISIBLE behind a scrollbar-less
     overflow, which reads as "the feature doesn't exist" — worse
     than a second row. */
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 2px 3px; padding: 2px 0;
}
.kind-toggle {
  display: inline-flex; align-items: center; gap: 3px;
  background: transparent; border: 1px solid transparent; border-radius: 8px;
  padding: 1px 5px; font: inherit; font-size: 0.6875rem;
  white-space: nowrap; flex: 0 0 auto;
  color: var(--muted-fg); cursor: pointer;
}
/* Hover = a faint neutral wash, deliberately DIFFERENT from the active
   accent below so a hovered inactive chip is never mistaken for the applied
   lens. Inactive hovered chips only lift their text to full-strength. */
.kind-toggle:hover:not([aria-pressed="true"]) {
  background: var(--hover-bg); color: var(--fg); border-color: transparent;
}
.kind-toggle[hidden] { display: none; }
.kind-glyph {
  display: inline-flex; font-size: 0.6875rem; line-height: 1;
  user-select: none;
}
.kind-label { user-select: none; }
.kind-count { font-size: 0.625rem; opacity: 0.85; user-select: none; }
.kind-count:empty { display: none; }
/* the ✓ tests chip carries "N · F✗" while any test fails */
.kind-count.kind-count-bad { color: var(--error-fg); opacity: 1; }
/* Active chip — this kind IS the applied lens (or "All" with an empty lens).
   A distinct ACCENT treatment (accent text + border + tint + weight) so the
   applied lens is unmistakable at a glance, and never collides with :hover. */
.kind-toggle[aria-pressed="true"] {
  /* flow-ink on flow-wash — the same AA-passing pair as .gd-pop-item.sel
     (bare --accent on --selected-bg measured 4.05:1 at 11px, under AA). */
  color: var(--gd-flow-ink);
  border-color: var(--accent);
  background: var(--gd-flow-wash);
  font-weight: 600;
}
.kind-toggle[aria-pressed="true"]:hover { filter: brightness(0.98); }
/* "+ New secret" — a CREATE action (not a filter), so it lives outside the
   kind-filters role-group. Contextual: syncKindFilterBar reveals it only under
   the `secrets` lens. `hidden` (attr) must beat the flex display. */
.gd-secret-add {
  display: inline-flex; align-items: center; gap: 4px;
  margin: 2px 0 4px; padding: 3px 8px;
  background: transparent; color: var(--accent);
  border: 1px solid var(--accent); border-radius: 8px;
  font: inherit; font-size: 0.6875rem; font-weight: 600; cursor: pointer;
}
.gd-secret-add::before { content: "+"; font-weight: 600; }
.gd-secret-add:hover { background: var(--selected-bg); }
.gd-secret-add[hidden] { display: none; }
#search-input {
  flex: 1; min-width: 0; padding: 4px 8px;
  border: 1px solid var(--input-border); border-radius: 3px;
  font-family: inherit; font-size: 0.8125rem; outline: none;
  background: var(--bg); color: var(--fg);
}
#search-input:focus { border-color: var(--light-fg); }
#search-clear {
  width: 24px; align-self: stretch;
  border: 1px solid var(--input-border); border-radius: 3px;
  background: var(--bg); color: var(--muted-fg);
  font-size: 1rem; cursor: pointer;
  display: flex; align-items: center; justify-content: center; padding: 0;
  flex-shrink: 0; box-sizing: border-box;
}
#search-clear:hover { background: var(--hover-bg); color: var(--fg); }

/* Namespace tree in sidebar */
#entity-list { padding: 4px 0; margin: 0; }
.ns-header { display: flex; align-items: center; padding: 3px 8px; cursor: pointer; user-select: none; }
/* The lens is a `hidden` overlay (applyLensVisibility flips rows/namespaces in
   place instead of rebuilding). `.ns-header`/`.entity-item` set an explicit
   `display`, which would beat the UA `[hidden]{display:none}`, so force it. */
#entity-list [hidden] { display: none !important; }

/* Search: pinned exact-match section above the matched-namespace tree. */
.search-exact-section {
  border-bottom: 1px solid var(--light-border);
  padding-bottom: 4px; margin-bottom: 4px;
}
.search-exact-label {
  font-size: 0.625rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted-fg); padding: 4px 8px 2px;
}
.ns-header:hover { background: var(--hover-bg); }
/* Workspace highlight: namespaces the user works in get
   a left accent. No-op without the addon — the class is only added when the
   X-Graphden-Workspace header lists the namespace. */
.ns-header.ns-in-workspace { border-left: 2px solid var(--accent); padding-left: 6px; }
.ns-arrow { width: 16px; font-size: 0.625rem; color: var(--muted-fg); flex-shrink: 0; text-align: center; }
.ns-label { font-size: 0.8125rem; font-weight: 600; color: var(--fg); }
/* Item-count chip on the pseudo "(root)" group header. Italic +
   muted so it reads as metadata not a name. */
.ns-count {
  margin-left: 6px;
  font-size: 0.625rem;
  color: var(--muted-fg);
  font-style: italic;
}
/* Per-namespace type-error chip (error-tolerance Phase 3) — ⚠ N on a
   namespace row when fns inside carry recorded type diagnostics on the
   current branch. Count-chip sizing, error-colored to stand out. */
.ns-type-error-chip {
  margin-left: 6px;
  font-size: 0.625rem;
  color: var(--error-fg-strong);
  flex-shrink: 0;
}
/* Failed-runs / lint chips on namespace rows and the matching per-row
   markers — the problem lenses' counts (editor-problems.js). */
.ns-problem-chip { margin-left: 6px; font-size: 0.625rem; flex-shrink: 0; white-space: nowrap; }
.ns-failed-chip { color: var(--error-fg-strong); }
.ns-lint-chip { color: var(--muted-fg); }
.kind-marker-failed { color: var(--error-fg-strong); }
.kind-marker-type-error { color: var(--warning-fg); }
.kind-marker-lint { color: var(--muted-fg); }

.ns-header-pseudo .ns-label { font-style: italic; color: var(--muted-fg); }
.ns-children { padding-left: 16px; }
#entity-list .entity-item { padding: 2px 8px 2px 24px; cursor: pointer;
  display: flex; align-items: center; }
#entity-list .entity-item:hover { background: var(--hover-bg); }
#entity-list .entity-item.selected { background: var(--selected-bg); }
#entity-list .entity-item .name { font-weight: 400; font-size: 0.8125rem; }

/* Proactive per-org plan-usage badge (editor-quota.js, task #8-frontend). */
.quota-badge {
  margin: 4px 8px;
  padding: 2px 8px;
  font-size: 0.6875rem;
  color: var(--muted-fg);
  border: 1px solid var(--border);
  border-radius: 4px;
  text-align: center;
  cursor: default;
}
.quota-badge-warn { color: var(--warning-fg); border-color: var(--warning-fg); }
.quota-badge-full { color: var(--error-fg); border-color: var(--error-fg); font-weight: 700; }

/* Menu header — asymmetric padding pulls the action buttons closer to
   the right edge while keeping the title comfortably indented on the
   left. */
/* Two stacked rows: a brand row (λ mark + wordmark) and a dedicated
   toolbar row for the JS-mounted actions (prefs / branch / auth). They
   used to share one space-between row, so a wide branch chip + the
   staggered JS mounts made the buttons jump at different heights and
   worse on sidebar resize. Separate rows keep each stable. */
.menu-header { padding: 12px 8px 10px 16px; border-bottom: 2px solid var(--border);
  background: var(--header-bg); color: var(--header-fg);
  display: flex; flex-direction: column; gap: 8px; position: relative; }
.menu-brand { display: flex; align-items: center; gap: 8px; }
.menu-logo { flex: 0 0 auto; display: block; color: var(--accent); }
.menu-header h2 { font-size: 0.9375rem; font-weight: 600; margin: 0; letter-spacing: 0.2px; }
.menu-header-actions { display: flex; flex-wrap: wrap; align-items: center;
  gap: 6px; min-height: 26px; }

/* Global utility — many JS/partial surfaces toggle `hidden` (auth-popover
   fields, the ⎋ logout-all button, applyAuthMode's tenant/single-tenant
   switch). Only SCOPED rules existed before, so partial-rendered elements
   carrying the class still displayed — single-tenant popovers showed the
   tenant username/org fields. !important so it beats the display:inline-flex
   component rules at equal specificity. */
.hidden { display: none !important; }

/* Prefs (theme + collapse) buttons — same shape/size as auth lock. */
#prefs-mount { display: inline-flex; align-items: center; gap: 6px; }
.prefs-btn {
  background: transparent; color: var(--header-fg);
  border: 1px solid var(--header-fg); border-radius: 3px;
  width: 26px; height: 26px; padding: 3px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.prefs-btn:hover { background: var(--header-fg); color: var(--header-bg); }

/* Auth lock — sits on the right of the menu header, opens a popover. */
#auth-mount { position: relative; display: inline-flex; align-items: center; }
.auth-lock-btn {
  background: transparent; color: var(--header-fg); border: 1px solid var(--header-fg); border-radius: 3px;
  width: 26px; height: 26px; padding: 3px; cursor: pointer; display: inline-flex;
  align-items: center; justify-content: center;
}
.auth-lock-btn:hover { background: var(--header-fg); color: var(--header-bg); }
.auth-lock-btn.auth-lock-open { background: var(--header-fg); color: var(--header-bg); }

/* Account chip (accounts mode) — the lock's chrome relaxes into an identity
   affordance: an avatar disc when signed in, a "Sign in" pill otherwise. */
.auth-lock-btn.auth-chip {
  width: auto; min-width: 26px; border-color: transparent; padding: 2px;
}
.auth-lock-btn.auth-chip:hover { background: transparent; }
.auth-lock-btn.auth-chip.auth-lock-open:hover .auth-avatar { filter: brightness(1.1); }
.auth-avatar {
  width: 22px; height: 22px; border-radius: 50%; display: inline-flex;
  align-items: center; justify-content: center;
  font-size: 0.6875rem; font-weight: 700; line-height: 1;
  background: var(--header-fg); color: var(--header-bg);
}
.auth-avatar.auth-avatar-op {
  background: var(--gd-flow, #06c); color: var(--gd-on-accent, #fff);
  box-shadow: 0 0 0 1px var(--header-fg);
}
.auth-signin {
  font-size: 0.75rem; padding: 3px 10px; border-radius: 3px;
  border: 1px solid var(--header-fg); color: var(--header-fg);
}
.auth-lock-btn.auth-chip:hover .auth-signin { background: var(--header-fg); color: var(--header-bg); }

/* Account menu (the chip's popover) */
.auth-menu { display: flex; flex-direction: column; gap: 2px; min-width: 200px; }
.auth-menu-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 4px 8px 8px; border-bottom: 1px solid var(--line, #2a3038); margin-bottom: 4px;
}
.auth-menu-who { font-size: 0.8125rem; font-weight: 600; word-break: break-all; }
.auth-menu-badge {
  font-size: 0.5625rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  padding: 1px 6px; border-radius: 3px;
  background: var(--gd-flow, #06c); color: var(--gd-on-accent, #fff);
}
.auth-menu-item {
  text-align: left; background: transparent; border: none; cursor: pointer;
  font-size: 0.8125rem; padding: 7px 8px; border-radius: 4px; color: var(--fg);
}
.auth-menu-item:hover { background: var(--bg); }
.auth-menu-div { height: 1px; margin: 4px 2px; background: var(--border); }
/* Community footer — support link (self-host only) + social row. Quiet by
   design: muted text, brightens on hover, sits under the last divider. */
.auth-menu-support { display: block; text-decoration: none; }
.auth-menu-social {
  display: flex; flex-wrap: wrap; gap: 2px 10px;
  padding: 4px 8px 6px; font-size: 0.6875rem;
}
.auth-menu-social a { color: var(--muted-fg); text-decoration: none; }
.auth-menu-social a:hover { color: var(--accent); text-decoration: underline; }

/* "Sign out everywhere" — same chrome as the lock, sits beside it. */
.auth-logout-all-btn {
  background: transparent; color: var(--header-fg); border: 1px solid var(--header-fg); border-radius: 3px;
  width: 26px; height: 26px; padding: 3px; margin-left: 4px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; font-size: 0.875rem;
}
.auth-logout-all-btn:hover { background: var(--header-fg); color: var(--header-bg); }
/* Popover positioned with `position: fixed` (relative to viewport,
   set via JS in `positionAuthPopover`) so it can escape #side-menu's
   scroll context — focusing the input would otherwise cause the
   browser to horizontally scroll the sidebar to reveal it. */
.auth-popover {
  position: fixed; z-index: 300;
  background: var(--bg); color: var(--fg); border: 1px solid var(--fg); border-radius: 3px;
  padding: 8px; min-width: 200px;
  box-shadow: var(--shadow-md);
  display: flex; flex-direction: column; gap: 6px;
}
.auth-popover.hidden { display: none; }
.auth-popover input {
  font-size: 0.75rem; padding: 4px 6px; border: 1px solid var(--fg); border-radius: 3px;
  outline: none; width: 100%; box-sizing: border-box;
  background: var(--bg); color: var(--fg);
}
.auth-popover input:focus { box-shadow: 0 0 0 1px var(--fg) inset; }
/* Wrapper that lets the eye-toggle button float over the right edge
   of the password input. The input gets extra right-padding so the
   typed text doesn't run under the icon. */
/* Username field (multi-tenant login only) sits above the password wrap. */
.auth-username-input:not(.hidden) { margin-bottom: 6px; }
/* Login⇄signup toggle (multi-tenant). */
.auth-mode-toggle { display: block; margin-top: 6px; font-size: 0.6875rem; color: var(--accent); cursor: pointer; text-decoration: underline; }
/* Fatal graph-load banner (editor-main.js initGraph catch) — tokenised so
   it themes with everything else instead of the old inline hardcoded red. */
.editor-fatal-banner { position: fixed; top: 0; left: 0; right: 0; z-index: 99999;
  padding: 8px 16px; background: var(--danger-bg); color: var(--on-accent);
  font: 14px sans-serif; }
.auth-input-wrap { position: relative; }
.auth-input-wrap input { padding-right: 28px; }
.auth-toggle-visibility {
  position: absolute; top: 50%; right: 4px; transform: translateY(-50%);
  width: 20px; height: 20px; padding: 0;
  background: transparent; border: 0; border-radius: 2px;
  cursor: pointer; color: var(--muted-fg);
  display: inline-flex; align-items: center; justify-content: center;
}
.auth-toggle-visibility:hover { color: var(--fg); }
.auth-popover-row { display: flex; gap: 6px; }
.auth-popover-btn {
  flex: 1; font-size: 0.75rem; padding: 4px 8px; cursor: pointer;
  background: var(--fg); color: var(--bg); border: 1px solid var(--fg); border-radius: 3px;
}
.auth-popover-btn:hover { background: var(--bg); color: var(--fg); }
.auth-popover-btn-secondary { background: var(--bg); color: var(--fg); }
.auth-popover-btn-secondary:hover { background: var(--fg); color: var(--bg); }
.auth-error {
  font-size: 0.6875rem; color: var(--fg); background: var(--hover-bg); border: 1px solid var(--fg);
  padding: 4px 6px; border-radius: 3px;
}

/* Branch selector chip + popover — sits between #prefs-mount and
   #auth-mount in the menu-header-actions row. Chip is text+icon so
   the current branch is always one glance away; non-default branches
   get an accent border so editors never forget they're on a feature
   branch. */
#branch-mount { position: relative; display: inline-flex; align-items: center; }
.branch-chip-btn {
  display: inline-flex; align-items: center; gap: 4px;
  background: transparent; color: var(--header-fg);
  border: 1px solid var(--header-fg); border-radius: 3px;
  padding: 3px 8px; height: 26px; cursor: pointer;
  font: inherit; font-size: 0.6875rem; line-height: 1;
  max-width: 140px;
}
.branch-chip-btn span {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.branch-chip-btn:hover {
  background: var(--header-fg); color: var(--header-bg);
}
.branch-chip-btn.branch-chip-non-default {
  /* Stronger affordance when off main — admin needs to see at a glance. */
  background: var(--header-fg); color: var(--header-bg);
}

.branch-popover {
  /* `position: fixed` + JS-positioned via `anchorBelowClamped` so the
     popover can escape the sidebar's scroll context (sidebar is
     280px wide; popover wants ≥ 260px and would clip otherwise). */
  position: fixed; z-index: 300;
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--fg); border-radius: 3px;
  padding: 6px; min-width: 260px; max-width: 360px;
  box-shadow: var(--shadow-md);
  display: flex; flex-direction: column; gap: 6px;
  line-height: 1.3;
}
.branch-popover.hidden { display: none; }
.branch-popover-loading,
.branch-popover-error {
  font-size: 0.6875rem; color: var(--muted-fg); padding: 4px 6px;
}
.branch-popover-error {
  color: var(--fg); background: var(--hover-bg); border: 1px solid var(--fg);
  border-radius: 3px;
}
.branch-popover-error.hidden { display: none; }

/* Org chip (Track B — multi-org). Mirrors the branch chip; shown for every
   member (the org is the outermost write-context, readable at a glance). */
.org-chip-btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: transparent; color: var(--header-fg);
  border: 1px solid var(--header-fg); border-radius: 3px;
  padding: 3px 8px; height: 26px; cursor: pointer;
  font: inherit; font-size: 0.6875rem; line-height: 1;
  max-width: 200px;
}
.org-chip-btn b {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-weight: 600;
}
.org-chip-k {
  font-size: 0.6rem; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase; opacity: 0.75;
}
.org-chip-btn:focus-visible { outline: 2px solid var(--gd-flow); outline-offset: 2px; }
.org-chip-btn:hover { background: var(--header-fg); color: var(--header-bg); }
.org-popover {
  /* Anchored under the chip, growing LEFT from the mount's right edge — the
     mount sits at the right end of the bar, so a left-anchored box would run
     off-screen. */
  position: absolute; top: calc(100% + 4px); right: 0; z-index: 300;
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--fg); border-radius: 3px;
  padding: 6px; min-width: 240px; max-width: 320px;
  box-shadow: var(--shadow-md);
  display: flex; flex-direction: column; gap: 2px;
}
.org-popover.hidden { display: none; }
/* The rows carry their own `display`, which beats the UA's [hidden] rule —
   say it explicitly so `el.hidden = true` actually hides. */
.org-popover [hidden] { display: none; }
.org-popover-item {
  display: flex; align-items: center; gap: 6px;
  text-align: left; background: transparent; color: var(--fg);
  border: none; border-radius: 3px; padding: 4px 8px; cursor: pointer;
  font: inherit; font-size: 0.75rem;
}
.org-popover-item:hover:not(:disabled) { background: var(--hover-bg); }
.org-popover-item:focus-visible { outline: 2px solid var(--gd-flow); outline-offset: -2px; }
.org-popover-item-current { color: var(--muted-fg); cursor: default; }
.org-popover-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.org-popover-head {
  font-size: 0.625rem; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--muted-fg); padding: 2px 8px 4px;
}
.org-popover-list { display: flex; flex-direction: column; gap: 2px; }
.org-popover-badge {
  font-size: 0.5625rem; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--gd-flow);
  border: 1px solid var(--gd-flow); border-radius: 3px; padding: 0 4px;
}
.org-popover-plan { font-size: 0.625rem; color: var(--muted-fg); }
.org-popover-new { margin-top: 4px; border-top: 1px solid var(--gd-line-2); border-radius: 0; padding-top: 6px; }
.org-popover-form { display: flex; flex-direction: column; gap: 4px; padding: 6px 8px 2px; border-top: 1px solid var(--gd-line-2); margin-top: 4px; }
.org-popover-form-label { font-size: 0.6875rem; color: var(--fg); }
.org-popover-input {
  font: inherit; font-size: 0.75rem; padding: 4px 6px;
  border: 1px solid var(--gd-line-2); border-radius: 3px;
  background: var(--bg); color: var(--fg); min-width: 0;
}
.org-popover-input:focus-visible { outline: 2px solid var(--gd-flow); outline-offset: 1px; }
.org-popover-hint { font-size: 0.625rem; color: var(--muted-fg); }
.org-popover-error { font-size: 0.6875rem; color: var(--error-fg-strong); }
.org-popover-form-row { display: flex; justify-content: flex-end; gap: 6px; margin-top: 2px; }
.org-popover-btn {
  font: inherit; font-size: 0.6875rem; padding: 3px 10px; cursor: pointer;
  border: 1px solid var(--gd-flow); border-radius: 3px;
  background: var(--gd-flow); color: var(--bg);
}
.org-popover-btn:disabled { opacity: 0.6; cursor: default; }
.org-popover-btn-secondary { background: transparent; color: var(--fg); border-color: var(--gd-line-2); }

.branch-popover-list {
  display: flex; flex-direction: column;
  max-height: 240px; overflow-y: auto;
}
.branch-row {
  display: grid;
  grid-template-columns: auto auto 1fr auto;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  border-radius: 3px;
  cursor: pointer;
  font-size: 0.75rem;
}
.branch-row:hover { background: var(--hover-bg); }
.branch-row-current {
  background: var(--hover-bg); cursor: default;
}
.branch-row-name { font-weight: 500; }
.branch-row-tag {
  font-size: 0.625rem; padding: 1px 5px; border-radius: 2px;
  background: var(--fg); color: var(--bg);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.branch-row-meta {
  font-size: 0.625rem; color: var(--muted-fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0;
}
.branch-row-actions {
  display: inline-flex; gap: 2px; align-items: center;
}
/* Readability redesign 2026-08: the row keeps THREE inline icon
   actions (Δ diff, ✅ approve, ⇢ merge) plus the ⋯ overflow trigger;
   everything administrative lives in the ⋯ menu with text labels.
   Inline icons get an INSTANT tooltip via data-tip (native title
   needs a ~1s hover and never shows on keyboard focus). */
.branch-row-merge,
.branch-row-diff,
.branch-row-approve,
.branch-row-more {
  background: transparent; color: var(--muted-fg);
  border: 0; border-radius: 2px;
  width: 20px; height: 20px; padding: 0; line-height: 1;
  font-size: 0.8125rem; cursor: pointer;
}
.branch-row-merge:hover,
.branch-row-diff:hover,
.branch-row-approve:hover,
.branch-row-more:hover { color: var(--fg); background: var(--bg); }
/* Accent dot on ⋯ when the row carries a proposal / active protection
   (the states that used to color the now-tucked-away 📤/⚙). */
.branch-row-more.on { color: var(--accent); }
.branch-row-approve { opacity: 1; }
body.gd-no-write .branch-row-approve,
body.gd-no-write .branch-row-more-menu .branch-row-propose,
body.gd-no-write .branch-row-more-menu .branch-row-protect,
body.gd-no-write .branch-row-more-menu .branch-row-delete { display: none; }

/* Instant tooltip under the inline icon buttons. */
.branch-row-actions [data-tip] { position: relative; }
.branch-row-actions [data-tip]:hover::after,
.branch-row-actions [data-tip]:focus-visible::after {
  content: attr(data-tip);
  position: absolute; top: calc(100% + 4px); left: 50%;
  transform: translateX(-50%);
  background: var(--tooltip-bg); color: var(--tooltip-fg);
  padding: 2px 6px; border-radius: 3px;
  font-size: 0.625rem; line-height: 1.2; white-space: nowrap;
  pointer-events: none; z-index: 20;
}

/* The ⋯ menu itself — fixed-positioned by JS so the popover list's
   scroll overflow can't clip it; labeled rows, gd-pop family look. */
.branch-row-more-menu {
  display: none;
  position: fixed; z-index: 340;
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--fg); border-radius: 3px;
  box-shadow: var(--shadow-md);
  min-width: 200px;
  padding: 3px;
  flex-direction: column;
}
.branch-row-more-menu.open { display: flex; }
.branch-row-more-menu button {
  display: flex; align-items: center; gap: 7px;
  background: transparent; color: var(--fg);
  border: 0; border-radius: 2px;
  padding: 5px 8px; cursor: pointer;
  font-size: 0.75rem; line-height: 1.2; text-align: left;
  width: 100%;
}
.branch-row-more-menu button:hover { background: var(--hover-bg); }
.branch-row-more-menu .brm-ic {
  width: 16px; flex: none; text-align: center;
  font-size: 0.75rem;
}
.branch-row-more-menu .brm-label { flex: 1; }
.branch-row-more-menu .branch-row-propose.on .brm-ic,
.branch-row-more-menu .branch-row-protect.on .brm-ic { color: var(--accent); }
/* ⛨ write-policy stays tenancy-only inside the menu too — the generic
   `.branch-row-more-menu button` display rule outweighs the bare
   `.branch-row-policy { display: none }` gate, so re-state it at
   matching specificity. */
.branch-row-more-menu .branch-row-policy { display: none; }
body.gd-tenancy .branch-row-more-menu .branch-row-policy { display: flex; }
body.gd-tenancy.gd-no-write .branch-row-more-menu .branch-row-policy { display: none; }
.branch-row-more-menu .branch-row-delete:hover { color: var(--error-fg-strong); }

/* Sidebar DIFF LENS bar — compare mode's second chip row under the
   kind-lens chips; same .kind-toggle skin. */
#gd-diff-lens {
  display: flex; flex-wrap: wrap; gap: 2px;
  padding: 2px 0 0;
}
#gd-diff-lens .gd-diff-lens-chip[aria-pressed="true"] {
  background: var(--warning-fg); color: var(--bg);
  border-color: var(--warning-fg);
}
.gd-diff-lens-hidden { display: none !important; }

/* Δ lit on the row being compared — same accent family as the chip. */
.branch-row-diff.on { color: var(--warning-fg); }

/* Inspector diff panel — the selected fn's change details + threads. */
.gd-diff-insp {
  margin: 6px 10px;
  padding: 6px 8px;
  border: 1px solid var(--light-border);
  border-left: 2px solid var(--warning-fg);
  border-radius: 3px;
  font-size: 0.6875rem;
}
.gd-diff-insp-head {
  display: flex; align-items: baseline; gap: 6px;
  font-weight: 600;
  margin-bottom: 4px;
}
.gd-diff-insp .branch-diff-entries { padding-left: 6px; }

/* Review dialog extras — the collapsible change list + per-suggestion
   Δ previews. */
.branch-diff-header-into { color: var(--muted-fg); font-weight: 400; }
.bd-review-changes > summary,
.bd-sugg-preview > summary {
  cursor: pointer; font-size: 0.6875rem; color: var(--muted-fg);
  padding: 4px 0;
  user-select: none;
}
.bd-review-changes[open] > summary { color: var(--fg); font-weight: 600; }
.bd-review-verification > summary { cursor: pointer; color: var(--fg-muted); }
.bd-review-verification[open] > summary { color: var(--fg); font-weight: 600; }
.bd-verif-runs { margin: 4px 0 0; padding-left: 1.2em; }
.bd-verif-run[data-status="failed"] { color: var(--danger); }
.bd-verif-empty, .bd-verif-tests { color: var(--fg-muted); }
.bd-sugg-preview { margin: 0 0 6px 12px; }
.bd-sugg-preview .branch-diff-row { margin-bottom: 4px; }

/* Δ compare-mode chip (next to the branch chip) + Explorer badges.
   The editor-wide diff lens: gd-diff-badge marks a changed fn row,
   gd-diff-ns-badge aggregates counts onto a namespace header. */
.gd-diff-chip {
  display: inline-flex; align-items: center; gap: 0;
  margin-left: 4px;
  border: 1px solid var(--warning-fg); border-radius: 3px;
  overflow: hidden;
  max-width: 100%; min-width: 0;
}
.gd-diff-chip-label {
  background: transparent; color: var(--warning-fg);
  border: 0; padding: 1px 6px;
  font-size: 0.6875rem; line-height: 1.4; cursor: pointer;
  white-space: nowrap;
  max-width: 148px;
  display: inline-flex; align-items: baseline;
}
/* A long branch name must not push the chip out of the context bar —
   the NAME span ellipsizes (full name in the title) while the change
   COUNT keeps its place at the right edge. */
.gd-diff-chip-name { overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.gd-diff-chip-count { flex: none; white-space: pre; }
.gd-diff-chip-label:hover { background: var(--hover-bg); }
.gd-diff-chip-off {
  background: transparent; color: var(--muted-fg);
  border: 0; border-left: 1px solid var(--warning-fg);
  padding: 1px 5px; font-size: 0.6875rem; line-height: 1.4; cursor: pointer;
}
.gd-diff-chip-off:hover { color: var(--fg); background: var(--hover-bg); }
/* Lens active = the annotations are a SUBSET — make the chip say so. */
.gd-diff-chip-filtered { border-style: dashed; }
.gd-diff-badge {
  margin-left: auto; flex: none;
  font-family: var(--mono); font-weight: 700; font-size: 0.625rem;
  padding: 0 3px;
}
.entity-item .gd-diff-badge.bd-added { color: var(--success-fg); }
.entity-item .gd-diff-badge.bd-removed { color: var(--error-fg); }
.entity-item .gd-diff-badge.bd-modified { color: var(--warning-fg); }
.entity-item .gd-diff-badge.bd-inside { color: var(--warning-fg); opacity: 0.8; }
/* The per-fn digest under a changed row (compare mode, UX-v4): the row
   wraps and the summary takes the next line. */
#entity-list .entity-item.gd-diff-changed { flex-wrap: wrap; }
.gd-diff-summary {
  flex-basis: 100%; min-width: 0;
  font-size: 0.625rem; line-height: 1.3; color: var(--muted-fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.entity-item.gd-diff-inside .gd-diff-summary { color: var(--warning-fg); opacity: 0.85; }
/* 💬 — anchored review comments on this fn's changes (compare mode).
   Sits right after the +/±/− badge; the count rides in the text. */
.gd-diff-note-badge {
  flex: none;
  font-size: 0.5625rem;
  opacity: 0.85;
  padding: 0 2px;
}
/* Ghost row — a fn that exists only on the COMPARED branch. Dimmed,
   dashed accent, click switches over. */
.gd-diff-ghost {
  opacity: 0.55;
  font-style: italic;
  cursor: pointer;
}
.gd-diff-ghost:hover { opacity: 0.85; }
.gd-diff-ghost .name { text-decoration: line-through dotted; }
.gd-diff-ns-badge {
  margin-left: 4px; flex: none;
  font-family: var(--mono); font-size: 0.5625rem;
  color: var(--warning-fg);
  border: 1px dotted var(--warning-fg); border-radius: 2px;
  padding: 0 3px;
}

/* n/N approvals badge next to ✅ on a proposed row. */
.branch-appr-count {
  font-size: 0.625rem; color: var(--muted-fg);
  padding: 0 3px; align-self: center;
}
.branch-appr-count.ok { color: var(--accent); }

/* "N proposals awaiting review" header above the branch list. */
.branch-proposals-header {
  font-size: 0.6875rem; color: var(--accent);
  padding: 2px 4px 4px;
}

/* ⚙ protection mini-menu — heading + three aligned option rows. The
   approvals control is a 0…3 SEGMENT (one tap each), not a <select>:
   a native dropdown over a floating menu was fragile and slower. */
#gd-protect-pop { min-width: 250px; }
.gd-protect-opt {
  display: flex; align-items: center; gap: 7px;
  padding: 5px 4px; border-radius: 2px;
  font-size: 0.75rem; color: var(--fg); cursor: pointer;
}
.gd-protect-opt:hover { background: var(--hover-bg); }
.gd-protect-opt input[type="checkbox"] {
  margin: 0; flex: none; accent-color: var(--accent);
}
.gd-protect-appr { cursor: default; }
.gd-protect-seg {
  margin-left: auto;
  display: inline-flex;
  border: 1px solid var(--input-border); border-radius: 3px;
  overflow: hidden;
}
.gd-protect-seg-btn {
  background: transparent; color: var(--muted-fg);
  border: 0; border-left: 1px solid var(--input-border);
  width: 24px; height: 20px; padding: 0;
  font-size: 0.6875rem; line-height: 1; cursor: pointer;
}
.gd-protect-seg-btn:first-child { border-left: 0; }
.gd-protect-seg-btn:hover { background: var(--hover-bg); color: var(--fg); }
.gd-protect-seg-btn.sel {
  background: var(--accent); color: var(--on-accent);
}

.branch-popover-create {
  display: flex; flex-direction: column; gap: 6px;
  border-top: 1px solid var(--border);
  padding-top: 6px;
}
.branch-popover-create-row { display: flex; gap: 4px; }
.branch-section-rows { display: flex; flex-direction: column; }
.branch-archived { margin-top: 4px; border-top: 1px dashed var(--light-border); }
.branch-archived-summary { cursor: pointer; font-size: 0.6875rem; color: var(--muted-fg); padding: 4px 6px; }
.branch-row-archived { opacity: 0.65; }
/* Protected branches (Stage 1): row lock + ⛨ policy editor + the Advanced
   create block. All THREE are tenancy-only — without principals a write
   policy has no one to tell apart — and the mutating affordances also
   respect gd-no-write. */
.branch-row-lock { font-size: 0.5625rem; margin-left: 2px; }
.branch-row-policy { display: none; }
body.gd-tenancy .branch-row-policy { display: inline-flex; }
body.gd-tenancy.gd-no-write .branch-row-policy { display: none; }
.branch-adv { display: none; }
body.gd-tenancy .branch-adv { display: block; margin-top: 4px; }
.branch-adv summary {
  font-size: 0.625rem; color: var(--muted-fg); cursor: pointer;
  user-select: none;
}
.branch-adv summary:hover { color: var(--fg); }
.branch-adv-label {
  display: block; font-size: 0.625rem; color: var(--muted-fg); margin: 6px 0 2px;
}
.branch-adv select {
  width: 100%; font-size: 0.6875rem; padding: 3px 4px;
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--border); border-radius: 3px;
}
/* Hub sync section — push/pull buttons the partial renders only when the
   server is wired to a hub (GRAPHDEN_HUB_URL). Mutating, so it respects
   gd-no-write like the create form. */
.branch-popover-hub {
  display: flex; flex-direction: column; gap: 6px;
  border-top: 1px solid var(--border);
  padding-top: 6px;
}
.branch-hub-head {
  display: flex; align-items: baseline; gap: 6px; min-width: 0;
}
.branch-hub-title { font-size: 0.6875rem; font-weight: 600; color: var(--fg); }
.branch-hub-url {
  font-size: 0.625rem; color: var(--muted-fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0;
}
.branch-hub-actions { display: flex; gap: 4px; }
.branch-hub-actions button:disabled { opacity: 0.5; cursor: default; }
.branch-hub-status { font-size: 0.625rem; color: var(--muted-fg); }
.branch-hub-status:empty { display: none; }
.branch-hub-status-error { color: var(--error-fg); }

.branch-popover-create input {
  flex: 1; font-size: 0.6875rem; padding: 4px 6px;
  border: 1px solid var(--fg); border-radius: 3px;
  outline: none; background: var(--bg); color: var(--fg);
  min-width: 0;
}
.branch-popover-create input:focus { box-shadow: 0 0 0 1px var(--fg) inset; }
.branch-popover-btn {
  font-size: 0.6875rem; padding: 4px 10px; cursor: pointer;
  background: var(--fg); color: var(--bg);
  border: 1px solid var(--fg); border-radius: 3px;
}
.branch-popover-btn:hover { background: var(--bg); color: var(--fg); }

/* fn-version history popover — anchored below the ⌛ button on a
   fn-card's row-actions popover. Same visual family as branch-popover
   (shared design tokens) but wider since version rows carry both
   branch name + timestamp + a summary line. */
.fn-versions-popover {
  position: fixed; z-index: 320;
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--fg); border-radius: 3px;
  padding: 6px 8px; min-width: 280px; max-width: 420px;
  box-shadow: var(--shadow-md);
  display: flex; flex-direction: column; gap: 6px;
  font-size: 0.75rem; line-height: 1.3;
}
.fn-versions-popover.hidden { display: none; }
.fn-versions-loading,
.fn-versions-error {
  color: var(--muted-fg); font-size: 0.6875rem;
}
.fn-versions-error { color: var(--fg); }
.fn-versions-header {
  font-weight: 600; font-size: 0.75rem;
  border-bottom: 1px solid var(--border); padding-bottom: 4px;
}
.fn-versions-empty {
  color: var(--muted-fg); font-size: 0.6875rem; padding: 4px 0;
}
.fn-versions-list {
  display: flex; flex-direction: column;
  max-height: 280px; overflow-y: auto;
  gap: 4px;
}
.fn-versions-row {
  display: flex; flex-direction: column; gap: 2px;
  padding: 4px 6px; border-radius: 3px;
  background: var(--hover-bg);
  cursor: pointer;
}
.fn-versions-row-top {
  display: flex; align-items: baseline; gap: 8px;
}
.fn-versions-branch {
  font-weight: 600;
}
.fn-versions-branch-current {
  background: var(--fg); color: var(--bg);
  padding: 1px 5px; border-radius: 2px;
  font-size: 0.6875rem;
}
.fn-versions-ts {
  font-family: var(--mono);
  font-size: 0.625rem; color: var(--muted-fg);
  margin-left: auto;
}
.fn-versions-switch,
.fn-versions-restore {
  font-size: 0.625rem; padding: 1px 6px; cursor: pointer;
  background: transparent; color: var(--fg);
  border: 1px solid var(--border); border-radius: 2px;
}
.fn-versions-switch:hover,
.fn-versions-restore:hover { background: var(--hover-bg); }
.fn-versions-row-meta {
  font-size: 0.625rem; color: var(--muted-fg);
  font-family: var(--mono);
  overflow-wrap: anywhere;
}




/* Inline per-version executions, lazy-loaded on row click. */
.fn-versions-execs {
  margin-top: 4px; padding-top: 4px;
  border-top: 1px dashed var(--border);
}
.fn-versions-execs-empty {
  font-size: 0.625rem; color: var(--muted-fg);
  padding: 2px 0;
}

.fn-versions-execs-list {
  display: flex; flex-direction: column; gap: 2px;
  max-height: 160px; overflow-y: auto;
}
.fn-versions-execs-row {
  display: flex; align-items: baseline; gap: 6px;
  font-size: 0.625rem;
  font-family: var(--mono);
  padding: 2px 0;
}
.fn-versions-execs-status {
  padding: 0 4px; border-radius: 2px;
  background: var(--hover-bg);
  text-transform: uppercase; font-size: 0.5625rem; letter-spacing: 0.04em;
}
.fn-versions-execs-status-succeeded { background: var(--fg); color: var(--bg); }
.fn-versions-execs-status-failed,
.fn-versions-execs-status-cancelled { background: var(--fg); color: var(--bg); }
.fn-versions-execs-ts { color: var(--muted-fg); }


/* Merge conflicts modal — full-viewport overlay + centred card.
   Shown when POST /api/branches/:target/merge returns
   `{ok: false, reason: "merge-conflict", conflicts: [...]}`. User
   picks source/target per row and the modal re-submits with
   `:conflict-resolutions`. */
.merge-conflicts-modal {
  position: fixed; inset: 0; z-index: 500;
  display: flex; align-items: center; justify-content: center;
}
.merge-conflicts-modal.hidden { display: none; }
.merge-conflicts-overlay {
  position: absolute; inset: 0;
  background: var(--modal-backdrop-bg);
}
.merge-conflicts-card {
  position: relative;
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--fg); border-radius: 3px;
  padding: 12px 16px;
  min-width: 520px; max-width: 720px;
  max-height: 80vh; overflow-y: auto;
  box-shadow: var(--shadow-md);
  display: flex; flex-direction: column; gap: 10px;
  font-size: 0.75rem; line-height: 1.4;
}
/* The inherited-content refusal's plan: one row per branch to merge
   first, in order, each with its own button. Same chrome as the
   conflicts modal. */
.merge-plan-step {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 0; border-bottom: 1px solid var(--border);
}
.merge-plan-step:last-child { border-bottom: none; }
.merge-plan-step-num { font-family: var(--mono); color: var(--muted-fg); }
.merge-plan-step-name { font-family: var(--mono); flex: 1; overflow-wrap: anywhere; }
.merge-plan-all-btn { margin-right: auto; }
.merge-conflicts-header {
  font-size: 0.875rem; font-weight: 600;
  border-bottom: 1px solid var(--border); padding-bottom: 6px;
}
.merge-conflicts-help {
  font-size: 0.6875rem; color: var(--muted-fg);
}
.merge-conflicts-batch {
  display: flex; gap: 6px; align-items: center;
  font-size: 0.6875rem; color: var(--muted-fg);
}
.merge-conflicts-batch-label { margin-right: 2px; }
.merge-conflicts-batch-btn {
  font-size: 0.625rem; padding: 2px 8px;
}
.merge-conflicts-rows {
  display: flex; flex-direction: column; gap: 8px;
}
.merge-conflict-row {
  border: 1px solid var(--border); border-radius: 3px;
  padding: 6px 8px;
}
.merge-conflict-row-head {
  display: flex; gap: 8px; align-items: baseline;
  margin-bottom: 4px;
}
.merge-conflict-entity {
  font-weight: 600; font-size: 0.75rem;
  background: var(--hover-bg); padding: 1px 5px; border-radius: 2px;
}
.merge-conflict-id {
  font-family: var(--mono);
  font-size: 0.625rem; color: var(--muted-fg);
}
.merge-conflict-choice {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 0.6875rem;
}
.merge-conflict-choice label {
  cursor: pointer; display: flex; gap: 6px; align-items: baseline;
}
.merge-conflict-choice code {
  font-family: var(--mono);
  background: var(--hover-bg); padding: 0 3px; border-radius: 2px;
  color: var(--muted-fg); font-size: 0.625rem;
}
.merge-conflicts-error {
  font-size: 0.6875rem; color: var(--fg); background: var(--hover-bg);
  border: 1px solid var(--fg); padding: 4px 6px; border-radius: 3px;
}
.merge-conflicts-error.hidden { display: none; }
.merge-conflicts-actions {
  display: flex; gap: 6px; justify-content: flex-end;
  padding-top: 8px; border-top: 1px solid var(--border);
}
.merge-conflicts-cancel {
  background: var(--bg); color: var(--fg);
}
.merge-conflicts-cancel:hover {
  background: var(--fg); color: var(--bg);
}

/* Branch diff modal — the review dialog (⋯ → Review & comments).
   Same overlay anatomy as the merge-conflicts modal so the user
   gets a familiar "full-viewport card on dim backdrop" pattern; the
   inner content is grouped by `:change` tag and shows a brief
   per-entity preview. */
.branch-diff-modal {
  position: fixed; inset: 0; z-index: 500;
  display: flex; align-items: center; justify-content: center;
}
.branch-diff-modal.hidden { display: none; }
.branch-diff-overlay {
  position: absolute; inset: 0;
  background: var(--modal-backdrop-bg);
}
.branch-diff-card {
  position: relative;
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--fg); border-radius: 3px;
  padding: 0;
  /* Clamp to the viewport — a phone is narrower than the old fixed
     560px floor and the card's Close must stay reachable. */
  min-width: min(560px, calc(100vw - 24px));
  max-width: min(800px, calc(100vw - 24px));
  width: 70vw;
  max-height: 80vh;
  display: flex; flex-direction: column;
  box-shadow: var(--shadow-md);
  font-size: 0.75rem; line-height: 1.4;
}
.branch-diff-header {
  font-size: 0.875rem; font-weight: 600;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: 8px;
}
.branch-diff-close {
  background: transparent; color: var(--muted-fg);
  border: 0; border-radius: 2px;
  width: 22px; height: 22px; padding: 0; line-height: 1;
  font-size: 1.125rem; cursor: pointer;
}
.branch-diff-close:hover { background: var(--hover-bg); color: var(--fg); }
.branch-diff-body {
  padding: 8px 16px 12px;
  overflow-y: auto;
  flex: 1;
}
.branch-diff-loading,
.branch-diff-error {
  font-size: 0.75rem; color: var(--muted-fg);
  padding: 8px 0;
}
.branch-diff-error {
  color: var(--fg); background: var(--hover-bg);
  border: 1px solid var(--fg); border-radius: 3px;
  padding: 6px 8px;
}

.branch-diff-row {
  padding: 6px 8px; border-radius: 3px;
  background: var(--hover-bg);
  border: 1px solid transparent;
}
.branch-diff-row-head {
  display: flex; align-items: baseline; gap: 8px;
  margin-bottom: 2px;
}
/* diff v2 — one row per owning fn; change marker + fn name in the
   head, the per-entity detail entries beneath. Colors ride the
   semantic tokens (success/error/warning) so both themes hold. */
.branch-diff-marker {
  font-family: var(--mono);
  font-weight: 700; font-size: 0.75rem;
  width: 14px; text-align: center; flex: none;
}
.bd-added > .branch-diff-marker,
.branch-diff-marker.bd-added { color: var(--success-fg); }
.bd-removed > .branch-diff-marker,
.branch-diff-marker.bd-removed { color: var(--error-fg); }
.bd-modified > .branch-diff-marker,
.branch-diff-marker.bd-modified { color: var(--warning-fg); }
.branch-diff-row.bd-added { border-left: 2px solid var(--success-fg); }
.branch-diff-row.bd-removed { border-left: 2px solid var(--error-fg); }
.branch-diff-row.bd-modified { border-left: 2px solid var(--warning-fg); }
.branch-diff-fn-name {
  font-family: var(--mono);
  font-size: 0.75rem;
  overflow-wrap: anywhere;
}
.branch-diff-entries {
  display: flex; flex-direction: column; gap: 2px;
  padding: 2px 0 0 20px;
}
.branch-diff-entry {
  display: flex; align-items: baseline; gap: 6px;
  font-size: 0.6875rem;
}
.branch-diff-entry-label {
  color: var(--muted-fg);
  white-space: nowrap;
}
.branch-diff-entry-preview {
  font-family: var(--mono);
  overflow-wrap: anywhere;
}
.branch-diff-fields {
  display: flex; flex-direction: column; gap: 1px;
  flex: 1; min-width: 0;
}
.branch-diff-field {
  display: flex; align-items: baseline; gap: 5px;
  font-family: var(--mono);
  overflow-wrap: anywhere;
}
.branch-diff-field-name { color: var(--muted-fg); flex: none; }
.branch-diff-field-name::after { content: ":"; }
.bd-old {
  color: var(--error-fg-strong);
  text-decoration: line-through;
  overflow-wrap: anywhere; min-width: 0;
}
.bd-new {
  color: var(--accent); font-weight: 600;
  overflow-wrap: anywhere; min-width: 0;
}
.bd-arrow { color: var(--muted-fg); flex: none; }
/* Effect-set delta chip on a diff group row — the "behaviour footprint
   changed" flag. */
.bd-effects-chip {
  font-family: var(--mono);
  font-size: 0.625rem;
  color: var(--warning-fg);
  border: 1px dashed var(--warning-fg); border-radius: 2px;
  padding: 0 4px;
  white-space: nowrap;
}
/* 💬 anchored-comment trigger — quiet until the row is hovered or it
   has focus; count badge appears when a thread exists. */
.branch-diff-comment-btn {
  margin-left: auto; flex: none;
  background: transparent; border: 0; border-radius: 2px;
  font-size: 0.6875rem; line-height: 1;
  padding: 1px 4px; cursor: pointer;
  opacity: 0;
  color: var(--muted-fg);
}
.branch-diff-row:hover .branch-diff-comment-btn,
.branch-diff-entry:hover .branch-diff-comment-btn,
.branch-diff-comment-btn:focus-visible,
.branch-diff-comment-btn.has-comments { opacity: 1; }
.branch-diff-comment-btn:hover { background: var(--hover-bg); color: var(--fg); }
.branch-diff-comment-btn .bd-comment-count {
  font-size: 0.5625rem; font-family: var(--mono);
  color: var(--accent); margin-left: 1px;
}
/* Inline anchored thread — inserted by JS right under its row/entry. */
.branch-diff-anchor-thread {
  margin: 4px 0 4px 20px;
  padding: 4px 8px;
  border-left: 2px solid var(--light-border);
  display: flex; flex-direction: column; gap: 3px;
}
.branch-diff-anchor-thread .branch-comment { font-size: 0.6875rem; }
.branch-diff-anchor-compose {
  display: flex; gap: 4px; align-items: flex-start;
}
.branch-diff-anchor-compose textarea {
  flex: 1; min-height: 20px; resize: vertical;
  font: inherit; font-size: 0.6875rem;
  color: var(--fg); background: var(--bg);
  border: 1px solid var(--input-border); border-radius: 2px;
  padding: 2px 5px;
}
/* Suggestions — reviewer-authored child branches of the proposal,
   listed with lazy Δ preview + apply(merge) actions; author applies in one
   click. */
.branch-diff-suggestions {
  border-top: 1px solid var(--border);
  margin-top: 10px; padding: 8px 0 4px;
}
.branch-diff-suggestions h4 {
  font-size: 0.75rem; margin: 0 0 6px; color: var(--fg);
}
.branch-diff-suggestion-row {
  display: flex; align-items: center; gap: 8px;
  font-size: 0.6875rem;
  padding: 3px 0;
}
.branch-diff-suggestion-name { font-family: var(--mono); overflow-wrap: anywhere; }
.branch-diff-suggestion-row button {
  background: transparent; color: var(--muted-fg);
  border: 1px solid var(--light-border); border-radius: 2px;
  font-size: 0.625rem; padding: 1px 6px; cursor: pointer;
}
.branch-diff-suggestion-row button:hover { color: var(--fg); border-color: var(--fg); }
.branch-diff-suggest-new {
  margin-top: 4px;
  background: transparent; color: var(--muted-fg);
  border: 1px dashed var(--light-border); border-radius: 2px;
  font-size: 0.6875rem; padding: 2px 8px; cursor: pointer;
}
.branch-diff-suggest-new:hover { color: var(--fg); border-color: var(--fg); }
.branch-diff-suggestions-empty { color: var(--muted-fg); font-size: 0.6875rem; }
/* Branch-local annotation — appears on diff rows for fns whose
   effective branch-local is true. The diff lists them (the version
   DOES differ), but the resolver filters them out at merge time, so
   the badge tells the user "this won't actually propagate". */
.branch-diff-row-local-badge {
  margin-left: auto;
  font-size: 0.625rem;
  font-style: italic;
  color: var(--muted-fg);
  padding: 1px 5px;
  border: 1px dashed var(--muted-fg);
  border-radius: 2px;
}
.branch-diff-row-local {
  opacity: 0.7;
}
.auth-error.hidden { display: none; }

/* Per-namespace row buttons — ✎ rename + + create-child. Hidden until
   the row is hovered. Both monochrome to match the sidebar palette. */
.ns-row-actions {
  margin-left: auto; display: inline-flex; gap: 4px; align-items: center;
}
/* `createDescriptionBadge` / `createOpenInNewTabButton` set an inline
   `margin-left: 4px` for non-pinned use (fn-overlay rows). Inside the
   sidebar's icon group the container already supplies a 4px `gap`,
   so the extra margin doubles up and the description-badge / ↗ drift
   right of the create-btn-inline siblings — the row's icons read as
   "dancing" at different distances. `!important` is required because
   inline styles otherwise win over external CSS; the alternative
   (changing the JS factories) would either need a per-callsite flag
   or break their default fn-overlay placement. */
.ns-row-actions > * {
  margin-left: 0 !important;
}
.create-btn {
  background: transparent; color: inherit; border: 1px solid currentcolor;
  border-radius: 3px; cursor: pointer; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.create-btn:hover { background: currentcolor; }
.create-btn:hover svg { color: var(--bg); }
/* Match `applyActionIconBox` (description-badge `i`) so the row's
   icons line up: same 15×15 frame, same 1px border. The action
   buttons stay hover-only — `i` is informational and harmless to
   show always; ✎/+ are actions and should not clutter the row when
   the user is just scanning the tree. */
.create-btn-inline {
  width: var(--icon-size); height: var(--icon-size);
  opacity: 0; transition: opacity 0.1s;
  color: var(--fg);
}
.ns-header:hover .ns-row-actions .create-btn-inline,
.entity-item:hover .ns-row-actions .create-btn-inline,
/* :focus-within — a keyboard user reaches these buttons by tabbing INTO the
   row, and an invisible-but-focusable control is worse than no control. */
.ns-header:focus-within .ns-row-actions .create-btn-inline,
.entity-item:focus-within .ns-row-actions .create-btn-inline { opacity: 1; }
.create-btn-inline:hover { background: var(--fg); color: var(--bg); }
/* The personal "hide from my view" (⊘) ns action — a text glyph, not an SVG,
   so size it to sit in the same 15×15 frame as the ✎/+ icons. */
.ns-hide-btn { font-size: 0.6875rem; line-height: 1; }

/* Packages popover (from the #gd-pkg-chip context-bar chip). Wider than the
   workspace popover — it hosts the installed pins + browse/install table (the
   `.packages-*` table styling lives in the shared `.gd-pop`-scoped block). */
#gd-pkg-pop { width: 460px; max-width: 92vw; max-height: 70vh; padding: 10px; }
#gd-pkg-pop .loading { padding: 8px; }

/* Disabled (greyed-out) state for the delete button when the entity
   can't be removed safely. The button stays in the row layout so its
   absence doesn't shift the icon group; tooltip carries the reason.
   Hover doesn't change anything — no background flip, no color change,
   no SVG colour swap — the row's hover rule still raises opacity to
   make the button visible, but everything else stays at the disabled
   palette so it reads as "not clickable". */
.create-btn-inline.create-btn-disabled,
.create-btn-inline.create-btn-disabled:hover,
.create-btn-inline:disabled,
.create-btn-inline:disabled:hover {
  color: var(--light-fg);
  background: transparent;
  cursor: not-allowed;
}
.create-btn-inline.create-btn-disabled:hover svg,
.create-btn-inline:disabled:hover svg {
  color: var(--light-fg);
}

/* Create-child popover menu (ns / fn). Floated above the sidebar. */
.create-menu {
  background: var(--bg); color: var(--fg); border: 1px solid var(--fg); border-radius: 3px;
  min-width: 180px; padding: 4px; display: flex; flex-direction: column; gap: 2px;
  box-shadow: var(--shadow-md);
}
.create-menu-item {
  background: transparent; border: 0; border-radius: 2px;
  padding: 6px 10px; text-align: left; font-size: 0.75rem; cursor: pointer;
  color: inherit;
}
.create-menu-item:hover { background: var(--fg); color: var(--bg); }

/* Inline input row — used both for create (indented under a parent ns)
   and for rename (replaces the ns-header label in place). */
.inline-input-row {
  display: flex; align-items: center; gap: 4px;
  padding: 4px 6px;
}
.inline-input {
  flex: 1; font-size: 0.75rem; padding: 3px 6px;
  border: 1px solid var(--fg); border-radius: 3px; outline: none;
  background: var(--bg); color: var(--fg);
}
.inline-input:focus { box-shadow: 0 0 0 1px var(--fg) inset; }
.inline-input:disabled { opacity: 0.6; }
.inline-btn {
  width: 22px; height: 22px; padding: 0;
  background: var(--bg); color: var(--fg); border: 1px solid var(--fg);
  border-radius: 3px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.inline-btn:hover { background: var(--fg); color: var(--bg); }
.inline-btn-save:hover { background: var(--fg); color: var(--bg); }
.inline-btn:disabled { opacity: 0.4; cursor: default; }
.inline-error {
  font-size: 0.625rem; color: var(--fg);
  background: var(--hover-bg); border: 1px solid var(--fg); border-radius: 3px;
  padding: 1px 5px; display: none;
}

/* "+ New namespace" button at the bottom of the sidebar. Side margins
   keep the dashed border off the sidebar edges. */
.create-root-ns-btn {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: calc(100% - 24px); box-sizing: border-box;
  padding: 10px 12px; margin: 8px 12px 12px;
  background: var(--bg); color: var(--fg);
  border: 1px dashed var(--fg); border-radius: 3px;
  font-size: 0.75rem; cursor: pointer;
}
.create-root-ns-btn:hover { background: var(--fg); color: var(--bg); }
.create-root-ns-plus { display: inline-flex; align-items: center; }
.create-root-ns-text { font-weight: 500; }

/* Node overlay for interactive ancestor list */
.node-overlay { pointer-events: auto; z-index: 10; }
/* The node whose fn is showing in the inspector — a calm ring, no layout shift. */
.node-overlay.gd-node-active {
  outline: 2px solid var(--gd-flow);
  outline-offset: 2px;
}
.node-overlay .ancestor-line {
  /* Each row must occupy one full text-line slot even when the content is
     empty — e.g. the top row of an anonymous fn keeps its black "no own name"
     band instead of collapsing. With global box-sizing: border-box the
     min-height must include vertical padding (4+4) + line-height (13) = 21,
     expressed in rem so it tracks the (rem) line-height above it. */
  min-height: 1.3125rem;
  line-height: 0.8125rem;
  transition: color 0.1s, font-weight 0.1s;
}

/* Return-type strip on root fn cards — sits at the bottom of the
   card, right above the optional/HOF strips and drag handle. Visual
   reuses the same compact treatment (1-px dashed top border + small
   italic font) so it feels like a peer of those informational strips
   rather than a primary control. Editable on the root card only;
   adds a hover-bg + cursor when so. */
.return-type-strip {
  padding: 2px 8px;
  color: var(--muted-fg);
  font-size: 0.625rem;
  font-style: italic;
  border-top: 1px dashed var(--input-border);
  background: var(--card-bg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Flex variant used when the strip wraps the display text in a span so
   a trailing provenance button stays hit-testable past long types. The
   text span takes the remaining width with its own ellipsis; the
   button is a non-shrinking sibling at the end. */
.return-type-strip-flex {
  display: flex;
  align-items: center;
  gap: 4px;
}
.return-type-strip-flex .return-type-strip-text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.return-type-strip-editable {
  cursor: pointer;
}
.return-type-strip-editable:hover {
  background: var(--hover-bg);
  color: var(--fg);
}
/* Refinement variant: stack `(constraint)` under the base in the same
 * spirit as the arg-overlay's stacked refinement chip. Arrow + base sit
 * on the first text run; the constraint span wraps to a second line at
 * smaller weight + reduced opacity. */
.return-type-strip-refine .return-type-strip-arrow,
.return-type-strip-refine .return-type-strip-base {
  display: inline;
}
.return-type-strip-refine .return-type-strip-constraint {
  display: block;
  padding-left: 1em;
  font-size: 0.5625rem;
  opacity: 0.7;
}

/* Edit-this-type-row strip — peer of `.return-type-strip-editable`,
   surfaces "open the type-edit form" for type-rows on their nav-root
   card. Italic muted text matches the other footer strips; hover
   raises contrast so the affordance is unambiguous. */
.edit-type-strip {
  padding: 2px 8px;
  color: var(--muted-fg);
  font-size: 0.625rem;
  font-style: italic;
  border-top: 1px dashed var(--input-border);
  background: var(--card-bg);
  cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.edit-type-strip:hover,
.edit-type-strip:focus-visible {
  background: var(--hover-bg);
  color: var(--fg);
  outline: none;
}

/* Effect badges — small per-category chips on a fn's footer. Pure
   fns omit the strip entirely; effectful fns show colour-coded
   pills (one per category) so the type of effect is visible at a
   glance without opening the full /api/types entry. */
.effects-strip {
  padding: 2px 6px;
  border-top: 1px dashed var(--input-border);
  background: var(--card-bg);
  display: flex; flex-wrap: wrap; gap: 3px;
  align-items: center;
}
.effects-chip {
  display: inline-block;
  padding: 0 5px;
  font-family: "SF Mono", Monaco, monospace;
  font-size: 0.5312rem;
  line-height: 0.75rem;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  border: 0;
  border-radius: 2px;
  color: var(--effect-chip-fg);
  background: var(--muted-fg);
  cursor: pointer;
}
.effects-chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.effects-chip:hover {
  filter: brightness(1.08);
}
/* On CANVAS cards the chips are ambient metadata, not the reading
   target — most app-level fns carry the same 8-category union, so at
   full saturation the strip dominates the card ("candy"). Dim them on
   the card; hover restores the true category colour. The inspector /
   panels keep full strength (there the chips ARE the content). */
.effects-strip .effects-chip { filter: saturate(0.55) brightness(1.03); }
.effects-strip .effects-chip:hover { filter: none; }
.effects-chip-db      { background: var(--effect-chip-db); }       /* storage CRUD */
.effects-chip-env     { background: var(--effect-chip-env); }      /* env-var read */
.effects-chip-io      { background: var(--effect-chip-io); }       /* file/classpath */
.effects-chip-network { background: var(--effect-chip-network); }  /* HTTP socket */
.effects-chip-time    { background: var(--effect-chip-time); }     /* wall-clock */
.effects-chip-random  { background: var(--effect-chip-random); }   /* non-determinism */
.effects-chip-process { background: var(--effect-chip-process); }  /* spawns supervised background work */
.effects-chip-raw-sql { background: var(--effect-chip-raw-sql); }  /* raw SQL — bypasses org-scoped storage */

/* Drift = computed effect that the author DIDN'T declare in
   `:expects-effects`. Ring it red so it stands out without changing
   the category colour. */
.effects-chip-drift {
  box-shadow: 0 0 0 1.5px var(--error-fg);
}
/* Ghost = declared but not computed yet. Outlined-only — keeps the
   colour signal but signals "expected, not seen" via the dashed ring. */
.effects-chip-ghost {
  background: transparent !important;
  color: var(--muted-fg);
  outline: 1px dashed currentcolor;
  outline-offset: -1px;
}

/* Pencil at the right of the effects-strip — opens the inline-edit
   popover that toggles `:expects-effects`. Text mode ("declare
   effects…") used when no contract is set yet, glyph (`✎`) when one
   exists. Same muted-fg → fg hover affordance as the other footer
   strip controls. */
.effects-strip-edit {
  margin-left: auto;
  padding: 0 6px;
  font-size: 0.625rem;
  background: transparent;
  border: none;
  color: var(--muted-fg);
  cursor: pointer;
  font-style: italic;
}
.effects-strip-edit:hover,
.effects-strip-edit:focus-visible {
  color: var(--fg);
  outline: none;
}

/* Effect-contract picker — rendered inside the inline-edit popover.
   Two-row layout: mode radio + 2-column checkbox grid. Disabled
   state (no-contract mode) dims the checkboxes so the user sees
   they're not currently consulted. */
.expects-effects-edit {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 220px;
}
.expects-effects-mode-row {
  display: flex;
  gap: 12px;
  font-size: 0.6875rem;
}
.expects-effects-mode-row label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  cursor: pointer;
}
.expects-effects-grid {
  display: grid;
  grid-template-columns: repeat(2, auto);
  gap: 4px 12px;
  font-size: 0.6875rem;
}
.expects-effects-checkbox {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  cursor: pointer;
}
.expects-effects-checkbox input:disabled + * { color: var(--muted-fg); }

/* Namespace badge `ns` — left slot in the row-actions popover, hover
   tinges to match the action-icon framework. The matching action-
   icon-disabled / action-icon-danger variants live where the rest
   of `.action-icon` styling is declared. */
.namespace-badge {
  opacity: 0.85;
  transition: opacity 0.1s, background-color 0.1s, color 0.1s;
}
.namespace-badge:hover {
  opacity: 1;
  background-color: color-mix(in srgb, currentcolor 28%, transparent);
}


/* Sidebar `i` description badge + ↗ open-in-new-tab — both hidden
   until the row is hovered, matching the ✎ / + buttons. The
   card-level `i`/↗ (inside fn-overlays) keep their always-visible
   defaults; the rules below only narrow the icons that live in the
   namespace tree. */
.ns-header .description-badge,
.entity-item .description-badge,
.entity-item .open-in-new-tab.sidebar-action {
  opacity: 0;
  transition: opacity 0.1s, background-color 0.1s;
}
.ns-header:hover .description-badge,
.entity-item:hover .description-badge,
.entity-item:hover .open-in-new-tab.sidebar-action,
/* Same reasoning as the row buttons above: reachable by keyboard means
   visible when the keyboard is there. */
.ns-header:focus-within .description-badge,
.entity-item:focus-within .description-badge,
.entity-item:focus-within .open-in-new-tab.sidebar-action {
  opacity: 0.85;
}
.ns-header .description-badge:hover,
.entity-item .description-badge:hover,
.entity-item .open-in-new-tab.sidebar-action:hover {
  opacity: 1;
}

/* More-actions trigger ("⋯") — always visible, sits at slot r-1 of
   every fn-row. Hovering / clicking opens the row-actions popover
   (rendered OUTSIDE the card silhouette, see editor-row-actions.js)
   that hosts the ns / i / ↗ / ✎ / × / + / ✕ glyphs. The popover
   itself uses the .row-actions-popover class. */
.more-actions-trigger {
  opacity: 0.55;
  transition: opacity 0.1s, background-color 0.1s;
}
.more-actions-trigger:hover {
  opacity: 1;
  background: var(--hover-bg);
}

/* Capability gating — per-action. The addon reports
   the caller's capabilities via X-Graphden-Capabilities; the fetch wrap sets
   gd-no-write / gd-no-execute body classes. No-op without the addon (header
   absent → classes never set → editor unchanged). Server-side enforcement
   still applies; this is the matching UX so users don't see what they can't
   do.

   A fully read-only caller (no write AND no execute) loses the whole
   entry; otherwise the trigger stays for the read actions (i / ↗ / ⌛) and
   only the disallowed per-action items are hidden. */
body.gd-no-write.gd-no-execute .more-actions-trigger {
  display: none !important;
}
body.gd-no-write [data-action="rename-fn"],
body.gd-no-write [data-action="extend-fn"],
body.gd-no-write [data-action="delete-fn"],
body.gd-no-write [data-action="add-mi-parent"],
body.gd-no-write [data-action="remove-mi-parent"],
body.gd-no-write [data-action="namespace-move"],
body.gd-no-write [data-action="change-use-site-value"],
body.gd-no-write [data-action="remove-use-site-binding"],
body.gd-no-write [data-action="service-settings"] {
  display: none !important;
}
body.gd-no-execute [data-action="run-fn"] {
  display: none !important;
}

/* --- Capability gating, full coverage (2026-08). The [data-action] rules
   above cover the fn-card row-actions popover; these extend the SAME
   gd-no-write / gd-no-execute contract to every OTHER write/execute affordance
   the editor exposes (create menu + sidebar-tree ns edits — a parallel path
   the row-actions gate missed — branch create/merge/delete, secrets, services,
   type promote/tighten, version restore, deploy, and all three execute
   buttons). No-op for self-hosted (these body classes are only set when the
   tenancy addon reports capabilities); server-side enforcement is the real
   gate — this is the matching UX so a tenant never sees a control it can't use. */
body.gd-no-write .create-menu-item,
body.gd-no-write #create-root-ns-btn,
body.gd-no-write .ns-edit-btn,
body.gd-no-write .ns-plus-btn,
body.gd-no-write .ns-delete-btn,
body.gd-no-write .fn-versions-restore,
body.gd-no-write .branch-popover-create,
body.gd-no-write .branch-popover-hub,
body.gd-no-write .branch-row-merge,
body.gd-no-write .branch-row-delete,
body.gd-no-write #secret-add-btn,
body.gd-no-write .sidebar-action-delete,
body.gd-no-write .sidebar-action[title="Rotate value"],
body.gd-no-write .service-popover-save-btn,
body.gd-no-write .service-popover-delete-btn,
body.gd-no-write .type-inline-promote-btn,
body.gd-no-write .type-explainer-btn {
  display: none !important;
}
body.gd-no-execute .execute-run-btn,
body.gd-no-execute .execute-cancel-btn {
  display: none !important;
}

/* Row-actions popover — floats outside the card to the right (or
   left if there's no room). Brief fade-in masks the position-shift
   when zoom changes apply scale/transform mid-frame. */
.row-actions-popover {
  transition: opacity 0.08s ease;
}
.row-actions-popover .description-badge,
.row-actions-popover .open-in-new-tab,
.row-actions-popover .action-icon,
.row-actions-popover .namespace-badge {
  opacity: 0.95;
  transition: opacity 0.1s, background-color 0.1s;
}
.row-actions-popover .description-badge:hover,
.row-actions-popover .open-in-new-tab:hover,
.row-actions-popover .action-icon:hover,
.row-actions-popover .namespace-badge:hover {
  opacity: 1;
}

/* Arg-value edit popover — small floating panel anchored below the
   clicked value box. Theme-aware via the same vars as the rest of
   the editor; uses `--card-*` so the popover visually matches a
   graph node rather than a tooltip. */
/* Shared chrome for every floating editor surface — arg-value /
   arg-rename / arg-type / fn-rename / fn-return-type /
   free-arg-bind / namespace-move popovers, the parent-set editor,
   the fn-picker, and the namespace-picker. Container differences
   (size, special accent stripe, mono vs system font) live in the
   per-popover blocks below; this rule centralises position,
   surface colours, border, shadow, padding, and the column flex
   so a future popover only needs to set its own min-width and
   accent. */
.arg-value-edit-popover,
.fn-picker-popover {
  position: fixed; z-index: 10001;
  background: var(--card-bg); color: var(--card-fg);
  border: 1px solid var(--card-border); border-radius: 4px;
  box-shadow: var(--shadow-xl);
  padding: 8px;
  display: flex; flex-direction: column; gap: 6px;
  font-family: 'SF Mono', Monaco, Inconsolata, monospace;
}
.arg-value-edit-popover {
  min-width: 220px;
  max-width: min(420px, 90vw);
}
.arg-value-edit-input {
  font-family: inherit; font-size: 0.75rem;
  padding: 4px 6px; border-radius: 3px;
  border: 1px solid var(--card-border);
  background: var(--bg); color: var(--fg);
  outline: none; box-sizing: border-box; width: 100%;
}
.arg-value-edit-input:focus { box-shadow: 0 0 0 1px var(--fg) inset; }
.arg-value-edit-buttons { display: flex; gap: 6px; justify-content: flex-end; }
.arg-value-edit-btn {
  font-family: inherit; font-size: 0.6875rem;
  padding: 3px 10px; border-radius: 3px; cursor: pointer;
  background: var(--fg); color: var(--bg);
  border: 1px solid var(--fg);
}
.arg-value-edit-btn:hover { opacity: 0.85; }
.arg-value-edit-btn:disabled { opacity: 0.5; cursor: default; }
.arg-value-edit-btn-secondary {
  background: transparent; color: var(--fg);
}
/* Danger variant for the popover button row — used by the Delete
   button on the value-edit popover (drops the binding so the slot
   reverts to a free-arg). Floated to the LEFT of the row so it's
   visually separated from the commit/dismiss pair on the right. */
.arg-value-edit-btn-danger {
  background: transparent; color: var(--error-fg);
  border-color: color-mix(in srgb, var(--error-fg) 50%, var(--card-border));
  margin-right: auto;
}
.arg-value-edit-btn-danger:hover {
  background: color-mix(in srgb, var(--error-fg) 12%, transparent);
}
/* Persistent error panel inside edit popovers — used by reparent /
   value-edit / type-edit flows. Bumped from "tiny red text" to a
   structured panel: tinted background, left accent, "!" icon. The
   message stays until the user fixes the cause and saves, or
   closes the popover. Screen readers should announce it via
   role="alert" set in JS at the call site. */
.arg-value-edit-error {
  /* Hidden until a save actually fails — class-toggled (not inline
     style), so an empty "!" chip can never render by default. */
  display: none; align-items: flex-start; gap: 6px;
  padding: 6px 8px;
  margin-top: 4px;
  background: color-mix(in srgb, var(--error-fg) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--error-fg) 35%, transparent);
  border-left: 3px solid var(--error-fg);
  border-radius: 3px;
  font-size: 0.7188rem; line-height: 1.4;
  color: var(--card-fg);
  overflow-wrap: anywhere;
}
.arg-value-edit-error.visible { display: flex; }
.arg-value-edit-error::before {
  content: '!';
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--error-fg); color: var(--card-bg);
  font-weight: 700; font-size: 0.6875rem;
  line-height: 1;
}

/* Type-hint line above the value input. Subtle italic so it reads
   as metadata, not as a label that competes with the input. */
.arg-value-edit-hint {
  font-size: 0.6875rem; color: var(--muted-fg);
  font-style: italic;
  padding: 2px 0; opacity: 0.85;
}

/* "in <namespace>" row of the Extend popover: caption + compact
   native select (typeahead for free) — a nested picker popover would
   fight the inline-editor's outside-click dismissal. */
.extend-ns-row {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 0;
}
.extend-ns-cap {
  font-size: 0.6875rem; color: var(--muted-fg); font-style: italic;
}
.extend-ns-select {
  flex: 1; min-width: 0;
  font-family: var(--mono); font-size: 0.75rem;
  padding: 3px 4px;
  background: var(--card-bg); color: var(--fg);
  border: 1px solid var(--card-border); border-radius: 6px;
}
.extend-ns-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Live-validation status line below the input. ✓ / ✗ + short
   message; mirrors what the backend would say on save. */
.arg-value-edit-status {
  font-size: 0.6875rem; padding: 2px 0; min-height: 14px;
  font-family: var(--mono);
}
.arg-value-edit-status.ok  { color: var(--success-fg); }
.arg-value-edit-status.err { color: var(--error-fg); }

/* Value-form — the type-aware editor form fetched from
   /api/value-form and rendered into the value-edit popover. The host
   is a scroll container so a tall textarea / record form can't push
   the Save/Cancel row off-screen. */
.value-form-host {
  display: flex; flex-direction: column; gap: 6px;
  max-height: 60vh; overflow-y: auto;
  outline: none;
}
.value-form-loading {
  font-size: 0.6875rem; color: var(--muted-fg);
  font-style: italic; padding: 4px 0;
}
.value-form-error {
  font-size: 0.7188rem; color: var(--error-fg);
  padding: 4px 0;
}
/* Raw-value escape hatch — the "{} raw" / "⊞ form" toggle row above a
   typed form. A quiet text button: the typed form stays the star. */
.value-form-toolbar {
  display: flex; justify-content: flex-end;
}
.value-form-raw-toggle {
  background: none; border: none; padding: 0 2px;
  font-family: var(--mono); font-size: 0.6562rem;
  color: var(--muted-fg); cursor: pointer;
}
.value-form-raw-toggle:hover { color: var(--accent); }
/* The raw textarea replaces the typed controls inside the form root. */
.value-form-raw-input { width: 100%; box-sizing: border-box; }
/* Textarea control (long text / JSON) — user-resizable, monospace. */
textarea.arg-value-edit-input {
  resize: vertical; min-height: 64px;
  font-family: var(--mono);
  line-height: 1.4;
}
/* Record / composite field grouping inside a value-form — a bordered
   fieldset with one stacked label+control per field. */
.value-form-group {
  display: flex; flex-direction: column; gap: 6px;
  border: 1px solid var(--card-border);
  border-radius: 3px; padding: 6px;
}
.value-form-field {
  display: flex; flex-direction: column; gap: 2px;
}
.value-form-label {
  font-size: 0.6875rem; color: var(--muted-fg);
}
/* Hiccup EDN textarea — a page/element source needs real width and
   height: widen the hosting popover (and the read-only viewer) past
   the 420px scalar default, and open the textarea tall. */
.arg-value-edit-popover:has(.value-form-hiccup),
.value-viewer:has(.value-form-hiccup) {
  max-width: min(720px, 92vw);
  width: min(720px, 92vw);
}
textarea.value-form-hiccup {
  min-height: 180px;
  white-space: pre;
}

/* Union — a branch <select> stacked over one sub-form per branch.
   Inactive branches carry the `hidden` attribute; no `display` is set
   on the branch so the UA `[hidden]` rule keeps working. */
.value-form-union {
  display: flex; flex-direction: column; gap: 6px;
}

/* Tier-2 custom-widget mount point — a registered JS widget (e.g. the
   `rating` slider) builds its control inside `.value-form-widget`. */
.value-form-widget {
  display: flex; align-items: center; gap: 8px;
}
.value-form-widget-range { flex: 1; }
.value-form-widget-readout {
  font-size: 0.6875rem; color: var(--muted-fg);
  font-family: var(--mono);
  white-space: nowrap;
}

/* Read-only value viewer — a singleton info-popover showing the
   type-aware form (controls disabled) for a structurally read-only
   arg row. Surface mirrors the mismatch-explainer info-popover. */
.value-viewer {
  position: fixed; z-index: 10001;
  display: none;
  background: var(--card-bg); color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-radius: 4px;
  box-shadow: var(--shadow-lg);
  padding: 8px 10px;
  min-width: 240px; max-width: min(420px, 90vw);
  font-family: inherit; font-size: 0.75rem; line-height: 1.4;
}
.value-viewer.visible { display: block; }
.value-viewer-header {
  display: flex; align-items: center; justify-content: space-between;
  border-bottom: 1px solid var(--light-border);
  margin-bottom: 6px; padding-bottom: 4px;
}
.value-viewer-title { font-weight: 600; font-size: 0.75rem; }
.value-viewer-close {
  width: 20px; height: 20px; padding: 0;
  background: transparent; color: var(--card-fg);
  border: 0; border-radius: 3px;
  font-size: 1.125rem; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0.65;
}
.value-viewer-close:hover { opacity: 1; background: var(--overlay-pressed); }

/* Type-mismatch indicator on arg-overlays. Surfaces broken bindings
   directly on the graph view without needing to open the value-edit
   popover. Box-shadow rather than border so the ring sits OUTSIDE
   the overlay's existing layout box (no width jitter). */
.arg-overlay-mismatch {
  box-shadow: 0 0 0 1.5px var(--error-fg) !important;
}

/* Diff-focus ring (diff v2) — after a diff-modal row click the changed
   args of that fn are ringed on the canvas; the Δ badge's tooltip
   carries the before → after summary, clicking it dismisses every
   ring. Warning tint, so it never reads as the red mismatch ring. */
.arg-overlay-diff-focus {
  box-shadow: 0 0 0 1.5px var(--warning-fg) !important;
}
.arg-diff-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 0.9375rem; height: 0.9375rem;
  margin-left: 4px; padding: 0;
  border: 1px solid var(--warning-fg); border-radius: 50%;
  background: transparent; color: var(--warning-fg);
  font-size: 0.5625rem; font-weight: 700; line-height: 1;
  cursor: pointer; pointer-events: auto;
}
.arg-diff-badge:hover { background: var(--hover-bg); }
/* Compare-mode CARD rings — the fn-overlay itself marks as changed, so
   the whole displayed graph reads as a diff. Same semantic colors as
   the sidebar badges. */
.fn-overlay-diff-added { box-shadow: 0 0 0 2px var(--success-fg) !important; }
.fn-overlay-diff-missing { box-shadow: 0 0 0 2px var(--error-fg) !important; }
.fn-overlay-diff-modified { box-shadow: 0 0 0 2px var(--warning-fg) !important; }
/* "Changed inside" (UX-v4) — the card's own rows are equal; an ancestor
   or a ref target differs. Dashed, so it never reads as the fn's own
   change; the ∿ badge reveals the change in this card. */
.fn-overlay-diff-inside { outline: 2px dashed var(--warning-fg); outline-offset: 1px; }
.fn-diff-inside-badge {
  position: absolute; top: -9px; right: -9px; z-index: 3;
  width: 1.125rem; height: 1.125rem; padding: 0;
  border: 1px solid var(--warning-fg); border-radius: 50%;
  background: var(--card-bg); color: var(--warning-fg);
  font-size: 0.6875rem; font-weight: 700; line-height: 1;
  cursor: pointer; pointer-events: auto;
}
.fn-diff-inside-badge:hover { background: var(--hover-bg); }
/* The fn's OWN row change (rename / description) as a strip on the card. */
.fn-diff-was {
  padding: 2px 8px; font-size: 0.625rem; line-height: 1.3;
  color: var(--warning-fg); border-top: 1px dashed var(--warning-fg);
  white-space: normal; overflow-wrap: anywhere;
}
/* The "there: …" block under an arg / on a placeholder — the value the
   compared branch holds, struck through. */
.arg-overlay-diff-added { box-shadow: 0 0 0 1.5px var(--success-fg) !important; }
.edge-label-overlay.edge-label-diff { outline: 1.5px dashed var(--warning-fg); outline-offset: 1px; border-radius: 3px; }
.arg-diff-was {
  margin-top: 2px; padding: 1px 4px;
  font-size: 0.625rem; line-height: 1.3; color: var(--error-fg);
  border-top: 1px dashed var(--error-fg);
  white-space: nowrap; width: max-content; max-width: 240px;
  overflow: hidden; text-overflow: ellipsis; pointer-events: auto;
}
/* A value node is as narrow as its value; the there-line may be wider —
   let it hang out of the box rather than wrap into a sliver. */
.arg-overlay-diff-focus, .arg-overlay-diff-added { overflow: visible !important; }
.arg-diff-was-line { display: block; }
.arg-diff-was-k { color: var(--muted-fg); }
.arg-diff-was-v { text-decoration-color: var(--error-fg); }
.placeholder-overlay.placeholder-diff .arg-diff-was {
  background: var(--card-bg); border: 1px dashed var(--error-fg); border-radius: 4px;
  max-width: 220px;
}
/* Ghost subtrees — the compared branch's side of a replaced ref, drawn
   beside the card. Dashed, dimmed, read-only. */
.gd-ghost-link path {
  fill: none; stroke: var(--error-fg); stroke-width: 1.5; stroke-dasharray: 5 4; opacity: 0.8;
}
.gd-ghost-cluster {
  position: absolute; z-index: 9; pointer-events: auto;
  font-size: 0.6875rem; line-height: 1.3; opacity: 0.9;
}
.gd-ghost-head {
  display: block; width: 100%; padding: 2px 6px; text-align: left;
  border: 1px dashed var(--error-fg); border-radius: 4px;
  background: var(--card-bg); color: var(--error-fg);
  font: inherit; font-weight: 600; cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gd-ghost-head:hover { background: var(--hover-bg); }
.gd-ghost-folded .gd-ghost-body { display: none; }
.gd-ghost-body { display: flex; flex-direction: column; gap: 10px; margin-top: 8px; }
.gd-ghost-card {
  position: relative; border: 1px dashed var(--error-fg); border-radius: 6px;
  background: var(--card-bg); color: var(--card-fg); opacity: 0.85;
}
.gd-ghost-card + .gd-ghost-card::before {
  content: ''; position: absolute; left: 14px; top: -11px; width: 0; height: 10px;
  border-left: 1.5px dashed var(--error-fg);
}
.gd-ghost-card-head {
  padding: 3px 6px; font-weight: 600; color: var(--error-fg);
  border-bottom: 1px dashed var(--error-fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gd-ghost-card-parents {
  padding: 1px 6px; color: var(--muted-fg); font-size: 0.625rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gd-ghost-row { display: flex; gap: 6px; padding: 1px 6px; }
.gd-ghost-row-k { color: var(--muted-fg); flex: none; }
.gd-ghost-row-v { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gd-ghost-row-ref .gd-ghost-row-v { color: var(--error-fg); }
.gd-ghost-more { color: var(--muted-fg); font-style: italic; }

/* Arg type-chip — small text label "[int]" / "[fn]" docked to the
   right edge of an arg-overlay or trailing an edge-label. Click flips
   the arg's type, optionally chaining into the fn-picker. */
.arg-type-chip {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0 4px; margin-left: 4px;
  font-family: inherit; font-size: 0.5625rem; line-height: 0.875rem;
  border: 1px solid currentcolor; border-radius: 3px;
  cursor: pointer; opacity: 0.7;
  pointer-events: auto;
}
.arg-type-chip:hover { opacity: 1; }
/* Read-only variant — no hover-up because it's not actionable. The
   parent's `--muted-fg` × 0.7 opacity lands at ~#888 on white, which
   the a11y MCP flags as 3.54-contrast against the canvas. Pin
   opacity at full and the colour at a token darker than its
   neighbour `currentColor` (which is itself muted) so the chip
   stays subordinate to actionable chips but clears WCAG 2 AA. */
.arg-type-chip-readonly {
  cursor: default;
  opacity: 1;
  color: var(--fg);
}
.arg-type-chip-readonly:hover { opacity: 1; }
/* When a read-only chip carries a `title` (always set when the
   compact rich-type label differs from the full type spelled-out
   via `formatTypeHumanReadable`), surface that via cursor: help so
   the user knows there's more info on hover. The displayed text
   often gets truncated to `(request)→ring-res…` — the full type
   only lives in the title attribute. */
.arg-type-chip-readonly[title]:hover { cursor: help; }

/* Effect-constraint summary row inside a fn-type inline-expand panel.
 * Shows the slot's allowed effect-set ("eff: pure" or "eff: <chips>")
 * in read-only mode — separate from the editable tightening widgets
 * that live below it when the binding is mutable. */
.type-inline-effects-readonly {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 8px;
  font-size: 0.625rem;
  color: var(--muted-fg);
}
.type-inline-effects-label {
  font-style: italic;
  opacity: 0.8;
}
.type-inline-effects-pure {
  font-size: 0.5625rem;
  padding: 0 6px;
  border: 1px solid currentcolor;
  border-radius: 8px;
  opacity: 0.85;
  color: var(--fg);
  background: var(--sidebar-bg);
}

/* Refinement chip — stacked two-line variant. The base type sits on
 * top at the chip's normal weight; the constraint sits below at a
 * smaller weight + reduced opacity so the visual reads "this is a
 * subtype of <base>, narrowed by <constraint>" without forcing the
 * eye to parse `int (> 0)` as a single phrase. */
.arg-type-chip-refine {
  flex-direction: column;
  line-height: 0.6875rem;
  padding: 1px 4px;
}
.arg-type-chip-refine .arg-type-chip-refine-base {
  font-size: 0.5625rem;
}
.arg-type-chip-refine .arg-type-chip-refine-constraint {
  font-size: 0.5rem;
  opacity: 0.7;
  margin-top: -1px;
}

/* Narrowed chip — when a binding-level override / inherited override /
 * ref-return narrowing produced a type whose declared slot-base is
 * different from the displayed text, append a `< :base` line below the
 * chip. Same column layout as the refinement variant so the visual
 * grammar stays consistent ("this is a subtype of X" reads top→bottom). */
.arg-type-chip-narrowed {
  flex-direction: column;
  line-height: 0.6875rem;
  padding: 1px 4px;
}
.arg-type-chip-narrowed .arg-type-chip-narrowed-base {
  font-size: 0.5rem;
  opacity: 0.7;
  margin-top: -1px;
  font-style: italic;
}

/* Sequence-item bracket chrome — wraps an `.arg-type-chip` so it reads
   as `[elem-type]` (graphden's `[:list T]` structural notation) when
   the edge represents one item of a sequence-bound slot. The brackets
   are purely visual; the chip inside keeps its click target / inline-
   expand / type-edit behaviour. Lower opacity than the chip itself so
   they read as container chrome, not a sibling type. */
.arg-type-chip-list-bracket {
  display: inline-flex;
  align-items: center;
  gap: 1px;
  margin-left: 4px;
  font-family: inherit;
  font-size: 0.5625rem;
  line-height: 0.875rem;
}
.arg-type-chip-list-bracket > .arg-type-chip { margin-left: 0; }
.arg-type-chip-list-bracket::before {
  content: '[';
  opacity: 0.65;
}
.arg-type-chip-list-bracket::after {
  content: ']';
  opacity: 0.65;
}

/* Type-create popover — opens from sidebar's "+ New type…" menu.
   Renders a kind picker first (refinement/record/union/variant/list)
   then swaps to the per-kind form on selection. Same visual idiom
   as the type-explainer popover: card-style frame, header strip,
   form body, action-button row. */
.type-create-popover {
  display: none;
  position: fixed;
  z-index: 100;
  width: 320px;
  background: var(--card-bg);
  color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-radius: 6px;
  box-shadow: var(--shadow-lg);
  padding: 0;
  font-size: 0.6875rem;
}
.type-create-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 6px 8px;
  border-bottom: 1px solid var(--light-border);
  background: var(--header-bg);
  color: var(--header-fg);
  border-radius: 6px 6px 0 0;
}
.type-create-title { font-weight: 600; font-size: 0.6875rem; }
.type-create-close {
  background: transparent; color: inherit;
  border: none; cursor: pointer; padding: 0 4px;
  font-size: 1rem; line-height: 1;
}
.type-create-close:hover { opacity: 0.7; }
.type-create-form {
  display: flex; flex-direction: column; gap: 8px;
  padding: 8px;
}
.type-create-field {
  display: flex; flex-direction: column; gap: 2px;
}
.type-create-field-label {
  font-size: 0.625rem; color: var(--muted-fg);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.type-create-input {
  font-family: inherit; font-size: 0.6875rem;
  padding: 4px 6px;
  border: 1px solid var(--input-border);
  border-radius: 3px;
  background: var(--bg); color: var(--fg);
}
.type-create-input-mono {
  font-family: 'SF Mono', Monaco, monospace;
}
.type-create-textarea {
  font-family: 'SF Mono', Monaco, monospace;
  resize: vertical;
  min-height: 60px;
}
.type-create-input:focus-visible { outline: 2px solid var(--accent); }

/* Variant / record pair-rows — two-column "(tag, type)" / "(name,
   type)" widget with per-row × remove and a "+ add row" button at
   the bottom. Replaces the freeform `tag: type\n` textarea so the
   author doesn't need to know any line syntax. */
.type-create-pair-list {
  display: flex; flex-direction: column; gap: 4px;
}
.type-create-pair-rows {
  display: flex; flex-direction: column; gap: 3px;
}
.type-create-pair-row {
  display: flex; gap: 4px; align-items: center;
  background: var(--bg);
}
.type-create-pair-row-dragging {
  opacity: 0.4;
}
.type-create-pair-drag {
  width: 16px; flex-shrink: 0;
  font-size: 0.75rem; line-height: 1;
  color: var(--muted-fg);
  cursor: grab;
  user-select: none;
  text-align: center;
}
.type-create-pair-drag:active { cursor: grabbing; }
.type-create-pair-drag:hover { color: var(--fg); }
.type-create-pair-key,
.type-create-pair-val { flex: 1; min-width: 0; }
.type-create-pair-rm {
  width: 22px; height: 22px;
  flex-shrink: 0;
  border: 1px solid var(--input-border);
  border-radius: 3px;
  background: var(--bg); color: var(--muted-fg);
  font-size: 0.8125rem; line-height: 1;
  cursor: pointer;
}
.type-create-pair-rm:hover {
  color: var(--error-fg);
  border-color: var(--error-fg);
}
.type-create-pair-add {
  align-self: flex-start;
  font-family: inherit; font-size: 0.625rem;
  padding: 2px 8px;
  border: 1px dashed var(--input-border);
  border-radius: 3px;
  background: transparent; color: var(--muted-fg);
  cursor: pointer;
}
.type-create-pair-add:hover {
  color: var(--fg); border-color: var(--fg);
}

/* Union branches input — single-line full-width without a "branches"
   label above it (the placeholder + form header carry the meaning). */
.type-create-input-fullrow { width: 100%; }

/* Type-create kind tabs — segmented control at the top of the
   unified create form. Saves a click vs the prior 2-step picker
   (kind menu → form) and keeps name/description carried across
   tab switches so the user doesn't retype while exploring kinds. */
.type-create-tabs {
  display: flex;
  border-top: 1px solid var(--card-border);
  border-bottom: 1px solid var(--card-border);
  background: var(--card-bg);
}
.type-create-tab {
  flex: 1;
  font-family: inherit; font-size: 0.625rem;
  padding: 5px 4px;
  background: transparent; border: none;
  color: var(--muted-fg);
  cursor: pointer;
  border-bottom: 2px solid transparent;
}
.type-create-tab:hover { color: var(--fg); }
.type-create-tab-active {
  color: var(--fg);
  border-bottom-color: var(--accent);
  font-weight: 600;
}

/* Refinement constraint builder — op-dropdown + value-input rows,
   stacked with optional `:and` / `:or` combinator visible only when
   >1 row. Mirrors the pair-row idiom (× per row, + add at the
   bottom) so the form has one consistent multi-row UX. */
.refinement-builder {
  display: flex; flex-direction: column; gap: 4px;
}
.refinement-rows {
  display: flex; flex-direction: column; gap: 3px;
}
.refinement-row {
  display: flex; gap: 4px; align-items: center;
}
.refinement-op {
  width: 70px; flex-shrink: 0;
  font-family: inherit; font-size: 0.6875rem;
  padding: 3px 4px;
  border: 1px solid var(--input-border);
  border-radius: 3px;
  background: var(--bg); color: var(--fg);
}
.refinement-val { flex: 1; min-width: 0; }
.refinement-combinator {
  display: flex; align-items: center; gap: 4px;
  font-size: 0.625rem; color: var(--muted-fg);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.refinement-combinator-select {
  font-family: inherit; font-size: 0.6875rem;
  padding: 2px 4px;
  text-transform: lowercase;
  border: 1px solid var(--input-border);
  border-radius: 3px;
  background: var(--bg); color: var(--fg);
}
.refinement-advanced-wrap {
  display: flex; justify-content: flex-end;
}
.refinement-advanced-toggle {
  font-family: inherit; font-size: 0.625rem;
  padding: 2px 6px;
  background: transparent;
  border: none;
  color: var(--muted-fg);
  cursor: pointer;
}
.refinement-advanced-toggle:hover {
  color: var(--fg);
}

.type-create-error {
  font-size: 0.625rem;
  color: var(--error-fg);
  white-space: pre-wrap;
}
.type-create-actions {
  display: flex; justify-content: space-between;
  gap: 6px; margin-top: 4px;
}
.type-create-back,
.type-create-submit {
  font-family: inherit; font-size: 0.6875rem;
  padding: 4px 10px;
  border-radius: 3px; cursor: pointer;
}
.type-create-back {
  background: var(--card-bg); color: var(--muted-fg);
  border: 1px solid var(--light-border);
}
.type-create-back:hover { background: var(--hover-bg); }
.type-create-submit {
  background: var(--header-bg); color: var(--header-fg);
  border: 1px solid var(--header-bg);
  font-weight: 600;
}
.type-create-submit:hover { opacity: 0.85; }
.type-create-submit:disabled { opacity: 0.5; cursor: not-allowed; }

/* Inline type-chip expansion — appears as a floating card below
   the chip when the user clicks an expandable chip. Reveals the
   type's constituent parts (refine→base+constraint, list→element,
   union→branches, record→fields) as a small indented tree. Same
   visual vocabulary as the chip itself; no special colours/borders. */
.type-chip-expandable {
  /* Chip itself IS the click target for expansion — give it a
     subtle dotted underline so users see the chip is interactive
     beyond just opening a popover. */
  text-decoration-line: underline;
  text-decoration-style: dotted;
  text-decoration-color: var(--muted-fg);
  text-underline-offset: 2px;
}
/* Expandable chips carry `role="button"` + `tabindex` — give keyboard
   focus a visible ring (WCAG 2.4.7). */
.type-chip-expandable:focus-visible,
.type-inline-chip-expandable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.type-inline-header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
  padding-bottom: 2px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--light-border);
}
.type-inline-header-name {
  font-weight: 600;
  font-size: 0.625rem;
  color: var(--card-fg);
}
/* Cycle indicator — a chip rendered in place of an expandable one
   when the type would recurse back to an ancestor in the open chain
   (e.g. `:tree = [:list :tree]`). Read-only; clicking does nothing,
   the `↻` glyph + name says "you've seen this; the loop closes here". */
.type-inline-chip-cycle {
  font-style: italic;
  color: var(--muted-fg);
  border-color: var(--light-border);
  background: var(--card-row-highlight);
  cursor: default;
}

/* Structural kind tag — Map / Record / Tuple / Refinement / Union /
   Function. Sits next to the name in the inline-expand header so an
   anonymous structural type isn't mistaken for the other kinds with
   similar-looking expanded bodies. */
.type-inline-header-kind {
  font-size: 0.5625rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted-fg);
  padding: 0 4px;
  border: 1px solid var(--light-border);
  border-radius: 2px;
}
.type-inline-header-edit {
  padding: 1px 6px;
  font-size: 0.5625rem;
  font-family: inherit;
  line-height: 1.2;
  border: 1px solid var(--card-border);
  border-radius: 3px;
  background: var(--card-bg);
  color: var(--card-fg);
  cursor: pointer;
}
.type-inline-header-edit:hover,
.type-inline-header-edit:focus-visible {
  background: var(--hover-bg);
  outline: none;
}
.type-inline-header-edit:focus-visible {
  box-shadow: 0 0 0 2px var(--accent);
}
/* "↳ provenance" link in the inline-panel header — opens the
   dedicated provenance popover. Same baseline as the Change-type
   button but reads as a link (text-coloured, no border) since it
   navigates rather than mutates. */
.type-inline-header-provenance {
  padding: 1px 6px;
  font-size: 0.5625rem;
  font-family: inherit;
  line-height: 1.2;
  border: none;
  background: transparent;
  color: var(--accent);
  cursor: pointer;
}
.type-inline-header-provenance:hover,
.type-inline-header-provenance:focus-visible {
  background: var(--hover-bg);
  text-decoration: underline;
  outline: none;
}
.type-inline-header-provenance:focus-visible {
  box-shadow: 0 0 0 2px var(--accent);
}
.type-inline-description {
  margin: 0 0 4px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--light-border);
  font-size: 0.625rem;
  color: var(--muted-fg);
  font-style: italic;
}
/* Promote-anonymous button — appears in the inline-expand panel
   when the type is anonymous (no name) and the user has edit
   rights. Naming the type creates no copy — the existing fn-row
   just gets a `:name`, so every callsite still points at it. */
.type-inline-promote { margin-top: 6px; }
.type-inline-promote-btn {
  font-family: inherit; font-size: 0.625rem;
  padding: 3px 8px;
  border: 1px dashed var(--muted-fg);
  border-radius: 3px;
  background: transparent;
  color: var(--muted-fg);
  cursor: pointer;
}
.type-inline-promote-btn:hover,
.type-inline-promote-btn:focus-visible {
  background: var(--hover-bg);
  color: var(--fg);
  border-color: var(--fg);
  outline: none;
}

/* "Used by N" back-link footer in the type-expansion panel. Lists
   every fn that references this type-row — clickable to navigate. */
/* "Resolved via" — the type-resolution priority chain in the
 * inline-expand panel. Winning tier highlighted; the rest dimmed. */
.type-inline-resolution {
  margin-top: 6px;
  padding-top: 4px;
  border-top: 1px solid var(--light-border);
}
.type-inline-resolution-head {
  font-size: 0.5625rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--light-fg);
  margin-bottom: 2px;
}
.type-inline-resolution-row {
  display: flex; align-items: baseline; gap: 6px;
  font-size: 0.625rem;
  white-space: nowrap;
  color: var(--muted-fg);
}

/* Refinement-chain breadcrumb inside an inline-expand panel — one
   single-line view of `:user-port ⊂ :int ⊂ :numeric` joined by ⊂
   glyphs. Each name is clickable when a named type-row backs it;
   constraints surface on hover so the row stays compact. */
.type-inline-refinement-chain {
  margin-top: 4px;
  margin-bottom: 6px;
  padding: 2px 0;
}
.type-inline-refinement-chain-row {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px;
  font-size: 0.625rem;
  color: var(--muted-fg);
}
.type-inline-refinement-chain-sep {
  color: var(--light-fg);
  font-size: 0.625rem;
}
.type-inline-refinement-chain-anon {
  font-style: italic;
  color: var(--light-fg);
}
.type-inline-resolution-active {
  color: var(--fg);
  font-weight: 600;
}
.type-inline-resolution-mark {
  flex: 0 0 auto;
  width: 9px;
  text-align: center;
  color: var(--light-fg);
}
.type-inline-resolution-active .type-inline-resolution-mark { color: var(--accent); }
.type-inline-resolution-label { flex: 0 1 auto; }
.type-inline-resolution-sep { flex: 0 0 auto; color: var(--light-fg); }
/* Clickable source-fn / ancestor link inside a resolution row. Reset
   default anchor underline + colour; keep it muted so it doesn't
   compete with the winning-tier highlight. */
.type-inline-resolution-link {
  flex: 0 1 auto;
  color: var(--accent);
  text-decoration: none;
  cursor: pointer;
}
.type-inline-resolution-link:hover { text-decoration: underline; }
.type-inline-resolution-active .type-inline-resolution-link { color: var(--accent); }
.type-inline-resolution-active .type-inline-resolution-type { color: var(--accent); }

/* Multi-override inheritance — when ≥ 2 ancestors carry a type-
   override for the same slot, the FIRST (closer-fn-wins) entry
   gets emphasised and tagged "(chosen)"; the rest dim out and
   read "(also by)" so the shadow-override decision is visible. */
.type-inline-resolution-chain-winner { color: var(--fg); font-weight: 600; }
.type-inline-resolution-chain-winner .type-inline-resolution-mark { color: var(--accent); }
.type-inline-resolution-chain-also   { color: var(--light-fg); }
.type-inline-resolution-chain-tag {
  flex: 0 0 auto;
  font-size: 0.5625rem;
  font-style: italic;
  color: var(--light-fg);
}
.type-inline-resolution-chain-winner .type-inline-resolution-chain-tag {
  color: var(--accent);
}

.type-inline-usages {
  margin-top: 6px;
  padding-top: 4px;
  border-top: 1px solid var(--light-border);
}
.type-inline-usages-head {
  font-size: 0.5625rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--light-fg);
  margin-bottom: 2px;
}
.type-inline-usages-list {
  display: flex; flex-direction: column;
  gap: 1px;
  max-height: 160px;
  overflow-y: auto;
}
.type-inline-usage-row {
  display: flex; align-items: baseline; gap: 6px;
  font-size: 0.625rem;
  white-space: nowrap;
}
.type-inline-usage-link {
  color: var(--accent);
  text-decoration: none;
  cursor: pointer;
}
.type-inline-usage-link:hover,
.type-inline-usage-link:focus-visible {
  text-decoration: underline;
  outline: none;
}
.type-inline-usage-kind {
  color: var(--muted-fg);
  font-size: 0.5625rem;
}
.type-inline-host {
  /* Body-level floating panel — `position: fixed` so it doesn't
     enlarge the edge-label overlay's bounding box (which is
     right-anchored to the target node; growing it would push under
     the source fn card). Positioned in JS to follow the chip. */
  display: none;
  position: fixed;
  z-index: 100;
  padding: 4px 6px;
  background: var(--card-bg);
  color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-radius: 4px;
  box-shadow: var(--shadow-md);
  font-family: 'SF Mono', Monaco, monospace;
  font-size: 0.625rem;
  line-height: 1.2;
  max-width: 260px;
  overflow-wrap: anywhere;
  pointer-events: auto;
}

/* Edge-label overlay sits above fn-overlays (z-index 10). Edges
   often pack the arg-name + type-chip into the gap between source
   and target; when that gap is too small the overlay inevitably
   extends into one of the cards. Without this, fn-cards stacked at
   z-index 10 cover the chip's right tail and the user loses the
   tail of the type signature. We trade: a few pixels of card chrome
   sit BEHIND the overlay rather than the type-chip disappearing. */
/* Edge-label overlay — arg-name + type-chip + description badge on an
   inheritance edge. JS owns only the dynamic bits (left/top placement
   after the taxi bend, scale transform). */
.edge-label-overlay {
  z-index: 11;
  position: absolute;
  pointer-events: auto;
  background: var(--bg);
  color: var(--muted-fg);
  font-family: 'SF Mono', Monaco, monospace;
  font-size: 0.625rem;
  line-height: 1.2;
  padding: 2px 4px;
  white-space: pre;
  text-align: left;
  transform-origin: top left;
  user-select: none;
}
.type-inline-row {
  display: block;
  white-space: normal;
  margin: 1px 0;
}
.type-inline-label {
  display: inline-block;
  margin-right: 4px;
  font-size: 0.5625rem;
  color: var(--muted-fg);
}
.type-inline-label-renameable {
  cursor: pointer;
  text-decoration: underline dotted var(--muted-fg);
  text-underline-offset: 2px;
}
.type-inline-label-renameable:hover { color: var(--fg); }
.type-inline-chip {
  font-size: 0.5625rem;
  line-height: 0.75rem;
  margin-left: 0;
}
.type-inline-chip-expandable {
  text-decoration: underline dotted var(--muted-fg);
  text-underline-offset: 2px;
}
.type-inline-child {
  display: block;
  margin-left: 8px;
  padding-left: 6px;
  border-left: 1px solid var(--light-border);
}
.type-inline-constraint-text {
  font-size: 0.5625rem;
  color: var(--muted-fg);
  font-family: inherit;
}

/* Stacked type-narrowing block — sits below the main edge-label row
   when ≥ 2 type groups are visible along the source-chain. One row
   per inherited level: `↑ [type] (source-fn)`. Same vocabulary as the
   stacked rename in the label text — no new visual idiom. */
.edge-type-chain {
  display: flex; flex-direction: column;
  margin-top: 2px;
  gap: 1px;
}
.edge-type-chain-row {
  display: inline-flex; align-items: center;
  white-space: nowrap;
}
.edge-type-chain-arrow {
  font-size: 0.5625rem;
  color: var(--muted-fg);
  margin-right: 2px;
  line-height: 0.875rem;
}
.edge-type-chain-source {
  font-size: 0.5625rem;
  color: var(--muted-fg);
  margin-left: 4px;
  line-height: 1.2;
}
/* Narrowing attribution — why a chain entry has its type
 * (binding-override vs the slot's own declaration). */
.edge-type-chain-kind {
  font-size: 0.5rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--light-fg);
  margin-left: 4px;
  padding: 0 3px;
  border: 1px solid var(--light-border);
  border-radius: 2px;
  line-height: 1.4;
}

/* Free-arg binding chooser — two-button row inside the inline-edit
   popover; user picks "Bind literal" or "Bind fn-ref". */
.free-arg-bind-chooser {
  display: flex; gap: 6px; flex-wrap: wrap;
}

/* Sequence add/remove buttons on edge-labels (Phase 5). Shape matches
   the type-chip / is-fn chip family — same border-radius, same line-
   height — so a row of edge-label affordances reads as one strip. */
.arg-seq-btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; margin-left: 4px;
  width: 14px; height: 14px;
  font-family: inherit; font-size: 0.6875rem; line-height: 1;
  border: 1px solid currentcolor; border-radius: 3px;
  background: transparent; color: currentcolor;
  cursor: pointer; opacity: 0.7;
  pointer-events: auto;
}
.arg-seq-btn:hover { opacity: 1; }
.arg-seq-btn-remove { color: var(--muted-fg); }
.arg-seq-btn-add { font-weight: bold; }

/* Placeholder binder — the small `+` button that sits at the endpoint
   of an unset edge. Replaces the old dashed type-name card; the
   type-chip on the inbound edge already shows what the slot expects,
   so this is purely a click affordance ("bind this slot" or, on a
   sequence anchor, "add the first item"). Sized as a non-critical
   touch-target — `--touch-target-min` kicks in on hover-less devices. */
.placeholder-overlay {
  display: flex; align-items: center; justify-content: center;
}
.placeholder-binder {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0;
  width: 18px; height: 18px;
  font-family: inherit; font-size: 0.875rem; line-height: 1;
  font-weight: 600;
  /* Free-arg language: amber + dashed, same as the inspector `free`
     chip and the card's ?-row — one signal across all three surfaces. */
  border: 1px dashed var(--gd-free);
  border-radius: 50%;
  background: var(--gd-free-wash);
  color: var(--gd-free);
  cursor: pointer;
  opacity: 0.85;
  transition: opacity 120ms, color 120ms, border-color 120ms,
              background-color 120ms;
}
.placeholder-binder:hover,
.placeholder-binder:focus-visible {
  opacity: 1;
  color: var(--fg);
  border-color: var(--fg);
  border-style: solid;
  outline: none;
}
.placeholder-binder:focus-visible {
  box-shadow: 0 0 0 2px var(--accent);
}

/* Fn-picker popover — search input + scrollable list of candidate
   fns, used by Phase 2 (arg-type → fn) and reused in later phases. */
.fn-picker-popover {
  /* Picker sits ABOVE other popovers (e.g. opens from inside the
     reparent or arg-type popover) so its z-index nudges above the
     shared 10001. Padding tighter than other popovers because the
     list itself fills the body. */
  z-index: 10002;
  padding: 6px;
  min-width: 280px; max-width: 360px;
}
.fn-picker-search {
  font-family: inherit; font-size: 0.75rem;
  padding: 5px 7px; border-radius: 3px;
  border: 1px solid var(--card-border);
  background: var(--bg); color: var(--fg);
  outline: none; box-sizing: border-box; width: 100%;
}
.fn-picker-expected {
  font-size: 0.625rem; padding: 2px 4px;
  opacity: 0.75; line-height: 1.3;
  color: var(--muted-fg);
  border-bottom: 1px dashed var(--card-border);
}
.fn-picker-search:focus { box-shadow: 0 0 0 1px var(--fg) inset; }
.fn-picker-list {
  max-height: 200px; overflow-y: auto;
  background: var(--bg);
}
.fn-picker-list-other {
  /* slightly dimmer background so the disclosed incompat section
     reads as "secondary" without relying on row-level opacity */
  background: color-mix(in srgb, var(--bg) 96%, var(--fg) 4%);
}
.fn-picker-row {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 8px; cursor: pointer;
  font-size: 0.6875rem;
  min-height: 28px;
}
.fn-picker-row-active {
  background: color-mix(in srgb, var(--fg) 12%, transparent);
}
.fn-picker-row-main { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.fn-picker-row-rt {
  font-style: italic; opacity: 0.7;
  flex-shrink: 0;
  font-size: 0.625rem;
  color: var(--muted-fg);
}
/* Kind annotation — small tag pill on the fn-picker row for type-row
   fns (refinement / record / union / variant / list / fn-type /
   base-fn / primitive). Composed fns get nothing — the return-type
   chip already carries the "what does this fn return" signal. */
.fn-picker-row-kind {
  display: inline-block;
  flex-shrink: 0;
  padding: 0 4px;
  font-size: 0.5625rem;
  line-height: 0.875rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border: 1px solid var(--input-border);
  border-radius: 3px;
  color: var(--muted-fg);
  background: var(--card-bg);
}
.fn-picker-row-kind-primitive {
  border-style: dashed;
}

/* Effect badges — same `.effects-chip-*` palette used on fn-overlays
   so users see the same colours throughout. Stacked horizontally
   inside a row; pre-existing chip styling handles padding/font. */
.fn-picker-row-effects {
  display: inline-flex; gap: 2px; flex-shrink: 0;
}
/* Type-compat sections (only when `expectedType` is in play). The ✓
   glyph keeps the row easy to scan; the section header makes the
   compat/incompat boundary explicit instead of having the user infer
   it from a dimmed row. */
.fn-picker-row-ok {
  color: var(--success-fg); font-weight: 600;
  flex-shrink: 0;
}
.fn-picker-row-compat {
  background: color-mix(in srgb, var(--success-fg) 6%, transparent);
}
.fn-picker-row-incompat {
  opacity: 0.7;
}

/* Section grouping — sticky-ish headers above each list, plus a
   disclosure for the "Other" (incompatible) section. */
.fn-picker-sections {
  display: flex; flex-direction: column;
  border: 1px solid var(--card-border); border-radius: 3px;
  overflow: hidden;
  background: var(--bg);
}
.fn-picker-section-header {
  font-size: 0.625rem; font-weight: 600;
  letter-spacing: 0.5px; text-transform: uppercase;
  padding: 4px 8px;
  color: var(--muted-fg);
  background: var(--sidebar-bg);
  border-bottom: 1px solid var(--card-border);
  display: flex; align-items: center; gap: 4px;
}
.fn-picker-section-disclosure {
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  border: 0; border-top: 1px solid var(--card-border);
  width: 100%;
}
.fn-picker-section-disclosure:hover {
  background: var(--hover-bg);
}
.fn-picker-disclosure-arrow {
  font-size: 0.5rem; opacity: 0.6;
  display: inline-block; min-width: 8px;
}
.fn-picker-empty {
  padding: 8px; font-size: 0.6875rem; opacity: 0.6; text-align: center;
}
.fn-picker-cancel-row {
  display: flex; justify-content: flex-end;
}

/* Phase 4 — Action toolbar pinned to the bottom of the selected
   (nav-root) fn's overlay. Surfaces every mutating action in one
   place so the user doesn't have to discover hover-revealed icons
   scattered across the card. Always visible (no hover gate),
   ≥44px touch targets on pointer-coarse devices. */
.fn-action-toolbar {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 4px;
  padding: 6px 8px;
  border-top: 1px solid var(--card-border);
  background: var(--card-bg);
  /* Slight inset so the toolbar reads as a discrete stripe and
     not a row of arbitrary buttons floating against the card. */
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--card-border) 40%, transparent);
}
.fn-action-toolbar-hint {
  flex: 1; padding: 0 4px;
  font-size: 0.6875rem; font-style: italic; color: var(--muted-fg);
}
.fn-action-btn {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 3px 6px;
  flex: 1 1 auto;
  justify-content: center;
  font-family: inherit; font-size: 0.6875rem;
  background: transparent; color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-radius: 3px;
  cursor: pointer;
  white-space: nowrap;
}
.fn-action-btn:hover {
  background: var(--hover-bg);
}
.fn-action-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.fn-action-btn-glyph {
  font-size: 0.8125rem; line-height: 1;
}
.fn-action-btn-label {
  /* Label is informational on desktop; on very narrow cards / phone
     viewports we may want icon-only. Keep it visible by default and
     hide it via the touch-narrow override below if needed. */
}
/* Edit-parents strip — same layout as return-type strip but distinct
   text colour so the two affordances on the root card don't blur. */
.reparent-strip {
  padding: 2px 8px;
  font-size: 0.625rem;
  color: var(--muted-fg);
  border-top: 1px solid var(--card-border);
  cursor: pointer;
  background: var(--card-bg);
}
.reparent-strip:hover {
  background: color-mix(in srgb, var(--fg) 6%, var(--card-bg));
}

/* Fn-card ancestor rows — statics for the overlay renderers
   (editor-overlay-fn.js / editor-overlay-fn-rows.js). JS keeps only
   the dynamic bits: border-bottom variant, cursor, paint-machine
   colours. */
.fn-ancestor-line {
  touch-action: none;
  user-select: none;
}
.fn-ancestor-more {
  padding: 2px 8px;
  color: var(--light-fg);
  font-size: 0.625rem;
}

/* Use-site header row of a named non-root card — an empty
   root-coloured marker row; bg comes from the paint machine. */
.fn-use-site-header {
  position: relative;
  color: var(--card-header-fg);
  border-bottom: none;
  touch-action: none;
  user-select: none;
}

/* Column-below-MI text overlay — floats over the per-parent column
   divs; the 8px/24px insets match the `colLeftInset`/`colRightInset`
   constants in editor-overlay-fn-rows.js (right inset clears the
   pinned ⋯ trigger slot). */
.mi-col-text {
  position: absolute;
  left: 8px;
  right: 24px;
  top: 4px;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
  z-index: 1;
}

/* Branch-local strip — informational marker that this fn (or an
   ancestor) is sticky-local: version rows do not propagate across
   branches on merge. No edit affordance — once an ancestor sets the
   flag, descendants inherit it monotonically and cannot widen back. */
.branch-local-strip {
  padding: 2px 8px;
  font-size: 0.625rem;
  color: var(--muted-fg);
  border-top: 1px solid var(--card-border);
  background: var(--card-bg);
  display: flex;
  align-items: center;
  gap: 4px;
  cursor: help;
}
.branch-local-strip-glyph {
  font-size: 0.6875rem;
  line-height: 1;
}
.branch-local-strip-label {
  font-style: italic;
  letter-spacing: 0.02em;
}

/* Optional-args strip — `?name` list of optional-but-unbound args
   (defaults in effect). Rendered by appendOptionalArgsStrip. */
.optional-args-strip {
  padding: 2px 8px;
  /* FREE args share one language everywhere: amber + dashed (the
     inspector's `free` chip, the ＋ binder circle, and this ?-row). */
  color: var(--gd-free);
  font-size: 0.625rem;
  font-style: italic;
  border-top: 1px dashed var(--gd-free);
  background: var(--sidebar-bg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* On an editable card each `?name` is a binder button. It has to read as
   part of the strip's prose rather than as a control tray, so it inherits
   the strip's type and only lifts on hover/focus. */
.optional-arg-binder {
  padding: 0 2px;
  border: 0;
  border-radius: 3px;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.optional-arg-binder:hover,
.optional-arg-binder:focus-visible {
  background: var(--hover-bg);
  text-decoration: underline;
}

/* HOF-context strips — `λname` (args the enclosing HOF supplies at
   runtime) and `⇣name` (free args threaded into the sub-tree). Same
   look for both; rendered by appendHofCapturedArgsStrip /
   appendDeepFreeArgsStrip. */
.hof-args-strip {
  padding: 2px 8px;
  color: var(--hof-fg);
  font-size: 0.625rem;
  font-style: italic;
  border-top: 1px dashed var(--hof-border);
  background: var(--hof-bg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Description tooltip — singleton bubble managed by
   editor-tooltips.js. JS owns only the dynamic bits (display,
   left/top placement); everything static lives here. `pointer-events`
   is toggled to `auto` by JS in sticky/edit mode. */
.description-tooltip {
  position: fixed;
  z-index: 10000;
  background: var(--tooltip-bg);
  color: var(--tooltip-fg);
  font-family: system-ui, sans-serif;
  font-size: 0.75rem;
  line-height: 1.4;
  padding: 6px 10px;
  border-radius: 4px;
  max-width: 360px;
  pointer-events: none;
  box-shadow: var(--shadow-md);
  display: none;
  white-space: pre-wrap;
}

/* In-tooltip dismiss — the × button is the obvious affordance for
   touch users; the document-level pointerdown handler is a fallback
   for desktop "tap-outside". Sized 20×20 to be a comfortable touch
   target without dominating the tooltip header. */
.description-tooltip-close {
  position: absolute; top: 4px; right: 4px;
  width: 20px; height: 20px; padding: 0;
  background: transparent; color: var(--tooltip-fg);
  border: 0; border-radius: 3px;
  font-size: 1.125rem; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0.65;
}
.description-tooltip-close:hover { opacity: 1; background: var(--overlay-pressed); }

/* In-tooltip edit affordances — Edit pencil (read mode) and the
   Save/Cancel + textarea triplet (edit mode). The tooltip itself
   inherits `var(--tooltip-bg/--tooltip-fg)` so the buttons float on
   the same dark/light bubble that already adapts to theme. */
.description-tooltip-btn {
  font-family: inherit; font-size: 0.6875rem;
  padding: 3px 10px; border-radius: 3px; cursor: pointer;
  background: var(--tooltip-fg); color: var(--tooltip-bg);
  border: 1px solid var(--tooltip-fg);
}
.description-tooltip-btn:hover { opacity: 0.85; }
.description-tooltip-btn:disabled { opacity: 0.5; cursor: default; }
.description-tooltip-btn-secondary {
  background: transparent; color: var(--tooltip-fg);
}
.description-tooltip-textarea {
  width: 320px; max-width: 100%; min-height: 60px;
  font-family: inherit; font-size: 0.75rem; line-height: 1.4;
  padding: 4px 6px; border-radius: 3px;
  border: 1px solid var(--tooltip-fg);
  background: var(--tooltip-bg); color: var(--tooltip-fg);
  resize: vertical; box-sizing: border-box; outline: none;
}
.description-tooltip-textarea:focus {
  box-shadow: 0 0 0 1px var(--tooltip-fg) inset;
}
.description-tooltip-error {
  margin-top: 4px; font-size: 0.6875rem;
  color: var(--error-fg);
}

/* Full-name popover — bubble shown over a truncated ancestor-row name.
   Singleton managed by editor-tooltips.js; JS toggles display,
   opacity + translateY for the fade, and left/top placement. */
.full-name-tooltip {
  position: fixed;
  z-index: 9999;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--input-border);
  box-shadow: var(--shadow-sm);
  padding: 4px 10px;
  border-radius: 4px;
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 110ms ease-out, transform 110ms ease-out;
  display: none;
}

/* Disabled-icon reason popover — click-to-reveal "why is this action
   disabled" bubble. Same singleton lifecycle as .full-name-tooltip
   but multi-line and interactive (outside-click dismissed). */
.icon-reason-popover {
  position: fixed;
  z-index: 9999;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--input-border);
  box-shadow: var(--shadow-md);
  padding: 6px 10px;
  border-radius: 4px;
  font-family: inherit;
  font-size: 0.75rem;
  line-height: 1.35;
  max-width: 260px;
  pointer-events: auto;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 110ms ease-out, transform 110ms ease-out;
  display: none;
}
/* Menu variant (the ns badge popover): namespace path + Reveal / Move actions. */
.icon-reason-popover.icon-reason-popover-menu { padding: 8px; max-width: 280px; }
.ns-menu { display: flex; flex-direction: column; gap: 3px; min-width: 190px; }
.ns-menu-label {
  font-size: 0.625rem; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--gd-ink-3);
}
.ns-menu-path {
  font-family: var(--mono); font-size: 0.75rem; color: var(--fg);
  margin-bottom: 6px; word-break: break-all;
}
.ns-menu-btn {
  appearance: none; text-align: left; font: inherit; font-size: 0.75rem;
  color: var(--gd-flow); background: transparent; border: 0; border-radius: 6px;
  padding: 6px 8px; cursor: pointer;
}
.ns-menu-btn:hover { background: var(--gd-flow-wash); }
/* Flash the tree row that a "Reveal in Explorer" landed on. */
.entity-item.gd-tree-flash { animation: gd-tree-flash 1.3s ease; }

@keyframes gd-tree-flash {
  0%, 100% { background-color: transparent; }
  20% { background-color: var(--gd-flow-wash); }
}

/* Right-edge action icons — `i` (description) and `↗` (open in new tab).
   Highlight on hover so the click-target is obvious; the tint uses
   `currentColor` so it picks up whichever fg the row has (white on a
   header row, dark on a body row) without needing to know the theme. */
.description-badge, .open-in-new-tab {
  opacity: 0.85;
  transition: opacity 0.1s, background-color 0.1s;
}
.description-badge:hover, .open-in-new-tab:hover {
  opacity: 1;
  background-color: color-mix(in srgb, currentcolor 28%, transparent);
}

/* Navigation controls — single-row pill at the bottom-right:
   `[+] [−] [⌂] [⟲] [⊙]`. The vertical slider was here historically
   (alongside +/−); it duplicated keyboard +/− and scroll-zoom while
   eating 120px of vertical real estate, so it now hides on default
   layouts and only re-surfaces in narrow-touch contexts (no
   scroll-wheel) — see the media-query block at the file's bottom. */
#nav-controls {
  position: absolute; bottom: 16px; right: 12px; z-index: 100;
  display: flex; flex-direction: row-reverse; align-items: center; gap: 4px;
  pointer-events: auto;
}
.nav-btn {
  width: 1.75rem; height: 1.75rem;
  border: 1px solid var(--input-border); border-radius: 4px;
  background: var(--bg); color: var(--fg);
  font-size: 1.5rem; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  padding: 0; user-select: none; flex-shrink: 0;
}
.nav-btn:hover { background: var(--hover-bg); }
.nav-btn:active { background: var(--selected-bg); }

/* Zoom column flattens into the parent row; +/− buttons sit beside
   the bottom-row buttons. Slider hidden by default — see media
   override below for the cases it survives. */
.nav-zoom-col {
  display: contents;
}
.nav-zoom-col input[type="range"] {
  display: none;
  writing-mode: vertical-lr;
  direction: rtl;
  height: 120px; width: 20px;
  cursor: pointer; accent-color: var(--fg);
  margin: 0;
}

/* Bottom row is now a sibling segment of the same flex row. */
.nav-bottom-row {
  display: flex; gap: 4px; align-items: center;
}

/* Re-enable the slider in coarse-pointer / no-hover contexts (touch
   devices, where scroll-wheel zoom isn't available). Switches the
   container back to a vertical stack so the slider's 120px height
   fits. */
@media (pointer: coarse) and (hover: none) {
  #nav-controls { flex-direction: column; align-items: flex-end; }
  .nav-zoom-col { display: flex; flex-direction: column; align-items: center; gap: 4px; }
  .nav-zoom-col input[type="range"] { display: block; }
}

/* =========================================================================
 * EDITOR BUSY — bottom-centre banner shown while a multi-step user
 * action (reparent cascade, extend-fn, delete-fn) is in flight.
 * Single-PUT inline edits don't get a banner; the Save button's own
 * disabled state covers them. Spinner is a CSS ::before so the
 * banner's textContent stays usable for screen readers.
 * ========================================================================= */
.editor-busy-banner {
  position: fixed;
  bottom: 16px; left: 50%;
  transform: translateX(-50%);
  z-index: 10003;
  background: var(--card-bg); color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-radius: 4px;
  padding: 8px 14px;
  font-family: inherit; font-size: 0.75rem;
  box-shadow: var(--shadow-lg);
  display: none;
  align-items: center;
  gap: 8px;
  pointer-events: none;
}
.editor-busy-banner.visible { display: inline-flex; }

/* One-shot warning variant (showTransientWarning) — same banner
   element, amber border + text, no spinner. */
.editor-busy-banner.warning {
  border-color: var(--warning-fg);
  color: var(--warning-fg);
}

.editor-busy-banner.warning::before { display: none; }
.editor-busy-banner::before {
  content: '';
  width: 12px; height: 12px;
  border: 2px solid var(--accent);
  border-right-color: transparent;
  border-radius: 50%;
  animation: editor-busy-spin 0.8s linear infinite;
  flex-shrink: 0;
}

@keyframes editor-busy-spin {
  to { transform: rotate(360deg); }
}
/* `cursor: progress` on the whole page reads better than `wait`
   here — it keeps interactive affordances feeling allowed even
   though they're temporarily disabled by the buttons themselves. */
body.editor-busy { cursor: progress; }
body.editor-busy .arg-value-edit-popover,
body.editor-busy .fn-picker-popover,
body.editor-busy .fn-action-toolbar { cursor: progress; }

/* =========================================================================
 * TYPE EXPLAINER — popover that opens on click of any type chip
 * (arg-overlay's `[int]` chip, edge-label's chip). Surfaces the
 * human-readable description plus the structural form below for
 * users who already know the DSL. Editable chips additionally
 * get a "Change type" button — clicking the chip is now a strict
 * superset of the legacy "click to edit" behaviour.
 * ========================================================================= */
.type-explainer {
  position: fixed; z-index: 10001;
  display: none;
  background: var(--card-bg); color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-left: 3px solid var(--accent);
  border-radius: 4px;
  box-shadow: var(--shadow-lg);
  padding: 8px 10px;
  min-width: 220px; max-width: 360px;
  font-family: inherit; font-size: 0.75rem; line-height: 1.4;
}
.type-explainer.visible { display: block; }

.type-explainer-header {
  display: flex; align-items: center; justify-content: space-between;
  border-bottom: 1px solid var(--light-border);
  margin-bottom: 6px; padding-bottom: 4px;
}
.type-explainer-title {
  font-weight: 600; font-size: 0.75rem;
  color: var(--accent);
  letter-spacing: 0.4px; text-transform: uppercase;
}
.type-explainer-close {
  width: 20px; height: 20px; padding: 0;
  background: transparent; color: var(--card-fg);
  border: 0; border-radius: 3px;
  font-size: 1.125rem; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0.65;
}
.type-explainer-close:hover { opacity: 1; background: var(--overlay-pressed); }

.type-explainer-human {
  font-size: 0.8125rem;
  margin: 4px 0;
  color: var(--card-fg);
  line-height: 1.5;
}
.type-explainer-structural {
  font-family: ui-monospace, monospace;
  font-size: 0.6875rem;
  margin-top: 4px;
  padding: 4px 6px;
  background: color-mix(in srgb, var(--card-fg) 5%, transparent);
  border-radius: 3px;
  color: var(--muted-fg);
  overflow-wrap: anywhere;
}

.type-explainer-btn {
  font-family: inherit; font-size: 0.6875rem;
  padding: 4px 10px; border-radius: 3px; cursor: pointer;
  background: var(--card-fg); color: var(--card-bg);
  border: 1px solid var(--card-fg);
}
.type-explainer-btn:hover { opacity: 0.85; }

/* Tighten-effects controls — used by the fn-type inline expansion
   panel (editor-overlay-type-expand.js) to narrow [:fn args ret
   #{eff-set}]. Each row contains a `<select>` for narrower types;
   the effect set sits in a row of inline checkboxes; the Tighten
   button submits the delta. */
.type-explainer-tighten {
  margin-top: 8px;
  padding-top: 6px;
  border-top: 1px solid var(--light-border);
}
.type-explainer-tighten-row {
  display: flex; flex-wrap: wrap; gap: 4px 10px;
  margin-bottom: 6px;
}
.type-explainer-tighten-select {
  font-family: inherit; font-size: 0.6875rem;
  padding: 2px 4px; border-radius: 3px;
  border: 1px solid var(--light-border);
  background: var(--card-bg); color: var(--card-fg);
  flex: 1;
  min-width: 0;
}
.type-explainer-tighten-chk {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 0.6875rem; cursor: pointer;
}
.type-explainer-tighten-chk input { margin: 0; }
.type-explainer-tighten-err {
  font-size: 0.625rem;
  color: var(--error-fg);
  margin: 4px 0;
  white-space: pre-wrap;
}

/* =========================================================================
 * MISMATCH EXPLAINER — popover that opens on click of an
 * `arg-overlay-mismatch` indicator. Distinct from the description
 * tooltip: error-themed left accent stripe, structured rows, always
 * persistent (clicked open, closed via × / outside-click / Esc).
 * Built fresh per arg in editor-mismatch-explainer.js.
 * ========================================================================= */
.mismatch-explainer {
  /* Above the fn-picker (10002): the "Pick anyway" explainer opens ON
     TOP of the picker (incompat-row click) and must stay clickable —
     at the shared 10001 the picker swallowed its pointer events. */
  position: fixed; z-index: 10003;
  display: none;
  background: var(--card-bg); color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-left: 3px solid var(--error-fg);
  border-radius: 4px;
  box-shadow: var(--shadow-lg);
  padding: 8px 10px;
  min-width: 240px; max-width: 360px;
  font-family: inherit; font-size: 0.75rem; line-height: 1.4;
}
.mismatch-explainer.visible { display: block; }

.mismatch-explainer-header {
  display: flex; align-items: center; justify-content: space-between;
  border-bottom: 1px solid var(--light-border);
  margin-bottom: 6px; padding-bottom: 4px;
}
.mismatch-explainer-title {
  font-weight: 600; font-size: 0.75rem;
  color: var(--error-fg);
}
.mismatch-explainer-close {
  width: 20px; height: 20px; padding: 0;
  background: transparent; color: var(--card-fg);
  border: 0; border-radius: 3px;
  font-size: 1.125rem; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0.65;
}
.mismatch-explainer-close:hover {
  opacity: 1; background: var(--overlay-pressed);
}

.mismatch-explainer-row {
  display: flex; gap: 6px;
  margin: 3px 0;
}
.mismatch-explainer-label {
  flex: 0 0 64px;
  color: var(--muted-fg);
  font-size: 0.6875rem;
}
.mismatch-explainer-value {
  flex: 1;
  font-family: ui-monospace, monospace;
  font-size: 0.7188rem;
  overflow-wrap: anywhere;
}
/* Reason text wraps onto multiple lines and reads as prose, not code,
   so flip to system font + slightly looser line-height. */
.mismatch-row-reason .mismatch-explainer-value {
  font-family: inherit;
  line-height: 1.5;
  color: var(--card-fg);
}

/* Leaf-level disagreement section — points at the EXACT failing
   field / element when expected and actual are structurally
   compatible at the top level but diverge inside. */
.mismatch-explainer-leaves {
  margin: 6px 0 4px;
  padding: 6px 0;
  border-top: 1px dashed var(--light-border);
}
.mismatch-explainer-leaves-head {
  font-size: 0.6875rem;
  color: var(--muted-fg);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 4px;
}
.mismatch-explainer-leaf {
  font-family: ui-monospace, monospace;
  font-size: 0.6875rem;
  line-height: 1.6;
  overflow-wrap: anywhere;
}
.mismatch-explainer-leaf-path {
  color: var(--card-fg);
  font-weight: 600;
}
.mismatch-explainer-leaf-sep {
  color: var(--muted-fg);
}
.mismatch-explainer-leaf-expected {
  color: var(--success-fg, currentcolor);
}
.mismatch-explainer-leaf-actual {
  color: var(--error-fg);
}
.mismatch-explainer-leaf-more {
  font-size: 0.6875rem;
  color: var(--muted-fg);
  font-style: italic;
  margin-top: 3px;
}

.mismatch-explainer-actions {
  display: flex; gap: 8px; align-items: center;
  margin-top: 8px; padding-top: 6px;
  border-top: 1px solid var(--light-border);
}
.mismatch-explainer-btn {
  font-family: inherit; font-size: 0.6875rem;
  padding: 4px 10px; border-radius: 3px; cursor: pointer;
  background: var(--card-fg); color: var(--card-bg);
  border: 1px solid var(--card-fg);
}
.mismatch-explainer-btn:hover { opacity: 0.85; }


/* Right-edge warning chip on a mismatched arg-overlay. Sits to the
   right of the type-chip when present; otherwise pinned alone. The
   visible glyph is a single ⚠-style "!" but lives inside a 1px ring
   to match `applyActionIconBox` siblings, so the whole row reads as
   "i ↗ ! type-chip" not "i ↗ × type-chip". */
.arg-mismatch-badge {
  display: inline-flex; align-items: center; justify-content: center;
  margin-left: 4px;
  width: var(--icon-size); height: var(--icon-size);
  border: 1px solid var(--error-fg);
  border-radius: 3px;
  background: transparent; color: var(--error-fg);
  font-family: inherit; font-size: var(--icon-font-size);
  font-weight: 700;
  line-height: 1; cursor: help;
  padding: 0;
}
.arg-mismatch-badge:hover {
  background: color-mix(in srgb, var(--error-fg) 18%, transparent);
}

/* Inheritance-provenance affordance — a compact ↖ on an arg row whose
 * slot was inherited from a parent fn. Hover reveals the ancestor name
 * (native title); the link opens that fn in a new tab. */
.arg-source-link {
  display: inline-flex; align-items: center; justify-content: center;
  margin-left: 4px;
  width: var(--icon-size); height: var(--icon-size);
  color: var(--muted-fg);
  font-size: var(--icon-font-size);
  line-height: 1; cursor: pointer;
  text-decoration: none;
  flex: 0 0 auto;
}
.arg-source-link:hover { color: var(--accent); }

/* Type-narrowing provenance — a ↳ pinned right after a type-chip when
 * the binding carries a :type-override-fn-id that's tighter than the
 * inherited slot type. Read-only informational badge; tooltip names the
 * fn that introduced the narrowing. Kept subdued so the chip itself
 * stays the primary focus. */
.arg-type-provenance {
  display: inline-flex; align-items: center; justify-content: center;
  margin-left: 2px;
  padding: 0 2px;
  background: transparent;
  border: 0;
  font: inherit;
  font-size: 0.5625rem; line-height: 0.875rem;
  color: var(--muted-fg);
  cursor: pointer;
  user-select: none;
}
.arg-type-provenance:hover { color: var(--accent); }
.arg-type-provenance:focus-visible {
  outline: 1px solid var(--accent);
  border-radius: 2px;
}

/* =========================================================================
 * PROVENANCE POPOVER — anchored to the `↳` badge on a type-chip. Shows
 * the FULL narrowing chain (4-tier resolution + inheritance steps) with
 * clickable source-fn names. Built fresh per arg in
 * editor-provenance-popover.js.
 * ========================================================================= */
.provenance-popover {
  position: fixed; z-index: 10001;
  display: none;
  background: var(--card-bg); color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-left: 3px solid var(--accent);
  border-radius: 4px;
  box-shadow: var(--shadow-lg);
  padding: 8px 10px;
  min-width: 240px; max-width: 380px;
  font-family: inherit; font-size: 0.75rem; line-height: 1.4;
}
.provenance-popover.visible { display: block; }

.provenance-popover-header {
  display: flex; align-items: center; justify-content: space-between;
  border-bottom: 1px solid var(--light-border);
  margin-bottom: 6px; padding-bottom: 4px;
}
.provenance-popover-title {
  font-weight: 600; font-size: 0.75rem;
  color: var(--accent);
}
.provenance-popover-close {
  width: 20px; height: 20px; padding: 0;
  background: transparent; color: var(--card-fg);
  border: 0; border-radius: 3px;
  font-size: 1.125rem; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0.65;
}
.provenance-popover-close:hover {
  opacity: 1; background: var(--overlay-pressed);
}
/* Inside the popover the resolution rows are the primary content (not
   a secondary footer), so drop the top border + spacer that the inline
   panel adds for visual separation from the type structure above. */
.provenance-popover .type-inline-resolution {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* Prose intro line in the type-rule variant of the popover —
   "Return type computed by :<parent>'s :return-type-rule" — reads as
   a sentence, not a key-value row. */
.provenance-popover-intro {
  font-size: 0.7188rem;
  line-height: 1.5;
  color: var(--card-fg);
  margin-bottom: 6px;
  overflow-wrap: anywhere;
}

/* Per-rule one-line interpretation, e.g. for :assoc — "Literal key
   \"name\", value typed :int — added field \"name\": :int to map's
   record shape". Distinguishes the INTERPRETATION (what the rule
   did) from the raw inputs table below. */
.provenance-popover-narrative {
  font-size: 0.7188rem;
  line-height: 1.5;
  color: var(--muted-fg);
  margin-bottom: 8px;
  padding: 4px 6px;
  background: var(--card-row-highlight);
  border-left: 2px solid var(--accent);
  border-radius: 2px;
  overflow-wrap: anywhere;
}

/* Closed-enum reveal — when the resolved slot type is a refinement
   pinning the value to a finite literal set, list every member as a
   read-only chip in the provenance popover. The value-edit popover
   is where the user PICKS one; here we just surface the choices. */
.provenance-popover-enum {
  margin-top: 6px;
  padding-top: 4px;
  border-top: 1px solid var(--light-border);
}

/* Slot-effect-bound reveal — when the resolved fn-typed slot has a
   concrete effect constraint, surface it in the provenance popover
   so the reader sees the bound directly (without opening `▸/▾`). */
.provenance-popover-effects {
  margin-top: 6px;
  padding-top: 4px;
  border-top: 1px solid var(--light-border);
}
.provenance-popover-enum-list {
  display: flex; flex-wrap: wrap; gap: 4px;
  margin-top: 2px;
}
.provenance-popover-enum-chip {
  display: inline-block;
  padding: 1px 5px;
  font-size: 0.625rem;
  font-family: monospace;
  background: var(--card-row-highlight);
  border: 1px solid var(--light-border);
  border-radius: 2px;
  color: var(--card-fg);
}

/* Type-rule provenance trigger on a fn-card's return-type strip.
   Mirrors the arg-overlay's `.arg-type-provenance` glyph so the two
   surfaces look like siblings — same `↳` character, same hit area. */
.return-type-strip-provenance {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  margin-left: 4px;
  padding: 0 2px;
  background: transparent;
  border: 0;
  font: inherit;
  font-size: 0.5625rem; line-height: 0.875rem;
  color: var(--muted-fg);
  cursor: pointer;
  user-select: none;
}
.return-type-strip-provenance:hover { color: var(--accent); }
.return-type-strip-provenance:focus-visible {
  outline: 1px solid var(--accent);
  border-radius: 2px;
}

/* =========================================================================
 * TOUCH-DEVICE OVERRIDES — fires on iPad / phones / any pointer-coarse
 * device. No-hover devices are exposed to the same hover-only affordances
 * that desktop users discover by mousing over them; here we bring those
 * up unconditionally and expand the hit area to ≥28px (WCAG 2.5.5
 * minimum) so taps don't miss.
 *
 * Strategy: bump the existing CSS vars instead of adding a parallel
 * sizing path. Component CSS that reads var(--icon-size) etc. picks up
 * the touch values automatically. Hover-gated reveal rules below
 * unconditionally surface the icons regardless of :hover state.
 *
 * Keep this block at the END of the file — last rule wins where
 * specificity ties.
 * ========================================================================= */
@media (hover: none) and (pointer: coarse) {
  :root {
    /* Action-icon visual + pinning vars: bigger glyph, more breathing room
       between stacked icons. Pin offsets recomputed so a 3-icon stack
       fits at the right edge without overlap (28 + 4 gap × 2 = 92, last
       slot = right:6+28+4+28+4 = 70 ≈ --icon-pin-r-3 below).
       `max(px, rem)` rather than plain rem: the px arm is the WCAG 2.5.5
       hit-area floor, which must hold even if the user SHRINKS their
       browser font-size; the rem arm lets the target grow with it. */
    --icon-size: max(28px, 1.75rem);
    --icon-font-size: max(14px, 0.875rem);
    --icon-pin-r-1: max(4px, 0.25rem);
    --icon-pin-r-2: max(38px, 2.375rem);
    --icon-pin-r-3: max(72px, 4.5rem);
  }

  /* Reveal hover-gated sidebar affordances unconditionally on touch.
     Desktop's `opacity:0 → :hover .85` pattern is invisible on a
     no-hover device — the user would never know these icons exist. */
  .ns-header .description-badge,
  .entity-item .description-badge,
  .entity-item .open-in-new-tab.sidebar-action,
  .ns-header:not(:hover) .ns-row-actions .create-btn-inline,
  .entity-item:not(:hover) .ns-row-actions .create-btn-inline {
    opacity: 0.85;
  }

  /* Sidebar rows: minimum tappable height. Without this a row is
     ~21px tall — finger taps overshoot to neighbours. */
  .ns-header,
  .entity-item {
    min-height: var(--touch-target);
    padding-top: 4px;
    padding-bottom: 4px;
  }

  /* ns-arrow doubles as the expand-collapse target. 16px is too small
     for a finger; bump to ≥ 28px and re-centre the glyph. */
  .ns-arrow {
    min-width: var(--touch-target-min);
    min-height: var(--touch-target-min);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.8125rem;
  }

  /* Floating + sidebar-header buttons should hit Apple's 44pt baseline
     since they're top-level navigation, not row-level glyphs. */
  .nav-btn,
  .prefs-btn,
  .auth-lock-btn,
  .sidebar-expand-floating {
    min-width: var(--touch-target);
    min-height: var(--touch-target);
  }
  .nav-btn { font-size: 1.75rem; }

  /* Sequence ±, type chip, popover close — small glyphs but real
     actions. Bring them up to --touch-target-min. */
  .arg-seq-btn,
  .arg-type-chip,
  .arg-type-chip-readonly,
  .description-tooltip-close,
  .arg-value-edit-btn,
  .description-tooltip-btn,
  .placeholder-binder {
    min-width: var(--touch-target-min);
    min-height: var(--touch-target-min);
  }
  /* Buttons that hold visible text (Save / Cancel) — don't constrain
     min-width, just min-height so click area is tappable without
     making a "Save" button absurdly wide. */
  .arg-value-edit-btn,
  .description-tooltip-btn {
    min-width: 0;
    min-height: var(--touch-target);
    padding: 6px 14px;
    font-size: 0.8125rem;
  }

  /* Sidebar overlays the graph on narrow viewports rather than
     pushing content. Easier than reflowing cy on every toggle. The
     graph keeps its pan/zoom; the sidebar floats on top. */
  body:not(.sidebar-collapsed) #side-menu {
    box-shadow: var(--shadow-lg);
  }

  /* SVG glyphs inside chrome buttons — every SVG in this editor was
     authored with hardcoded width="11"/height="13". On touch the
     parent button grows to ≥28px but the SVG stays small,
     producing a tiny dot inside a giant target. CSS wins over the
     inline width/height attributes for sizing — bump SVGs to 18px. */
  .create-btn-inline svg,
  .prefs-btn svg,
  .auth-lock-btn svg,
  .sidebar-expand-floating svg,
  .nav-btn svg {
    width: 18px;
    height: 18px;
  }

  /* Fn-picker rows — taps need ≥44px on touch (Apple HIG). The 11px
     row text gets a hair bigger too so the iPad keyboard's
     auto-zoom-on-focus doesn't kick in (Safari triggers when input
     font-size < 16px on focus, but the surrounding rows stay legible
     either way). */
  .fn-picker-row { min-height: var(--touch-target); padding: 8px 10px; }
  .fn-picker-search { font-size: 1rem; padding: 8px 10px; }
  .fn-picker-section-header { padding: 6px 10px; }

  /* Phase 4 fn-action toolbar — buttons grow to Apple-HIG min-target
     so the user can hit Rename / Describe / Re-parent / Delete with
     a finger. Glyph reads larger; label still inline. */
  .fn-action-btn {
    min-height: var(--touch-target);
    padding: 6px 12px;
    font-size: 0.75rem;
  }
  .fn-action-btn-glyph { font-size: 1rem; }

  /* Polish round — every text/select input across the editor's
     popovers needs to reach 16px so iPad Safari doesn't auto-zoom
     on focus (which immediately clips the popover off-screen and
     leaves the user staring at the keyboard). */
  .arg-value-edit-input,
  .description-tooltip-textarea {
    font-size: 1rem;
    min-height: var(--touch-target);
    padding: 8px 10px;
  }
  /* Save / Cancel buttons in popovers need the Apple-HIG target
     too — applies to every button created by openInlineEditPopover
     plus the description tooltip's Save/Cancel pair. */
}

/* =========================================================================
 * RESPONSIVE — narrow viewports (phones, iPad portrait, iPad split view).
 * Independent of pointer kind: a narrow desktop window (rare) gets the
 * same layout treatment.
 * ========================================================================= */
@media (width <= 900px) {
  /* Sidebar overlays the graph instead of taking 30 %+ of the screen. */
  #side-menu {
    box-shadow: var(--shadow-lg);
    z-index: 50;
  }
  /* Cap sidebar width so it doesn't blanket the whole screen on a
     phone — leaves enough graph visible to remind the user where
     they came from. */
  :root {
    --sidebar-width: min(280px, 80vw);
  }
}


/* =========================================================================
 * EXECUTE POPOVER — anchored to the ▶ button on a fn-card root row. Hosts
 * the type-aware free-arg form (via /api/value-form) + effect-warning chips
 * + Run/Cancel buttons + result pane. Singleton, dismissed on outside-click
 * or Esc. See editor-execute.js.
 * ========================================================================= */
.execute-popover {
  position: fixed; z-index: 10002;
  display: none;
  background: var(--card-bg); color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-left: 3px solid var(--accent);
  border-radius: 4px;
  box-shadow: var(--shadow-lg);
  padding: 10px 12px;
  min-width: 360px; max-width: 520px;
  max-height: 80vh;
  overflow-y: auto;
  font-family: inherit; font-size: 0.75rem; line-height: 1.4;
}
.execute-popover.visible { display: block; }

/* In-panel mode — the run pane lives in the inspector's Runs tab
   (editor-execute.js gdMountRunPane), not floating over the canvas.
   Same inner styling family; only the floating geometry is undone. */
.execute-popover.gd-insp-run {
  position: static;
  z-index: auto;
  box-shadow: none;
  border: 0;
  border-left: 3px solid var(--accent);
  border-radius: 0;
  min-width: 0;
  max-width: none;
  max-height: none;
  overflow-y: visible;
  padding: 8px 0 8px 10px;
  margin-bottom: 10px;
}

/* Free-arg form widgets inside the Run popover: cap the auto-generated
   textareas (record fields like :headers / :body arrived as huge empty
   boxes that dwarfed the form) — still resizable when more room is
   really needed. */
.execute-popover .execute-arg-form textarea {
  height: 56px;
  min-height: 34px;
  resize: vertical;
}
/* Non-enterable widget (fn-typed slot) — the explainer line injected
   by mountArgFormHost instead of a silent empty bar. */
.execute-arg-form-note {
  font-size: 0.6875rem;
  font-style: italic;
  color: var(--muted-fg);
  padding: 3px 2px;
}

/* Transient toast (gdToast, editor-ui.js) — e.g. "Function not found"
   on a hash navigation that resolves to nothing. */
.gd-toast {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%) translateY(8px);
  z-index: 10010;
  padding: 8px 14px;
  border-radius: 8px;
  background: var(--gd-ink);
  color: var(--gd-paper);
  font-size: 0.7812rem;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s, transform 0.18s;
}
.gd-toast-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* A toast that names something which did NOT happen — the cleanup that was
   refused, the branch that is still there. Reads as a failure, not as a
   receipt, and `gdToast(msg, 'error')` also keeps it up longer. */
.gd-toast-error {
  background: var(--error-fg);
  color: var(--gd-paper);
  max-width: min(520px, 82vw);
  text-align: left;
  line-height: 1.45;
}

/* "internal N" — the per-namespace collapse for `_`-private and
   anonymous fns in the explorer tree (editor-sidebar.js). Styled as a
   quiet meta-row, not a fn row. */
.ns-internal-toggle {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 0;
  padding: 2px 8px 2px 18px;
  font: inherit;
  font-size: 0.6562rem;
  font-style: italic;
  color: var(--muted-fg);
  cursor: pointer;
}
.ns-internal-toggle:hover { color: var(--fg); }
.ns-internal-group .entity-item { opacity: 0.85; }

/* Value-node type chip: the SAME chip already sits on the arg's edge
   label 60px to the left — showing it twice per binding doubled the
   noise on every literal. Keep it as a hover / expanded affordance
   (it's still the inline type-expand anchor). */
.arg-overlay-row .arg-type-chip {
  opacity: 0;
  transition: opacity 0.12s;
}
.arg-overlay-row:hover .arg-type-chip,
.arg-overlay-row .arg-type-chip:focus-visible,
.arg-overlay-row .arg-type-chip[aria-expanded="true"] {
  opacity: 1;
}

/* Editable literal — reveal a ✎ on row hover so click-to-edit is
   discoverable, not a cursor-only secret. */
.arg-value-editable::after {
  content: " ✎";
  opacity: 0;
  font-size: 0.625rem;
  transition: opacity 0.12s;
}
.arg-overlay-row:hover .arg-value-editable::after { opacity: 0.6; }

/* Empty-lens hint in the explorer tree (applyLensVisibility). */
.lens-empty-hint {
  padding: 10px 12px;
  font-size: 0.6875rem;
  font-style: italic;
  color: var(--muted-fg);
}

.execute-popover-header {
  display: flex; align-items: center; justify-content: space-between;
  border-bottom: 1px solid var(--light-border);
  margin-bottom: 8px; padding-bottom: 4px;
}
.execute-popover-title {
  font-weight: 600; font-size: 0.8125rem;
  color: var(--accent);
}
/* Branch indicator — only shown when running off main; mirrors the
 * top-bar chip's inverted-pill style so the visual cue is the same
 * across the editor. */
.execute-popover-branch {
  font-size: 0.625rem; letter-spacing: 0.02em;
  background: var(--fg); color: var(--bg);
  padding: 2px 6px; border-radius: 2px;
  margin-left: 8px;
  white-space: nowrap;
}

/* Effect-warning banner — only rendered when the fn declares effects.
 * Reuses --error-fg as the warning color (red); a dedicated warning-
 * fg token doesn't exist yet in the editor theme, and red on the
 * "side effects" label is the right severity signal anyway. */
.execute-effects-warning {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  padding: 6px 8px;
  margin-bottom: 8px;
  background: var(--card-row-highlight);
  border-left: 2px solid var(--error-fg);
  border-radius: 2px;
  font-size: 0.6875rem;
}
.execute-effects-warning-label {
  font-weight: 600;
  color: var(--error-fg);
  margin-right: 2px;
}

/* Runtime-effects strip — shown below the result pane when the
 * fn-execution row carries an observed `:runtime-effects` set.
 * Subtler than the declared-effects banner (it's an audit/info
 * artifact, not a gate); muted background + chips. Drift chips
 * carry a red dashed outline so widening impls are visible. */
.execute-runtime-effects-strip {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  margin-top: 8px;
  padding: 4px 6px;
  background: var(--card-row-highlight);
  border-radius: 2px;
  font-size: 0.625rem;
  color: var(--muted-fg);
}
.execute-runtime-effects-label {
  font-weight: 600;
  margin-right: 2px;
}
.execute-effects-drift {
  outline: 1px dashed var(--error-fg);
  outline-offset: 1px;
}
/* "Declared but not observed at runtime" — fn-def promised this
 * effect but the actual run didn't fire it. Could be a conditional
 * branch that wasn't taken (legitimate) OR an over-declaration in
 * the rich-type (cleanup signal). Greyed-out + dashed outline
 * distinguishes from the red-outline runtime-drift case above. */
.execute-effects-unobserved {
  outline: 1px dashed var(--muted-fg);
  outline-offset: 1px;
  opacity: 0.45;
}

/* Service settings popover — anchored from the ⚙ button in the
 * row-actions popover. Compact form for managing a :service row
 * (declare / edit / delete / reconcile). Visual language matches
 * the execute popover so the two read as siblings. */
.service-popover {
  position: fixed; z-index: 10002;
  display: none;
  background: var(--card-bg); color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-left: 3px solid var(--accent);
  border-radius: 4px;
  box-shadow: var(--shadow-lg);
  padding: 10px 12px;
  min-width: 280px; max-width: 360px;
  font-family: inherit; font-size: 0.75rem; line-height: 1.4;
}
.service-popover.visible { display: block; }

.service-popover-header {
  display: flex; align-items: center; justify-content: space-between;
  border-bottom: 1px solid var(--light-border);
  margin-bottom: 8px; padding-bottom: 4px;
}
/* Tenant-mode (client-rendered) body — the platform partial uses its own
   layout; these style only the minimal dedicated-tenant form. */
.service-popover-body {
  display: flex; flex-direction: column; gap: 10px;
  padding: 2px 2px 6px;
}
.service-popover-enabled-row {
  display: flex; align-items: center; gap: 6px;
  font-size: 0.75rem;
}
.service-popover-note {
  margin: 0; font-size: 0.6875rem; color: var(--muted-fg);
}
.service-popover-title {
  font-weight: 600; font-size: 0.8125rem;
  color: var(--accent);
}
.service-popover-close {
  width: 22px; height: 22px; padding: 0;
  background: transparent; color: var(--card-fg);
  border: 0; border-radius: 3px;
  font-size: 1.25rem; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0.65;
}
.service-popover-close:hover {
  opacity: 1; background: var(--overlay-pressed);
}

.service-popover-status {
  font-size: 0.6875rem;
  color: var(--muted-fg);
  margin-bottom: 8px;
  padding: 4px 6px;
  background: var(--card-row-highlight);
  border-radius: 2px;
}

.service-popover-option {
  display: flex; align-items: center;
  cursor: pointer;
  padding: 2px 0;
}

.service-popover-policy {
  margin: 8px 0;
}
.service-popover-policy-label {
  font-weight: 600;
  font-size: 0.6875rem;
  margin-bottom: 4px;
}
.service-popover-policy-option {
  display: inline-flex; align-items: center;
  margin-right: 10px;
  cursor: pointer;
  font-size: 0.6875rem;
}

/* Cross-branch sibling-services warning — appears when another
   :service row targets the same fn-id on a different branch.
   Dashed border + warn-tone background so it visually separates
   from regular status text without screaming. */
.service-popover-sibling-warn {
  margin: 6px 0;
  padding: 4px 8px;
  font-size: 0.6875rem;
  color: var(--fg);
  background: color-mix(in srgb, var(--warning-fg) 12%, var(--card-bg));
  border: 1px dashed var(--warning-fg);
  border-radius: 3px;
  cursor: help;
}

/* Branch picker — per-branch service scoping. The select is full-
   width so longer branch names don't truncate; size matches the
   restart-policy block visually. */
.service-popover-branch {
  margin: 8px 0;
}
.service-popover-branch-label {
  font-weight: 600;
  font-size: 0.6875rem;
  margin-bottom: 4px;
}
.service-popover-branch-select {
  width: 100%;
  font-family: inherit;
  font-size: 0.6875rem;
  padding: 2px 4px;
  background: var(--card-bg);
  color: var(--fg);
  border: 1px solid var(--card-border);
  border-radius: 3px;
}

.service-popover-actions {
  display: flex; gap: 6px;
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px solid var(--light-border);
}
.service-popover-save-btn,
.service-popover-delete-btn {
  font-family: inherit; font-size: 0.75rem;
  padding: 4px 10px;
  border-radius: 3px;
  cursor: pointer;
}
.service-popover-save-btn {
  background: var(--accent); color: var(--card-bg);
  border: 1px solid var(--accent);
}
.service-popover-save-btn:hover:not(:disabled) {
  filter: brightness(1.1);
}
.service-popover-save-btn:disabled {
  opacity: 0.6; cursor: not-allowed;
}
.service-popover-delete-btn {
  background: transparent; color: var(--error-fg);
  border: 1px solid var(--error-fg);
}
.service-popover-delete-btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--error-fg) 12%, transparent);
}
.service-popover-delete-btn:disabled {
  opacity: 0.5; cursor: not-allowed;
}

/* Service badge — small status dot on the root row of any fn-card
 * whose fn is declared as a :service. Sits between the fn name and
 * the row-actions trigger; click opens the service settings popover.
 * Colors reuse design tokens used elsewhere for analogous status
 * (success / error / muted) so themes stay coherent. */
/* Floated over the row's left edge (next to the fn name) instead of
 * inserted into the flex flow — avoids ellipsizing long fn names
 * just to make room for a 9-px dot. Z-indexed above the row bg
 * stripe; pointer events kept on so clicks still open the popover. */
.service-badge {
  position: absolute;
  left: 2px; top: 50%;
  transform: translateY(-50%);
  width: 0.5rem; height: 0.5rem;
  font-size: 0.5625rem; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  user-select: none;
  z-index: 1;
}
.service-badge:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.service-badge-running   { color: var(--success-fg); }
.service-badge-failed    { color: var(--error-fg); }
.service-badge-backoff   { color: var(--warning-fg); animation: service-badge-pulse 1.5s ease-in-out infinite; }
.service-badge-exited    { color: var(--warning-fg); opacity: 0.8; }
.service-badge-elsewhere { color: var(--muted-fg); }
.service-badge-disabled  { color: var(--muted-fg); opacity: 0.65; }
.service-badge-pending  {
  color: var(--muted-fg);
  animation: service-badge-pulse 1.5s ease-in-out infinite;
}

@keyframes service-badge-pulse {
  0%, 100% { opacity: 0.45; }
  50%      { opacity: 1; }
}

/* Type-error badge (error-tolerance Phase 3) — small ⚠ on the root row
 * of an fn-card whose fn currently fails the aggregate type-check.
 * Anchored just inside the row's reserved more-actions right padding;
 * hover title explains, the Type errors panel lists details. */
.type-error-badge {
  position: absolute;
  right: 26px; top: 50%;
  transform: translateY(-50%);
  font-size: 0.625rem; line-height: 1;
  color: var(--error-fg-strong);
  user-select: none;
  z-index: 1;
}
/* Failed-runs / lint badges share the ⚠ slot's geometry, stacked leftward
   so all three fit before the ⋯ trigger. */
.failed-run-badge, .lint-badge {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: 0.625rem; line-height: 1;
  user-select: none;
  z-index: 1;
}
.failed-run-badge { right: 44px; color: var(--error-fg-strong); }
.lint-badge { right: 66px; color: var(--muted-fg); }


.execute-popover-body {
  margin-bottom: 10px;
}
.execute-no-args-note {
  font-style: italic;
  color: var(--muted-fg);
  padding: 4px 0;
}

.execute-arg-row {
  margin-bottom: 8px;
}
.execute-arg-label {
  font-weight: 600;
  font-size: 0.6875rem;
  color: var(--muted-fg);
  margin-bottom: 2px;
}
.execute-arg-form {
  padding: 4px 6px;
  background: var(--card-row-highlight);
  border: 1px solid var(--light-border);
  border-radius: 2px;
}
.execute-arg-form-error {
  color: var(--error-fg);
  font-style: italic;
}
.execute-optional-args {
  margin-bottom: 8px;
}
.execute-optional-args > summary {
  cursor: pointer;
  font-size: 0.6875rem;
  color: var(--muted-fg);
  padding: 2px 0;
  user-select: none;
}
.execute-optional-args[open] > summary {
  margin-bottom: 6px;
}

.execute-options-row {
  display: flex; flex-direction: column; gap: 4px;
  margin-bottom: 8px;
}
.execute-option-label {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 0.6875rem;
  color: var(--muted-fg);
  cursor: pointer;
  user-select: none;
}
.execute-option-label input[type="checkbox"] { margin: 0; }
/* Confirm-effects checkbox sits in red so the gate is hard to miss
 * when the run will produce side effects. */
.execute-confirm-label { color: var(--error-fg); font-weight: 600; }
.execute-option-label-locked {
  cursor: help;
  opacity: 0.7;
}

.execute-action-bar {
  display: flex; gap: 8px;
  margin-bottom: 8px;
}
.execute-run-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.execute-run-btn, .execute-cancel-btn {
  padding: 4px 12px;
  font-family: inherit; font-size: 0.75rem;
  border: 1px solid var(--card-border);
  border-radius: 3px;
  background: var(--card-bg); color: var(--card-fg);
  cursor: pointer;
}
.execute-run-btn {
  background: var(--accent);
  color: var(--card-bg);
  border-color: var(--accent);
  font-weight: 600;
}
.execute-run-btn:hover { opacity: 0.9; }
.execute-cancel-btn:hover { background: var(--hover-bg); }

.execute-result-host {
  border-top: 1px solid var(--light-border);
  padding-top: 8px;
}
.execute-submit-spinner, .execute-pending-pane {
  display: flex; align-items: center; gap: 6px;
  font-size: 0.6875rem;
  color: var(--muted-fg);
}
.execute-pending-spinner {
  font-size: 0.875rem;
  color: var(--accent);
}
.execute-pending-text { font-family: inherit; }

.execute-result-pane {
  font-family: monospace; font-size: 0.7188rem;
  overflow-x: auto;
}
.execute-result-scalar {
  padding: 6px 8px;
  background: var(--card-row-highlight);
  border-radius: 2px;
  font-weight: 600;
}
.execute-result-list {
  list-style: disc inside;
  padding-left: 4px;
  margin: 0;
}
.execute-result-list li { padding: 2px 0; }
.execute-result-list-more {
  list-style: none;
  font-style: italic;
  color: var(--muted-fg);
}
.execute-result-record {
  border-collapse: collapse;
  width: 100%;
}
.execute-result-record th, .execute-result-record td {
  text-align: left; padding: 3px 6px;
  border-bottom: 1px solid var(--light-border);
  vertical-align: top;
}
.execute-result-record th {
  font-weight: 600;
  color: var(--muted-fg);
  width: 30%;
}
.execute-result-record pre {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.execute-result-json {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin: 0;
  padding: 6px 8px;
  background: var(--card-row-highlight);
  border-radius: 2px;
}
/* Collapsed raw-JSON view under a shaped result pane (bullets/table/repr) —
   the Chrome-devtools preview/raw pair, server-rendered as <details>. */
.execute-result-raw {
  margin-top: 6px;
}
.execute-result-raw summary {
  cursor: pointer;
  color: var(--muted-fg);
  font-size: 0.6875rem;
  user-select: none;
}
.execute-result-raw[open] summary {
  margin-bottom: 4px;
}
/* Sandboxed iframe previewing a Ring HTML response — the page picks
   its own colors inside the frame, the shell just gives it room. */
.execute-result-html-caption {
  padding: 4px 6px;
  margin-bottom: 6px;
  color: var(--muted-fg);
  font-size: 0.6875rem;
}
/* "Open interactive preview" — new-tab link in the component-preview
   caption (self-host only; server hides it under a tenancy addon). */
.execute-result-html-caption .execute-result-open-preview {
  float: right;
  color: var(--accent);
  text-decoration: none;
  /* also covers the cloud BUTTON variant (capsule-minting click) */
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
.execute-result-html-caption .execute-result-open-preview:hover {
  text-decoration: underline;
}
.execute-result-html-preview {
  width: 100%;
  min-height: 320px;
  border: 1px solid var(--card-border);
  border-radius: 3px;
  background: var(--card-bg);
}
/* Component preview (declared :hiccup-node return) — a fragment, not
   a page: fragment-sized room. */
.execute-result-component-preview {
  min-height: 120px;
}
/* Typed-repr pane (:_value-repr-registry) — sanitized graph-authored
   hiccup inlined into the pane. `color` is the ink repr SVG strokes
   pick up via currentColor. */
.execute-result-repr .repr-sparkline-wrap {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px 8px;
  background: var(--card-row-highlight);
  border-radius: 2px;
}
.execute-result-repr .repr-sparkline {
  width: 100%;
  height: 48px;
  color: var(--accent);
}
.execute-result-repr .repr-sparkline-caption {
  color: var(--muted-fg);
  font-size: 0.6875rem;
}
/* Record-list table repr — reuses the record-pane table styling via
   the shared execute-result-record class; only the wrap + caption
   are new. */
.execute-result-repr .repr-record-wrap {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.execute-result-repr .repr-record-table th {
  text-align: left;
}
.execute-result-repr .repr-record-caption {
  color: var(--muted-fg);
  font-size: 0.6875rem;
}
/* Source-block repr (:script-tag / :style-tag / :js-source /
   :css-source) — caption line over a formatted source body. */
.execute-result-repr .repr-source-wrap {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.execute-result-repr .repr-source-caption {
  color: var(--muted-fg);
  font-size: 0.6875rem;
}
.execute-result-repr .repr-source {
  margin: 0;
  padding: 6px 8px;
  background: var(--card-row-highlight);
  border-radius: 2px;
  font-size: 0.75rem;
  overflow-x: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.execute-result-truncated {
  padding: 4px 6px;
  margin-bottom: 6px;
  background: var(--card-row-highlight);
  border-left: 2px solid var(--error-fg);
  border-radius: 2px;
  font-style: italic;
  font-size: 0.6875rem;
  color: var(--muted-fg);
}
.execute-cancelled {
  padding: 4px 6px;
  font-style: italic;
  color: var(--muted-fg);
}

.execute-error-pane {
  padding: 6px 8px;
  background: var(--card-row-highlight);
  border-left: 2px solid var(--error-fg);
  border-radius: 2px;
}
.execute-error-head {
  font-weight: 600;
  color: var(--error-fg);
  margin-bottom: 4px;
}
.execute-error-data {
  margin: 0;
  font-family: monospace; font-size: 0.6875rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}


/* History list — persisted runs for the current fn, mounted below
 * the run form in the inspector's Runs tab. Each row shows status /
 * timestamp / duration / result preview. Click a row to expand its
 * full result into the main result host; Repeat re-fills the form
 * with that run's args. */
.execute-history-host-wrap {
  padding: 6px 8px;
  background: var(--card-row-highlight);
  border-left: 2px solid var(--accent);
  border-radius: 2px;
}
/* Phase C2 error-log sidebar section. */
.error-log-ack { position: absolute; top: 4px; right: 4px;
  background: none; border: 0; padding: 0 4px; cursor: pointer;
  font-size: 0.6875rem; color: var(--muted-fg); }
.error-log-ack:hover { color: var(--fg); }


/* Lint rows — the Inspector's Lint section (rule chip + member links +
   message + "Not an issue" / "Restore", all server-rendered). */
.lint-rule { grid-area: rule; color: var(--muted-fg); font-family: var(--mono);
  white-space: nowrap; }
.lint-fns { grid-area: fns; display: flex; flex-wrap: wrap; gap: 2px 10px; }
.lint-fn { color: var(--accent); }
.lint-fn-ns { color: var(--muted-fg); margin-left: 4px; }
.lint-fn-ns:empty { display: none; }
.lint-msg { grid-area: msg; color: var(--light-fg); overflow-wrap: anywhere; }
.lint-suppress, .lint-restore { grid-area: btn; font-size: 0.625rem; padding: 1px 6px;
  color: var(--muted-fg); background: transparent; border: 1px solid var(--light-border);
  border-radius: 3px; cursor: pointer; white-space: nowrap; }
.lint-suppress:hover, .lint-restore:hover { color: var(--fg); border-color: var(--accent); }
.lint-row-hidden { opacity: 0.7; }
.execute-history-failure .error-log-ack { position: static; }
.gd-insp-lint-row .lint-fns { margin-bottom: 2px; }
.gd-insp-lint-hint { color: var(--muted-fg); font-size: 0.6875rem; padding: 2px 0; }
.gd-insp-lint-row .lint-suppress, .gd-insp-lint-row .lint-restore { position: static; margin-top: 2px; }
.gd-bind-diag { flex-basis: 100%; color: var(--error-fg-strong); font-size: 0.6875rem;
  white-space: pre-wrap; overflow-wrap: anywhere; padding-left: 4px; }
/* Runs tab — the fn's unresolved failures above its history. */
.execute-history-failures { border-bottom: 1px dashed var(--light-border); margin-bottom: 4px; }
.execute-history-failures-head { font-size: 0.6875rem; color: var(--error-fg-strong); padding: 4px 8px 2px; }
.execute-history-failure { display: grid; grid-template-columns: auto auto 1fr; column-gap: 8px;
  align-items: baseline; padding: 2px 8px; font-size: 0.6875rem; }
.execute-history-failure-time { color: var(--muted-fg); white-space: nowrap; }
.execute-history-failure-msg { color: var(--error-fg-strong); overflow-wrap: anywhere; }

/* Tests (Block 3.1) — the Inspector's Test section (app/editor-provenance
   `_insp-test-*`): status dot + label, the assertion's message, [Run this
   test]. The Explorer's dots are .kind-marker-test below. */
.test-dot.test-passed { color: var(--success-fg); }
.test-dot.test-failed { color: var(--error-fg); }
.test-dot.test-stale  { color: var(--muted-fg); opacity: 0.65; }
.tests-err { color: var(--error-fg); overflow-wrap: anywhere; margin-top: 2px; }
.gd-insp-test-row .gd-prov-k { user-select: none; }
.gd-insp-test-status { font-size: 0.75rem; }
.gd-insp-test-row .tests-run-one { display: block; font-size: 0.625rem; padding: 1px 6px; margin-top: 4px;
  cursor: pointer; color: var(--muted-fg); background: transparent;
  border: 1px solid var(--light-border); border-radius: 3px; }
.gd-insp-test-row .tests-run-one:hover { color: var(--fg); border-color: var(--accent); }
.gd-insp-test-row .tests-run-one:disabled, .gd-insp-test-row .tests-run-one.htmx-request { color: var(--muted-fg); cursor: default; }

/* UI Step 1 — Assets ops section: frontend-asset override panel. */
.gd-asset-hint { padding: 4px 8px; color: var(--muted-fg); font-size: 0.6875rem; }
.gd-asset-list { max-height: 260px; overflow-y: auto; }

/* Operate → Queues */
.gd-queues-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.gd-queues-table th { text-align: left; font-weight: 600; padding: 4px 6px; }
.gd-queues-table td { padding: 4px 6px; vertical-align: top; }
.gd-queues-name { font-family: var(--mono); }
.gd-queues-error { font-family: var(--mono); max-width: 320px; overflow-wrap: anywhere; }
.gd-queues-actions { white-space: nowrap; }
.gd-queues-btn { margin-right: 4px; }
.gd-queues-subtitle { margin: 10px 0 4px; font-weight: 600; }
.gd-queues-empty { padding: 6px; opacity: 0.8; }
.gd-asset-row { display: flex; align-items: center; gap: 6px; padding: 3px 8px;
  border-bottom: 1px dashed var(--light-border); font-size: 0.6875rem; }
.gd-asset-path { color: var(--fg); overflow-wrap: anywhere; flex: 1; }
.gd-asset-chip { color: var(--muted-fg); font-size: 0.625rem; border: 1px solid var(--light-border);
  border-radius: 3px; padding: 0 4px; white-space: nowrap; }
.gd-asset-chip-override { color: var(--accent); border-color: var(--accent); }
.gd-asset-edit-btn { font-size: 0.625rem; padding: 0 6px; border: 1px solid var(--input-border);
  border-radius: 3px; background: var(--card-bg); color: var(--fg); cursor: pointer; }
.gd-asset-edit { padding: 6px 8px; }
.gd-asset-edit > .gd-asset-path { display: block; margin-bottom: 4px; }
.gd-asset-edit-form { display: flex; flex-direction: column; gap: 6px; }
.gd-asset-textarea { width: 100%; font-size: 0.6875rem; }
.gd-asset-save-btn, .gd-asset-revert-btn, .gd-asset-diff-btn {
  align-self: flex-start; font-size: 0.6875rem; padding: 2px 8px;
  border: 1px solid var(--input-border); border-radius: 3px;
  background: var(--card-bg); color: var(--fg); cursor: pointer; }
.gd-asset-revert-btn { color: var(--error-fg); margin-top: 4px; }
.gd-asset-diff-btn { margin-top: 4px; }
.gd-asset-error { color: var(--error-fg); font-size: 0.6875rem; padding: 2px 0; }
.gd-asset-diff { border: 1px solid var(--light-border); border-radius: 3px;
  max-height: 420px; overflow: auto; font-size: 0.75rem; margin-top: 4px; }

/* CodeMirror host (editor-code.js upgrade of code textareas). The
   bundle injects its own .cm-* structural styles; ours only frame it. */
.gd-code-editor { border: 1px solid var(--input-border); border-radius: 3px;
  background: var(--card-bg); color: var(--fg); font-size: 0.75rem;
  max-height: 420px; overflow: auto; }
.gd-code-editor .cm-gutters { background: var(--card-row-highlight);
  color: var(--muted-fg); border-right: 1px solid var(--light-border); }

/* Phase C stats sidebar section — org-scoped run rollups. */
.stats-panel { padding: 4px 8px 8px; font-size: 0.6875rem;
  max-height: 480px; overflow-y: auto; }
.stats-headline { color: var(--light-fg); font-weight: 600; padding: 4px 0 6px; }
.stats-subhead { color: var(--muted-fg); text-transform: uppercase;
  letter-spacing: 0.5px; font-size: 0.625rem; margin: 8px 0 2px; }
.stats-table { width: 100%; border-collapse: collapse; }
.stats-table th { text-align: right; color: var(--muted-fg); font-weight: 500;
  font-size: 0.625rem; padding: 2px 4px; border-bottom: 1px solid var(--light-border); }
.stats-table th:first-child { text-align: left; }
.stats-row { border-bottom: 1px dashed var(--light-border); }
.stats-day { color: var(--light-fg); font-variant-numeric: tabular-nums; }
.stats-fn-cell { overflow-wrap: anywhere; }
.stats-fn { color: var(--accent); }
.stats-num { text-align: right; font-variant-numeric: tabular-nums;
  color: var(--light-fg); padding: 2px 4px; }
.stats-failed { color: var(--error-fg); }
.stats-empty { padding: 4px 0; color: var(--muted-fg); }
.stats-period-bar { display: flex; gap: 4px; margin: 2px 0 6px; }
.stats-period { font-size: 0.625rem; padding: 2px 8px; border-radius: 3px;
  border: 1px solid var(--light-border); background: none;
  color: var(--muted-fg); cursor: pointer; }
.stats-period:hover { background: var(--hover-bg); color: var(--fg); }
.stats-period-active { border-color: var(--accent); color: var(--accent); }
.stats-bar-cell { width: 56px; padding: 2px 4px; }
.stats-bar { height: 6px; border-radius: 3px; background: var(--hover-bg);
  overflow: hidden; }
.stats-bar-fill { height: 100%; border-radius: 3px; background: var(--accent);
  transition: opacity 0.15s; }
.stats-org-block { margin-top: 2px; }

/* Phase C1 usage-rollup strip above the history rows. */
.execute-history-stats { font-size: 0.6875rem; color: var(--muted-fg);
  padding: 4px 6px; border-bottom: 1px dashed var(--light-border);
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.execute-history-secrets-toggle {
  margin-left: auto;
  font-size: 0.6875rem;
  padding: 1px 6px;
  border: 1px solid var(--light-border);
  border-radius: 10px;
  background: transparent;
  color: var(--muted-fg);
  cursor: pointer;
}
.execute-history-secrets-toggle-active {
  background: var(--card-row-highlight);
  color: var(--fg);
  border-color: var(--fg);
}
.execute-history-lock { font-size: 0.75rem; margin-right: 4px; }
.execute-history-hash {
  font-family: var(--mono);
  font-size: 0.625rem;
  padding: 0 5px;
  border: 1px solid var(--light-border);
  border-radius: 10px;
  background: transparent;
  color: var(--muted-fg);
  cursor: pointer;
}
.execute-history-hash-active {
  background: var(--card-row-highlight);
  color: var(--fg);
  border-color: var(--fg);
}
.execute-history-empty {
  font-size: 0.6875rem;
  font-style: italic;
  color: var(--muted-fg);
}
.execute-history-panel {
  display: flex; flex-direction: column; gap: 4px;
}
.execute-history-row {
  padding: 4px 6px;
  border-bottom: 1px solid var(--light-border);
  cursor: pointer;
}
.execute-history-row:last-child { border-bottom: 0; }
.execute-history-row:hover { background: var(--hover-bg); }
.execute-history-row-head {
  display: flex; align-items: center; gap: 8px;
  font-size: 0.6562rem;
  line-height: 1.4;
}
.execute-history-status {
  font-weight: 600;
  text-transform: uppercase;
  font-size: 0.5625rem;
  padding: 1px 4px;
  border-radius: 2px;
}
.execute-history-status-succeeded {
  color: var(--success-fg);
  background: color-mix(in srgb, var(--success-fg) 15%, transparent);
}
.execute-history-status-failed {
  color: var(--error-fg);
  background: color-mix(in srgb, var(--error-fg) 15%, transparent);
}
.execute-history-status-cancelled, .execute-history-status-pending {
  color: var(--muted-fg);
  background: var(--light-border);
}
.execute-history-ts {
  color: var(--muted-fg);
  font-family: monospace;
}

.execute-history-repeat-btn {
  margin-left: auto;
  padding: 1px 6px;
  font-family: inherit; font-size: 0.625rem;
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--card-border);
  border-radius: 3px;
  cursor: pointer;
}
.execute-history-repeat-btn:hover { background: var(--hover-bg); }
/* "path" button — same chip styling as Repeat; only rendered on rows
   whose execution captured a :path-trace (Debug P2). */
.execute-history-path-btn {
  padding: 1px 6px;
  font-family: inherit; font-size: 0.625rem;
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--card-border);
  border-radius: 3px;
  cursor: pointer;
}
.execute-history-path-btn:hover { background: var(--hover-bg); }
.execute-history-preview {
  font-family: monospace; font-size: 0.6562rem;
  color: var(--card-fg);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* =========================================================================
   EXECUTION PATH VIEW (Debug P2) — transient canvas highlight of a
   traced run's :path-trace. Traversed fn cards wear an accent ring +
   a compact per-fn timing badge; the rest of the canvas dims while
   the view is active. Cleared by the ✕ panel action or any overlay
   rebuild (relayout / fn switch / branch switch).
   ========================================================================= */

#graph-layer.path-view-active .node-overlay:not(.path-highlighted) {
  opacity: 0.35;
  transition: opacity 0.15s ease;
}
.node-overlay.path-highlighted {
  box-shadow: 0 0 0 3px var(--accent);
}
/* Per-fn aggregate badge — count × total duration / "cache" / "secret".
   Sits INSIDE the card (overlays clip via overflow:hidden), top-right
   over the header band, so it carries its own opaque ground for
   contrast. */
.path-trace-badge {
  position: absolute;
  top: 2px; right: 2px;
  z-index: 6;
  font-size: 0.625rem;
  line-height: 1.4;
  padding: 0 5px;
  border-radius: 8px;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--accent);
  pointer-events: auto;
  white-space: nowrap;
}
/* Secret-hidden node — a11y-strong error tone (the --error-fg-strong
   precedent from the type-errors panel). */
.path-trace-badge-secret {
  color: var(--error-fg-strong);
  border-color: var(--error-fg-strong);
}

/* Summary panel — the editor-busy banner slot (bottom-centre), but
   interactive: carries the ✕ clear action. */
.path-view-panel {
  position: fixed;
  bottom: 16px; left: 50%;
  transform: translateX(-50%);
  z-index: 10002;
  background: var(--card-bg); color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-radius: 4px;
  padding: 8px 14px;
  font-family: inherit; font-size: 0.75rem;
  box-shadow: var(--shadow-lg);
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.path-view-off-canvas,
.path-view-truncated { color: var(--muted-fg); }
.path-view-clear {
  padding: 1px 6px;
  font-family: inherit; font-size: 0.6875rem;
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--card-border);
  border-radius: 3px;
  cursor: pointer;
}
.path-view-clear:hover,
.path-view-clear:focus-visible { background: var(--hover-bg); outline: 2px solid var(--gd-flow); outline-offset: 1px; }

/* "Show path on canvas" affordance appended under a traced run's
   result pane (inline run, poll completion, history row expand). */
.execute-path-affordance { margin-top: 6px; }
.execute-show-path-btn {
  padding: 2px 8px;
  font-family: inherit; font-size: 0.6562rem;
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--card-border);
  border-radius: 3px;
  cursor: pointer;
}
.execute-show-path-btn:hover,
.execute-show-path-btn:focus-visible { background: var(--hover-bg); outline: 2px solid var(--gd-flow); outline-offset: 1px; }

/* Captured-value affordance (Debug P3) — a small "= value" chip under
   the timing badge; opens the singleton value popover. */
.path-value-badge {
  position: absolute;
  top: 20px; right: 2px;
  z-index: 6;
  font-family: inherit; font-size: 0.625rem;
  line-height: 1.4;
  padding: 0 5px;
  border-radius: 8px;
  background: var(--bg);
  color: var(--accent);
  border: 1px solid var(--accent);
  cursor: pointer;
  pointer-events: auto;
  white-space: nowrap;
}
.path-value-badge:hover,
.path-value-badge:focus-visible { background: var(--hover-bg); outline: 2px solid var(--gd-flow); outline-offset: 1px; }

/* Captured-value popover — singleton, fixed-positioned by
   anchorBelowClamped. The <pre> body scrolls inside its own box so a
   4 KB JSON value can't blow the popover past the viewport. */
.path-value-popover {
  position: fixed;
  z-index: 10003;
  max-width: 420px;
  background: var(--card-bg); color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-radius: 4px;
  padding: 8px 10px;
  font-size: 0.75rem;
  box-shadow: var(--shadow-lg);
}
.path-value-popover-head {
  font-weight: 600;
  margin-bottom: 6px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.path-value-popover-body {
  margin: 0;
  max-height: 260px;
  max-width: 100%;
  overflow: auto;
  font-size: 0.6875rem;
  background: var(--bg);
  border: 1px solid var(--light-border);
  border-radius: 3px;
  padding: 6px 8px;
}
.path-value-popover-note {
  color: var(--muted-fg);
  margin-top: 6px;
}

/* =========================================================================
   SECRETS — sidebar section + 🔒 indicator + create / rotate popover.
   ========================================================================= */

#entity-list .entity-secret { display: flex; align-items: center; gap: 4px; }
#entity-list .entity-secret .name { flex: 0 0 auto; }
#entity-list .entity-secret .secret-path {
  flex: 1 1 auto;
  font-family: monospace;
  font-size: 0.6875rem;
  color: var(--card-fg);
  opacity: 0.7;
  margin-left: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.secret-lock-icon {
  font-size: 0.6875rem;
  margin-right: 2px;
  flex: 0 0 auto;
}
/* Tree-row kind markers — the same glyphs as the lens chips, so a mixed
   (All-lens) tree reads at a glance. Service markers reuse the badge's
   state palette (running/failed/disabled/pending). */
.fn-kind-marker {
  font-size: 0.625rem;
  margin-right: 2px;
  flex: 0 0 auto;
  color: var(--muted-fg);
  user-select: none;
}
.kind-marker-service.svc-running   { color: var(--success-fg); }
.kind-marker-service.svc-failed    { color: var(--error-fg); }
.kind-marker-service.svc-backoff   { color: var(--warning-fg); }
.kind-marker-service.svc-exited    { color: var(--warning-fg); opacity: 0.8; }
.kind-marker-service.svc-elsewhere { color: var(--muted-fg); }
.kind-marker-service.svc-disabled  { color: var(--muted-fg); opacity: 0.65; }
.kind-marker-app { color: var(--accent); }
/* fx detail marker — effect footprint; italic + muted so a tree full of
   effectful web fns reads as texture, not alarm. */
.kind-marker-fx { font-style: italic; opacity: 0.8; }
.kind-marker-type { font-size: 0.5625rem; opacity: 0.8; }
/* Test status dot — latest execution of the fn's CURRENT version:
   passed / failed / stale (not run since the last edit). */
.kind-marker-test.test-passed { color: var(--success-fg); }
.kind-marker-test.test-failed { color: var(--error-fg); }
.kind-marker-test.test-stale  { color: var(--muted-fg); opacity: 0.65; }

.secrets-popover {
  position: absolute;
  z-index: 1000;
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  border-radius: 6px;
  padding: 12px;
  box-shadow: var(--shadow-lg);
  min-width: 280px;
}

.secrets-popover .popover-title {
  font-weight: 600;
  margin-bottom: 8px;
  color: var(--card-fg);
}

.secrets-popover .popover-label {
  display: block;
  margin-bottom: 8px;
  font-size: 0.75rem;
  color: var(--card-fg);
}

.secrets-popover .popover-label .muted {
  color: var(--muted-fg);
  font-weight: normal;
}

.secrets-popover input {
  display: block;
  width: 100%;
  margin-top: 2px;
  padding: 4px 6px;
  background: var(--card-bg);
  color: var(--card-fg);
  border: 1px solid var(--input-border);
  border-radius: 3px;
  font-family: inherit;
  font-size: 0.75rem;
}

.secrets-popover .muted {
  color: var(--muted-fg);
  font-size: 0.6875rem;
  margin-bottom: 8px;
}

.secrets-popover .popover-error {
  color: var(--error-fg);
  font-size: 0.6875rem;
  margin: 4px 0 8px;
}

.secrets-popover .popover-buttons {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
}

.secrets-popover .popover-buttons button {
  padding: 4px 10px;
  background: var(--card-bg);
  color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-radius: 3px;
  cursor: pointer;
  font-size: 0.75rem;
}

.secrets-popover .popover-buttons button.primary {
  background: var(--accent);
  color: var(--header-fg);
  border-color: var(--accent);
}

.secrets-ns-chip {
  display: inline-block;
  margin-top: 2px;
  padding: 3px 8px;
  background: var(--card-bg);
  color: var(--card-fg);
  border: 1px solid var(--input-border);
  border-radius: 12px;
  font-family: inherit;
  font-size: 0.75rem;
  cursor: pointer;
  text-align: left;
  min-width: 80px;
}

.secrets-ns-chip:hover {
  background: var(--hover-bg);
}

/* =========================================================================
   TAINTED EXECUTE — Result pane + History badge for :secret-typed runs.
   Shown when the fn's effective return-type carries the :secret marker;
   the backend redacts the value and the editor refuses to surface
   anything (even an attempt at rendering the null result body — the
   tainted pane stands in for the result entirely).
   ========================================================================= */

.execute-result-tainted {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 8px 10px;
  border: 1px solid var(--card-border);
  background: var(--card-bg);
  border-radius: 4px;
}

.execute-tainted-icon {
  font-size: 1.125rem;
  line-height: 1;
  margin-top: 1px;
  flex: 0 0 auto;
}

.execute-tainted-text {
  flex: 1 1 auto;
  font-size: 0.75rem;
  color: var(--card-fg);
}

.execute-tainted-head {
  font-weight: 600;
  margin-bottom: 2px;
}

.execute-tainted-body {
  color: var(--muted-fg);
}



/* Audit trail badge + filter. Distinct from the
   tainted-result badge above — fires when the fn touched a secret
   AND observed a side-effect, even if its return-type is plain. */







/* Row-actions popover content (server-rendered partial; HTMX
   migration Phase A1). Flex container hosts ns / i / ↗ buttons.
   The wrapping `.row-actions-popover` (built JS-side) handles
   background / border / shadow; this rule covers only the inner
   layout + the loading / error placeholders during the swap-in. */
/* Redesign 2026-08 (landing 2): the row-actions popover is a LABELED
   vertical MENU, not a strip of cryptic glyphs. Each action keeps its glyph
   but now shows its aria-label as a visible label — so "what does this do" is
   answered at a glance instead of by hovering each icon in turn. All rules are
   scoped INSIDE `.row-actions-content` (which exists only in the popover), so
   the same `.action-icon` factory still renders as a compact square when it's
   used inline on a card row. */
.row-actions-content {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 1px;
  min-width: 186px;
}
/* Each button/link becomes a full-width menu row: a fixed glyph lane, then the
   label. Grid puts the element's own glyph text-node in column 1 and the
   ::after label in column 2, so labels align regardless of glyph width. */
.row-actions-content .action-icon,
.row-actions-content .namespace-badge,
.row-actions-content .description-badge,
.row-actions-content .open-in-new-tab {
  display: grid;
  grid-template-columns: 18px 1fr;
  align-items: center;
  gap: 9px;
  box-sizing: border-box;
  width: 100%;
  height: auto;
  min-height: 30px;
  padding: 5px 10px;
  border: none;
  border-radius: 6px;
  font-size: 0.8125rem;
  line-height: 1.2;
  text-align: left;
  text-decoration: none;
  color: var(--card-fg, currentColor);
  cursor: pointer;
}
/* The visible label, from the aria-label already present on every button/link.
   Only buttons/links get one — never the toolbar container (also aria-labelled). */
.row-actions-content button[aria-label]::after,
.row-actions-content a[aria-label]::after {
  content: attr(aria-label);
  font-size: 0.8125rem;
  font-style: normal;
  font-weight: 400;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.row-actions-content .action-icon:hover,
.row-actions-content .namespace-badge:hover,
.row-actions-content .description-badge:hover,
.row-actions-content .open-in-new-tab:hover {
  background: var(--hover-bg);
  opacity: 1;
}
/* Destructive (Delete) reads as danger — red label carries the signal; the
   hover bg stays the shared menu-hover so we don't invent a one-off colour. */
.row-actions-content .action-icon-danger,
.row-actions-content .action-icon-danger::after { color: var(--error-fg); }
/* Disabled stays muted (the reason is in its title on hover). */
.row-actions-content .action-icon-disabled { opacity: 0.5; cursor: help; }
.row-actions-content .action-icon-disabled:hover { background: transparent; }
.row-actions-loading,
.row-actions-error {
  font-size: 0.75rem;
  padding: 6px 10px;
}
.action-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: var(--icon-size);
  height: var(--icon-size);
  line-height: 1;
  border: 1px solid currentcolor;
  border-radius: 3px;
  font-size: var(--icon-font-size);
  background: transparent;
  color: currentcolor;
  padding: 0;
  cursor: pointer;
  text-decoration: none;
}
.action-icon:hover {
  background: var(--hover-bg);
}
.action-icon-danger {
  color: var(--error-fg);
}
.action-icon-disabled {
  opacity: 0.45;
  cursor: help;
}
.action-icon-disabled:hover {
  background: transparent;
}
.row-actions-content .namespace-badge {
  /* The legacy `applyActionIconBox` set font-size 1px smaller for
     the 2-char "ns" glyph in the same 15×15 frame — preserve here. */
  font-size: calc(var(--icon-font-size) - 1px);
  font-style: italic;
  font-weight: normal;
}




/* Org-admin Grants (§6) + Users (§4.1) panels — sidebar sections rendered from
   /partials/grants-admin and /partials/users-admin. Both share these styles;
   only shown to authed users when the tenancy addon is active (see
   editor-grants-admin.js / editor-users-admin.js). */
.sidebar-grants-admin .grants-admin-table,
.sidebar-users-admin .grants-admin-table { width: 100%; font-size: 0.6875rem; border-collapse: collapse; }
.sidebar-grants-admin .grants-admin-table th,
.sidebar-grants-admin .grants-admin-table td,
.sidebar-users-admin .grants-admin-table th,
.sidebar-users-admin .grants-admin-table td { text-align: left; padding: 2px 4px; border-bottom: 1px solid var(--border); }
.sidebar-grants-admin .grants-admin-disabled,
.sidebar-users-admin .grants-admin-disabled { padding: 6px 8px; color: var(--muted-fg); font-style: italic; }

/* Per-fn Apps popover — ▣ in the root-row actions menu (editor-apps.js).
   Content is the tenancy addon's /partials/fn-apps partial: this fn's
   :app-route rows (host link + delete) + a create form. Same dialog shell
   family as .service-popover. */
.fn-apps-popover {
  position: fixed; z-index: 10002;
  display: none;
  background: var(--card-bg); color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-left: 3px solid var(--accent);
  border-radius: 4px;
  box-shadow: var(--shadow-lg);
  padding: 10px 12px;
  min-width: 280px; max-width: 360px;
  font-family: inherit; font-size: 0.75rem; line-height: 1.4;
}
.fn-apps-popover.visible { display: block; }
.fn-apps-popover .fn-apps-title {
  margin: 0 0 6px; font-size: 0.75rem; font-weight: 600;
  border-bottom: 1px solid var(--light-border); padding-bottom: 4px;
}
.fn-apps-popover .fn-app-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 3px 0; border-bottom: 1px solid var(--light-border);
}
.fn-apps-popover .app-host-link { color: var(--fg); text-decoration: none; min-width: 0; overflow-wrap: anywhere; }
.fn-apps-popover .app-host-link:hover { color: var(--accent); text-decoration: underline; }
.fn-apps-popover .fn-apps-empty,
.fn-apps-popover .apps-admin-disabled { padding: 4px 0; color: var(--muted-fg); font-style: italic; }
.fn-apps-popover .fn-apps-error { padding: 4px 0; color: var(--error-fg); }
.fn-apps-popover .app-delete-form { display: inline; flex: none; }
.fn-apps-popover .app-delete { background: none; border: none; color: var(--muted-fg); cursor: pointer; padding: 0 4px; line-height: 1; }
.fn-apps-popover .app-delete:hover { color: var(--error-fg); }
.fn-apps-popover .app-delete:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Create form — hint + label input + add button. */
.fn-apps-popover .app-create-form { display: flex; flex-direction: column; gap: 4px; padding-top: 6px; }
.fn-apps-popover .apps-create-hint { font-size: 0.6875rem; color: var(--muted-fg); }
.fn-apps-popover .app-input { font-size: 0.6875rem; padding: 2px 4px; border: 1px solid var(--input-border); border-radius: 3px; background: var(--card-bg); color: var(--fg); }
.fn-apps-popover .app-create-btn { align-self: flex-start; background: none; border: 1px solid var(--input-border); border-radius: 3px; color: var(--accent); cursor: pointer; font-size: 0.6875rem; padding: 2px 8px; }
.fn-apps-popover .app-create-btn:hover { background: var(--hover-bg); }
.fn-apps-popover .app-create-btn:focus-visible,
.fn-apps-popover .app-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Packages content — installed pins + browse/install registry (chip popover
   `#gd-pkg-pop`) plus the per-namespace publish popover's inputs/button
   (`#gd-nspub-pop`). Both are `.gd-pop`s; these `.packages-*` classes appear
   only in packages content, so scoping to `.gd-pop` never touches other
   popovers. (Was `.sidebar-packages` when the panel lived in the Organization
   sidebar; that mount was retired when install became a Build act.) */
.gd-pop .packages-panel-table { width: 100%; font-size: 0.6875rem; border-collapse: collapse; }
.gd-pop .packages-panel-table th,
.gd-pop .packages-panel-table td { text-align: left; padding: 2px 4px; border-bottom: 1px solid var(--border); }
.gd-pop .packages-panel-empty { padding: 6px 8px; color: var(--muted-fg); font-style: italic; }
.gd-pop .packages-uninstall { background: none; border: none; color: var(--muted-fg); cursor: pointer; padding: 0 4px; line-height: 1; }
.gd-pop .packages-uninstall:hover { color: var(--error-fg); }
/* Browse/install registry — native <details> disclosure below the pins. */
.gd-pop .packages-available { margin-top: 6px; border-top: 1px solid var(--border); }
.gd-pop .packages-available > summary { padding: 4px; font-size: 0.6875rem; color: var(--muted-fg); cursor: pointer; }
.gd-pop .packages-available > summary:hover { color: var(--fg); }
.gd-pop .packages-install-btn { background: none; border: 1px solid var(--input-border); border-radius: 3px; color: var(--accent); cursor: pointer; font-size: 0.625rem; padding: 1px 6px; }
.gd-pop .packages-install-btn:hover { background: var(--hover-bg); }
/* per-row update/rollback form — version input + ↑ submit, inline with ×. */
.gd-pop .packages-update-form { display: inline-flex; gap: 2px; align-items: center; }
.gd-pop .packages-version-input { width: 44px; font-size: 0.625rem; padding: 0 2px; border: 1px solid var(--input-border); border-radius: 3px; background: var(--card-bg); color: var(--fg); }
.gd-pop .packages-update-btn { background: none; border: none; color: var(--muted-fg); cursor: pointer; padding: 0 2px; line-height: 1; }
.gd-pop .packages-update-btn:hover { color: var(--accent); }
/* Remote-registry install form — pull a published package from another
   graphden (cross-install), below the local browse table. */
.gd-pop .packages-remote-note { padding: 6px 4px 2px; font-size: 0.625rem; color: var(--muted-fg); }
.gd-pop .packages-remote-form { display: flex; gap: 3px; align-items: center; padding: 2px 4px 6px; flex-wrap: wrap; }
.gd-pop .packages-remote-input { flex: 1; min-width: 90px; font-size: 0.625rem; padding: 2px 4px; border: 1px solid var(--input-border); border-radius: 3px; background: var(--card-bg); color: var(--fg); }
.gd-pop .packages-remote-version { flex: 0 0 56px; min-width: 56px; }
/* Fork button (browse row) + transient fork notice. */
.gd-pop .packages-fork-btn { background: none; border: 1px solid var(--input-border); border-radius: 3px; color: var(--muted-fg); cursor: pointer; font-size: 0.625rem; margin-left: 3px; padding: 1px 6px; }
.gd-pop .packages-fork-btn:hover { background: var(--hover-bg); color: var(--fg); }
.gd-pop .packages-private-badge { border: 1px solid var(--input-border); border-radius: 3px; color: var(--muted-fg); font-size: 0.5625rem; margin-left: 5px; padding: 0 4px; text-transform: uppercase; letter-spacing: 0.04em; }

/* Packages governance (Organization surface) — read-mostly catalog + audit. */
.sidebar-packages-governance .packages-gov-who { color: var(--muted-fg); font-size: 0.75rem; line-height: 1.5; margin-bottom: 10px; }
.sidebar-packages-governance .packages-gov-who code { font-family: var(--gd-mono, monospace); font-size: 0.6875rem; }
.sidebar-packages-governance .packages-gov-title { font-size: 0.75rem; margin: 12px 0 6px; }
.sidebar-packages-governance .packages-panel-table { width: 100%; font-size: 0.6875rem; border-collapse: collapse; }
.sidebar-packages-governance .packages-panel-table th,
.sidebar-packages-governance .packages-panel-table td { text-align: left; padding: 2px 4px; border-bottom: 1px solid var(--border); }
.sidebar-packages-governance .packages-panel-empty { padding: 6px 8px; color: var(--muted-fg); font-style: italic; }
.gd-pop .packages-fork-note { font-size: 0.625rem; padding: 3px 6px; margin-bottom: 4px; border-radius: 3px; }
.gd-pop .packages-fork-ok { color: var(--success-fg); }
.gd-pop .packages-fork-err { color: var(--error-fg); }
/* Publish-a-namespace form (rendered in the panel-publish response). */
.gd-pop .packages-publish-form { display: flex; flex-direction: column; gap: 3px; padding: 4px; }
.gd-pop .packages-publish-input { font-size: 0.625rem; padding: 1px 3px; border: 1px solid var(--input-border); border-radius: 3px; background: var(--card-bg); color: var(--fg); }

/* Per-namespace publish popover (⬆ on a namespace row → JSON publish). */
#gd-nspub-pop { width: 300px; max-width: 92vw; padding: 12px; }
#gd-nspub-pop .gd-nspub-sub { font-size: 0.6875rem; color: var(--muted-fg); font-family: var(--gd-mono, monospace); margin: -4px 0 8px; word-break: break-all; }
#gd-nspub-pop .gd-nspub-field { display: flex; flex-direction: column; gap: 2px; margin-bottom: 8px; }
#gd-nspub-pop .gd-nspub-field label { font-size: 0.6875rem; color: var(--muted-fg); }
#gd-nspub-pop .packages-publish-input { font-size: 0.75rem; padding: 4px 6px; }
#gd-nspub-pop .gd-nspub-public { display: flex; align-items: center; gap: 6px; font-size: 0.6875rem; color: var(--muted-fg); margin-bottom: 8px; cursor: pointer; }
#gd-nspub-pop .gd-nspub-actions { display: flex; justify-content: flex-end; }
#gd-nspub-pop .gd-nspub-result { margin-top: 8px; font-size: 0.6875rem; }
#gd-nspub-pop .gd-nspub-result.packages-fork-ok { color: var(--success-fg); }
#gd-nspub-pop .gd-nspub-result.packages-fork-err { color: var(--error-fg); }

.sidebar-grants-admin .grant-delete,
.sidebar-users-admin .grant-delete { background: none; border: none; color: var(--muted-fg); cursor: pointer; padding: 0 4px; }
.sidebar-grants-admin .grant-delete:hover,
.sidebar-users-admin .grant-delete:hover { color: var(--error-fg); }
.sidebar-grants-admin .grant-create-form,
.sidebar-users-admin .grant-create-form { display: flex; flex-direction: column; gap: 4px; padding: 6px 8px; }
.sidebar-grants-admin .grant-input,
.sidebar-users-admin .grant-input { font-size: 0.6875rem; padding: 2px 4px; }
.sidebar-grants-admin .grant-create-btn { font-size: 0.6875rem; padding: 2px 6px; cursor: pointer; align-self: flex-start; }

/* ============================================================================
 * REDESIGN 2026-08 — SHELL LAYOUT (rail + explorer | canvas | inspector).
 * Appended after the legacy rules so equal-specificity selectors win. Reads
 * the `--gd-*` design system. The proportional UI font is applied ONLY to new
 * chrome — the canvas cards + tree stay monospace (they are code, and the
 * layout engine measures their text width). See memory
 * project_editor_ui_redesign_2026_08_08.
 * ========================================================================= */

/* App shell — two tracks: the rail, then everything else. The `.gd-redesign`
   marker on #app keeps these overriding selectors distinct from the legacy
   rules above (no-duplicate-selectors) while winning on specificity. */
#app.gd-redesign {
  height: 100vh;
  display: grid;
  /* One column; FIXED-height row tracks (not `1fr`) so the tall explorer
     content can't blow a track out past the viewport — `minmax(0,1fr)`
     resolved to the content's max-content here. The top bar tops the
     single column; descendants scroll within the main track. */
  grid-template:
    "bar" var(--gd-ctxbar-h)
    "main" calc(100vh - var(--gd-ctxbar-h))
    / 1fr;
}

/* ---- CONTEXT BAR ---- */
#gd-ctxbar {
  grid-area: bar;
  z-index: 280;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 12px;
  font-family: var(--gd-ui-font);
  background: var(--gd-panel);
  border-bottom: 1px solid var(--gd-line);
}
/* Brand — the way BACK to the editor from any management surface (and a
   no-op resting state on Build). A real button, not decoration. */
.gd-brand-home {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 32px;
  padding: 0 9px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--gd-ink);
  font-family: inherit;
  font-size: 0.8438rem;
  letter-spacing: -0.01em;
  cursor: pointer;
  transition: background 0.12s;
}
.gd-brand-home svg { color: var(--gd-flow); }
.gd-brand-home:hover { background: var(--gd-paper-2); }
.gd-brand-home:focus-visible { outline: 2px solid var(--gd-flow); outline-offset: 2px; }
.gd-ctx-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 2rem;
  padding: 0 11px;
  border: 1px solid var(--gd-line-2);
  border-radius: 8px;
  background: var(--gd-paper-2);
  color: var(--gd-ink);
  font-family: inherit;
  font-size: 0.7812rem;
  cursor: pointer;
  transition: border-color 0.12s;
}
.gd-ctx-chip:hover { border-color: var(--gd-ink-4); }
.gd-ctx-chip:focus-visible { outline: 2px solid var(--gd-flow); outline-offset: 2px; }
/* Explorer context row — workspace / branch / packages, the Build context.
   The branch chip is built by editor-branches.js with its own legacy chrome
   (26px, radius 3, mixed font sizes); unify size / radius / type here so the
   row reads as one control family. Chips wrap on narrow sidebars. */
#gd-exp-ctx {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 8px 10px 8px 14px;
  font-family: var(--gd-ui-font);
  border-bottom: 1px solid var(--gd-line);
}
#gd-exp-ctx .branch-chip-btn {
  height: 28px;
  border-radius: 8px;
  padding: 0 10px;
  font-size: 0.75rem;
  border: 1px solid var(--gd-line-2);
  background: var(--gd-paper-2);
  color: var(--gd-ink);
  max-width: 180px;
}
#gd-exp-ctx .branch-chip-btn:hover {
  background: var(--gd-paper-2);
  color: var(--gd-ink);
  border-color: var(--gd-ink-4);
}
#gd-exp-ctx .branch-chip-btn.branch-chip-non-default,
#gd-exp-ctx .branch-chip-btn.branch-chip-non-default:hover {
  background: var(--gd-flow);
  border-color: var(--gd-flow);
  color: var(--on-accent);
}
#gd-exp-ctx .gd-ctx-chip { min-height: 1.75rem; padding: 0 10px; font-size: 0.75rem; }
#gd-exp-ctx #branch-mount { display: flex; align-items: center; }
/* Collapsed-state branch badge on the left-edge expand tab: only rendered
   (by editor-branches.js) when the branch is NON-default — on main the tab
   stays a bare chevron and the screen stays clean. */
.gd-edge-branch {
  writing-mode: vertical-rl;
  padding: 6px 0;
  max-height: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 0.6562rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--on-accent);
}
.sidebar-expand-floating.gd-edge-nondefault {
  flex-direction: column;
  gap: 2px;
  height: auto;
  padding: 8px 0;
  background: var(--gd-flow);
  color: var(--on-accent);
  border-color: var(--gd-flow);
}
.sidebar-expand-floating.gd-edge-nondefault:hover {
  background: var(--gd-flow-2);
  color: var(--on-accent);
}
#gd-ctxbar .auth-lock-btn { height: 32px; }
#gd-ctxbar .auth-signin {
  font-size: 0.7812rem;
  padding: 6px 11px;
  border-radius: 8px;
  border: 1px solid var(--gd-line-2);
  background: var(--gd-paper-2);
  color: var(--gd-ink);
}
.gd-ctx-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--gd-flow); }
.gd-ctx-k {
  font-size: 0.6562rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--gd-ink-3);
}
.gd-bar-spacer { flex: 1 1 auto; }
/* The org/auth mounts flow inline on the bar's right side. */
#gd-ctxbar #org-mount,
#gd-ctxbar #auth-mount { display: flex; align-items: center; gap: 6px; }
#gd-ctxbar #org-mount { position: relative; }
/* Narrow / phone: the bar holds only brand + org + account now; let the org
   mount shrink + truncate so the bar, and the page, never overflow. The
   Build-context chips live in the Explorer, which is collapsed by default on
   narrow viewports. */
@media (width <= 640px) {
  #gd-ctxbar { gap: 6px; padding: 0 8px; min-width: 0; }
  #gd-ctxbar #org-mount { min-width: 0; }
  #gd-ctxbar #org-mount > * {
    max-width: 96px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}
/* (No surface-scoped chip hiding needed: the Build context lives inside the
   Explorer, and the management surfaces overlay the whole #main-container —
   the chrome disappears with the surface it belongs to.) */

/* ---- Context-bar popover (workspace switcher) ---- */
.gd-pop-scrim { position: fixed; inset: 0; z-index: 340; }
.gd-pop {
  position: fixed;
  z-index: 350;
  min-width: 234px;
  max-height: 60vh;
  overflow: auto;
  padding: 8px;
  border: 1px solid var(--gd-line-2);
  border-radius: 11px;
  background: var(--gd-panel);
  box-shadow: var(--gd-shadow-pop);
  font-family: var(--gd-ui-font);
}
.gd-pop h5 {
  margin: 2px 6px 8px;
  font-size: 0.6562rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gd-ink-3);
}
.gd-pop-item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 8px 9px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--gd-ink);
  font-family: inherit;
  font-size: 0.7812rem;
  text-align: left;
  cursor: pointer;
}
.gd-pop-item:hover { background: var(--gd-paper-2); }
.gd-pop-item.sel { background: var(--gd-flow-wash); color: var(--gd-flow-ink); font-weight: 600; }
.gd-pop-item .gd-pi { width: 18px; text-align: center; color: var(--gd-ink-3); font-family: var(--gd-mono); }
.gd-pop-item.sel .gd-pi { color: var(--gd-flow); }
.gd-pop-div { height: 1px; margin: 6px 4px; background: var(--gd-line); }
.gd-pop-row { display: flex; align-items: center; gap: 2px; }
.gd-pop-row .gd-pop-item { flex: 1 1 auto; }
.gd-pop-pin {
  flex: none;
  width: 30px; height: 30px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--gd-ink-4);
  font-size: 0.8125rem;
  cursor: pointer;
  opacity: 0.5;
  transition: opacity 0.12s, color 0.12s, background 0.12s;
}
.gd-pop-pin:hover { background: var(--gd-paper-2); opacity: 1; }
.gd-pop-pin.pinned { color: var(--gd-flow); opacity: 1; background: var(--gd-flow-wash); }
.gd-pop-pin:focus-visible { outline: 2px solid var(--gd-flow); outline-offset: 1px; }
.gd-pop-hint {
  margin: 8px 6px 2px;
  font-size: 0.6562rem;
  line-height: 1.4;
  color: var(--gd-ink-3);
}
/* Workspace popover — section caption + the project checklist rows. */
.gd-pop-cap {
  margin: 4px 6px;
  font-size: 0.625rem;
  letter-spacing: 0.04em;
  color: var(--gd-ink-4);
}
/* Bound the workspace popover so long namespace descriptions ellipsize inside
   it instead of stretching the whole panel wide. */
#gd-ws-pop { max-width: 340px; }
.gd-ws-opt { min-width: 0; }
.gd-ws-opt .gd-ws-nm { flex: 0 0 auto; }
.gd-ws-opt .gd-ws-desc {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: right;
  font-size: 0.6562rem;
  color: var(--gd-ink-4);
}
.gd-ws-opt.sel .gd-ws-desc { color: var(--gd-flow-ink); }
.gd-ws-hidden { flex: 1 1 auto; color: var(--gd-ink-3); cursor: default; }
.gd-ws-hidden .gd-pi { color: var(--gd-ink-4); }

/* ---- Explorer slim head (brand + mounts moved to the rail / context bar) ---- */
.gd-exp-head {
  display: flex;
  align-items: center;
  height: 40px;
  padding: 0 14px;
  font-family: var(--gd-ui-font);
  border-bottom: 1px solid var(--gd-line);
}
.gd-exp-title {
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gd-ink-3);
}
/* Collapse chevron — pushed to the right end of the Explorer header, subtle
   until hovered. The toggle sits WITH the panel it controls. */
.gd-exp-collapse {
  margin-left: auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0;
  background: none; border: none; border-radius: 5px;
  color: var(--gd-ink-3); cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.gd-exp-collapse:hover { background: var(--gd-paper-2); color: var(--gd-ink); }
.gd-exp-collapse:focus-visible { outline: 2px solid var(--gd-flow); outline-offset: 1px; }

/* ---- MAIN: explorer | canvas | inspector ---- */
#app.gd-redesign #main-container {
  grid-area: main;
  position: relative;
  height: 100%;
  min-height: 0;
  display: grid;
  grid-template-columns: var(--sidebar-width) minmax(0, 1fr) var(--gd-inspector-w);
  /* Row 2 is the diagnostics drawer under the canvas; explorer and
     inspector span both rows and keep their full height. */
  grid-template-rows: minmax(0, 1fr);
}
body.sidebar-collapsed #app.gd-redesign #main-container {
  grid-template-columns: 0 minmax(0, 1fr) var(--gd-inspector-w);
}

/* Explorer — the former absolute-overlay sidebar, now a real grid column. */
#app.gd-redesign #side-menu {
  /* explicit track: display:none (collapsed) removes this pane from grid
     auto-placement — unpinned, the canvas would shift into column 1 (0px) */
  grid-column: 1;
  grid-row: 1 / -1;
  position: relative;
  top: auto; left: auto;
  width: auto; height: 100%;
  min-height: 0;
  transform: none;
  transition: none;
  overflow: hidden auto;
  z-index: auto;
  background: var(--gd-panel);
  border-right: 1px solid var(--gd-line);
}
body.sidebar-collapsed #app.gd-redesign #side-menu { display: none; }
/* Blend the legacy header band into the new panel. */
#app.gd-redesign .menu-header {
  font-family: var(--gd-ui-font);
  background: var(--gd-panel);
  border-bottom: 1px solid var(--gd-line);
}

/* Canvas — the dot-grid drafting substrate (the signature). */
#app.gd-redesign #graph-container {
  grid-column: 2;
  grid-row: 1;
  position: relative;
  min-height: 0;
  overflow: hidden;
  background-color: var(--gd-paper);
  background-image: radial-gradient(var(--gd-grid) 1.1px, transparent 1.1px);
  background-size: 22px 22px;
  background-position: -1px -1px;
}


/* ---- RIGHT INSPECTOR ---- */
#gd-inspector {
  grid-column: 3;
  grid-row: 1 / -1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: auto;
  font-family: var(--gd-ui-font);
  background: var(--gd-panel);
  border-left: 1px solid var(--gd-line);
}
.gd-insp-empty {
  margin: auto;
  max-width: 280px;
  padding: 0 28px;
  text-align: center;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--gd-ink-2); /* ink-3 fails AA (3.63) on the panel; ink-2 clears it */
}
/* Inspector content — identity Overview (first slice; rich panels via partial). */
.gd-insp-head {
  position: relative;
  padding: 14px 16px 12px;
  border-bottom: 1px solid var(--gd-line);
}
/* Close ✕ — only shown when the inspector is a bottom sheet (≤1100). */
.gd-insp-close {
  position: absolute;
  top: 8px;
  right: 10px;
  width: 30px;
  height: 30px;
  padding: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: var(--gd-r-sm);
  font-size: 1.375rem;
  line-height: 1;
  color: var(--gd-ink-3);
  cursor: pointer;
}
.gd-insp-close:hover { background: var(--gd-line); color: var(--gd-ink); }
.gd-insp-title { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.gd-insp-name {
  font-family: var(--gd-mono);
  font-weight: 700;
  font-size: 0.9375rem;
  color: var(--gd-ink);
}
.gd-insp-kind {
  padding: 1px 6px;
  border: 1px solid var(--gd-line-2);
  border-radius: 5px;
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gd-ink-3);
}
.gd-insp-ns {
  margin-top: 4px;
  font-family: var(--gd-mono);
  font-size: 0.6875rem;
  color: var(--gd-ink-3);
}
.gd-insp-desc {
  margin-top: 8px;
  font-size: 0.7812rem;
  line-height: 1.5;
  color: var(--gd-ink-2);
}
.gd-insp-tabs {
  display: flex;
  flex: none;
  gap: 2px;
  padding: 0 12px;
  border-bottom: 1px solid var(--gd-line);
}
.gd-insp-tab {
  appearance: none;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: 8px 10px;
  font: inherit;
  font-size: 0.75rem;
  color: var(--gd-ink-3);
  cursor: pointer;
}
.gd-insp-tab:hover { color: var(--gd-ink); }
.gd-insp-tab.active { color: var(--gd-flow); border-bottom-color: var(--gd-flow); font-weight: 600; }
.gd-insp-tab:focus-visible { outline: 2px solid var(--gd-flow); outline-offset: -2px; }
.gd-insp-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: 12px 16px 28px;
}
.gd-insp-row { display: flex; gap: 10px; align-items: baseline; padding: 6px 0; }
.gd-insp-k {
  flex: none;
  min-width: 64px;
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--gd-ink-3);
}
.gd-insp-v { display: flex; flex-wrap: wrap; gap: 5px; }
.gd-chip {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 8px;
  border-radius: 6px;
  font-family: var(--gd-mono);
  font-size: 0.6875rem;
  font-weight: 600;
}
.gd-chip-ref { background: var(--gd-ref-wash); color: var(--gd-flow-ink); }
/* The Returns type chip holds a whole (possibly long, shaped) type signature —
   every HTTP handler/router return type wraps to several lines. Let it grow in
   normal flow instead of the base chip's fixed 20px height, so it never spills
   onto the Effects row below. Returns-only (no other `.gd-chip-type` use). */
.gd-chip-type {
  background: var(--gd-lit-wash); color: var(--gd-ink-2);
  height: auto; min-height: 20px; align-items: flex-start;
  padding: 3px 8px; line-height: 1.5; max-width: 100%;
  white-space: normal; overflow-wrap: anywhere;
}
.gd-insp-pure { font-size: 0.75rem; font-style: italic; color: var(--gd-ink-3); }
.gd-insp-runs-head {
  margin: 16px 0 8px;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gd-ink-3);
}
.gd-insp-runs { font-size: 0.75rem; }
.gd-insp-runs-loading { padding: 8px 0; font-size: 0.75rem; color: var(--gd-ink-3); }

/* ---- Fn peek (editor-fn-peek.js): a fn's bindings in a floating
 *      read-only panel — reading a leaf ref without re-rooting. ---- */
.fn-peek-panel {
  position: fixed;
  z-index: 10003;
  width: 400px;
  max-width: calc(100vw - 24px);
  max-height: 60vh;
  display: flex; flex-direction: column;
  background: var(--card-bg); color: var(--fg);
  border: 1px solid var(--card-border);
  border-radius: 10px;
  box-shadow: var(--shadow-xl);
}
.fn-peek-head {
  display: flex; align-items: baseline; gap: 8px;
  padding: 10px 12px 6px;
  border-bottom: 1px solid var(--card-border);
}
.fn-peek-title { font-family: var(--gd-mono); font-weight: 700; }
.fn-peek-ns {
  flex: 1; min-width: 0;
  font-size: 0.6875rem; color: var(--gd-ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fn-peek-open {
  border: 1px solid var(--card-border); border-radius: 6px;
  background: transparent; color: var(--fg);
  font-size: 0.75rem; padding: 2px 8px; cursor: pointer;
}
.fn-peek-open:hover { background: var(--hover-bg); }
.fn-peek-close {
  border: 0; background: transparent; color: var(--muted-fg);
  font-size: 1rem; line-height: 1; padding: 2px 4px; cursor: pointer;
}
.fn-peek-close:hover { color: var(--fg); }
.fn-peek-open:focus-visible,
.fn-peek-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.fn-peek-body { overflow-y: auto; padding: 8px 12px 12px; font-size: 0.75rem; }

/* ---- Smart views (editor-smart-views.js): the ✦ chip + popover —
 *      saved virtual groupings the server computes per rule. ---- */
.gd-views-chip {
  display: inline-flex; align-items: center; gap: 4px;
  margin: 2px 8px 0; padding: 2px 8px;
  border: 1px dashed var(--card-border); border-radius: 999px;
  background: transparent; color: var(--muted-fg);
  font-size: 0.6875rem; cursor: pointer;
}
.gd-views-chip:hover { color: var(--fg); background: var(--hover-bg); }
.gd-views-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.gd-views-chip.gd-views-active {
  border-style: solid; border-color: var(--accent); color: var(--fg);
}
.gd-views-pop {
  position: fixed;
  z-index: 10003;
  width: 320px;
  max-width: calc(100vw - 24px);
  background: var(--card-bg); color: var(--fg);
  border: 1px solid var(--card-border);
  border-radius: 10px;
  box-shadow: var(--shadow-xl);
  padding: 10px 12px;
}
.gd-views-pop-head {
  font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--gd-ink-3);
  margin-bottom: 4px;
}
.gd-views-pop-hint {
  font-size: 0.6875rem; font-style: italic; color: var(--muted-fg);
  margin-bottom: 8px;
}
.gd-views-list { display: flex; flex-direction: column; margin-bottom: 8px; }
.gd-views-row { display: flex; align-items: center; gap: 4px; }
.gd-views-apply {
  flex: 1; min-width: 0; text-align: left;
  border: 0; border-radius: 4px; padding: 3px 6px; margin: 0;
  background: transparent; color: var(--fg); cursor: pointer;
  font-family: var(--gd-mono); font-size: 0.75rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gd-views-apply:hover { background: var(--hover-bg); }
.gd-views-del {
  border: 0; background: transparent; color: var(--muted-fg);
  cursor: pointer; padding: 2px 6px; border-radius: 4px;
}
.gd-views-del:hover { color: var(--fg); background: var(--hover-bg); }
.gd-views-row-clear {
  border: 0; border-radius: 4px; padding: 3px 6px; margin: 0 0 4px;
  background: transparent; color: var(--muted-fg); cursor: pointer;
  text-align: left; font-size: 0.75rem;
}
.gd-views-row-clear:hover { color: var(--fg); background: var(--hover-bg); }
.gd-views-empty { font-size: 0.75rem; font-style: italic; color: var(--gd-ink-3); padding: 2px 6px; }
.gd-views-form { display: flex; flex-direction: column; gap: 6px; }
.gd-views-input {
  font-family: var(--gd-mono); font-size: 0.75rem;
  padding: 4px 6px;
  background: var(--card-bg); color: var(--fg);
  border: 1px solid var(--card-border); border-radius: 6px;
}
.gd-views-save {
  align-self: flex-end;
  border: 1px solid var(--card-border); border-radius: 6px;
  background: transparent; color: var(--fg);
  font-size: 0.75rem; padding: 3px 10px; cursor: pointer;
}
.gd-views-save:hover { background: var(--hover-bg); }
.gd-views-apply:focus-visible,
.gd-views-del:focus-visible,
.gd-views-row-clear:focus-visible,
.gd-views-input:focus-visible,
.gd-views-save:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---- Recent fns (editor-sidebar.js): the navigation trail above
 *      the tree — last selected named fns, rows navigate back. ---- */
#gd-recent-fns { padding: 4px 8px 2px; }
.gd-recent-cap {
  font-size: 0.625rem; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--gd-ink-3);
  padding: 2px 4px;
}
.gd-recent-row {
  display: block; width: 100%;
  border: 0; border-radius: 4px; margin: 0; padding: 2px 6px;
  background: transparent; color: var(--fg);
  text-align: left; cursor: pointer;
  font-family: var(--gd-mono); font-size: 0.75rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gd-recent-line { display: flex; align-items: center; }
.gd-recent-line .gd-recent-row { flex: 1; min-width: 0; }
.gd-recent-pin {
  flex: none;
  border: 0; background: transparent; color: var(--muted-fg);
  cursor: pointer; padding: 2px 6px; border-radius: 4px;
  font-size: 0.75rem;
  opacity: 0;
}
.gd-recent-line:hover .gd-recent-pin,
.gd-recent-line:focus-within .gd-recent-pin { opacity: 1; }
.gd-recent-pin:hover { color: var(--fg); background: var(--hover-bg); }
.gd-recent-pin:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; opacity: 1; }
.gd-recent-row:hover { background: var(--hover-bg); }
.gd-recent-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---- Inspector "Used by" (editor-fn-usages.js): who extends /
 *      references the selected fn, grouped by kind, rows navigate. ---- */
.gd-insp-usages { margin-top: 18px; }
.gd-insp-usages-head {
  display: flex; align-items: baseline; gap: 6px;
  margin-bottom: 4px;
  font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--gd-ink-3);
}
.gd-insp-usages-count { font-weight: 400; color: var(--gd-ink-3); }
.gd-insp-usage-filter {
  width: 100%;
  margin: 4px 0 2px;
  font-family: var(--gd-mono); font-size: 0.75rem;
  padding: 3px 6px;
  background: var(--card-bg); color: var(--fg);
  border: 1px solid var(--card-border); border-radius: 6px;
}
.gd-insp-usage-filter:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.gd-insp-usage-glabel {
  margin: 8px 0 2px;
  font-size: 0.6875rem; color: var(--gd-ink-3);
}
.gd-insp-usage-list { display: flex; flex-direction: column; }
.gd-insp-usage-row {
  display: flex; align-items: baseline; gap: 6px;
  padding: 3px 4px; margin: 0; border: 0; border-radius: 4px;
  background: transparent; text-align: left; cursor: pointer;
  font-size: 0.75rem;
}
.gd-insp-usage-row:hover { background: var(--hover-bg); }
.gd-insp-usage-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.gd-insp-usage-name {
  font-family: var(--gd-mono); font-weight: 600; color: var(--gd-flow-ink);
  overflow-wrap: anywhere;
}
.gd-insp-usage-slot { font-family: var(--gd-mono); color: var(--gd-ink-3); }
.gd-insp-usage-ns {
  margin-left: auto; padding-left: 8px;
  font-size: 0.6875rem; color: var(--gd-ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 45%;
}
.gd-insp-usage-more { padding: 3px 4px; font-size: 0.6875rem; font-style: italic; color: var(--gd-ink-3); }

/* ---- Inspector detail (server partial /partials/inspector-detail):
 *      bindings (the 3-way literal/ref/free language) + provenance. ---- */
.gd-insp-detail-host { margin-top: 4px; }
.gd-insp-sec { margin-top: 18px; }
.gd-insp-sec-head {
  margin-bottom: 8px;
  font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--gd-ink-3);
}
.gd-insp-sec-empty { font-size: 0.75rem; font-style: italic; color: var(--gd-ink-3); }
.gd-bind-list { display: flex; flex-direction: column; }
.gd-bind-row {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px;
  padding: 6px 0; border-bottom: 1px solid var(--gd-line);
}
.gd-bind-name {
  font-family: var(--gd-mono); font-size: 0.75rem; font-weight: 600;
  color: var(--gd-ink);
}
/* The 3-way chip is encoded by SHAPE (free = dashed outline) as well as
   hue, so the newcomer trap reads even without colour. */
.gd-bind {
  margin-left: auto; max-width: 210px;
  display: inline-flex; align-items: center;
  padding: 1px 8px; border: 1px solid transparent; border-radius: 6px;
  font-family: var(--gd-mono); font-size: 0.6875rem; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gd-bind-lit { background: var(--gd-lit-wash); color: var(--gd-lit); }
.gd-bind-ref { background: var(--gd-ref-wash); color: var(--gd-flow-ink); }
.gd-bind-free { background: transparent; color: var(--gd-free); border: 1px dashed var(--gd-free); }
.gd-bind-type {
  flex-basis: 100%;
  font-family: var(--gd-mono); font-size: 0.625rem; color: var(--gd-ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gd-prov-row { display: flex; gap: 10px; align-items: baseline; padding: 5px 0; }
.gd-prov-k {
  flex: none; min-width: 96px;
  font-size: 0.625rem; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--gd-ink-3);
}
.gd-prov-v { font-size: 0.75rem; color: var(--gd-ink-2); min-width: 0; overflow-wrap: anywhere; }
.gd-prov-mono { font-family: var(--gd-mono); }

/* ---- Progressive disclosure: compact cards hide the dense metadata strips
 *      (return-type + effects) until the nav-controls "Details" toggle. ---- */
body.gd-cards-compact .return-type-strip,
body.gd-cards-compact .effects-strip { display: none; }
.nav-btn[aria-pressed="true"] { color: var(--gd-flow); }
.nav-btn svg { width: 15px; height: 15px; }

/* ---- Placeholder overlay for not-yet-rebuilt surfaces ---- */
#gd-surface-overlay {
  position: absolute;
  inset: 0;
  z-index: 250;
  display: grid;
  place-items: center;
  font-family: var(--gd-ui-font);
  background: var(--gd-paper);
}
#gd-surface-overlay[hidden] { display: none; }
.gd-surface-card {
  max-width: 380px;
  padding: 26px 30px;
  text-align: center;
  border-radius: 14px;
  background: var(--gd-panel);
  border: 1px solid var(--gd-line);
  box-shadow: var(--gd-shadow);
}
.gd-surface-title {
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--gd-ink);
}
.gd-surface-sub {
  margin: 8px 0 18px;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--gd-ink-2);
}
.gd-surface-back {
  height: 34px;
  padding: 0 16px;
  border-radius: 8px;
  border: 1px solid var(--gd-flow);
  background: var(--gd-flow);
  color: var(--on-accent);
  font-family: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.12s;
}
.gd-surface-back:hover { background: var(--gd-flow-2); }
.gd-surface-back:focus-visible { outline: 2px solid var(--gd-flow); outline-offset: 2px; }

/* ---- OPERATE surface — relocated ops/admin panels ---- */
#gd-operate,
#gd-platform {
  position: absolute;
  inset: 0;
  z-index: 250;
  overflow: auto;
  padding: 26px 30px 60px;
  font-family: var(--gd-ui-font);
  background: var(--gd-paper);
}
#gd-operate[hidden], #gd-platform[hidden] { display: none; }
/* (The Platform ENTRY is a menu item in the account chip's menu now —
   editor-auth.js gates it on body.gd-platform / the capability probe.) */
/* Tenancy-adaptive copy: show the org/RBAC wording only when the tenancy addon
   is active (body.gd-tenancy, set from X-Graphden-Capabilities); a single-tenant
   instance sees the operational-only variant. */
.gd-tenancy-only { display: none; }
body.gd-tenancy .gd-tenancy-only { display: revert; }
body.gd-tenancy .gd-solo-only { display: none; }
.gd-operate-head { max-width: 1100px; margin: 0 auto 18px; }
.gd-operate-head h1 {
  margin: 0 0 4px;
  font-size: 1.375rem;
  letter-spacing: -0.01em;
  color: var(--gd-ink);
}
.gd-operate-head p {
  margin: 0;
  max-width: 62ch;
  font-size: 0.8438rem;
  line-height: 1.5;
  color: var(--gd-ink-2);
}
/* Section-list layout: a vertical list of sections on the left, ONE full-width
   pane on the right — a settings-style screen, not a grid of look-alike tiles.
   Scales to long member/grant tables and reads calm, not like a cockpit. */
.gd-op-body {
  max-width: 1100px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 196px minmax(0, 1fr);
  gap: 28px;
  align-items: start;
}
.gd-op-nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  position: sticky;
  top: 0;
}
.gd-op-nav-btn {
  appearance: none;
  text-align: left;
  font: inherit; font-size: 0.8438rem;
  color: var(--gd-ink-2);
  background: transparent;
  border: 0; border-radius: 8px;
  padding: 8px 12px;
  cursor: pointer;
}
.gd-op-nav-btn:hover { background: var(--gd-paper-2); color: var(--gd-ink); }
.gd-op-nav-btn[aria-current="page"] {
  background: var(--gd-flow-wash);
  /* flow-INK, not flow: the accent proper is 4.14:1 on the wash — under AA
     for 13.5px text. The ink variant clears it in both themes (6.5 / 9.1). */
  color: var(--gd-flow-ink);
  font-weight: 600;
}
/* The pane holds exactly one section at a time; the rest are [hidden]. */
#gd-operate-panels, #gd-platform-panels, #gd-settings-panels { display: block; min-width: 0; }
.gd-op-card { min-width: 0; width: 100%; }
.gd-op-card[hidden] { display: none; }
.gd-op-card-title {
  margin: 0 0 18px;
  font-size: 1.125rem; font-weight: 600; letter-spacing: -0.01em;
  text-transform: none;
  color: var(--gd-ink);
}
/* ONE heading per pane — the card title above. Drop the redundant headings the
   build wrapper / server partials still carry (the section list already names
   the pane). */
.gd-op-card .ns-header { display: none; }
.gd-op-card .ns-children > h3:first-child,
.gd-op-card .ns-children > * > h3:first-child { display: none; }

/* Forms across ops panels — inline field+button ROWS with tokened controls,
   not bare stacked inputs with a button dangling underneath. The panels'
   original `.sidebar-*-admin .*-form { flex-direction: column }` styling was
   for the NARROW rail; in the wide pane, override to a row. The id scope
   (1,0,1) beats those two-class column rules (0,2,0). */
#gd-operate-panels form, #gd-platform-panels form {
  display: flex; flex-flow: row wrap;
  gap: 8px; align-items: center; margin-top: 14px;
}
#gd-operate-panels form input, #gd-operate-panels form select,
#gd-platform-panels form input, #gd-platform-panels form select {
  flex: 1 1 150px; min-width: 120px; width: auto;
}
#gd-operate-panels form button, #gd-platform-panels form button { flex: 0 0 auto; }
.gd-op-card input[type="text"], .gd-op-card input[type="email"],
.gd-op-card input[type="search"], .gd-op-card input:not([type]) {
  font: inherit; font-size: 0.8125rem;
  color: var(--gd-ink); background: var(--gd-paper);
  border: 1px solid var(--gd-line); border-radius: 7px;
  padding: 7px 10px;
}
.gd-op-card input:focus-visible {
  outline: 2px solid var(--gd-flow); outline-offset: 1px; border-color: var(--gd-flow);
}
.gd-op-card form button, .gd-op-card .grant-create-btn, .gd-op-card .invite-btn {
  font: inherit; font-size: 0.8125rem; font-weight: 600;
  color: var(--on-accent); background: var(--gd-flow);
  border: 0; border-radius: 7px; padding: 8px 14px; cursor: pointer;
}
.gd-op-card form button:hover, .gd-op-card .grant-create-btn:hover { filter: brightness(1.06); }
/* Full-surface admin TABLES + per-row edit forms. The panels reuse the
   `.grants-admin-table` class whose `width:100%` is scoped to the retired
   `.sidebar-*` mounts, so on the wide surface the table shrank to content.
   And a per-row edit form (a plan `select` + Apply in one cell) inherited the
   wrapping `.gd-op-card form` layout and dropped Apply BELOW the select. Fix
   both on the surface: full-width tables, and per-row cell forms stay on one
   line (select then Apply, side by side). */
#gd-operate-panels table,
#gd-platform-panels table { width: 100%; }
#gd-operate-panels table form,
#gd-platform-panels table form {
  flex-wrap: nowrap;
  gap: 6px;
  margin-top: 0;
  align-items: center;
}
#gd-operate-panels table form select,
#gd-operate-panels table form input,
#gd-platform-panels table form select,
#gd-platform-panels table form input {
  flex: 0 1 auto;
  min-width: 0;
  width: auto;
}
/* Consistent tokened selects across the Operate panels (tenancy Users/Orgs
   plan+role pickers, branch scopers, …) — they were raw browser controls. */
#gd-operate-panels select,
#gd-platform-panels select {
  font: inherit; font-size: 0.8125rem;
  color: var(--gd-ink); background: var(--gd-paper);
  border: 1px solid var(--gd-line); border-radius: 6px;
  padding: 5px 8px; cursor: pointer;
}
#gd-operate-panels select:hover, #gd-platform-panels select:hover { border-color: var(--gd-flow); }
#gd-operate-panels select:focus-visible,
#gd-platform-panels select:focus-visible { outline: 2px solid var(--gd-flow); outline-offset: 1px; }

/* ---- Settings surface (personal editor settings) ---- */
#gd-settings, #gd-workspaces {
  position: absolute;
  inset: 0;
  z-index: 250;
  overflow: auto;
  padding: 26px 30px 60px;
  font-family: var(--gd-ui-font);
  background: var(--gd-paper);
}
#gd-settings[hidden], #gd-workspaces[hidden] { display: none; }
/* Settings shares the Operate nav+pane shell (.gd-op-body / .gd-op-nav /
   .gd-op-card). Its panes are short rows of copy + one control, so cap the
   line length — a 900px-wide settings row reads like a lost table. */
.gd-set-pane { max-width: 640px; }
.gd-set-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 8px 0;
}
.gd-set-row + .gd-set-row { border-top: 1px solid var(--gd-line); }
.gd-set-copy { min-width: 0; }
.gd-set-label { font-size: 0.8125rem; color: var(--gd-ink); }
.gd-set-hint { margin-top: 2px; font-size: 0.7188rem; line-height: 1.45; color: var(--gd-ink-3); }
.gd-set-btn {
  flex: none;
  font-size: 0.75rem; padding: 5px 12px; cursor: pointer;
  background: var(--gd-panel); color: var(--gd-ink);
  border: 1px solid var(--gd-line-2); border-radius: var(--gd-r-sm);
}
.gd-set-btn:hover { border-color: var(--gd-flow); color: var(--gd-flow); }
.gd-set-btn:focus-visible { outline: 2px solid var(--gd-flow); outline-offset: 2px; }
a.gd-set-btn { text-decoration: none; display: inline-flex; align-items: center; }
/* ---- Settings: Account section (the merged /account page) ---- */
.gd-acct-h {
  margin: 16px 0 6px;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--gd-ink-3);
}
.gd-acct-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 7px 0;
}
.gd-acct-row + .gd-acct-row { border-top: 1px solid var(--gd-line); }
.gd-acct-btns { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.gd-acct-secret {
  display: block;
  margin: 8px 0;
  padding: 6px 8px;
  word-break: break-all;
  user-select: all;
}
.gd-acct-input, .gd-acct-mint select {
  height: 30px;
  padding: 0 9px;
  border: 1px solid var(--gd-line-2);
  border-radius: 8px;
  background: var(--gd-paper-2);
  color: var(--gd-ink);
  font-family: var(--gd-ui-font);
  font-size: 0.7812rem;
}
.gd-acct-input:focus-visible, .gd-acct-mint select:focus-visible {
  outline: 2px solid var(--gd-flow); outline-offset: 1px;
}
.gd-acct-scopes { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; margin: 10px 0; }
.gd-acct-scopes label {
  display: flex; align-items: center; gap: 7px;
  font-size: 0.7812rem; color: var(--gd-ink-2);
}
.gd-acct-mint { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
.gd-acct-mint .gd-acct-input { flex: 1 1 auto; min-width: 0; }
.gd-acct-banner {
  margin: 10px 0;
  padding: 10px 12px;
  border: 1px solid var(--gd-warn);
  border-radius: 8px;
  font-size: 0.7812rem;
  color: var(--gd-ink-2);
}
.gd-acct-banner .gd-set-btn { margin-top: 8px; }
.gd-acct-msg { margin-top: 10px; font-size: 0.7812rem; min-height: 16px; }
.gd-acct-msg.ok { color: var(--gd-ok); }
.gd-acct-msg.err { color: var(--gd-crit); }
.gd-set-acct-btns { display: flex; gap: 8px; flex: none; }
.gd-set-badge {
  font-size: 0.625rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em;
  padding: 1px 6px; margin-left: 6px; border-radius: var(--gd-r-sm);
  background: var(--gd-flow); color: var(--gd-on-accent);
}
.gd-set-code {
  font-family: var(--gd-mono); font-size: 0.75rem; color: var(--gd-ink-2);
  background: var(--gd-paper-2); padding: 2px 6px; border-radius: 4px;
}
.gd-set-versions { margin: 8px 0 12px; display: flex; flex-direction: column; gap: 4px; }
.gd-set-ver { display: flex; justify-content: space-between; font-size: 0.7188rem; color: var(--gd-ink-3); }
.gd-set-ver code { font-family: var(--gd-mono); color: var(--gd-ink-2); }
.gd-set-caps { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.gd-cap {
  font-family: var(--gd-mono); font-size: 0.6875rem; font-weight: 600;
  padding: 2px 8px; border-radius: 6px;
}
.gd-cap-on { background: var(--gd-flow-wash); color: var(--gd-flow-ink); }
.gd-cap-off { background: var(--gd-crit-wash); color: var(--gd-crit); }

/* ---- Workspaces surface ---- */
.gd-ws-grid {
  max-width: 1100px; margin: 0 auto;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 16px; align-items: start;
}
.gd-ws-summary { font-size: 0.7812rem; color: var(--gd-ink-2); margin: 4px 0 12px; }
.gd-ws-summary b { color: var(--gd-ink); }
.gd-ws-nslist { display: flex; flex-direction: column; gap: 4px; }
.gd-ws-nsrow { display: flex; align-items: center; gap: 6px; }
.gd-ws-nsbtn {
  flex: 1 1 auto; min-width: 0; text-align: left;
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--gd-mono); font-size: 0.75rem;
  padding: 6px 10px; cursor: pointer;
  background: var(--gd-panel); color: var(--gd-ink);
  border: 1px solid var(--gd-line-2); border-radius: var(--gd-r-sm);
}
.gd-ws-nsbtn .gd-pi { color: var(--gd-ink-3); }
.gd-ws-nsbtn:hover { border-color: var(--gd-flow); }
.gd-ws-nsbtn.active { background: var(--gd-flow-wash); color: var(--gd-flow-ink); border-color: var(--gd-flow); font-weight: 600; }
.gd-ws-nsbtn.active .gd-pi { color: var(--gd-flow); }
.gd-ws-pin {
  flex: none; width: 30px; height: 30px; cursor: pointer;
  background: none; border: 1px solid transparent; border-radius: var(--gd-r-sm);
  opacity: 0.4; font-size: 0.8125rem;
}
.gd-ws-pin:hover { opacity: 0.8; }
.gd-ws-pin.pinned { opacity: 1; background: var(--gd-flow-wash); border-color: var(--gd-flow); }

/* ---- Responsive: iPad / narrow — inspector becomes a bottom sheet.
 *      Below the sidebar breakpoint the explorer auto-collapses (handled
 *      by editor-prefs.js). ---- */
@media (width <= 1100px) {
  /* Two columns (explorer | canvas); the inspector leaves the grid and
     returns as a bottom sheet, revealed on selection (body.gd-insp-open). */
  #app.gd-redesign #main-container { grid-template-columns: var(--sidebar-width) minmax(0, 1fr); }
  body.sidebar-collapsed #app.gd-redesign #main-container { grid-template-columns: 0 minmax(0, 1fr); }
  #gd-inspector {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 260;
    max-height: 60vh;
    border-left: none;
    border-top: 1px solid var(--gd-line);
    box-shadow: var(--shadow-lg);
    transform: translateY(100%);
    transition: transform 0.18s ease;
  }
  body.gd-insp-open #gd-inspector { transform: translateY(0); }
  .gd-insp-close { display: inline-flex; }
}

/* =========================================================================
   TRACE VIEW — the step-through CALL TREE panel of a stored run's
   :path-trace (server hiccup from /partials/execute-trace;
   editor-trace-view.js owns selection/stepping/canvas highlight).
   ========================================================================= */

.execute-history-tree-btn {
  padding: 1px 6px;
  font-family: inherit; font-size: 0.625rem;
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--card-border);
  border-radius: 3px;
  cursor: pointer;
}
.execute-history-tree-btn:hover { background: var(--hover-bg); }

.trace-view-panel {
  position: fixed;
  top: 64px; right: 16px; bottom: 16px;
  width: 360px;
  z-index: 10002;
  background: var(--card-bg); color: var(--card-fg);
  border: 1px solid var(--card-border);
  border-radius: 6px;
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  font-size: 0.75rem;
}
.trace-view-body { display: flex; flex-direction: column; min-height: 0; height: 100%; }
.trace-view-header {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--card-border);
  background: var(--header-bg); color: var(--header-fg);
}
.trace-view-title { font-weight: 600; flex: 1 1 auto; }
.trace-note { color: var(--muted-fg); font-size: 0.625rem; }
.trace-step-btn, .trace-close-btn {
  background: transparent;
  color: var(--header-fg);
  border: 1px solid var(--light-border);
  border-radius: 3px;
  font-size: 0.6875rem;
  padding: 1px 7px;
  cursor: pointer;
}
.trace-step-btn:hover, .trace-close-btn:hover { background: var(--hover-bg); }

.trace-rows { overflow-y: auto; flex: 1 1 auto; padding: 4px 0; }
.trace-empty { padding: 12px; color: var(--muted-fg); }
.trace-row {
  padding: 3px 10px;
  cursor: pointer;
  border-left: 2px solid transparent;
  display: block;
}
.trace-row:hover { background: var(--hover-bg); }
.trace-row.trace-row-selected {
  background: var(--selected-bg);
  border-left-color: var(--accent);
}
.trace-row-name {
  font-family: monospace; font-size: 0.6875rem;
  margin-right: 6px;
  overflow-wrap: anywhere;
}
.trace-chip {
  display: inline-block;
  font-size: 0.5938rem;
  line-height: 1.5;
  padding: 0 5px;
  margin-right: 4px;
  border-radius: 7px;
  border: 1px solid var(--card-border);
  color: var(--muted-fg);
  white-space: nowrap;
}
.trace-chip-time { color: var(--fg); }
.trace-chip-cache { color: var(--muted-fg); }
.trace-chip-secret, .trace-chip-unknown, .trace-chip-derived {
  color: var(--error-fg-strong);
  border-color: var(--error-fg-strong);
}
.trace-chip-truncated { color: var(--muted-fg); }
.trace-value { margin: 2px 0 2px 6px; }
.trace-value summary {
  cursor: pointer;
  color: var(--accent);
  font-size: 0.625rem;
}
.trace-value pre {
  margin: 3px 0 0;
  padding: 5px 7px;
  max-height: 180px;
  overflow: auto;
  font-size: 0.6562rem;
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--light-border);
  border-radius: 3px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* =========================================================================
   OPERATE → DEBUG — the «catch next request» trap panel
   (/partials/debug-catch; editor-debug.js owns arm/cancel/refresh).
   ========================================================================= */

.gd-run-trap { padding: 6px 8px 8px; margin-bottom: 6px; border-bottom: 1px dashed var(--light-border); }
.debug-panel { display: flex; flex-direction: column; gap: 10px; font-size: 0.75rem; }
.debug-help { color: var(--muted-fg); font-size: 0.6875rem; }
.debug-arm-form { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.debug-prefix-input {
  flex: 1 1 220px;
  font-family: monospace; font-size: 0.6875rem;
  padding: 4px 6px;
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--input-border);
  border-radius: 3px;
}
.debug-capture-label {
  display: inline-flex; align-items: center; gap: 4px;
  color: var(--card-fg); font-size: 0.6875rem;
  white-space: nowrap;
}
.debug-arm-btn, .debug-cancel-btn, .debug-open-trace-btn {
  padding: 3px 10px;
  font-family: inherit; font-size: 0.6875rem;
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--card-border);
  border-radius: 3px;
  cursor: pointer;
}
.debug-arm-btn:hover, .debug-cancel-btn:hover, .debug-open-trace-btn:hover {
  background: var(--hover-bg);
}
.debug-armed-line {
  display: flex; align-items: center; gap: 8px;
  color: var(--card-fg);
}
.debug-armed-dot { color: var(--success-fg); }
.debug-last-capture { color: var(--muted-fg); }

/* ============================================================================
 * Interactive tutorial (editor-tour.js) — spotlight ring + step popover.
 * The spotlight is an accent ring on the current stage plus a full-viewport
 * SVG scrim (same token the modal backdrop uses) whose mask punches one
 * hole per bright region — every element a multi-action step involves
 * stays readable, not just the current stage. pointer-events stays none so
 * the user CLICKS THE REAL UI — the tour only observes. The scrim sits
 * ABOVE the base layout (sidebar 200, ctxbar 280) but BELOW every
 * transient surface the editor floats — and the LOWEST of those is the
 * 300 family (auth/branch/org popovers), not just the 10002+ menus. At
 * 9000 the scrim dimmed the very dropdown a step had just told the reader
 * to pick from (branch popover, workspace chip, ⌛ versions); at 290
 * anything that pops up is bright by construction. The tour's own popover
 * stays above the scrim.
 * ========================================================================== */
/* Two layers (see _tourEnsureEls): the scrim must sit UNDER every transient
   surface, the ring must draw OVER them. */
#gd-tour-dim {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 290;
  display: none;
  pointer-events: none;
}
#gd-tour-dim .gd-tour-dim-fill { fill: var(--modal-backdrop-bg); }
#gd-tour-spot {
  position: fixed;
  /* Above even the editor's heavy floating UI (10002+) and tooltips
     (10010): a `:targets` chain rings items INSIDE those surfaces.
     pointer-events stays none, so nothing is ever blocked by the ring. */
  z-index: 10050;
  display: none;
  pointer-events: none;
  border: 2px solid var(--accent);
  border-radius: 8px;
}
#gd-tour-dim.gd-tour-visible,
#gd-tour-spot.gd-tour-visible { display: block; }

#gd-tour-pop {
  position: fixed;
  /* Above the spotlight scrim AND above the transparent full-screen
     click-catchers some popovers float (.gd-pop-scrim, 340) — lower and
     the tour's own Next button would need two clicks while one of those
     is open. Still BELOW the editor's heavy floating UI (10002+): a
     run/edit popover the user opens mid-step must win over the tour's
     popover, never hide behind it. The engine's placement scorer keeps
     the tour popover clear of the 300-family popovers this z-index would
     otherwise cover. */
  z-index: 9001;
  display: none;
  width: 360px;
  max-width: calc(100vw - 24px);
  background: var(--card-bg);
  color: var(--fg);
  border: 1px solid var(--card-border);
  border-radius: 12px;
  box-shadow: var(--shadow-xl);
  padding: 14px 16px;
}
#gd-tour-pop.gd-tour-visible { display: block; }
#gd-tour-pop.gd-tour-centered {
  left: 50%; top: 10%;
  transform: translateX(-50%);
  /* The catalogue is 25 lessons + 5 chapter headings — taller than a laptop
     viewport. Cap the panel and let the LIST scroll inside it, so the filter
     above and Cancel below stay put. */
  max-height: 80vh;
}
#gd-tour-pop.gd-tour-visible.gd-tour-centered {
  display: flex;
  flex-direction: column;
}

/* ---- Phone: the tour is a bottom sheet ----------------------------------
   A 360px popover on a 390px screen sits on top of the Explorer — which is
   where every early lesson tells the reader to click. Docking it to the
   bottom edge keeps the target half of the screen free, and the engine
   scrolls a target that ends up under the sheet back into view. The
   catalogue gets the same treatment: a centered 360px panel with a scrolling
   list reads as a cramped box on a phone and as a sheet everywhere else. */
#gd-tour-pop.gd-tour-sheet,
#gd-tour-pop.gd-tour-visible.gd-tour-sheet.gd-tour-centered {
  inset: auto 0 0;
  width: 100%;
  max-width: 100%;
  max-height: 52vh;
  transform: none;
  border-radius: 12px 12px 0 0;
  border-bottom: none;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  /* Clear of a phone's home indicator / browser chrome. */
  padding-bottom: max(14px, env(safe-area-inset-bottom));
}

/* While the sheet is up, the panels a lesson points at get its height as
   bottom padding — otherwise a row under the sheet cannot be scrolled clear
   of it: the Explorer is exactly viewport-tall, so there is nothing below to
   scroll to (lesson 05's "New namespace" button, at the bottom of the list,
   was simply unreachable). */
body.gd-tour-sheet-open #side-menu {
  padding-bottom: var(--gd-tour-sheet-h, 0);
}

/* Inside a sheet the actions are the thing a thumb reaches for: full-width
   rows, and a touch target that is not a 29px desktop button. */
@media (pointer: coarse) {
  #gd-tour-pop .gd-tour-btn {
    min-height: 40px;
    padding: 9px 14px;
  }
  #gd-tour-pop .gd-tour-foot {
    flex-wrap: wrap;
  }
}

.gd-tour-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
}
.gd-tour-progress {
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted-fg);
}
.gd-tour-title {
  font-weight: 600;
  font-size: 0.875rem;
  margin-bottom: 6px;
}
.gd-tour-body {
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--fg);
}
/* Inline marks inside step prose (see _tourRenderBody):
   [[Label]] — a UI element to press, rendered as a keycap-style chip;
   `text`    — text to type, rendered as a click-to-copy monospace chip. */
.gd-tour-ui {
  display: inline;
  padding: 0.5px 5px;
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  border-radius: 5px;
  background: var(--card-bg);
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
  /* A long label ("I understand this will produce side effects") must wrap
     inside a 360px popover — clone repeats the keycap frame per fragment. */
  box-decoration-break: clone;
}

@media (width <= 700px) {
  .gd-tour-ui { white-space: normal; }
}
.gd-tour-copy {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  max-width: 100%;
  padding: 0.5px 6px;
  border: 1px dashed var(--input-border);
  border-radius: 5px;
  background: var(--hover-bg);
  color: var(--fg);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.75rem;
  line-height: 1.5;
  cursor: copy;
  vertical-align: baseline;
}
.gd-tour-copy-text {
  text-align: left;
  overflow-wrap: anywhere;  /* a JSON payload wraps instead of overflowing */
}
.gd-tour-copy-ic {
  font-size: 0.6875rem;
  color: var(--muted-fg);
}
.gd-tour-copy:hover { border-color: var(--accent); }
.gd-tour-copy:hover .gd-tour-copy-ic { color: var(--accent); }
.gd-tour-copy:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.gd-tour-copied { border-style: solid; border-color: var(--success-fg); }
.gd-tour-copied .gd-tour-copy-ic { color: var(--success-fg); }
.gd-tour-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 12px;
}
.gd-tour-hint {
  margin-right: auto;
  font-size: 0.6875rem;
  color: var(--muted-fg);
}
.gd-tour-btn {
  font: inherit;
  font-size: 0.7812rem;
  font-weight: 600;
  padding: 6px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--fg);
  cursor: pointer;
}
.gd-tour-btn:hover { background: var(--hover-bg); }
.gd-tour-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.gd-tour-btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  /* --bg flips with the theme: white on the light theme's teal accent,
     ink on the dark theme's lighter accent — both clear WCAG AA (the
     --header-fg pick failed contrast at 3.75:1). */
  color: var(--bg);
}
.gd-tour-btn-primary:hover { background: var(--accent); filter: brightness(1.1); }
.gd-tour-btn-quiet {
  border-color: transparent;
  background: transparent;
  color: var(--muted-fg);
}
.gd-tour-btn-quiet:hover { color: var(--fg); background: var(--hover-bg); }
/* NOT a `.gd-tour-foot` — it was styled as one for the flex + gap, and
   inherited that class's `justify-content: flex-end`, which makes an
   overflowing COLUMN spill past its start edge where no scrollbar can reach
   it (the rows read as squashed and the list refused to scroll). */
.gd-tour-lesson-list {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: 8px;
  margin-top: 12px;
  /* The one scrolling region: `min-height: 0` is what lets a flex child
     shrink below its content and actually scroll. */
  overflow-y: auto;
  min-height: 0;
}

/* The list is a flex COLUMN, so its rows would otherwise shrink to fit the
   capped panel instead of overflowing it — 25 lessons squeezed to 29px each,
   never scrolling. Rows keep their height; the list scrolls. */
.gd-tour-lesson-list > * { flex-shrink: 0; }

/* Cancel — outside the scroller, so it is reachable at any scroll position. */
.gd-tour-picker-foot {
  flex-shrink: 0;
  margin-top: 4px;
}
.gd-tour-lesson-list .gd-tour-btn { text-align: left; }

/* The catalogue's own controls. A 25-lesson list needs a way in (filter), a
   way back to where you stopped (resume) and a way to see what you have
   already read (done) — without any of them the list is a wall. */
.gd-tour-filter {
  width: 100%;
  margin: 10px 0 2px;
  padding: 6px 9px;
  border: 1px solid var(--input-border);
  border-radius: 6px;
  background: var(--card-bg);
  color: var(--fg);
  font-size: 0.75rem;
}

.gd-tour-filter:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.gd-tour-btn-resume {
  display: block;
  width: 100%;
  margin-top: 10px;
  text-align: left;
}

/* Read already — still offered (re-reading is normal), just not the thing
   the eye should land on first. */
.gd-tour-btn-done {
  opacity: 0.72;
}

/* Chapter heading in the lesson picker — a quiet label, not a control:
   it must group the buttons under it without competing with them. */
.gd-tour-chapter {
  margin: 10px 2px 2px;
  color: var(--muted-fg);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.gd-tour-chapter:first-child { margin-top: 0; }

/* A lesson this session cannot complete (`:requires` capability missing):
   still listed — the catalogue is the point — but visibly inert. */
.gd-tour-btn-locked {
  cursor: not-allowed;
  opacity: 0.55;
}

/* The row note (“✓ done”, “— needs an organization”) sits INSIDE an
   accent-filled button, where a grey `--muted-fg` is barely legible. Inherit
   the button's own foreground and step it back with opacity instead — quiet
   on any background the row can have. */
.gd-tour-lesson-note {
  color: inherit;
  opacity: 0.85;
  font-style: italic;
}

/* Review-comment thread under the branch-diff modal. */
.branch-comments { border-top: 1px solid var(--border); margin-top: 10px; padding-top: 8px; }
.branch-comments h4 { font-size: 0.75rem; margin: 0 0 6px; color: var(--fg); }
.branch-comments-list { display: flex; flex-direction: column; gap: 4px; max-height: 180px; overflow-y: auto; }
.branch-comment { display: flex; gap: 6px; align-items: baseline; font-size: 0.75rem; }
.branch-comment-meta { color: var(--muted-fg); font-size: 0.625rem; white-space: nowrap; }
.branch-comment-body { color: var(--fg); flex: 1; overflow-wrap: anywhere; }
.branch-comment-del {
  background: transparent; color: var(--muted-fg); border: 0;
  cursor: pointer; font-size: 0.75rem; padding: 0 2px;
}
.branch-comment-del:hover { color: var(--fg); }
.branch-comment-empty { color: var(--muted-fg); font-size: 0.6875rem; }
.branch-comment-error {
  color: var(--error-fg); font-size: 0.6875rem; margin-top: 4px;
}
.branch-comment-error.hidden { display: none; }
.branch-comment-form { display: flex; gap: 6px; margin-top: 6px; align-items: flex-end; }
.branch-comment-input {
  flex: 1; resize: vertical; font-size: 0.75rem;
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--border); border-radius: 3px; padding: 4px 6px;
}
.branch-comment-send {
  background: var(--accent); color: var(--on-accent);
  border: 0; border-radius: 3px; padding: 4px 10px; font-size: 0.75rem; cursor: pointer;
}
body.gd-no-write .branch-comment-form { display: none; }

/* Feedback ("Report a problem") — full-viewport backdrop + centred card
   (editor-feedback.js). Same modal conventions as .merge-conflicts-modal:
   z-index 500 layer, token backdrop, --bg card. */
#feedback-backdrop {
  position: fixed; inset: 0; z-index: 500;
  display: flex; align-items: center; justify-content: center;
  background: var(--modal-backdrop-bg);
}
.feedback-popover {
  position: relative;
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--fg); border-radius: 3px;
  padding: 12px 16px;
  width: 440px; max-width: 92vw;
  max-height: 85vh; overflow-y: auto;
  box-shadow: var(--shadow-md);
  display: flex; flex-direction: column; gap: 8px;
  font-size: 0.75rem; line-height: 1.4;
}
.feedback-title {
  font-size: 0.875rem; font-weight: 600;
  border-bottom: 1px solid var(--border); padding-bottom: 6px;
}
.feedback-row { display: flex; flex-direction: column; gap: 3px; }
.feedback-row > span { font-size: 0.6875rem; color: var(--muted-fg); }
.feedback-row select,
.feedback-row input,
.feedback-row textarea {
  font: inherit; color: var(--fg); background: var(--bg);
  border: 1px solid var(--input-border); border-radius: 3px;
  padding: 4px 6px;
}
.feedback-row textarea { resize: vertical; }
/* Honeypot — removed from layout AND from assistive tech; only bots see it. */
.feedback-honeypot { display: none; }
.feedback-attach { display: flex; flex-direction: column; gap: 2px; }
.feedback-attach-title { font-size: 0.6875rem; color: var(--muted-fg); }
.feedback-check { font-size: 0.6875rem; display: flex; align-items: center; gap: 4px; }
.feedback-actions { display: flex; gap: 8px; margin-top: 2px; }
.feedback-send {
  background: var(--accent); color: var(--on-accent);
  border: 0; border-radius: 3px; padding: 4px 12px; font-size: 0.75rem; cursor: pointer;
}
.feedback-send:disabled { opacity: 0.6; cursor: default; }
.feedback-cancel {
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--input-border); border-radius: 3px;
  padding: 4px 12px; font-size: 0.75rem; cursor: pointer;
}
.feedback-status { font-size: 0.6875rem; color: var(--muted-fg); min-height: 14px; }
.feedback-status a { color: var(--accent); }

/* Errors panel footer — the "report these failures" entry point
   (editor-errors.js). Quiet text-button, muted → fg on hover like the
   other footer strips. */
.errors-report-btn {
  margin-top: 6px;
  background: none; border: 0; padding: 0;
  font-size: 0.6875rem; color: var(--muted-fg);
  cursor: pointer; text-align: left;
}
.errors-report-btn:hover { color: var(--fg); }

/* =========================================================================
 * FOCUS VISIBILITY (WCAG 2.4.7 Focus Visible)
 *
 * Every selector below suppresses `outline` in its base rule — inputs to
 * avoid the double border next to their own focus border-colour, buttons
 * because their rule styles :hover and :focus-visible together and the
 * background tint was doing all the work. A tint alone fails 2.4.7: it is
 * indistinguishable from a mouse hover and can sit below 3:1 against the
 * surrounding surface.
 *
 * These restore a real ring for keyboard focus only (`:focus-visible`, so
 * a mouse click still doesn't draw one). Higher specificity than the base
 * rule and declared later than the :hover rules, so it wins either way.
 * ========================================================================= */

/* Text entry — the ring sits outside the field's own focus border. */
#search-input:focus-visible,
.auth-popover input:focus-visible,
.branch-popover-create input:focus-visible,
.inline-input:focus-visible,
.arg-value-edit-input:focus-visible,
.fn-picker-search:focus-visible,
.description-tooltip-textarea:focus-visible,
.value-form-host:focus-visible {
  outline: 2px solid var(--gd-flow);
  outline-offset: 1px;
}

/* Icon/text buttons whose base rule tints :hover and :focus-visible alike. */
.edit-type-strip:focus-visible,
.effects-strip-edit:focus-visible,
.type-inline-header-edit:focus-visible,
.type-inline-header-provenance:focus-visible,
.type-inline-promote-btn:focus-visible,
.type-inline-usage-link:focus-visible,
.placeholder-binder:focus-visible {
  outline: 2px solid var(--gd-flow);
  outline-offset: 1px;
}

/* =========================================================================
 * EXPLORER TREE — keyboard focus (editor-tree-keys.js)
 *
 * The tree is a roving-tabindex widget: one row is tabbable, arrows move
 * between rows. That makes the focus ring the ONLY indication of where the
 * user is, so it is drawn on the row itself rather than relying on the
 * `.selected` background (which means something else — the fn open on the
 * canvas).
 * ========================================================================= */
#entity-list [role="treeitem"]:focus-visible {
  outline: 2px solid var(--gd-flow);
  outline-offset: -2px;
  border-radius: 3px;
}

/* =========================================================================
 * KEYBOARD: which-key menu + cheatsheet (editor-shortcuts.js)
 *
 * The which-key menu is anchored bottom-left rather than centred: it appears
 * on every leader press, and a centred panel flashing over the canvas each
 * time reads as an interruption. The cheatsheet IS centred — it is a
 * destination, opened deliberately with `?`.
 * ========================================================================= */
.gd-which-key {
  position: fixed;
  left: 12px; bottom: 12px;
  z-index: 9000;
  display: none;
  min-width: 15rem; max-width: 26rem;
  padding: 8px 10px;
  background: var(--card-bg); color: var(--fg);
  border: 1px solid var(--border); border-radius: 8px;
  box-shadow: var(--shadow-lg);
  font-size: 0.75rem;
}
.gd-which-key.visible { display: block; }
.gd-which-key:focus { outline: none; }
.gd-which-key-head {
  font-weight: 600; color: var(--muted-fg);
  padding-bottom: 6px; margin-bottom: 6px;
  border-bottom: 1px solid var(--light-border);
}
.gd-which-key-rows {
  display: grid; grid-template-columns: auto 1fr; gap: 3px 8px;
  align-items: baseline;
  max-height: 50vh; overflow-y: auto;
}
.gd-which-key-row { display: contents; }
.gd-which-key-label { color: var(--fg); }
/* A group leads somewhere rather than doing something — tint it so the two
   kinds of row are distinguishable without reading the ellipsis. */
.gd-which-key-label.is-group { color: var(--accent); }
.gd-which-key-foot {
  margin-top: 7px; padding-top: 6px;
  border-top: 1px solid var(--light-border);
  color: var(--muted-fg); font-size: 0.6875rem;
}

.gd-key-cap {
  display: inline-block;
  min-width: 1.25rem;
  padding: 1px 5px; margin-right: 3px;
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--input-border); border-radius: 4px;
  font-family: var(--mono); font-size: 0.6875rem; line-height: 1.4;
  text-align: center;
}

.gd-cheatsheet {
  position: fixed;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  z-index: 9100;
  display: none;
  width: min(42rem, 92vw); max-height: 82vh; overflow-y: auto;
  padding: 18px 20px;
  background: var(--card-bg); color: var(--fg);
  border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow-xl);
}
.gd-cheatsheet.visible { display: block; }
.gd-cheatsheet-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 14px;
}
.gd-cheatsheet-head h2 { font-size: 1rem; margin: 0; }
.gd-cheatsheet-close {
  background: none; border: 0; cursor: pointer;
  color: var(--muted-fg); font-size: 1.25rem; line-height: 1;
  padding: 2px 6px; border-radius: 4px;
}
.gd-cheatsheet-close:hover { color: var(--fg); background: var(--hover-bg); }
.gd-cheatsheet-close:focus-visible { outline: 2px solid var(--gd-flow); outline-offset: 1px; }
.gd-cheatsheet-group { margin-bottom: 16px; }
.gd-cheatsheet-group h3 {
  font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--muted-fg); margin: 0 0 6px;
}
.gd-cheatsheet-group dl {
  display: grid; grid-template-columns: 10rem 1fr; gap: 4px 12px;
  margin: 0; font-size: 0.8125rem;
}
.gd-cheatsheet-group dt { margin: 0; }
.gd-cheatsheet-group dd { margin: 0; color: var(--fg); }

/* =========================================================================
 * GRAPH CANVAS — keyboard focus (editor-canvas-keys.js)
 *
 * The canvas is a roving-tabindex widget like the tree: one card is
 * tabbable, arrows walk the edges. The ring has to read against both the
 * card's own border and the dotted canvas behind it, so it sits OUTSIDE the
 * card rather than inset.
 * ========================================================================= */
.node-overlay:focus-visible {
  outline: 2px solid var(--gd-flow);
  outline-offset: 2px;
}

/* =========================================================================
 * INCREASED CONTRAST (prefers-contrast: more)
 *
 * The OS "increase contrast" setting, honoured as a token overlay. The
 * base palette is audited to WCAG AA (4.5:1); this block re-picks the
 * tokens that sit BETWEEN AA and AAA so that under the setting every text
 * sample reaches 7:1 and every border is unmistakable. Values are
 * computed, not eyeballed — the audit suite re-runs its live contrast
 * sweep at the AAA threshold with this media feature emulated, so a
 * token edit that regresses it fails the gate.
 *
 * Only the deltas: tokens already past 7:1 (--fg, --gd-ink-2, the dark
 * theme's accent family) keep their base values.
 * ========================================================================= */
@media (prefers-contrast: more) {
  :root {
    --muted-fg: #3f4854;        /* 9.3:1 on --bg, 8.7:1 on --sidebar-bg */
    --light-fg: #4a5560;        /* 7.6:1 — hint text stops being a hint */
    --accent: #0b5d57;          /* 7.7:1 */
    --success-fg: #0c5a28;      /* 8.4:1 */
    --error-fg: #a31515;        /* 7.9:1 */
    --warning-fg: #7a4e00;      /* 7.2:1 */
    --gd-ink-3: #454d4f;        /* 7.6:1 on --gd-paper */
    --hof-fg: #3d3d66;          /* 9.4:1 on --hof-bg */
    --card-header-bg: #0b544e;  /* white header text: 6.4 -> 8.8:1 */
    --gd-flow: #0b5d57;         /* the focus ring follows the accent */
    --gd-flow-ink: #0a4a45;     /* pressed lens chip: 6.5 -> 8.3:1 */
    /* Hairline borders read as texture at low vision; make them lines. */
    --light-border: #6e7781;
    --input-border: #57606a;
    --gd-line: #8c9694;
  }
  body.theme-dark {
    --muted-fg: #b3bdc9;        /* 10.0:1 on --bg */
    --light-fg: #a3adbb;        /* 8.3:1 */
    --gd-ink-3: #aab5b7;        /* 8.1:1 even on the brighter sidebar surface */
    --gd-flow-ink: #8feee4;     /* pressed lens chip: 6.5 -> 10.2:1 */
    --card-header-bg: #0b544e;
    --light-border: #6e7781;
    --input-border: #8b949e;
    --gd-line: #57606a;
  }
  /* The ring is the only "where am I" signal for keyboard users; at this
     setting it gets heavier than the 2px the component rules declare.
     !important because those rules set the shorthand at higher
     specificity, and out-heavying every one of them here would fork the
     component styles this overlay exists to avoid. */
  :focus-visible { outline-width: 3px !important; }
}

/* =========================================================================
 * FORCED COLORS (Windows High Contrast)
 *
 * The browser repaints everything with the system palette; author colours
 * vanish. Most of the editor survives (focus rings are `outline`-based,
 * which forced-colors keeps), but states conveyed ONLY by background —
 * the selected tree row, the active card, the picker highlight — become
 * invisible. Give each a border-shaped signal drawn in system colours.
 * ========================================================================= */
@media (forced-colors: active) {
  .entity-item.selected,
  [role="treeitem"][aria-selected="true"],
  .fn-picker-row-active,
  .node-overlay.gd-node-active {
    outline: 2px solid Highlight;
    outline-offset: -2px;
  }
  .gd-insp-tab.active,
  .kind-toggle[aria-pressed="true"] {
    text-decoration: underline;
    text-decoration-thickness: 2px;
  }
}

/* =========================================================================
 * REDUCED MOTION (WCAG 2.3.3 Animation from Interactions)
 *
 * Must stay the LAST block in the file: it overrides earlier rules at
 * equal specificity, including the touch block above.
 *
 * Motion is removed, information is not. Each animation below keeps the
 * state it was communicating and drops only the movement:
 *   - tree-flash  → a steady wash for the 1.3s the class is applied
 *                   (editor-sidebar.js removes it on a timer), so
 *                   "Reveal in Explorer" still points at the row
 *   - busy spinner → kept, only slowed: it is the progress signal
 *                   itself, and WCAG's carve-out for loading indicators
 *                   applies. Screen readers get the parallel
 *                   role="status" banner from editor-busy.js
 *   - service pending pulse → decorative attention-getter, dropped for a
 *                   steady mid opacity; the badge's colour still encodes
 *                   the state
 *
 * The JS half lives in editor-a11y.js (`prefersReducedMotion`): the
 * rAF position tween and smooth scrollIntoView cannot be reached from
 * CSS.
 * ========================================================================= */
@media (prefers-reduced-motion: reduce) {
  /* Near-zero rather than `none`: keeps transitionend firing for any
     future listener while being imperceptible. */
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }

  .entity-item.gd-tree-flash {
    animation: none;
    background-color: var(--gd-flow-wash);
  }

  /* Progress indicators are exempt — but slow the rotation down. */
  .editor-busy-banner::before {
    animation-duration: 1.6s !important;
    animation-iteration-count: infinite !important;
  }

  .service-badge-pending {
    animation: none;
    opacity: 0.75;
  }
}
