/* binder.css — the Binder editor: structure AND the one skin.
   ---------------------------------------------------------------------------
   The look, per the design mock-up (rev 2): pure black board with the grid
   drawn as tiny crosses; nodes are black cards in a 1px white wireframe;
   titles are 32px Trim (Trim is a TITLE font, caps only — everything else
   speaks Helvetica); categories 16px Helvetica at 50% white. Ports sit on
   the frame line: an 8px hollow square when empty, a 4px solid square when
   occupied. Cords are 2px white drafting lines with 8px-radius chamfers at
   every bend, dotted while showing an auto-join. NO SHADING ANYWHERE — no
   gradients, no glows, no recesses. EVERYTHING IS WHITE — colour, motion
   and polish come later; this style lives or dies by equal spacing and
   alignment.

   (The original charcoal skin is retired — it was only ever its own design
   study. This file now carries the structure and the wireframe look as one.)

   Tokens in src/ui/binder/tokens.css. Everything is scoped under .binder so
   it is safe to mount as an overlay in the main app. */

/* --- type ------------------------------------------------------------------
   Trim — the editor's face. Four weights ship in fonts/: Thin (200), Light
   (300), Regular (400), Medium (500). Node labels use Light, categories Thin. */
@font-face {
  font-family: "Trim";
  src: url("fonts/Trim-Thin.woff2") format("woff2");
  font-weight: 200;
  font-display: swap;
}
@font-face {
  font-family: "Trim";
  src: url("fonts/Trim-Light.woff2") format("woff2");
  font-weight: 300;
  font-display: swap;
}
@font-face {
  font-family: "Trim";
  src: url("fonts/Trim-Regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Trim";
  src: url("fonts/Trim-Medium.woff2") format("woff2");
  font-weight: 500;
  font-display: swap;
}

/* --- tokens ----------------------------------------------------------------
   Colours and family ride the shared tokens so the whole editor themes from
   here. --alt-inset is THE spacing unit inside a node: the text block sits
   exactly this far from the frame on every side that matters. */
:root {
  /* ---- the charcoal tool look (carried over from Case Builder) ------------
     Every node is a solid card on a gridded charcoal board: a gradient face
     lifted off the grid by a soft shadow, ring-stud ports on its edges, thick
     colour-matched cords between them. One family of tokens; the skin keys
     off these so a palette pass flows through here, not 40 selectors. */
  --cb-canvas:        #151617;
  --cb-grid-line:     #0f1011;             /* half the old #1e2023 — a quieter grid on the black */
  --cb-grid-size:     64px;                /* the base of the 64 system */
  --cb-node-radius:   16px;
  --cb-node-grad-top: #212329;
  --cb-node-grad-mid: #191b1f;
  --cb-node-grad-bot: #141619;
  --cb-node-shadow:
      0 14px 30px -14px rgba(0,0,0,.7),
      0 2px 6px rgba(0,0,0,.4),
      inset 0 1px 0 rgba(255,255,255,.05),
      inset 0 -1px 0 rgba(0,0,0,.45);
  --cb-node-shadow-hover:
      0 22px 44px -16px rgba(0,0,0,.78),
      0 5px 12px rgba(0,0,0,.5),
      inset 0 1px 0 rgba(255,255,255,.07),
      inset 0 -1px 0 rgba(0,0,0,.5);
  --cb-ease:          cubic-bezier(.2,.9,.25,1);
  --cb-specular:      rgba(255,255,255,.08);
  --cb-title:         #eceef0;             /* node NAME — white */
  --cb-data:          #868a90;             /* secondary text — plain grey */
  --cb-silver:        #bec2c9;
  --cb-port-d:        16px;                /* ring-stud diameter */
  --cb-port-ring:     2px;
  --cb-cord-w:        4.5px;
  --cb-panel:         #16181c;
  --cb-panel-edge:    #2a2d33;
  --cb-chip:          #1f2127;
  --cb-chip-edge:     #2d2f36;

  /* ---- functional wire colours (by the role a port carries) --------------
     Restrained accents on the charcoal, the way Case Builder colour-codes its
     categories: a value is green, a filter gold, a shape orange, an operator
     violet. A cord takes the colour of the value it carries. */
  --role-value:  #4cb782;                  /* a normalised value (Value / phase / knob / interp out) */
  --role-filter: #f1bf00;                  /* a segment gate */
  --role-shape:  #e08a4c;                  /* a transfer curve */
  --role-op:     #9a7fd1;                  /* an operator (Interpolate, Sum, …) */

  --eu-canvas: var(--cb-canvas);
  --eu-font: 'Trim', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --alt-line: var(--cb-title);            /* the ink for names / traces */
  --alt-dim: var(--cb-data);              /* categories, secondary text */
  --alt-faint: #2a2d34;                   /* control borders */
  --alt-inset: 16px;                      /* THE text inset — a quarter board square */
  --alt-stroke: 1px;                      /* outline weight */
  --alt-cord: var(--cb-cord-w);           /* cord weight */
  --alt-half: var(--cb-data);
  --alt-sel: #ff9900;                     /* selection ring */
  --wire: var(--role-value);              /* the live-value accent (knob arc, slider fill, meter) */
  --wire-dim: rgba(76, 183, 130, 0.45);
  --dest: var(--role-value);
  --node-dim: rgba(255, 255, 255, 0.32);  /* an unwired input label */
  --dot-gap: 12px;
  /* Node names are mixed-case Helvetica — Trim (caps-only) stays available. */
  --eu-prose: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

/* One ink. Caps belong to TITLES ONLY (.node-name carries the transform) —
   everything else renders as authored, in Helvetica: port labels, categories,
   readouts, the menu, comments, prose. Trim simply has no lowercase, so if a
   thing isn't a title it isn't Trim. Every category colour collapses to
   white — menus, pills, toggles, marquee, control widgets. Monochrome until
   the polish round says otherwise. */
.binder { font-weight: 400; }
.binder-menu-btn { font-weight: 700; }
.binder-menu-item, .binder-json-toggle, #binder-toast { font-weight: 600; }
.binder-readout pre { text-transform: none; }
/* Every node's accent (--type) defaults to silver; a role rule below tints
   the ones that carry a typed value. Ports and cords take their colour from
   the value's role (data-accept / data-role), the way Case Builder does. */
.binder .binder-menu { --type: var(--cb-silver); }
.binder .cell { --type: var(--cb-silver); }
.binder .cell.is-op { --type: var(--role-op); }
.binder .cell[data-role="filter"] { --type: var(--role-filter); }
.binder .cell[data-role="shape"]  { --type: var(--role-shape); }
.binder .cell[data-role="value"]  { --type: var(--role-value); }

/* Docked host — the Binder lives in the region below the tracks, filling
   whatever height is left once the tracks sit at their natural size. It's a
   flex child of <body>. Press B to maximise it to fullscreen (data-mode="max");
   B / Esc / the × restores it to the dock. */
.binder-overlay {
  position: relative;
  flex: 1 1 0;
  min-height: 0;
  /* Same near-black as the tracks page (--eu-bg) so the editor and the tracks
     read as one continuous window, not two panels with a seam. The board grid
     and the charcoal node cards sit on this shared black, exactly the way the
     charcoal track lanes sit on it up top. */
  background: var(--eu-bg, #070808);
  border-top: 1px solid #000;
}
.binder-overlay[data-mode="max"] {
  position: fixed; inset: 0; z-index: 9500; border-top: 0;
}
/* The × only closes the maximised view — in the dock there's nothing to close. */
.binder-overlay:not([data-mode="max"]) .binder-close { display: none; }

/* Self-owned save toast — sits above the overlay so it's visible whether the
   Binder is open or just closing over the app. */
#binder-toast {
  position: fixed; bottom: 20px; left: 50%; z-index: 9600;
  transform: translateX(-50%) translateY(8px);
  padding: 8px 14px; border-radius: 8px;
  background: #1f2127; color: var(--eu-title); border: 1px solid #2d2f36;
  font-family: var(--eu-prose); font-size: 12px; font-weight: 400;
  opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s;
}
#binder-toast[data-show="true"] { opacity: 1; transform: translateX(-50%) translateY(0); }
.binder-close {
  position: absolute; top: 10px; right: 12px; z-index: 10;
  width: 30px; height: 30px; border-radius: 6px; cursor: pointer;
  background: #1f2127; color: var(--eu-title); border: 1px solid #2d2f36;
  font: inherit; font-size: 18px; line-height: 1;
}
.binder-close:hover { background: #272a31; }

.binder {
  position: absolute; inset: 0;
  display: grid;
  grid-template-rows: auto 1fr;      /* menu bar on top, canvas below */
  grid-template-columns: 1fr;        /* JSON readout column added when open */
  font-family: var(--eu-font); color: var(--eu-title);
  background: var(--eu-bg, #070808); outline: none;
}
.binder.is-readout-open { grid-template-columns: 1fr 320px; }
.binder-toolbar { grid-area: 1 / 1 / 2 / -1; }   /* the menu bar spans the top */
.binder-canvas  { grid-area: 2 / 1 / 3 / 2; }
/* The right column holds both readouts (LOGIC + JSON), stacked. */
.binder-readouts { grid-area: 2 / 2 / 3 / 3; display: flex; flex-direction: column; min-height: 0; }
.binder-readouts .binder-readout { flex: 1 1 0; min-height: 0; }
.binder-readouts .binder-readout + .binder-readout { border-top: 1px solid #000; }

/* Bottom-right toggles for the readout panels (both hidden by default). */
.binder-toggles { position: absolute; bottom: 12px; right: 12px; z-index: 40; display: flex; gap: 6px; }
.binder-json-toggle {
  cursor: pointer;
  padding: 6px 10px; border-radius: 8px;
  background: #1f2127; color: var(--eu-title); border: 1px solid #2d2f36;
  font: inherit; font-size: 11px; font-weight: 400; letter-spacing: 0.02em;
}
.binder-json-toggle:hover { background: #272a31; }
.binder-json-toggle[aria-pressed="true"] {
  background: rgba(255, 255, 255, 0.12);
  border-color: var(--alt-dim);
  color: var(--alt-line);
}

/* --- toolbar: category menu bar ------------------------------------------- */
.binder-toolbar { background: #121315; border-bottom: 1px solid #000; }
/* The menu speaks mixed case ("Anticlockwise", "Knob 13"), so it speaks
   Helvetica — Trim is caps-only. The sub-heads keep their own small-caps
   style, just in the prose face. */
.binder-menubar { font-family: var(--eu-prose); text-transform: none; }
.binder-menubar {
  display: flex; align-items: stretch; justify-content: center; gap: 2px; min-height: 44px;
  padding: 0 8px;               /* centred; the toggles now live bottom-right, out of the bar */
}

/* A category: a button that drops a panel below it. All one ink (--type is
   collapsed to white above); the plumbing stays for a future colour pass. */
.binder-menu { position: relative; display: flex; align-items: stretch; }

.binder-menu-btn {
  display: inline-flex; align-items: center; padding: 0 14px; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 500; letter-spacing: -.01em;
  background: none; border: 0; border-bottom: 2px solid transparent;
  color: var(--type, var(--eu-data));
}
.binder-menu-btn:hover { background: #1b1d21; }
.binder-menu-btn[aria-expanded="true"] { background: #1b1d21; border-bottom-color: var(--type, var(--eu-data)); }

/* The dropdown panel — floats over the canvas below its button. */
.binder-menu-panel {
  position: absolute; top: 100%; left: 0; z-index: 60;
  min-width: 220px; max-height: 72vh; overflow-y: auto;
  display: flex; flex-direction: column; gap: 1px; padding: 8px;
  background: #16181c; border: 1px solid #2a2d33; border-radius: 10px;
  box-shadow: 0 18px 40px -16px rgba(0,0,0,.8), 0 4px 12px rgba(0,0,0,.5);
}
.binder-menu-panel[hidden] { display: none; }

/* --- context palette (mountBinder opts.palette: 'context') ------------------
   The menu bar leaves the top of the editor: the canvas takes the whole frame,
   and the SAME menu floats beside the pointer on right-click (view.js sets
   left/top and toggles .is-open). Categories stack vertically; each category's
   item panel flies out to the right. */
.binder--ctxpal .binder-toolbar {
  display: none;
  position: absolute; z-index: 70;
  background: none; border-bottom: 0;
}
.binder--ctxpal .binder-toolbar.is-open { display: block; }
.binder--ctxpal .binder-menubar {
  flex-direction: column; align-items: stretch; gap: 1px;
  min-height: 0; min-width: 140px; padding: 6px;
  background: #16181c; border: 1px solid #2a2d33; border-radius: 10px;
  box-shadow: 0 18px 40px -16px rgba(0, 0, 0, .8), 0 4px 12px rgba(0, 0, 0, .5);
}
.binder--ctxpal .binder-menu { display: block; }
.binder--ctxpal .binder-menu-btn {
  display: block; width: 100%; text-align: left;
  padding: 6px 12px; border-bottom: 0; border-radius: 6px;
}
.binder--ctxpal .binder-menu-panel { top: 0; left: 100%; margin-left: 6px; }

/* A subcategory: a light-grey head over its item pills. */
.binder-menu-sub { display: flex; flex-direction: column; }
.binder-menu-sub + .binder-menu-sub { margin-top: 6px; }
.binder-menu-sub-head {
  font-size: 9px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--eu-grey-light); margin: 2px 0 3px; padding: 0 4px;
}
.binder-menu-sub-items { display: flex; flex-direction: column; gap: 3px; }

/* An item is a fixed-length pill (all one ink). Drag it onto the canvas to
   place a node where you release; a plain click drops it at the view centre.
   Width is sized to the longest label ("Anticlockwise", 13ch) + 4ch of slack,
   plus the 14px×2 padding + 1px×2 border the pill carries. */
.binder-menu-item {
  position: relative;           /* anchors the ⇧X shortcut badge */
  display: block; width: calc(17ch + 30px); box-sizing: border-box; cursor: grab;
  padding: 3px 14px; text-align: center; border-radius: 999px;
  font: inherit; font-size: 13px; font-weight: 400; letter-spacing: -.01em;
  color: var(--type);
  background: color-mix(in srgb, var(--type), transparent 88%);
  border: 1px solid color-mix(in srgb, var(--type), transparent 55%);
}
.binder-menu-item:hover { background: color-mix(in srgb, var(--type), transparent 78%); }
.binder-menu-item:active { cursor: grabbing; }
/* The "All" entry — a dashed pill, reads as "the generic node". */
.binder-menu-item--all {
  margin-bottom: 6px; font-weight: 400; color: var(--type);
  background: none; border-style: dashed; border-color: color-mix(in srgb, var(--type), transparent 55%);
}
.binder-menu-item--all:hover { background: color-mix(in srgb, var(--type), transparent 88%); }

/* Auto-assigned keyboard shortcut (⇧X) — a dim tag at the pill's trailing
   edge. Shown only on the canonical "add this kind" chip. */
.binder-menu-key {
  position: absolute; right: 9px; top: 50%; transform: translateY(-50%);
  font-size: 10px; font-weight: 700; letter-spacing: .02em; opacity: .55;
  pointer-events: none;
}

/* --- the board: a faint dot where each grid cross was ----------------------
   One repeating tile with a small dot at the intersection, 64px apart. The
   editor's pan/zoom tracking (inline background-size / -position) moves and
   scales it. */
.binder-canvas {
  position: relative; overflow: hidden;   /* pan/zoom is via the stage transform, not scroll */
  background-color: var(--eu-bg, #070808);   /* one black field with the tracks above */
  background-image:
    linear-gradient(to right,  var(--cb-grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--cb-grid-line) 1px, transparent 1px);
  background-size: var(--eu-grid-size) var(--eu-grid-size);
}
/* Bare canvas: left-drag marquee-selects (crosshair), right-drag pans (grabbing).
   A node / port / cord shows its own cursor. */
.binder-canvas { cursor: crosshair; }
.binder-canvas.is-panning { cursor: grabbing; }
.binder-canvas.is-marquee { cursor: crosshair; }
/* The drag-selection box drawn over the canvas (screen-space, above the stage). */
.binder-marquee {
  position: absolute; z-index: 5; pointer-events: none;
  border: 1px solid var(--alt-dim);
  background: rgba(255, 255, 255, 0.08);
  border-radius: 2px;
}
.binder-stage { position: absolute; top: 0; left: 0; transform-origin: 0 0; }
/* overflow visible so cords to nodes placed up/left of the origin still draw. */
.binder-cords { position: absolute; top: 0; left: 0; pointer-events: none; overflow: visible; }
.binder-nodes { position: absolute; top: 0; left: 0; pointer-events: none; }

/* --- node card ------------------------------------------------------------ */
.binder .cell { position: absolute; pointer-events: auto; }

/* --- module nodes: a grid footprint as the consistent base ----------------
   Standard nodes use the standard card. A LARGER module (e.g. the ADSR) opts
   in by declaring a grid footprint geom.cols × geom.rows of the module unit
   (--eu-mod-w × --eu-mod-h) in the registry (src/nodes/operators.js); the
   view sizes it from that, tags it .is-module, and publishes the footprint
   here as --node-cols / --node-rows. Same wireframe card as everything else;
   the unit vars let module CSS place things on the same grid the box uses,
   e.g. left: calc(2 * var(--eu-mod-w)). */
.binder .cell.is-module { --node-cols: 6; --node-rows: 3; }   /* footprint fallback */

/* --- the node: a word (or a dial), no frame ---------------------------------
   The words-and-dots language: a node is its face — a name, a knob's dial, a
   constant's number — with dots for ports. No border, no fill; the board shows
   through. A plain node hugs its own text (width:max-content), so the output
   dot sits right after the word. A structured node (interp, a module with a
   grid footprint) keeps an explicit size set inline by the view (.is-boxed). */
/* Every node is a solid charcoal card: a top-lit gradient face, rounded, lifted
   off the grid by a soft shadow. The card fills the footprint the view sizes
   for it (all nodes are boxed now); its ports straddle the left/right edges. */
.binder .body {
  position: relative; box-sizing: border-box; height: 100%;
  padding: 14px 18px;
  display: flex; flex-direction: column; justify-content: center; align-items: flex-start;
  border-radius: var(--cb-node-radius); cursor: grab;
  background: linear-gradient(180deg, var(--cb-node-grad-top) 0%, var(--cb-node-grad-mid) 60%, var(--cb-node-grad-bot) 100%);
  box-shadow: var(--cb-node-shadow);
  transition: box-shadow .4s var(--cb-ease);
}
/* A sink (a param) reads from the right: name and category hug the right edge,
   its one input stud on the left. */
.binder .cell[data-side="sink"] .body { align-items: flex-end; text-align: right; }
/* A boxed node (interp / module / skin) lays out its own absolutely-placed
   rows, so let the body stretch rather than centre a column. */
.binder .body.is-boxed { align-items: stretch; justify-content: flex-start; }
.binder .node-row { margin-top: 6px; }
/* Hover deepens the shadow — no movement (a lifting card felt jarring). */
.binder .cell:hover .body { box-shadow: var(--cb-node-shadow-hover); }
/* Selection — a bright ring around the card (the node's own colour would be
   lost against a busy board, so selection stays the one unambiguous orange). */
.binder .cell.is-selected .body {
  box-shadow: var(--cb-node-shadow), 0 0 0 2px var(--alt-sel);
  outline: none;
}

/* The fake lighting: a fixed sheen from the top-left, and a specular highlight
   that follows the pointer (--mx/--my set by the view) while the card is
   hovered. Both clip to the card's rounded corners via border-radius:inherit. */
.binder .sheen, .binder .spec {
  position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
}
.binder .sheen { background: linear-gradient(168deg, rgba(255,255,255,.05), rgba(255,255,255,0) 36%); }
.binder .spec {
  opacity: 0; transition: opacity .3s;
  background: radial-gradient(140px circle at var(--mx,50%) var(--my,-30%), var(--cb-specular), transparent 55%);
}
.binder .cell:hover .spec { opacity: 1; }

/* --- ports: ring studs on the card edges ------------------------------------
   A port is a hollow ring in the colour of the value it carries (--pc, keyed
   by role below). Inputs straddle the card's left edge, the output its right.
   Always visible — a card wears its wiring points. */
.binder .port {
  position: absolute; top: 50%; transform: translateY(-50%); box-sizing: border-box;
  width: var(--cb-port-d); height: var(--cb-port-d); border-radius: 50%;
  border: var(--cb-port-ring) solid var(--pc, var(--cb-silver));
  background: var(--cb-canvas);
  box-shadow: inset 0 1px 1px rgba(255,255,255,.20),
              inset 0 0 0 1px color-mix(in srgb, var(--pc, var(--cb-silver)), black 30%);
  cursor: crosshair; z-index: 4; opacity: 1; pointer-events: auto;
  transition: box-shadow .12s ease, transform .12s ease;
}
.binder .port::before { content: ''; position: absolute; inset: -8px; border-radius: 50%; }  /* grab halo */
/* Studs centre on the card edges (a 16px stud offset by -8px). Free ports
   (module layouts, skinned jacks) carry their own inline position. */
.binder .port-in:not(.port--free)  { left: -8px; }
.binder .port-out:not(.port--free) { right: -8px; }
/* role → colour, on inputs (the role they accept) and the output (its own). */
.binder .port[data-accept="value"],
.binder .port[data-accept="interp"] { --pc: var(--role-value); }
.binder .port[data-accept="filter"] { --pc: var(--role-filter); }
.binder .port[data-accept="shape"]  { --pc: var(--role-shape); }
/* pulling a cord out of the output */
.binder .port-out.is-dragging { transform: translateY(-50%) scale(1.15); }
/* the landing target under an in-flight cord glows in its colour (the view
   also swells it via the magnet). */
.binder .port.is-magnet {
  border-color: var(--pc, var(--wire));
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--pc, var(--wire)), transparent 55%),
              inset 0 1px 1px rgba(255,255,255,.2);
}
/* the node an in-flight cord hovers: ring its card in the wire colour */
.binder .cell.is-wire-target .body {
  box-shadow: var(--cb-node-shadow-hover), 0 0 0 2px var(--role-value);
}

/* A port fed by a hardware knob shows the knob NUMBER beside it — no cord is
   drawn (the number + the lit knob are the whole story). Still a real port:
   drag it off to move or clear the binding. */
.binder .port-bind { border-style: dashed; }
.binder .port-badge {
  position: absolute; right: calc(100% + 6px); top: 50%; transform: translateY(-50%);
  font-family: var(--eu-prose); font-size: 14px; font-weight: 600; line-height: 1;
  color: var(--role-value); pointer-events: none;
}

/* --- ADSR module face --------------------------------------------------------
   Only the layout (curve + bottom stage row) is bespoke; the ink is the one
   ink. Ports are the real draggable wire points, placed by MODULE_PORTS. */
/* The ADSR's bottom band is full (stage labels + ports), so its category
   balances the title instead: top-right, sitting on the title's baseline. */
.binder .cell[data-role="adsr"] .body > .node-cat {
  bottom: auto; top: calc(var(--alt-inset) + 8px);
}
.binder .cell[data-role="adsr"] .adsr-stage {
  position: absolute; left: var(--eu-mod-inset); right: var(--eu-mod-inset);
  top: 64px; bottom: 40%; pointer-events: none;
}
.binder .adsr-curve { width: 100%; height: 100%; display: block; overflow: visible; }
.binder .adsr-trace {
  fill: none; stroke: var(--alt-line); stroke-width: 2; vector-effect: non-scaling-stroke;
  stroke-linejoin: round; stroke-linecap: round;
}
.binder .cell[data-role="adsr"] .adsr-lbl {
  position: absolute; font-family: var(--eu-prose); font-size: 8px; font-weight: 400; letter-spacing: 0;
  text-transform: none; color: var(--alt-dim); transform: translateX(-50%); pointer-events: none; white-space: nowrap;
}
.binder .adsr-lbl--atk { left: 12.5%; bottom: 32px; }
.binder .adsr-lbl--dec { left: 37.5%; bottom: 32px; }
.binder .adsr-lbl--sus { left: 62.5%; bottom: 32px; }
.binder .adsr-lbl--rel { left: 87.5%; bottom: 32px; }
.binder .adsr-lbl--in  { left: var(--alt-inset); top: 60%; transform: translateY(-50%); }
.binder .adsr-lbl--out { right: var(--alt-inset); top: 60%; transform: translateY(-50%); }

/* --- node contents -------------------------------------------------------- */
/* Module icon slot — the node's own icon, else its category's (resolved in
   operators.js). Empty today (no glyphs assigned). */
.binder .node-icon { font-size: 15px; line-height: 1; color: var(--alt-line); margin-bottom: 3px; }
/* The template pair: a 16px Trim Light TITLE top-left, an 8px Helvetica
   category at 50% bottom-right — diagonal balance, equal insets. The pair is
   the whole face of a simple node, so its rhythm IS the design. A title
   longer than the card auto-steps down (view.js fitTitles) — nothing may
   ever clip. */
/* The node's name IS its face: mixed-case Helvetica, white, no frame around
   it. A destination parameter (a sink) reads green. */
.binder .node-name {
  font-family: var(--eu-prose); font-size: 24px; font-weight: 600; letter-spacing: -.015em;
  text-transform: none; color: var(--cb-title); text-shadow: 0 1px 2px rgba(0,0,0,.55);
  /* 1.35, not a tight 1.15: the line is clipped to its own box for the "…"
     truncation (overflow:hidden below), and a tight line-box cropped the
     descenders (the tail of a q / p) on real Helvetica Neue. The extra room
     keeps the whole glyph inside the clip on every face. */
  line-height: 1.35; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
/* The category line, back on the face: small, in the node's own colour — the
   quiet tag under the name that tells you what kind of thing this is. */
.binder .node-cat {
  display: block; font-family: var(--eu-prose);
  font-size: 12px; font-weight: 600; letter-spacing: -.01em; margin-top: 3px;
  color: var(--type, var(--cb-data));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
/* The interp title sits at the top-left of its boxed body. */
.binder .node-name--abs { position: absolute; top: var(--alt-inset); left: var(--alt-inset); margin: 0; }
.binder .node-row { display: flex; align-items: center; gap: 8px; }
.binder .node-row--split { gap: 8px; }
.binder .node-row--end { justify-content: flex-end; }
/* Port labels: 8px Helvetica, as authored ("Filter", "Min") — never Trim,
   never forced caps. */
.binder .node-lbl { font-family: var(--eu-prose); font-size: 14px; font-weight: 400; color: rgba(255,255,255,0.78); letter-spacing: 0; text-transform: none; }
.binder .node-lbl.is-idle { color: var(--node-dim); }
.binder .vpick { flex: 1; display: flex; align-items: center; gap: 5px; min-width: 0; }

/* --- comment — free text on the board --------------------------------------
   PROSE, not chrome: no frame, white Helvetica as typed (the caps rule and
   Trim don't apply to what the user wrote). Drag anywhere on it to move;
   double-click to write (view.js). */
.binder .cell[data-role="comment"] { text-transform: none; }
.binder .cell[data-role="comment"] .body {
  background: none; border: none; box-shadow: none;
  padding: 8px 16px; justify-content: flex-start;
}
.binder .note-text {
  font-family: var(--eu-prose);
  font-size: 14px; font-weight: 400; line-height: 1.45; letter-spacing: 0;
  color: #fff; text-transform: none;
  width: 100%; min-height: 1.45em; white-space: pre-wrap; word-break: break-word;
  outline: none;
}
.binder .note-text[contenteditable]:not([contenteditable="false"]) {
  cursor: text; user-select: text;
  outline: 1px dashed rgba(255, 255, 255, 0.35); outline-offset: 6px;
}

/* Double-clicking a card must never word-select its labels (the browser's
   native selection) — cards aren't prose. Typing surfaces opt back in:
   fields select as normal, and a comment does while it's being written. */
.binder .cell { user-select: none; }
.binder .cell input { user-select: text; }

/* The "All" picker node — the one configurable card per class — keeps its
   dashed distinction, in the one ink. Selected, it keeps that dashed ring
   (now orange, like the rest of the node) rather than gaining a halo. */
.binder .cell.is-pick .body { outline: 1px dashed var(--alt-faint); outline-offset: -8px; }
.binder .cell.is-pick.is-selected .body { outline: 1px dashed var(--alt-sel); outline-offset: -8px; }

/* FUSED flush edges (view.js stamps fused-l/r/t/b — the Knob Bank row):
   the shared seam's border drops per side so the row reads as one unit */
.binder .cell.fused-l .body { border-left: none; }
.binder .cell.fused-r .body { border-right: none; }
.binder .cell.fused-t .body { border-top: none; }
.binder .cell.fused-b .body { border-bottom: none; }

/* --- Launch Control knob -----------------------------------------------------
   One board square, frameless: a numbered circle that IS an output port. It
   reads dim while idle and lights (bright ring + number) while it drives a
   param. Binding is by dropping a param's input onto it (drop-anywhere-on-cell),
   so the tiny output square is hidden — the circle is the whole affordance. */
.binder .cell[data-role="bankknob"] .body {
  align-items: center; justify-content: center; padding: 0;
  border: none; background: none;
}
.binder .cell[data-role="bankknob"] .port-out { display: none; }
.binder .lc-knob {
  width: 48px; height: 48px; border-radius: 50%; box-sizing: border-box;
  display: flex; align-items: center; justify-content: center;
  border: var(--alt-stroke) solid var(--alt-faint);
}
.binder .lc-knob.is-bound { border-color: var(--alt-line); }
.binder .lc-num {
  font-family: var(--eu-prose); font-size: 16px; font-weight: 400; letter-spacing: 0;
  color: var(--alt-faint); line-height: 1;
}
.binder .lc-knob.is-bound .lc-num { color: var(--alt-line); font-weight: 500; }
/* the group title, hosted on the top-left knob, sits above the grid */
.binder .lc-title {
  position: absolute; left: 0; bottom: calc(100% + 8px); white-space: nowrap;
  font-family: var(--eu-prose); font-size: 16px; font-weight: 400; color: var(--alt-half);
}
/* a selected knob follows the orange-selection ink like every other node */
.binder .cell[data-role="bankknob"].is-selected .lc-knob { border-color: var(--alt-sel); }
.binder .cell[data-role="bankknob"].is-selected .lc-num,
.binder .cell[data-role="bankknob"].is-selected .lc-title { color: var(--alt-sel); }

/* --- Knob / Slider controls ----------------------------------------------- */
/* The inner widget is the value control; the card around it is the grab area for
   moving the node. The widget stretches to the card width (the node body is a
   left-aligned flex column, so without this the slider track / knob collapse). */
/* slider — a horizontal track + its value, inline (the track is the face) */
.binder .ctrl-slider { display: flex; align-items: center; gap: 10px; cursor: ew-resize; touch-action: none; }
.binder .ctrl-val {
  font-family: var(--eu-prose); font-size: 15px; line-height: 1; font-weight: 400; color: var(--alt-line);
}
.binder .ctrl-widget.is-driven { opacity: .85; cursor: default; }
.binder .ctrl-val--driven {
  font-size: 15px; font-weight: 400; color: var(--alt-line);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.binder .slider-track {
  position: relative; width: 96px; height: 6px; border-radius: 3px; flex: 0 0 auto;
  background: var(--node-dim); overflow: hidden;
}
.binder .slider-fill { position: absolute; left: 0; top: 0; bottom: 0; background: var(--wire); }
/* knob — a value-arc dial with the number in the middle. A 270° gauge (a gap
   at the foot): orange up to the value, dim for the rest. The value 0..1 rides
   in on --v. The centre is punched out to a ring. */
.binder .knob-dial {
  position: relative; width: 64px; height: 64px; border-radius: 50%; box-shadow: none;
  cursor: ns-resize; touch-action: none;
  background: conic-gradient(from 225deg,
    var(--wire) 0 calc(var(--v, 0.5) * 270deg),
    var(--node-dim) calc(var(--v, 0.5) * 270deg) 270deg,
    transparent 270deg 360deg);
  display: flex; align-items: center; justify-content: center;
}
.binder .knob-dial::after {
  content: ''; position: absolute; inset: 5px; border-radius: 50%; background: var(--eu-canvas);
}
.binder .knob-val {
  position: relative; z-index: 1;
  font-family: var(--eu-prose); font-size: 15px; font-weight: 400; color: var(--alt-line);
}
.binder .knob-ind { display: none; }
/* A knob / slider is a control card — centre its widget in the card face. */
.binder .cell[data-role="knob"] .body,
.binder .cell[data-role="slider"] .body { align-items: center; justify-content: center; }
/* one Knob Bank channel is a single board square — its dial is the 32px unit */
.binder .cell[data-role="bankknob"] .knob-dial { width: 32px; height: 32px; }
.binder .cell[data-role="bankknob"] .knob-ind { top: 3px; height: 10px; width: 2px; margin-left: -1px; }
.binder .bank-knob { gap: 2px; }

/* --- Meter (value monitor) ------------------------------------------------ */
/* An LED bar reading a normalised 0..1 value, in the one ink. */
.binder .meter-label { position: absolute; left: 16px; top: 16px; z-index: 3;
  font-family: var(--eu-prose); font-size: 8px; font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--alt-dim);
  max-width: calc(100% - 64px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.binder .meter-read { position: absolute; right: 16px; top: 16px; z-index: 3;
  font-family: var(--eu-prose); font-size: 8px; font-weight: 400; color: var(--alt-line); font-variant-numeric: tabular-nums; }
.binder .meter-track {
  --m-w: 160px;                              /* NODE_W (192) − 2×16 inset */
  position: absolute; left: 16px; right: 16px; bottom: 16px; height: 16px;
  border-radius: 0; background: #000; overflow: hidden;
  border: var(--alt-stroke) solid var(--alt-faint); box-shadow: none;
}
.binder .m-fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  background: var(--alt-line); transition: width .12s linear;
}
.binder .m-ghost, .binder .m-glow, .binder .m-tlip { display: none; }
.binder .m-segs  { position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(90deg, transparent 0 6px, #000 6px 8px); }

/* interp is a stack of labelled input rows (its title + each row absolutely
   placed so the row centre lines up with its left-edge port). The right-hand
   hint shows what's wired into that slot (or the default). */
.binder .irow { position: absolute; left: 0; right: 0; height: 32px; display: flex; align-items: center; padding: 0 20px; }

/* form controls inside a card — functional in the one ink, no styling
   ambition until the interactivity polish round */
.binder .node-ctrl, .binder .node-num,
.binder .body select,
.binder .body input[type="number"],
.binder .body input[type="text"] {
  font: inherit; font-family: var(--eu-prose); font-size: 13px; letter-spacing: 0;
  text-transform: none;
  background: transparent; color: var(--alt-line);
  border: 1px solid var(--node-dim); border-radius: 3px; padding: 2px 6px; cursor: pointer;
}
.binder .node-ctrl { flex: 0 0 auto; min-width: 0; }
.binder .node-num { flex: 0 0 auto; width: 56px; cursor: text; }
.binder .node-num--wide { flex: 0 0 auto; width: 64px; }
.binder .node-ctrl:focus, .binder .node-num:focus { outline: none; border-color: var(--alt-line); }

.binder .not-btn {
  flex: 0 0 auto; font-family: var(--eu-prose); font-size: 11px; letter-spacing: 0.06em; cursor: pointer;
  padding: 1px 6px; border-radius: 3px; color: var(--node-dim);
  background: transparent; border: 1px solid var(--node-dim); text-transform: none;
}
.binder .not-btn.is-on { background: var(--wire); color: #000; border-color: var(--wire); }
/* A filter is its word; its NOT toggle rides inline just after it. */
.binder .cell[data-role="filter"] .body { flex-direction: row; align-items: baseline; gap: 8px; }
.binder .not-btn--corner { position: static; transform: none; align-self: center; }

/* --- cords ---------------------------------------------------------------------
   Thin orange lines, straight from dot to dot (endpoints measured off the live
   ports). The drag-in-progress line is the only dashed one. */
.binder-cord-glow { display: none; }
/* Thick, round-capped cords, coloured by the value they carry so a cord and
   the ring studs at both ends read as one run: a value is green, a filter
   gold, a shape orange. A value that leaves 0–1 flags pink. */
.binder-cord-line {
  stroke: var(--role-value); stroke-width: var(--cb-cord-w);
  stroke-linecap: round; stroke-linejoin: round;
  fill: none; opacity: 1; pointer-events: stroke; cursor: pointer;
}
.binder-cord-line[data-role="filter"] { stroke: var(--role-filter); }
.binder-cord-line[data-role="shape"]  { stroke: var(--role-shape); }
.binder-cord-line.is-oob { stroke: #d76fa8; }
.binder-cord-snap {
  stroke: var(--cb-silver); stroke-width: var(--cb-cord-w);
  stroke-linecap: round; stroke-dasharray: 4 6; opacity: .85; pointer-events: none; fill: none;
}

/* --- attribute preview window (DF-399) ------------------------------------ */
/* A floating window shown on hover over a palette item — the toolbar analogue of
   the Segment Inspector. Positioned in JS, scoped to .binder so it rides the
   overlay. A filter shows a faithful segment; a shape its transfer curve;
   values / params / interp a worded readout. */
.binder-preview {
  position: absolute; z-index: 120; pointer-events: none;   /* above the dropdown panels (z 60) */
  width: 210px; padding: 0;
  background: #1b1d22; border: 1px solid #2d2f36; border-radius: 10px;
  box-shadow: 0 14px 30px -12px rgba(0,0,0,.7), 0 2px 6px rgba(0,0,0,.4);
}
.binder-preview[hidden] { display: none; }
.binder-preview-card { padding: 10px; }
.binder-preview-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.binder-preview-title { font-size: 13px; font-weight: 600; color: var(--alt-line); }
.binder-preview-sub { font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--eu-data); }
/* The picture (segment / curve) sits in the media slot up top, description below. */
.binder-preview-media {
  display: flex; align-items: center; justify-content: center;
  background: #121315; border-radius: 6px; padding: 10px; margin-bottom: 8px;
}
.binder-preview-summary {
  margin: 0; font-size: 11px; line-height: 1.45; color: var(--eu-data);
  font-family: var(--eu-prose); text-transform: none;
}
.binder-preview-seg { width: 100%; height: 48px; }
/* the full reference segment is dimmed so the previewed attribute (redrawn over
   it at full strength) reads as the one part the gate keys on. */
.binder-preview-seg .binder-preview-dim { opacity: .1; }
/* In the preview only, the Increment stepped bars read brighter than the
   faithful track keeps them (0.4) — so the fill demo pops. Scoped to the
   preview, so the real render is untouched. */
.binder-preview-seg .seg-inc { stroke: #6fe3a4; stroke-opacity: .9; }
.binder-preview-curve { width: 100%; height: 70px; }
.binder-preview-curve path { stroke: var(--eu-orange); stroke-width: 2; }
.binder-preview-axis { stroke: #2d2f36; stroke-width: 1; vector-effect: non-scaling-stroke; }

/* --- readout -------------------------------------------------------------- */
.binder-readout { overflow: auto; background: #121315; border-left: 1px solid #000; padding: 10px; }
.binder-readout-head { font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--eu-data); margin-bottom: 8px; }
.binder-readout pre { margin: 0; font-family: 'SF Mono', Menlo, Consolas, monospace; font-size: 11px; line-height: 1.45; color: #9aa0a8; white-space: pre-wrap; word-break: break-word; }
