/* NODE CANVAS — the one stylesheet for every `.nc-*` class.
 *
 * Scope: the whole node editor — shell, toolbar, palette, canvas surface, nodes,
 * ports, edges, overlays, the NDV modal and the shared form atoms. Ten JS modules
 * emit markup against the DOM contract in _ops/node-system/PLAN.md 6b; this file
 * is the only place their appearance is decided.
 *
 * ─────────────────────────────────────────────────────────────────────────────
 * WHY THERE IS NO NEW PALETTE HERE
 *
 * Every colour below resolves through the app's own tokens — --card-bg,
 * --color-primary, --color-secondary, --text-primary/-secondary/-muted,
 * --ui-border, --ui-border-soft, --hover-bg, --accent-color, --color-accent-light
 * /-dark, --muted, --vd-st-active-fg, --radius-sm/md/lg. ui/css/theme.css
 * re-declares those under html[data-theme="dark"] and LOADS LAST, so a page built
 * on them flips for free and this file needs almost no dark-mode block at all
 * (same reasoning sequences-page.css:24-27 spells out).
 *
 * The few `--nc-*` tokens declared in the next block are compositions of those
 * app tokens, not new colours. They exist so a value used in eight places is
 * written once, and so the edge palette has the exact names nc-edges.js already
 * looks for (NcEdges.CSS_VARS: --nc-edge-advance / -back / -branch / -auto /
 * -dash). They live on :root because four of these surfaces — the NDV, the
 * context menu, the palette tooltip and the drag ghost — are appended to
 * <body>, outside .nc-root, and would otherwise inherit nothing.
 *
 * SPECIFICITY, AND WHO WINS WHAT.
 *  - nc-ndv.js injects a fallback sheet wrapped in :where(...) — zero
 *    specificity, explicitly so this file wins. The NDV shell and the form atoms
 *    below are therefore the real design; that block only keeps the dialog
 *    legible if this file is missing.
 *  - nc-keyboard.js (.nc-ctx*), nc-expressions.js (.nc-expr*), nc-ndv-status.js
 *    (.nc-ps*), nc-ndv-trigger.js (.nc-trg*), nc-ndv-flow.js (.nc-flow*) and
 *    nc-ndv-dashboard.js (.nc-dashpane*) inject their OWN normal-specificity
 *    rules at runtime, i.e. after this <link>. Those namespaces are theirs; this
 *    file only sets a shared base for .nc-ctx so the menu has a surface before
 *    they load, and never fights them for the rest.
 *  - nc-canvas.js writes functional geometry inline (viewport transform, node
 *    left/top/width/height, the grid's background-image, the minimap boxes, the
 *    edge toolbar, the insert menu). Inline beats a stylesheet, so none of that
 *    is restyled here — what IS styled is everything it leaves bare:
 *    .nc-canvas-controls, .nc-legend, .nc-hint, the nodes, the ports, the edges.
 *  - nc-edges.js / nc-canvas.js paint stroke, stroke-dasharray and marker-end as
 *    SVG *presentation attributes*, which sit at the bottom of the cascade, so
 *    the `stroke:` rules below win without !important. stroke-dasharray is
 *    deliberately NOT set here: nc-canvas.js feeds the same dashFor() value to
 *    the edge and to the legend swatch, and overriding one and not the other
 *    would make the legend lie.
 *
 * COLOUR IS NEVER THE ONLY SIGNAL. Edge kinds differ by dash pattern, arrowhead
 * shape and route (set in JS) as well as hue. Ports differ by shape — a circle
 * for the flow ports, a diamond for the dashboard socket and plug. Node kinds
 * carry an icon and a worded badge. Strip every colour out and the graph reads.
 *
 * LAYOUT. .nc-root is a two-row grid (toolbar / body) with a bounded height, so
 * the canvas — which is an absolutely-positioned world — has something to be
 * absolute inside. .nc-body is palette + canvas. At <=900px the palette becomes
 * a 56px rail that opens into a drawer OVER the canvas (see the responsive
 * section) and the NDV stacks to one column. Every scrollable box is bounded and
 * uses overscroll-behavior:contain, so nothing ever scrolls the page body
 * sideways.
 *
 * WHAT THE OTHER MODULES ASK OF THIS FILE (round 2). Three contracts are
 * carried entirely here, because the module that emits the markup inlines none
 * of the shape:
 *  - `.nc-node--first` / `[data-first="true"]` (nc-nodes.js) — the first status
 *    wears the trigger's D. Both forms are matched; see the nodes section.
 *  - `.nc-chip--start` and `.nc-chip.nc-chip--count.nc-node-count`
 *    (nc-nodes.js) — the entry-point badge and the live row count.
 *  - `.nc-plus-layer / -shield / -line / -btn` (nc-canvas.js) — the quick-add
 *    stub. That module writes every dimension and the hover colours inline, so
 *    this file styles only the stacking, the focus ring and the easing.
 */

/* ══ tokens ═══════════════════════════════════════════════════════════════════
 * Compositions of app tokens. Each falls back to the light literal the app
 * itself uses, because --hover-bg, --accent-color, --muted and --vd-st-active-fg
 * are declared ONLY under html[data-theme="dark"] (research 04, 5.2).
 */

:root {
  /* surfaces */
  --nc-ground:      var(--color-primary, #dce1e8);   /* the canvas floor — the app's own page ground */
  --nc-surface:     var(--card-bg, #ffffff);          /* nodes, panels, menus */
  --nc-surface-2:   var(--hover-bg, #f1f5f9);         /* hover / inset wells */
  --nc-border:      var(--ui-border, #e5e7eb);
  --nc-border-soft: var(--ui-border-soft, rgba(128, 128, 128, 0.1));

  /* ink */
  --nc-ink:    var(--text-primary, #1a1a1a);
  --nc-ink-2:  var(--text-secondary, #444444);
  --nc-ink-3:  var(--text-muted, #666666);

  /* intent — the same three the rest of the app already uses */
  --nc-accent: var(--accent-color, #3b82f6);
  --nc-focus:  var(--accent-color, #3b82f6);
  --nc-ok:     #16a34a;
  --nc-warn:   var(--color-accent-dark, #d4791a);
  --nc-danger: #dc2626;

  /* geometry — dense-dashboard scale (8/12/16), n8n-ish radii */
  --nc-radius-sm: var(--radius-sm, 6px);
  --nc-radius:    var(--radius-md, 8px);
  --nc-radius-lg: var(--radius-lg, 12px);
  --nc-gap:       8px;
  --nc-pad:       12px;

  --nc-shadow:    0 1px 2px rgba(15, 23, 42, 0.06), 0 1px 3px rgba(15, 23, 42, 0.05);
  --nc-shadow-md: 0 4px 14px rgba(15, 23, 42, 0.12);
  --nc-shadow-lg: 0 18px 44px rgba(15, 23, 42, 0.20);

  /* the shell's height. One place to retune how much room the canvas gets. */
  --nc-shell-h: clamp(460px, calc(100vh - 280px), 940px);

  /* The closed palette rail on a phone: one row holding the title and the search
   * box, 44px target plus 6px of padding either side. It is a GRID TRACK size as
   * well as a height — see the responsive section for why the track has to be
   * explicit rather than `auto`. */
  --nc-rail-h: 56px;

  /* how much amber a sticky note carries — lifted in dark, see below */
  --nc-sticky-mix: 10%;

  /* Edge palette — the names nc-edges.js reads (NcEdges.CSS_VARS).
   * The three saturated tokens are pulled 22% toward --text-primary, which is
   * the same measured fix nc-edges.js:95-121 documents: as 2px lines on the
   * pale canvas they sit under 3:1, and because --text-primary flips per theme
   * the one expression darkens on light and lightens on dark. The plain token
   * is declared first so a browser without color-mix keeps a valid value. */
  --nc-edge-advance: var(--text-muted, #666666);
  --nc-edge-back:    var(--color-accent-dark, #d4791a);
  --nc-edge-branch:  var(--accent-color, #3b82f6);
  --nc-edge-auto:    var(--vd-st-active-fg, #16a34a);
  --nc-edge-dash:    var(--muted, #64748b);
}

@supports (color: color-mix(in srgb, red 50%, blue)) {
  :root {
    --nc-edge-back:   color-mix(in srgb, var(--color-accent-dark, #d4791a) 78%, var(--text-primary, #1a1a1a));
    --nc-edge-branch: color-mix(in srgb, var(--accent-color, #3b82f6) 78%, var(--text-primary, #1a1a1a));
    --nc-edge-auto:   color-mix(in srgb, var(--vd-st-active-fg, #16a34a) 78%, var(--text-primary, #1a1a1a));
  }
}

/* The only real dark block in this file: three literals that have no token to
 * ride, plus deeper shadows for a dark ground. Same lift sequences-page.css
 * makes at :305 and theme.css makes for .a4p-btn-primary. */
html[data-theme="dark"] {
  /* A 10% amber wash reads as mud on slate, so the note gets a little more of
     it — the paper still has to look like paper. */
  --nc-sticky-mix: 16%;
  --nc-ok:     #4ade80;
  --nc-warn:   var(--color-accent-light, #f5a623);
  --nc-danger: #f87171;

  --nc-shadow:    0 1px 2px rgba(0, 0, 0, 0.40);
  --nc-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.50);
  --nc-shadow-lg: 0 22px 56px rgba(0, 0, 0, 0.62);
}

/* ══ shell ════════════════════════════════════════════════════════════════════ */

.nc-root {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 10px;
  min-width: 0;
  height: var(--nc-shell-h);
  font-family: var(--font-family, inherit);
  font-size: 13px;
  color: var(--nc-ink);
}

.nc-toolbar {
  display: flex;
  align-items: center;
  gap: var(--nc-gap);
  flex-wrap: wrap;
  min-width: 0;
  padding: 8px 10px;
  background: var(--nc-surface);
  border: 1px solid var(--nc-border);
  border-radius: var(--nc-radius-lg);
}

/* A separator inside the toolbar: any element with this data hook, or a plain
 * <hr>, becomes a hairline. Cheap for the integrator, invisible if unused. */
.nc-toolbar > hr,
.nc-toolbar > [data-nc-sep] {
  align-self: stretch;
  width: 1px;
  height: auto;
  margin: 2px 2px;
  padding: 0;
  border: 0;
  background: var(--nc-border);
}

/* Pushes everything after it to the right end of the toolbar. */
.nc-toolbar > [data-nc-spacer] {
  margin-left: auto;
}

.nc-body {
  display: grid;
  grid-template-columns: minmax(220px, 264px) minmax(0, 1fr);
  gap: 10px;
  min-width: 0;
  min-height: 0;
}

/* ══ palette (left node panel) ════════════════════════════════════════════════
 * nc-palette.js emits a flat panel: head, the search INPUT itself as a direct
 * child, a live count, then one .nc-palette-group per category. The panel is the
 * scroll container, which is why the search box is sticky here — nothing else
 * keeps it on screen.
 */

.nc-palette {
  display: flex;
  flex-direction: column;
  gap: var(--nc-gap);
  min-width: 0;
  min-height: 0;
  padding: 10px;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  background: var(--nc-surface);
  border: 1px solid var(--nc-border);
  border-radius: var(--nc-radius-lg);
}

.nc-palette-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.nc-palette-title {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--nc-ink);
}

.nc-palette-sub {
  margin: 0;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--nc-ink-3);
}

/* The input IS the search box (no wrapper), so the sticky lives on it. The
 * negative inline margin + matching padding keeps its focus ring from being
 * clipped by the panel's overflow. */
.nc-palette-search.nc-input,
.nc-palette-search {
  position: sticky;
  top: -2px;
  z-index: 2;
  flex: 0 0 auto;
  min-height: 40px;
  scroll-margin-top: 8px;
}

.nc-palette-count {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--nc-ink-3);
}

.nc-palette-count:empty {
  display: none;
}

.nc-palette-group {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.nc-palette-group-title {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 4px 2px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--nc-ink-3);
}

.nc-palette-group-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 16px;
  padding: 0 5px;
  border-radius: 999px;
  font-size: 10px;
  letter-spacing: 0;
  background: var(--nc-surface-2);
  color: var(--nc-ink-3);
}

.nc-palette-group-help {
  margin: 0 0 2px;
  padding: 0 4px;
  font-size: 11px;
  line-height: 1.45;
  color: var(--nc-ink-3);
}

/* Icon in column 1 spanning the rows; the label and its help take the FULL
 * remaining width (a 90px "Part of the chain" chip sharing row 1 truncated every
 * label to about ten characters in a 230px panel); the slug and that chip share
 * the last row. role="button" + draggable, not a <button>, so the focus ring and
 * the 44px row have to be spelled out. */
.nc-palette-item {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 9px;
  row-gap: 1px;
  min-height: 48px;
  padding: 7px 8px;
  border: 1px solid transparent;
  border-radius: var(--nc-radius);
  background: transparent;
  cursor: grab;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}

.nc-palette-item:hover {
  background: var(--nc-surface-2);
  border-color: var(--nc-border);
}

.nc-palette-item:active {
  cursor: grabbing;
}

.nc-palette-item:focus-visible {
  outline: 2px solid var(--nc-focus);
  outline-offset: 1px;
}

/* The "Enter adds this one" row during a search. A keyboard target, deliberately
 * quieter than a pressed state. */
.nc-palette-item.is-selected {
  border-color: var(--nc-focus);
  background: var(--nc-surface-2);
}

.nc-palette-item.is-dragging {
  opacity: 0.45;
}

.nc-palette-item.is-disabled,
.nc-palette-item[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

.nc-palette-item-icon {
  grid-column: 1;
  grid-row: 1 / span 3;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--nc-radius-sm);
  background: var(--nc-surface-2);
  color: var(--nc-ink-2);
}

.nc-palette-item-icon svg {
  display: block;
  width: 18px;
  height: 18px;
}

.nc-palette-item-label {
  grid-column: 2 / -1;
  grid-row: 1;
  min-width: 0;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--nc-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nc-palette-item-help {
  grid-column: 2 / -1;
  grid-row: 2;
  min-width: 0;
  font-size: 11px;
  line-height: 1.4;
  color: var(--nc-ink-3);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.nc-palette-item-slug {
  grid-column: 2;
  grid-row: 3;
  justify-self: start;
  margin-top: 2px;
  padding: 1px 5px;
  border-radius: 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 10.5px;
  color: var(--nc-ink-3);
  background: var(--nc-surface-2);
  word-break: break-all;
}

.nc-palette-item > .nc-chip {
  grid-column: 3;
  grid-row: 3;
  align-self: center;
  justify-self: end;
  margin-top: 2px;
}

/* Chain vs drawing. The chip already says which in words; the tone only seconds
 * it, so nothing is carried by colour alone. */
.nc-chip[data-persists="chain"] {
  background: color-mix(in srgb, var(--nc-accent) 14%, transparent);
  color: var(--nc-ink);
}

.nc-palette-suggest {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 2px 0;
}

.nc-palette-clear {
  align-self: flex-start;
}

/* The tooltip is positioned by JS with inline position:fixed/left/top so the
 * panel's overflow cannot clip it — those three are never touched here. */
.nc-palette-tip {
  z-index: 10030;
  display: flex;
  flex-direction: column;
  gap: 5px;
  max-width: min(320px, calc(100vw - 24px));
  padding: 10px 12px;
  border: 1px solid var(--nc-border);
  border-radius: var(--nc-radius);
  background: var(--nc-surface);
  color: var(--nc-ink-2);
  box-shadow: var(--nc-shadow-md);
  font-size: 11.5px;
  line-height: 1.5;
  pointer-events: none;
}

.nc-palette-tip[hidden] {
  display: none;
}

.nc-palette-tip-title {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--nc-ink);
}

.nc-palette-tip-slug {
  align-self: flex-start;
  padding: 1px 5px;
  border-radius: 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 10.5px;
  background: var(--nc-surface-2);
  color: var(--nc-ink-3);
}

.nc-palette-tip-help,
.nc-palette-tip-long,
.nc-palette-tip-ports,
.nc-palette-tip-persist {
  margin: 0;
}

.nc-palette-tip-persist {
  padding-top: 5px;
  border-top: 1px solid var(--nc-border-soft);
  color: var(--nc-ink-3);
}

/* The drag image. Laid out off-screen by JS, so it must look like a node at
 * rest — this is the only preview of what is about to land. */
.nc-palette-drag-ghost {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 12px;
  border: 1px solid var(--nc-border);
  border-radius: 10px;
  background: var(--nc-surface);
  color: var(--nc-ink);
  box-shadow: var(--nc-shadow-md);
  font-size: 12.5px;
  font-weight: 600;
}

/* ══ canvas surface ═══════════════════════════════════════════════════════════ */

.nc-canvas {
  position: relative;      /* set here so nc-canvas.js does not have to inline it */
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  background: var(--nc-ground);
  border: 1px solid var(--nc-border);
  border-radius: var(--nc-radius-lg);
  cursor: grab;
  touch-action: none;
}

.nc-canvas:focus-visible {
  outline: 2px solid var(--nc-focus);
  outline-offset: -2px;
}

/* While a port drag is in flight the surface is a targeting mode, not a pannable
 * map — the cursor has to say so. */
.nc-canvas.is-connecting {
  cursor: crosshair;
}

/* A palette item is being dragged over the canvas. */
.nc-canvas.is-dropping::after {
  content: "";
  position: absolute;
  inset: 6px;
  z-index: 3;
  border: 2px dashed var(--nc-accent);
  border-radius: var(--nc-radius);
  background: color-mix(in srgb, var(--nc-accent) 6%, transparent);
  pointer-events: none;
}

/* The dot/line grid. Its background-image, size, position and opacity are all
 * written by paintGrid() from the live zoom — that is what makes it scale with
 * the viewport — so this rule sets only the stacking. */
.nc-grid {
  z-index: 0;
}

.nc-viewport {
  z-index: 1;
  will-change: transform;
}

.nc-edge-svg {
  z-index: 1;
  pointer-events: none;   /* only .nc-edge-hit re-enables it, on the stroke */
}

.nc-node-layer {
  z-index: 2;
}

/* — floating overlays (screen space, never scaled) ————————————————————— */

.nc-canvas-controls,
.nc-legend,
.nc-hint {
  position: absolute;
  z-index: 4;
}

.nc-canvas-controls {
  right: 10px;
  bottom: 10px;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--nc-border);
  border-radius: 10px;
  background: var(--nc-surface);
  box-shadow: var(--nc-shadow);
}

/* 44x44 is set inline by ctlBtn(); this makes them read as one segmented group
 * rather than six separate buttons. */
.nc-canvas-controls .nc-btn,
.nc-canvas-ctl {
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--nc-ink-2);
  padding: 0;
}

.nc-canvas-controls .nc-btn:hover:not(:disabled),
.nc-canvas-ctl:hover:not(:disabled) {
  background: var(--nc-surface-2);
  color: var(--nc-ink);
}

.nc-canvas-controls .nc-btn:focus-visible {
  outline: 2px solid var(--nc-focus);
  outline-offset: -2px;
}

.nc-canvas-controls svg {
  display: block;
  width: 17px;
  height: 17px;
}

.nc-zoom-value {
  display: inline-block;
  min-width: 44px;
  font-size: 11.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--nc-ink-2);
}

.nc-legend {
  right: 10px;
  top: 10px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 12px;
  max-width: min(420px, calc(100% - 20px));
  padding: 7px 10px;
  border: 1px solid var(--nc-border);
  border-radius: 10px;
  /* Plain value first, translucent second: a browser without color-mix drops
     the second declaration and still gets a solid, legible panel. Same pattern
     everywhere color-mix appears below. */
  background: var(--nc-surface);
  background: color-mix(in srgb, var(--nc-surface) 92%, transparent);
  box-shadow: var(--nc-shadow);
}

.nc-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--nc-ink-2);
}

/* The swatch is drawn with stroke="currentColor", so `color` here is what makes
 * the legend line match its edge exactly. The dash pattern comes from the same
 * dashFor() the edge uses, so the two can never drift. */
.nc-legend-swatch {
  flex: 0 0 auto;
  display: block;
  overflow: visible;
}

.nc-legend-item--advance { color: var(--nc-edge-advance); }
.nc-legend-item--back    { color: var(--nc-edge-back); }
.nc-legend-item--branch  { color: var(--nc-edge-branch); }
.nc-legend-item--auto    { color: var(--nc-edge-auto); }
.nc-legend-item--dash    { color: var(--nc-edge-dash); }

.nc-legend-label {
  white-space: nowrap;
}

/* One live line of guidance — what a gesture will do, or why a connection was
 * refused. role="status", so it is announced as well as shown. */
.nc-hint {
  left: 10px;
  top: 10px;
  max-width: min(560px, calc(100% - 20px));
  padding: 6px 10px;
  border: 1px solid var(--nc-border);
  border-radius: 999px;
  background: var(--nc-surface);
  background: color-mix(in srgb, var(--nc-surface) 92%, transparent);
  color: var(--nc-ink-3);
  font-size: 11.5px;
  line-height: 1.4;
  box-shadow: var(--nc-shadow);
  pointer-events: none;
  transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease;
}

/* A refusal. Never silent, never colour-only — the sentence in the pill is the
 * real message; the tint just makes it findable. */
.nc-hint.is-invalid {
  border-color: var(--nc-danger);
  border-color: color-mix(in srgb, var(--nc-danger) 45%, transparent);
  background: var(--nc-surface);
  background: color-mix(in srgb, var(--nc-danger) 12%, var(--nc-surface));
  color: var(--nc-danger);
  font-weight: 600;
}

/* Position, size, border, background and the fade are inline (buildMinimap), so
 * only the stacking and the viewport rectangle are left to say. */
.nc-minimap {
  z-index: 4;
  box-shadow: var(--nc-shadow);
}

.nc-minimap-view {
  background: color-mix(in srgb, var(--nc-accent) 12%, transparent);
}

/* The marquee and the edge toolbar carry inline geometry from nc-canvas.js;
 * these two rules only lift them above the node layer. */
.nc-marquee { z-index: 5; }
.nc-edge-toolbar { z-index: 6; }

.nc-edge-toolbar .nc-btn {
  border: 0;
  background: transparent;
  color: var(--nc-ink-2);
}

.nc-edge-toolbar .nc-btn:hover:not(:disabled) {
  background: var(--nc-surface-2);
  color: var(--nc-ink);
}

.nc-edge-toolbar .nc-btn--danger {
  color: var(--nc-danger);
}

.nc-edge-toolbar .nc-btn--danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--nc-danger) 12%, transparent);
}

.nc-edge-toolbar svg,
.nc-insert-menu svg {
  display: block;
  width: 16px;
  height: 16px;
}

.nc-insert-menu-head {
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.nc-insert-item:hover {
  background: var(--nc-surface-2) !important;   /* the item's background is inline */
}

.nc-insert-item:focus-visible {
  outline: 2px solid var(--nc-focus);
  outline-offset: -2px;
}

/* ══ nodes ════════════════════════════════════════════════════════════════════
 * left/top/width/height and --nc-node-accent arrive inline from NcNodes.nodeHtml
 * (status 212x88, dashboard 200x76, sticky 260x140). Nothing here may set
 * overflow:hidden on the node: the ports live at its edges and translate outside
 * the box, so the clipping is done per-child instead.
 */

.nc-node {
  position: absolute;
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  grid-template-rows: auto auto auto;
  align-content: center;
  column-gap: 9px;
  row-gap: 2px;
  box-sizing: border-box;
  padding: 9px 11px 9px 14px;
  border: 1px solid var(--nc-border);
  border-radius: 10px;
  background: var(--nc-surface);
  color: var(--nc-ink);
  box-shadow: var(--nc-shadow);
  cursor: grab;
  user-select: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

/* The kind stripe. One rule for every family; which colour it takes is decided
 * by --nc-node-accent, which a status overrides inline with the chain's own
 * colour. This is the shape that survives greyscale — position, not hue. */
.nc-node::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  border-radius: 10px 0 0 10px;
  background: var(--nc-node-accent, var(--nc-accent));
}

.nc-node:hover {
  border-color: color-mix(in srgb, var(--nc-node-accent, var(--nc-accent)) 55%, var(--nc-border));
  box-shadow: var(--nc-shadow-md);
}

.nc-node:focus-visible {
  outline: 2px solid var(--nc-focus);
  outline-offset: 2px;
}

.nc-node.is-selected {
  border-color: var(--nc-focus);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--nc-focus) 45%, transparent), var(--nc-shadow-md);
}

.nc-node.is-dragging {
  cursor: grabbing;
  box-shadow: var(--nc-shadow-lg);
  transition: none;        /* a drag must not lag its own pointer */
}

/* Kept on the canvas but skipped. The "Disabled" badge says it in words. */
.nc-node.is-disabled {
  opacity: 0.58;
  filter: saturate(0.35);
}

/* A kind this build does not know. Dashed = "shown as it arrived, saved back
 * untouched", the same promise seq-step-panel.js:22-33 makes. */
.nc-node.is-readonly {
  border-style: dashed;
}

.nc-node.is-invalid {
  border-color: var(--nc-danger);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--nc-danger) 30%, transparent);
}

/* Currently the source of an in-flight connection. */
.nc-node.is-connecting {
  border-color: var(--nc-focus);
}

/* A valid drop target while connecting. */
.nc-node.is-dropping {
  border-color: var(--nc-ok);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--nc-ok) 40%, transparent);
}

.nc-node-icon {
  grid-column: 1;
  grid-row: 1 / span 2;
  align-self: center;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--nc-radius-sm);
  background: var(--nc-surface-2);
  background: color-mix(in srgb, var(--nc-node-accent, var(--nc-accent)) 14%, transparent);
  color: var(--nc-node-accent, var(--nc-accent));
}

.nc-node-icon svg {
  display: block;
  width: 18px;
  height: 18px;
}

.nc-node-title {
  grid-column: 2;
  grid-row: 1;
  min-width: 0;
  font-size: 13px;
  font-weight: 650;
  line-height: 1.25;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nc-node-sub {
  grid-column: 2;
  grid-row: 2;
  min-width: 0;
  font-size: 11px;
  line-height: 1.35;
  color: var(--nc-ink-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Always emitted, often empty, and never allowed to grow the node past the
 * height JS measured — hence nowrap + hidden rather than wrap. */
.nc-node-badges {
  grid-column: 1 / -1;
  grid-row: 3;
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  overflow: hidden;
  flex-wrap: nowrap;
}

.nc-node-badges:empty {
  display: none;
}

/* — family accents ————————————————————————————————————————————————————
 * Each is an existing app token, and each family also has its own icon and, for
 * the flow kinds, a "Not executed yet" badge. Style the family; the variant
 * class (.nc-node--trigger-schedule) only tints the icon. */

.nc-node--trigger    { --nc-node-accent: var(--nc-warn); }
.nc-node--status     { --nc-node-accent: var(--nc-accent); }
.nc-node--dashboard  { --nc-node-accent: var(--nc-ok); }
.nc-node--wait,
.nc-node--notify,
.nc-node--automation,
.nc-node--merge      { --nc-node-accent: var(--muted, #64748b); }
.nc-node--unknown    { --nc-node-accent: var(--nc-ink-3); }

/* A trigger is where the work comes from, so it reads as an entry point rather
 * than as another card in the row. */
.nc-node--trigger {
  border-radius: 10px 10px 10px 22px;
}

.nc-node--trigger::before {
  border-radius: 10px 0 0 22px;
}

/* — the first status (A5) ————————————————————————————————————————————————
 * `firstStatusForDept()` reads exactly this entry, so it is where every new
 * deliverable of the type is created. nc-nodes.js marks it BOTH ways —
 * `.nc-node--first` and `data-first="true"` — and inlines none of the shape, so
 * the whole contract is here. Matching both is deliberate: the attribute keeps
 * the rule alive if the class is ever renamed.
 *
 * It takes the trigger's D — 36px on the entry side — because "this is where
 * work enters" is the same claim the trigger makes.
 *
 * WHY THE STRIPE BECOMES A GRADIENT. `.nc-node::before` is a 4px-wide bar down
 * the left edge. At a 10px corner it sits inside the card; at 36px the card's
 * own edge has curved away to x=36 at the very top, so the bar paints where the
 * node no longer is and pokes out of the D. Giving the bar `36px 0 0 36px` does
 * not fix it either — CSS scales radii down to fit a 4px-wide box, so it comes
 * back as ~4px and still pokes. A background image, though, is clipped by the
 * border box, so a hard-stop gradient is the same 5px of chain colour, following
 * the curve exactly. It also survives hover and selection, which repaint
 * `border-color` and `box-shadow` but never `background-image`.
 */

.nc-node--first,
.nc-node[data-first="true"] {
  border-radius: 36px 8px 8px 36px;
  /* The plain declaration first: a browser without color-mix keeps a solid
     card rather than losing the accent to an invalid value. */
  background-image: linear-gradient(90deg,
    var(--nc-node-accent, var(--nc-accent)) 0,
    var(--nc-node-accent, var(--nc-accent)) 5px,
    transparent 5px);
  /* Clear of the nose. The badge row runs along the bottom-left, where the
     curve has eaten ~12px of the box by the row's lowest pixel. */
  padding-left: 20px;
}

.nc-node--first::before,
.nc-node[data-first="true"]::before {
  display: none;
}

.nc-node--first .nc-node-icon,
.nc-node[data-first="true"] .nc-node-icon {
  /* The icon well would otherwise read as a second, competing rounded shape
     right inside the D. */
  background: transparent;
  background: color-mix(in srgb, var(--nc-node-accent, var(--nc-accent)) 10%, transparent);
}

/* The "New deliverables start here" badge and the live-row count are both
 * `.nc-chip`s, so their tones live with the rest of the chip family in the form
 * atoms below — `.nc-chip` itself is declared there and would otherwise win the
 * cascade over anything set up here. */

/* Flow kinds are authored but not executed by anything yet (PLAN 3b). The
 * badge says so; this just keeps them visually secondary to the spine. */
.nc-node--wait,
.nc-node--notify,
.nc-node--automation,
.nc-node--merge {
  background: color-mix(in srgb, var(--nc-surface) 88%, var(--nc-ground));
}

/* — sticky note ————————————————————————————————————————————————————————
 * A drawing, not a step: paper stock, no stripe, no ports, and its text runs to
 * several lines because the note IS the content. */
.nc-node--sticky {
  grid-template-columns: 20px minmax(0, 1fr);
  align-content: start;
  padding: 10px 12px;
  border-color: var(--nc-border);
  border-color: color-mix(in srgb, var(--color-accent-light, #f5a623) 40%, var(--nc-border));
  border-radius: 4px 12px 4px 4px;
  background: var(--nc-surface);
  background: color-mix(in srgb, var(--color-accent-light, #f5a623) var(--nc-sticky-mix), var(--nc-surface));
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.10);
}

.nc-node--sticky::before {
  display: none;
}

.nc-node--sticky .nc-node-icon {
  grid-row: 1;
  width: 20px;
  height: 20px;
  background: transparent;
  color: var(--nc-warn);
}

.nc-node--sticky .nc-node-icon svg {
  width: 15px;
  height: 15px;
}

.nc-node--sticky .nc-node-title {
  font-size: 12px;
  font-weight: 700;
  color: var(--nc-ink-2);
}

.nc-node--sticky .nc-node-sub {
  grid-column: 1 / -1;
  grid-row: 2 / -1;
  white-space: normal;
  font-size: 12px;
  line-height: 1.45;
  color: var(--nc-ink);
  display: -webkit-box;
  -webkit-line-clamp: 5;
  -webkit-box-orient: vertical;
}

/* ══ ports ════════════════════════════════════════════════════════════════════
 * Rendered only where the kind declares one (no render-then-hide), positioned on
 * the exact geometric points NcNodes.portPoint() reports:
 *   in     left edge, centre        out     right edge, centre
 *   dash   TOP edge, centre         bottom  BOTTOM edge, centre
 * The dot is 11px because a bigger one would eat the node; the ::before gives it
 * the 44x44 a finger needs.
 */

.nc-port {
  position: absolute;
  z-index: 2;
  width: 11px;
  height: 11px;
  box-sizing: border-box;
  border: 2px solid var(--nc-surface);
  border-radius: 50%;
  background: var(--nc-edge-advance);
  cursor: crosshair;
  transition: transform 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}

/* The hit target. Invisible, 44x44, and it is what actually receives the
 * pointer — the visible dot is only the mark. */
.nc-port::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
}

.nc-port:hover,
.nc-port:focus-visible {
  transform: translate(var(--nc-port-tx, 0), var(--nc-port-ty, 0)) scale(1.35);
  background: var(--nc-focus);
}

.nc-port:focus-visible {
  outline: 2px solid var(--nc-focus);
  outline-offset: 3px;
}

/* Each side stores its own centring translate in two custom properties so the
 * hover/valid/invalid rules can scale WITHOUT losing the centring. */
.nc-port--in {
  --nc-port-tx: -50%;
  --nc-port-ty: -50%;
  left: 0;
  top: 50%;
  transform: translate(-50%, -50%);
}

.nc-port--out {
  --nc-port-tx: -50%;
  --nc-port-ty: -50%;
  left: 100%;
  top: 50%;
  transform: translate(-50%, -50%);
}

/* The dashboard socket and plug are DIAMONDS, not circles — the one visual
 * difference that survives a greyscale screenshot and tells you this port is not
 * part of the left-to-right flow. */
.nc-port--dash,
.nc-port--bottom,
.nc-port--dash-out {
  --nc-port-tx: -50%;
  --nc-port-ty: -50%;
  left: 50%;
  border-radius: 2px;
  background: var(--nc-edge-dash);
}

.nc-port--dash {
  top: 0;
  transform: translate(-50%, -50%) rotate(45deg);
}

.nc-port--bottom,
.nc-port--dash-out {
  top: 100%;
  transform: translate(-50%, -50%) rotate(45deg);
}

.nc-port--dash:hover,
.nc-port--dash:focus-visible,
.nc-port--bottom:hover,
.nc-port--bottom:focus-visible {
  transform: translate(-50%, -50%) rotate(45deg) scale(1.35);
}

/* While a connection is being dragged every candidate port answers: green ring
 * = this one takes it, red ring + a bar through it = it does not. The refusal
 * also lands as a sentence in .nc-hint; this is only the local echo. */
.nc-port.is-dropping {
  background: var(--nc-ok);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--nc-ok) 32%, transparent);
}

.nc-port.is-invalid {
  background: var(--nc-danger);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--nc-danger) 26%, transparent);
}

.nc-port.is-invalid::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 15px;
  height: 2px;
  transform: translate(-50%, -50%) rotate(45deg);
  background: var(--nc-surface);
  border-radius: 2px;
}

/* Ports are the target of the whole gesture, so they grow while one is running
 * — on the surface, not per node. */
.nc-canvas.is-connecting .nc-port {
  transform: translate(var(--nc-port-tx, 0), var(--nc-port-ty, 0)) scale(1.15);
}

.nc-canvas.is-connecting .nc-port--dash,
.nc-canvas.is-connecting .nc-port--bottom,
.nc-canvas.is-connecting .nc-port--dash-out {
  transform: translate(-50%, -50%) rotate(45deg) scale(1.15);
}

/* The descendant selector above out-ranks `.nc-port:hover`, so the hover growth
   has to be restated inside the connecting state or the port stops answering
   the pointer exactly when it matters most. */
.nc-canvas.is-connecting .nc-port:hover,
.nc-canvas.is-connecting .nc-port:focus-visible {
  transform: translate(var(--nc-port-tx, 0), var(--nc-port-ty, 0)) scale(1.45);
}

.nc-canvas.is-connecting .nc-port--dash:hover,
.nc-canvas.is-connecting .nc-port--dash:focus-visible,
.nc-canvas.is-connecting .nc-port--bottom:hover,
.nc-canvas.is-connecting .nc-port--bottom:focus-visible {
  transform: translate(-50%, -50%) rotate(45deg) scale(1.45);
}

/* ══ quick-add stubs (A2) ═════════════════════════════════════════════════════
 * The one control a non-technical person will find: every unconnected output
 * grows a 46px stub ending in a 24px rounded-square +. nc-canvas.js builds it
 * inside the viewport so it stays glued to its port while the canvas pans, and
 * writes ALL of the geometry inline — position, the two lengths, the shield's
 * negative insets, and the counter-scale that keeps a 24px button 24px at 0.4
 * zoom. It also drives the button's hover colours from JS (pointerover on the
 * shield lights the button it belongs to, which a CSS `:hover` on the button
 * alone could never do).
 *
 * So this block deliberately styles only what that leaves bare:
 *   - the layer's stacking, which is a real bug otherwise (below),
 *   - the FOCUS ring, the only affordance the JS hover path cannot provide,
 *   - the easing, so the JS colour swap reads as a deliberate response,
 *   - keeping the shield invisible in every rendering mode.
 */

/* `.nc-node-layer` is z-index 2 and this layer is appended after it with no
 * z-index at all — and a positioned `z-index: auto` box paints BELOW every
 * positive one regardless of DOM order. The stub reaches 70px to the right of
 * its node and its shield another 12px beyond that, so on a tight 128px gap the
 * + and its hover shield slide underneath the next node and stop answering the
 * pointer. Naming the level fixes it, and matches how every other layer here is
 * ordered (grid 0, edges 1, nodes 2). */
.nc-plus-layer {
  z-index: 3;
}

/* The invisible hover margin. It must catch pointers (inline `pointer-events`
 * says so) and paint nothing, ever — including in forced-colours, where an
 * un-styled box can pick up a system border and become a visible rectangle. */
.nc-plus-shield {
  display: block;
  background: transparent;
  border: 0;
  forced-color-adjust: none;
}

.nc-plus-line {
  border-radius: 1px;
}

/* Colour is inline and JS-driven; what is missing is the keyboard. Tabbing to a
 * + gets no pointerover, so without this rule the focused button is identical to
 * an idle one and the whole quick-add path is invisible to a keyboard user. The
 * offset ring sits outside the 24px square, which is why the shield's 12px of
 * margin is exactly enough room for it. */
.nc-plus-btn {
  box-shadow: var(--nc-shadow);
  transition: background 0.14s ease, border-color 0.14s ease, color 0.14s ease;
}

.nc-plus-btn:focus-visible {
  outline: 2px solid var(--nc-focus);
  outline-offset: 2px;
}

.nc-plus-btn svg {
  display: block;
}

/* ══ edges ════════════════════════════════════════════════════════════════════
 * stroke, stroke-dasharray and marker-end are presentation attributes written by
 * JS, so these rules win without !important. The dash arrays are deliberately
 * left to JS — the legend swatch is fed by the same dashFor(), and overriding
 * one and not the other would make the legend lie.
 */

.nc-edge {
  fill: none;
  stroke-width: 2;
  stroke-linejoin: round;
  transition: stroke-width 0.15s ease, opacity 0.15s ease;
}

/* The marker is painted with fill="context-stroke", so colouring the stroke
 * colours its arrowhead too. `color` is set as well because nc-canvas.js's own
 * fallback path writes stroke="currentColor". */
.nc-edge--advance { stroke: var(--nc-edge-advance); color: var(--nc-edge-advance); }
.nc-edge--back    { stroke: var(--nc-edge-back);    color: var(--nc-edge-back); }
.nc-edge--branch  { stroke: var(--nc-edge-branch);  color: var(--nc-edge-branch); }
.nc-edge--auto    { stroke: var(--nc-edge-auto);    color: var(--nc-edge-auto); }

/* An attachment is not a route, so it recedes and carries no arrowhead. */
.nc-edge--dash {
  stroke: var(--nc-edge-dash);
  color: var(--nc-edge-dash);
  stroke-width: 1.5;
  opacity: 0.85;
}

/* Hover and the hover-toolbar selection both thicken the line rather than only
 * recolouring it. */
.nc-edge.is-selected,
.nc-edge-group:hover .nc-edge {
  stroke-width: 3.5;
}

.nc-edge--dash.is-selected,
.nc-edge-group:hover .nc-edge--dash {
  stroke-width: 2.5;
  opacity: 1;
}

.nc-edge-hit {
  cursor: pointer;
}

.nc-edge-group:focus-visible {
  outline: none;
}

.nc-edge-group:focus-visible .nc-edge {
  stroke-width: 3.5;
  stroke: var(--nc-focus);
}

/* The ghost drawn while a connection is being dragged. */
.nc-edge--ghost,
.nc-edge.is-connecting {
  stroke: var(--nc-focus);
  stroke-dasharray: 4 4;
  opacity: 0.9;
}

/* ══ empty states ═════════════════════════════════════════════════════════════
 * Every one of these guides. nc-canvas.js's canvas-level empty box carries its
 * own inline geometry; these rules dress the in-panel ones.
 */

.nc-empty {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 16px 18px;
  border: 1px dashed var(--nc-border);
  border-radius: var(--nc-radius-lg);
  background: var(--nc-surface);
  color: var(--nc-ink-2);
  font-size: 12.5px;
  line-height: 1.55;
  text-align: center;
}

.nc-empty-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--nc-ink);
}

.nc-empty-body {
  max-width: 58ch;
  align-self: center;
  color: var(--nc-ink-2);
}

.nc-empty-actions {
  display: flex;
  gap: 6px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 4px;
}

/* ══ form atoms ═══════════════════════════════════════════════════════════════
 * Shared by the palette, the NDV and every registered pane. nc-ndv.js ships the
 * same set wrapped in :where(), purely as a fallback; these are the real ones.
 */

.nc-field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}

.nc-field-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--nc-ink-3);
}

.nc-field-hint {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--nc-ink-3);
}

.nc-input,
.nc-select,
.nc-textarea {
  width: 100%;
  box-sizing: border-box;
  min-height: 38px;
  padding: 8px 10px;
  font: inherit;
  font-size: 13px;
  color: var(--nc-ink);
  background: var(--nc-surface);
  border: 1px solid var(--nc-border);
  border-radius: var(--nc-radius);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.nc-textarea {
  min-height: 80px;
  line-height: 1.5;
  resize: vertical;
}

.nc-input::placeholder,
.nc-textarea::placeholder {
  color: var(--nc-ink-3);
  opacity: 0.85;
}

.nc-input:focus,
.nc-select:focus,
.nc-textarea:focus {
  outline: 2px solid var(--nc-focus);
  outline-offset: 1px;
  border-color: transparent;
}

/* Chrome's own dark "x" on type=search vanishes on a dark surface — the same
 * removal sequences-page.css:113 makes for the sequence search. */
.nc-input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

.nc-input:disabled,
.nc-select:disabled,
.nc-textarea:disabled,
.nc-input.is-disabled,
.nc-select.is-disabled {
  opacity: 0.6;
  cursor: not-allowed;
  background: var(--nc-surface-2);
}

/* A status token is immutable — renaming one orphans live rows (562 video rows,
 * 06-22) — so the field that shows it is readonly by design and has to LOOK it,
 * not look broken. */
.nc-input.is-readonly,
.nc-input[readonly],
.nc-textarea[readonly] {
  background: var(--nc-surface-2);
  color: var(--nc-ink-2);
  border-style: dashed;
  cursor: default;
}

.nc-input.is-invalid,
.nc-select.is-invalid,
.nc-textarea.is-invalid {
  border-color: var(--nc-danger);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--nc-danger) 14%, transparent);
}

/* An expression token is being dragged over this input. */
.nc-input.is-dropping,
.nc-textarea.is-dropping {
  border-color: var(--nc-focus);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--nc-focus) 18%, transparent);
}

.nc-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 34px;
  padding: 7px 12px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--nc-ink);
  background: var(--nc-surface);
  border: 1px solid var(--nc-border);
  border-radius: var(--nc-radius);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
}

.nc-btn:hover:not(:disabled):not(.is-disabled) {
  background: var(--nc-surface-2);
}

.nc-btn:focus-visible {
  outline: 2px solid var(--nc-focus);
  outline-offset: 2px;
}

.nc-btn:disabled,
.nc-btn.is-disabled,
.nc-btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

.nc-btn--primary {
  background: var(--nc-accent);
  border-color: transparent;
  color: #ffffff;
}

.nc-btn--primary:hover:not(:disabled):not(.is-disabled) {
  background: var(--nc-accent);
  filter: brightness(0.94);
}

/* --accent-color is a LIGHTER blue in dark mode, so white on it falls to ~2.5:1.
 * The same fix theme.css makes for .a4p-btn-primary and sequences-page.css:357
 * makes for .seq-page-btn-primary. */
html[data-theme="dark"] .nc-btn--primary {
  color: #0b1220;
}

.nc-btn--danger {
  background: var(--nc-danger);
  border-color: transparent;
  color: #ffffff;
}

html[data-theme="dark"] .nc-btn--danger {
  color: #1a0b0b;
}

.nc-btn--danger:hover:not(:disabled):not(.is-disabled) {
  background: var(--nc-danger);
  filter: brightness(0.94);
}

.nc-btn svg {
  display: block;
  width: 15px;
  height: 15px;
}

.nc-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: 100%;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  background: var(--nc-surface-2);
  color: var(--nc-ink-2);
}

.nc-chip--live {
  background: color-mix(in srgb, var(--nc-ok) 16%, transparent);
  color: color-mix(in srgb, var(--nc-ok) 70%, var(--nc-ink));
}

.nc-chip--warn {
  background: color-mix(in srgb, var(--nc-warn) 20%, transparent);
  color: color-mix(in srgb, var(--nc-warn) 60%, var(--nc-ink));
}

.nc-chip--danger {
  background: color-mix(in srgb, var(--nc-danger) 16%, transparent);
  color: color-mix(in srgb, var(--nc-danger) 70%, var(--nc-ink));
}

.nc-chip--muted {
  background: var(--nc-surface-2);
  color: var(--nc-ink-3);
}

/* A5 — "New deliverables start here", tone `start`, emitted on the first status
 * and nowhere else. It borrows the chain's own colour (`--nc-node-accent`, an
 * inline custom property on the node that inherits down to the chip) instead of
 * one of the three intents, because this is not a warning: it is the entry
 * point, and the node it sits on is already wearing that colour. */
.nc-chip--start {
  background: var(--nc-surface-2);
  background: color-mix(in srgb, var(--nc-node-accent, var(--nc-accent)) 18%, transparent);
  color: var(--nc-ink);
  font-weight: 700;
}

/* A4 — the live row count. It ships as `.nc-chip.nc-chip--live.nc-chip--count`
 * (or `--muted` at zero), and `--live` is a GREEN chip: on a node that colour
 * says "healthy", which is a claim nobody made. 132 rows sitting on a status is
 * neither good nor bad, it is a reading — so the count is a quiet numeric well,
 * the same tone the muted chip already uses, with tabular figures so a column of
 * nodes lines up and a changing number does not jitter.
 *
 * `.nc-node .nc-chip--count` rather than `.nc-chip--count`: the element carries
 * BOTH tone classes, and two class selectors of equal weight would be settled by
 * source order alone. Scoping to the node makes it explicit, and leaves
 * `--live` free to stay green wherever else a pane wants a health chip.
 * `--muted` needs no override — it is already exactly this. */
.nc-node .nc-chip--count {
  background: var(--nc-surface-2);
  color: var(--nc-ink-2);
  font-variant-numeric: tabular-nums;
}

/* A draggable expression token. Dashed = "pick me up and drop me in a field". */
.nc-token {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  padding: 5px 9px;
  font: inherit;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  text-align: left;
  color: var(--nc-ink);
  background: var(--nc-surface-2);
  border: 1px dashed var(--nc-border);
  border-radius: 7px;
  cursor: grab;
}

.nc-token:hover {
  border-style: solid;
  border-color: var(--nc-focus);
}

.nc-token:focus-visible {
  outline: 2px solid var(--nc-focus);
  outline-offset: 2px;
}

.nc-token.is-dragging {
  opacity: 0.5;
  cursor: grabbing;
}

.nc-kbd,
.nc-ctx-keys kbd {
  display: inline-block;
  min-width: 18px;
  padding: 1px 5px;
  border: 1px solid var(--nc-border);
  border-bottom-width: 2px;
  border-radius: 5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 10.5px;
  line-height: 1.5;
  color: var(--nc-ink-2);
  background: var(--nc-surface-2);
}

/* ══ context menu ═════════════════════════════════════════════════════════════
 * nc-keyboard.js injects its own full rules for .nc-ctx* at runtime, i.e. after
 * this file, so it owns the detail. This is only the surface, so the menu is
 * never an unstyled white box in the half-second before that script runs.
 */

.nc-ctx {
  min-width: 224px;
  padding: 5px;
  border: 1px solid var(--nc-border);
  border-radius: 10px;
  background: var(--nc-surface);
  color: var(--nc-ink);
  box-shadow: var(--nc-shadow-lg);
  font-size: 12.5px;
}

.nc-ctx-sep {
  height: 1px;
  margin: 4px 2px;
  background: var(--nc-border-soft);
}

/* ══ NDV — the node detail view ═══════════════════════════════════════════════
 * Three panes: what comes IN, the node's own PARAMETERS, what goes OUT. The
 * splitters between them are real buttons that nc-ndv.js resizes by writing
 * `flex` inline on the pane either side — so the panes must stay flex items of a
 * row flexbox for that to have any meaning.
 */

.nc-ndv-backdrop {
  position: fixed;
  inset: 0;
  z-index: 10040;          /* under .seq-confirm-overlay (10050), on purpose */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(15, 23, 42, 0.45);
  backdrop-filter: blur(2px);
}

html[data-theme="dark"] .nc-ndv-backdrop {
  background: rgba(0, 0, 0, 0.62);
}

.nc-ndv {
  display: flex;
  flex-direction: column;
  width: min(1280px, 100%);
  max-height: min(88vh, 940px);
  min-height: 0;
  overflow: hidden;
  border: 1px solid var(--nc-border);
  border-radius: 14px;
  background: var(--color-secondary, #ffffff);
  color: var(--nc-ink);
  font-family: var(--font-family, inherit);
  font-size: 13px;
  box-shadow: var(--nc-shadow-lg);
}

.nc-ndv-bar {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  column-gap: 12px;
  align-items: center;
  padding: 12px 16px;
  border-bottom: 1px solid var(--nc-border);
}

.nc-ndv-icon {
  grid-column: 1;
  grid-row: 1 / 3;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 9px;
  background: var(--nc-surface-2);
  color: var(--nc-ink-2);
}

.nc-ndv-icon svg {
  display: block;
  width: 20px;
  height: 20px;
}

/* The title is an <input>: the label is editable in place. It has to read as
 * text until you touch it, and as a field the moment you do. */
.nc-ndv-title {
  grid-column: 2;
  grid-row: 1;
  width: 100%;
  min-width: 0;
  margin: 0;
  padding: 3px 6px;
  font: inherit;
  font-family: inherit;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: inherit;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 7px;
}

.nc-ndv-title:hover:not(:disabled):not([readonly]) {
  border-color: var(--nc-border);
}

.nc-ndv-title:focus {
  outline: 2px solid var(--nc-focus);
  outline-offset: 1px;
  border-color: transparent;
  background: var(--nc-surface);
}

.nc-ndv-title[readonly],
.nc-ndv-title:disabled {
  cursor: default;
}

.nc-ndv-sub {
  grid-column: 2;
  grid-row: 2;
  min-width: 0;
  padding: 0 6px;
  font-size: 11.5px;
  color: var(--nc-ink-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nc-ndv-actions {
  grid-column: 3;
  grid-row: 1 / 3;
  display: flex;
  align-items: center;
  gap: 8px;
}

.nc-ndv-body {
  flex: 1 1 auto;
  display: flex;
  align-items: stretch;
  min-height: 0;
}

.nc-ndv-pane {
  display: flex;
  flex-direction: column;
  flex: 1 1 0;
  min-width: 0;
  min-height: 0;
}

/* The middle pane is the one people actually work in. */
.nc-ndv-pane--params {
  flex: 1.35 1 0;
  background: var(--nc-surface);
}

.nc-ndv-pane--input,
.nc-ndv-pane--output {
  background: color-mix(in srgb, var(--nc-surface) 55%, var(--nc-ground));
}

.nc-ndv-pane-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 9px 14px;
  border-bottom: 1px solid var(--nc-border-soft);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--nc-ink-3);
}

.nc-ndv-pane-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-height: 0;
  padding: 14px;
  overflow: auto;
  overscroll-behavior: contain;
}

/* ══ responsive ═══════════════════════════════════════════════════════════════
 * <=900px is the same breakpoint the rest of the Sequences page stacks at.
 */

@media (max-width: 900px) {
  /* The toolbar wraps to two or three rows on a phone. Splitting a fixed shell
     height between that, a palette and a canvas leaves the canvas about 180px —
     unusable. So the shell sizes to its content here and the PAGE scrolls, the
     same trade sequences-page.css:549-557 makes for its stacked columns. */
  .nc-root {
    height: auto;
  }

  /* One column, and row 1 is an EXPLICIT track rather than `auto`: the open
     palette is absolutely positioned, which takes it out of the grid, and an
     `auto` track would then collapse to zero and jolt the canvas 56px up the
     screen every time the drawer opens. Sizing the track to the closed rail
     holds the layout still through the whole open/close cycle.
     position:relative is what the drawer is absolute inside.

     Row 2 is `auto`, NOT `minmax(0, 1fr)`. `.nc-root` is height:auto here, so
     the grid's block size is indefinite, and a `minmax(0, 1fr)` track in an
     indefinite grid resolves to its base size — zero. The canvas then overflows
     a 66px-tall .nc-body, which looks fine (nothing clips it) and quietly
     breaks the drawer, because `bottom: 0` is 66px from the top. Measured:
     drawer 66px tall instead of 623. The canvas carries its own height, so
     `auto` is both correct and honest. */
  .nc-body {
    position: relative;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: var(--nc-rail-h) auto;
  }

  /* The palette used to take 253px of an 844px phone — 30% of the screen, and
     it pushed the canvas to y=594, below the fold, so the thing you opened the
     page for was off-screen until you scrolled. 56px buys most of that back. */
  .nc-canvas {
    /* Pinned, because auto-placement is not stable across the two states. The
       moment the palette goes absolute it stops being a grid item, the canvas
       becomes the first auto-placed one and slides up into row 1 — row 2 then
       measures 0, .nc-body collapses to 66px, and the drawer's `bottom: 0` is
       66px from the top of a 623px body. Measured exactly that before pinning:
       a 66px-tall drawer with all eleven rows spilling out of it. */
    grid-row: 2;
    height: 66vh;
    min-height: 360px;
  }

  /* ── the palette: a rail that opens into a drawer ─────────────────────────
     PLAN said "collapses to a drawer at <=900px"; round 1 stacked it instead.
     This is the drawer.

     CLOSED is one 56px row: the title, the search box, and a chevron. Not an
     empty handle — the search IS the control, so the panel stays useful at its
     smallest and a touch user is never stranded looking for a way in.

     OPEN is the same panel promoted to an absolute, full-height sheet over the
     canvas. Three things open it, and any one is enough:
       - `:focus-within` — tap or tab into the search. This is the path that
         works with NO javascript at all, which matters because nothing in the
         canvas emits a toggle button today.
       - `.is-open` on the panel, or `.is-palette-open` on `.nc-root` — the
         hooks for a real toggle whenever one is wired.

     Written as ONE closed state guarded by :not(), rather than a closed rule
     plus an open rule that restores each child: there is nothing to keep in
     sync, so a child added to the palette later cannot end up hidden in the
     drawer because somebody forgot to un-hide it.

     Focus is NOT trapped, and cannot be — this is CSS. Tab moves search → the
     palette rows → out of the panel, and leaving is exactly what closes it.
     Nothing here sets `inert`, steals focus, or blocks the page behind it. */

  .nc-root:not(.is-palette-open) .nc-palette:not(.is-open):not(:focus-within) {
    flex-direction: row;
    align-items: center;
    gap: 8px;
    height: 100%;
    max-height: none;
    padding: 6px 8px;
    overflow: hidden;
  }

  /* Everything below the search line, out of the way while closed. */
  .nc-root:not(.is-palette-open) .nc-palette:not(.is-open):not(:focus-within) .nc-palette-sub,
  .nc-root:not(.is-palette-open) .nc-palette:not(.is-open):not(:focus-within) .nc-palette-count,
  .nc-root:not(.is-palette-open) .nc-palette:not(.is-open):not(:focus-within) .nc-palette-group,
  .nc-root:not(.is-palette-open) .nc-palette:not(.is-open):not(:focus-within) .nc-palette-suggest,
  .nc-root:not(.is-palette-open) .nc-palette:not(.is-open):not(:focus-within) .nc-palette-clear {
    display: none;
  }

  .nc-root:not(.is-palette-open) .nc-palette:not(.is-open):not(:focus-within) .nc-palette-head {
    flex: 0 0 auto;
  }

  .nc-root:not(.is-palette-open) .nc-palette:not(.is-open):not(:focus-within) .nc-palette-title {
    white-space: nowrap;
  }

  /* Sticky is meaningless in a 56px rail and would fight the row layout. */
  .nc-root:not(.is-palette-open) .nc-palette:not(.is-open):not(:focus-within) .nc-palette-search {
    position: static;
    flex: 1 1 auto;
    min-width: 0;
    width: auto;
  }

  /* The affordance that says the row expands. Drawn from two borders rather
     than a glyph so no screen reader reads a stray character, and it is the
     LAST flex item, so it needs no positioning. */
  .nc-root:not(.is-palette-open) .nc-palette:not(.is-open):not(:focus-within)::after {
    content: "";
    flex: 0 0 auto;
    width: 7px;
    height: 7px;
    margin: 0 3px 3px 0;
    border-right: 2px solid var(--nc-ink-3);
    border-bottom: 2px solid var(--nc-ink-3);
    transform: rotate(45deg);
  }

  /* OPEN — over the canvas, never beside it. */
  .nc-palette:focus-within,
  .nc-palette.is-open,
  .nc-root.is-palette-open .nc-palette {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    z-index: 20;
    width: min(320px, 88%);
    height: auto;
    max-height: none;
    box-shadow: var(--nc-shadow-lg);
    animation: nc-drawer-in 0.22s ease;
  }

  /* A drawer over a graph needs the graph to step back, or the two read as one
     picture. Not a modal though: `pointer-events: none` keeps the canvas live
     underneath, and tapping it is what closes the drawer (focus leaves).
     Wrapped in @supports because one unsupported selector invalidates a whole
     comma list — without the guard a browser without :has() would lose the
     `.is-palette-open` scrim too. */
  @supports selector(:has(*)) {
    .nc-body:has(> .nc-palette:focus-within)::after,
    .nc-body:has(> .nc-palette.is-open)::after,
    .nc-root.is-palette-open .nc-body::after {
      content: "";
      position: absolute;
      inset: 0;
      z-index: 19;
      background: rgba(15, 23, 42, 0.28);
      border-radius: var(--nc-radius-lg);
      pointer-events: none;
    }
  }

  /* Touch targets everywhere it matters. The search box needs the two-class
     form: its base rule is `.nc-palette-search.nc-input` and a single class
     here would lose the tie and stay 40px. The + stub's 24px button is the one
     real exception and needs no rule — nc-canvas.js wraps it in a 12px
     invisible shield on every side, which is a 48px target already. */
  .nc-palette-item,
  .nc-palette-search.nc-input,
  .nc-palette-search,
  .nc-btn,
  .nc-token,
  .nc-ctx-item {
    min-height: 44px;
  }

  /* A 200px minimap on a 390px canvas is half the surface for a map of a graph
     you can already see. `display` is the one property buildMinimap() does not
     write inline, so this actually takes. */
  .nc-minimap {
    display: none;
  }

  /* The legend then moves into the space the minimap left, stopping short of
     the zoom controls rather than sliding under them. */
  .nc-legend {
    right: auto;
    left: 10px;
    top: auto;
    bottom: 10px;
    max-width: calc(100% - 80px);
    gap: 2px 10px;
    padding: 6px 8px;
  }

  .nc-hint {
    max-width: calc(100% - 20px);
    font-size: 11px;
  }

  /* The NDV becomes a sheet: full bleed, one column, no splitters. display:block
     is deliberate — it makes the inline `flex` the splitter drag left on a pane
     inert, so a desktop drag can never freeze a pane's height here. */
  .nc-ndv-backdrop {
    padding: 0;
  }

  .nc-ndv {
    width: 100%;
    height: 100%;
    max-height: 100vh;
    border: 0;
    border-radius: 0;
  }

  .nc-ndv-body {
    display: block;
    overflow: auto;
    overscroll-behavior: contain;
  }

  .nc-ndv-pane {
    display: block;
    border-bottom: 1px solid var(--nc-border);
  }

  .nc-ndv-pane-body {
    display: block;
    overflow: visible;
  }

  .nc-ndv-pane-body > * + * {
    margin-top: 12px;
  }

  .nc-ndv-split {
    display: none;
  }
}

@keyframes nc-drawer-in {
  from { transform: translateX(-100%); }
  to   { transform: none; }
}

/* ══ motion ═══════════════════════════════════════════════════════════════════ */

@media (prefers-reduced-motion: reduce) {
  .nc-node,
  .nc-port,
  .nc-edge,
  .nc-palette-item,
  .nc-btn,
  .nc-plus-btn,
  .nc-input,
  .nc-select,
  .nc-textarea,
  .nc-hint {
    transition: none;
  }

  /* :focus-within is listed because it is the drawer's real opener on a phone —
     leaving it out would let the one gesture people actually use keep sliding. */
  .nc-root.is-palette-open .nc-palette,
  .nc-palette.is-open,
  .nc-palette:focus-within {
    animation: none;
  }

  /* Scale is the affordance that says "this port is the target", so it stays —
     it just arrives instantly instead of easing in. */
}

/* ══ forced colours ═══════════════════════════════════════════════════════════
 * Windows high-contrast throws away every background, which would leave the
 * nodes and ports as bare text. Borders keep the boxes; the ports keep a shape.
 */

@media (forced-colors: active) {
  .nc-node,
  .nc-palette-item,
  .nc-btn,
  .nc-plus-btn,
  .nc-chip,
  .nc-token {
    border: 1px solid CanvasText;
  }

  /* The stub's line is painted with an inline `background`, which forced-colours
     drops — without this the + floats 46px from its port with nothing joining
     them to the node it belongs to. */
  .nc-plus-line {
    background: CanvasText;
    forced-color-adjust: none;
  }

  .nc-port {
    background: CanvasText;
    border-color: Canvas;
    forced-color-adjust: none;
  }

  .nc-edge {
    stroke: CanvasText;
  }

  .nc-node.is-selected,
  .nc-palette-item.is-selected {
    outline: 2px solid Highlight;
  }
}
