/* ==========================================================================
   minigraph.css : the S5 mini node-graph
   --------------------------------------------------------------------------
   Standalone module. Loads AFTER styles.css and reads its tokens; it defines
   no typography, spacing or colour system of its own beyond what the product
   itself doesn't expose as a site-level token (see FAMILY COLOURS below).

   LANE C TRANSPLANT (v3): the two prior passes measured the roster PNGs
   (screenshots) to derive these numbers. That method is retired — the
   founder caught real drift from it ("the clipping is not proper, the
   buttons are butt up against things... the code literally exists in the
   product"). Every geometry value below is instead read DIRECTLY off the
   product component source + its CSS:
     apps/desktop/src/graph/GraphWorkspaceProcessCard.tsx  (+ canvas-overlays.css
       .maestro-card / .maestro-card.process rules)
     apps/desktop/src/graph/GenerateNodeCard.tsx           (+ generate-node-card.css)
     apps/desktop/src/graph/GraphWorkspaceStageCard.tsx + StageBodyPreview.tsx
       (+ canvas-overlays.css .maestro-card.stage rules)
     apps/desktop/src/graph/GraphWorkspacePortConnector.tsx
     apps/desktop/src/app/canvas-controls-cluster/CanvasControlsCluster.tsx
       + GraphWorkspaceCanvasControls.tsx (the bottom control bar)
   The roster PNGs turned out to be stale against current source in one real
   way (see the process-card note below the KINDS table in minigraph.js) —
   proof the screenshot method drifts and the source-read method catches it.

   Those px are expressed in em against one unit, --mg-u, which is the node
   font-size. 8px unit = product size (the product's own node metrics are
   literal px at 1x; this module's --mg-u:8px rung is what makes 1em = 1
   product px, so every conversion below is `product_px / 8`). That is the
   only knob that resizes the whole module, which is how the mobile 44vw
   card is reached without a transform (a transform would have blurred the
   type and broken port maths).
   ========================================================================== */

.mg {
  --mg-u: clamp(8.4px, 0.76vw, 11.5px);

  /* ---- FAMILY COLOURS -------------------------------------------------
     Source: tokens.css --category-upscale / --category-depth (exact) and
     the GF-28 plotted GENERATE per-medium gradient stops (tokens.css
     --gen-image-a/b/c, --gen-video-a/b/c — "founder LAW, do not round").
     styles.css carries --family-source / --family-ae for the flat single-
     tone uses (wires, port rings); the three-stop identity gradient itself
     has no site token yet, so it is transcribed verbatim here. */
  --mg-fam-image:   var(--family-source, #9277FF);   /* = --gen-image-strong (--gen-image-a) */
  --mg-gi-image-a:  #7034DF;
  --mg-gi-image-b:  #9C60F6;
  --mg-gi-image-c:  #9980F4;
  --mg-fam-video:   #3452B7;                          /* = --gen-video-strong (--gen-video-a) */
  --mg-gi-video-a:  #3452B7;
  --mg-gi-video-b:  #30189A;
  --mg-gi-video-c:  #B59FDB;
  --mg-fam-upscale: #4FC7DD;                          /* = --category-upscale, exact */
  --mg-fam-depth:   #5B9CF5;                          /* = --category-depth, exact */
  --mg-fam-stage:   var(--family-ae, #5C5CE0);

  --mg-card:    var(--paper-warm);                    /* = --bg-card-raised */
  --mg-well:    var(--canvas-bg);                     /* = --bg-canvas */
  --mg-letterbox: #0F1115;                            /* = --bg-letterbox (neutral-100, dark) */
  --mg-elevated: #202638;                             /* = --bg-elevated (neutral-250, dark) */
  --mg-inset:   #181C28;                              /* = --bg-panel (neutral-200) — process chip fill */
  --mg-comp:    #08081A;                               /* = --stage-canvas exact */
  --mg-stage-panel:    #1A1B2E;                        /* = --stage-panel exact */
  --mg-stage-timeline: #141425;                        /* = --stage-timeline exact */
  --mg-stage-border:   #3A3A5C;                        /* = --stage-border exact */
  --mg-stage-text:     #CCCCEE;                        /* = --stage-text exact */
  --mg-stage-text-dim: #7777AA;                        /* = --stage-text-dim exact */
  --mg-ink-faint: #566287;                             /* = --text-faint (neutral-600, dark) — no site token */
  --mg-danger:  #F15E5E;                               /* = --danger-500 (dark) */
  --mg-success: #50CC8A;                               /* = --success-600 (dark) */
  --mg-radius:  1.75em;                  /* 14px = --radius-card, at product unit */
  --mg-radius-inner: 1.625em;            /* 13px = --radius-card-inner (14 - 1px border) */
  --mg-radius-ring:  2.125em;            /* 17px = --radius-card-ring (14 + 3px gap) */
  /* Stage's shell/inner are already overridden below to the product's own
     10px/9px (not the 14/13 generic pair above) — but the OUTER RING was
     not, so it kept drawing at 17px around a 10px shell: two circles with
     the same center but very different curvature, which is what read as
     "corners not following the concentric rule" in the founder's red-
     circle screenshot. canvas-overlays.css:70-72 (`.maestro-card.stage::
     before { border-radius: 13px; }`) is the real product's own override —
     ported verbatim below on `.mg-stage[data-fam]::before`. */
  --mg-radius-stage-ring: 1.625em;       /* 13px = .maestro-card.stage::before, canvas-overlays.css:70-72 */
  --mg-canvas-h: clamp(30rem, 70vh, 46rem);

  /* control bar chrome — CanvasControlsCluster / CanvasControlButton source
     (app/canvas-controls-cluster). picker / context-menu chrome stays the
     prior-lane ui-picker.png / ui-context-menu.png reading (out of this
     transplant's named scope: card + port + control-bar source only). */
  --mg-panel:       #181C28;   /* = --bg-panel exact */
  --mg-panel-well:  #07090D;   /* = --bg-base exact (neutral-0) */
  --mg-row-hover:   #202638;   /* = --bg-elevated exact, same token as --mg-elevated above */
  --mg-menu-shadow:
    0 8px 20px rgba(0, 0, 0, 0.4),
    0 0 0 1px rgba(9, 12, 18, 0.35),
    inset 0 1px 0 rgba(255, 255, 255, 0.03),
    0 4px 16px rgba(0, 0, 0, 0.25);

  margin-top: var(--section-gap-media);
}

/* ---------- canvas -------------------------------------------------------
   v2: the top .mg-bar toolbar (Add node / Clear graph as a row above the
   canvas) is GONE — founder ruling. Its two actions now live in the
   bottom control bar overlay (.mg-ctrl, defined after .mg-world below) and
   on the canvas right-click menu (.mg-ctxmenu, further down). */

.mg-canvas {
  position: relative;
  width: 100%;
  height: var(--mg-canvas-h);
  overflow: hidden;
  background: var(--canvas-bg);      /* the dot field itself now lives on
                                         .mg-world so zoom scales it too;
                                         this is the plain fallback fill
                                         visible past the world's edges at
                                         zoom < 1 */
  /* v3 / founder note: NO border-block. The 1px rules here were the "line
     dividing this spot" he flagged twice — the section fades are seamless,
     these edges were the only divider left. */
  touch-action: manipulation;   /* nodes drag; the page still scrolls        */
  font-size: var(--mg-u);       /* JS reads this to size a new node's slot   */
  -webkit-user-select: none;
  user-select: none;
  contain: layout paint;
}

/* ---------- world (the zoom layer) --------------------------------------
   Nodes and wires mount inside here, not directly in .mg-canvas. JS scales
   THIS element (transform: scale(zoom)) so cards, wires and the dot grid
   magnify together as one unit. transform-origin is pinned to 0 0 (not
   center) on purpose: with a corner origin, a screen-space pointer delta
   maps to world-space with a single division by zoom (no origin-shift term
   to track), which is what keeps wire drag and node drag exact at every
   zoom level. Node layout coordinates (n.x/n.y) are therefore always in
   this unscaled world space, sized to match .mg-canvas at zoom 1. */
.mg-world {
  position: absolute;
  inset: 0;
  transform-origin: 0 0;
  background: var(--dot-field);
}

.mg-wires {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

.mg-wire { fill: none; stroke-width: 2; stroke-linecap: round; }
.mg-wire-live { stroke-dasharray: 5 5; opacity: 0.85; }

/* ---------- node shell -----------------------------------------------------
   LANE C: real product nesting, ported verbatim from canvas-overlays.css'
   THREE concentric layers (the "concentric-geometry rule": outer = inner +
   gap, docs/knowledge/design-system.md) — this is the direct fix for "the
   clipping is not proper": v1/v2 faked the ring with INSET box-shadows on a
   single flat element, which cannot produce genuinely concentric corners
   (an inset shadow's corner arc is derived from the element's own radius
   minus its spread, not an independently-drawn smaller circle) and left the
   ring drawn INWARD, eating into the corner instead of framing it from
   outside. The product's real structure:
     .mg-node   = .maestro-card  (position:absolute, ports mount here as
                  DIRECT CHILDREN — siblings of .mg-shell, exactly like
                  GraphWorkspacePortConnector's mount site — never inside
                  .mg-inner, so nothing the inner content clips ever touches
                  a port)
       ::before = .maestro-card::before (the outer RING: -4px inset, r17,
                  a real solid --bg-panel rail + 1px border, painted BEHIND
                  the shell at z-index:-1 — genuinely outside the card, not
                  an inward wash)
       .mg-shell  = .shell   (r14, 1px border, the family-tinted EDGE color,
                  inset top-highlight; overflow:visible on every family)
         .mg-inner  = .inner  (r13 = 14 - 1px border, --bg-card-raised fill;
                  overflow:visible for generate/process — nothing in their
                  real content ever bleeds past its own box — overflow:
                  hidden ONLY for stage, whose composition tiles/plates are
                  the one real family that paints past its own edge) */

.mg-node {
  position: absolute;
  top: 0;
  left: 0;
  font-family: var(--font-sans);
  font-size: var(--mg-u);
  line-height: 1;
  color: var(--ink);
  cursor: grab;
  touch-action: none;           /* the node itself owns the pointer          */
}

.mg-node:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--mg-radius); }

/* the outer ring — `.maestro-card::before` verbatim: -4px inset, r17,
   solid bg-panel rail + a family-tinted 25%-mix border at rest. z-index -1
   so it paints behind .mg-shell; pointer-events none so it never eats
   drag/click. Declared on [data-fam] (same rule the prior pass used) so
   the color-mix(var(--fam)) template resolves same-element, not inherited. */
.mg-node[data-fam]::before {
  content: "";
  position: absolute;
  top: -0.5em; left: -0.5em; right: -0.5em; bottom: -0.5em;   /* -4px */
  border-radius: var(--mg-radius-ring);
  background: color-mix(in srgb, var(--fam) 6%, var(--mg-panel));
  border: 0.125em solid color-mix(in srgb, var(--fam) 25%, var(--mg-panel));  /* 1px */
  box-shadow: var(--mg-menu-shadow);
  z-index: -1;
  pointer-events: none;
  transition: border-color 0.15s var(--ease), background-color 0.15s var(--ease);
}
.mg-node[data-dragging] { cursor: grabbing; z-index: 40; }
.mg-node[data-dragging]::before { box-shadow: 0 20px 44px rgba(0,0,0,0.46), var(--mg-menu-shadow); }
.mg-node[data-just-done]::before {
  box-shadow: var(--mg-menu-shadow), 0 0 0 1px color-mix(in srgb, var(--fam) 55%, transparent), 0 0 22px color-mix(in srgb, var(--fam) 24%, transparent);
}
.mg-node[data-refuse] { animation: mg-refuse 0.34s var(--ease); }
/* no independent translate = no shake, or the card would jump to the origin */
@supports not (translate: 1px 1px) {
  .mg-node[data-refuse] { animation: none; outline: 2px solid var(--fam); outline-offset: 2px; }
}

@keyframes mg-refuse {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(-4px); }
  60%      { transform: translateX(3px); }
}

/* .mg-shell / .mg-inner — `.shell` / `.inner` verbatim. `.mg-shell` carries
   the width (GraphWorkspaceProcessCard/GenerateNodeCard/StageCard all size
   the SHELL, not the outer card); `.mg-node` itself stays auto-sized so the
   -4px ring band computes off the shell's real box, matching the product's
   `.maestro-card::before` inset-from-shell geometry exactly. */
.mg-shell {
  position: relative;
  overflow: visible;
  border: 0.125em solid var(--rule-soft);   /* 1px, family-tinted below */
  border-radius: var(--mg-radius);          /* 14px */
  box-shadow: inset 0 0.125em 0 rgba(255, 255, 255, 0.05);  /* --card-edge-highlight */
  display: flex;
  flex-direction: column;
  transition: border-color 0.15s var(--ease);
}
.mg-inner {
  position: relative;
  background: var(--mg-card);
  border-radius: var(--mg-radius-inner);    /* 13px = 14 - 1px border */
  overflow: visible;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
/* process / generate shell edge = the SAME 25%-mix the ring border uses
   (`.maestro-card.process .shell` / `.maestro-card.generate .shell`
   resolve to the identical color-mix formula as `.maestro-card::before`). */
.mg-gen .mg-shell, .mg-proc .mg-shell {
  border-color: color-mix(in srgb, var(--fam) 25%, var(--mg-panel));
}
/* generate `.inner`: ONE padded region, 9px/10px, 8px flex gap between
   header/media/controls/GENERATE (generate-node-card.css:126-131) — no
   divider rule anywhere. process `.inner`: NO padding of its own — the
   header (own 10px inset) and `.mg-chipwrap` (own bordered-top region)
   each carry their own, exactly like the real `.header` + `.chip-section`
   siblings do (canvas-overlays.css:358-363 / 806-828). */
.mg-gen .mg-inner { padding: 1.125em 1.25em; gap: 1em; }
.mg-proc .mg-inner { padding: 0; }
.mg-node[data-dragging] .mg-shell { box-shadow: inset 0 0.125em 0 rgba(255, 255, 255, 0.05), 0 0 0 1px color-mix(in srgb, var(--fam) 55%, transparent); }
.mg-node[data-just-done] .mg-shell { border-color: color-mix(in srgb, var(--fam) 45%, var(--rule-soft)); }

.mg-gen   { --fam: var(--mg-fam-image); }
.mg-gen .mg-shell   { width: 27em; }   /* 216px = GenerateNodeCard `.shell` width */
.mg-gen[data-kind="generate-video"] { --fam: var(--mg-fam-video); }
.mg-proc  { --fam: var(--mg-fam-upscale); }
.mg-proc .mg-shell  { width: 25em; min-height: 14em; }  /* 200x112 = GRAPH_WORKSPACE_PROCESS_NODE_WITH_RUN_HEIGHT (constants.ts) */
.mg-proc[data-kind="depth-map"]     { --fam: var(--mg-fam-depth); }
.mg-stage { --fam: var(--mg-fam-stage); }
.mg-stage .mg-shell { width: 37.5em; }  /* 300px = GRAPH_WORKSPACE_STAGE_NODE_WIDTH */

/* Process card height is CONSTANT in the product (GRAPH_WORKSPACE_PROCESS_
   NODE_WITH_RUN_HEIGHT = 112, always — nodeHeight only grows off a
   PERSISTED user resize, which this demo has no affordance for). The prior
   pass's "grows a result well on done" behavior does not exist in the real
   ProcessCard at all: it renders NO image well, ever — see the note below
   the KINDS table in minigraph.js for the source read that found this. */

/* ---------- head ----------------------------------------------------------
   Title geometry is IDENTICAL between process and generate in the product
   (both InlineEditableTitle/`.gen-title`: 11px/12px line-height, weight
   600, tracking -0.01em) — one shared `.mg-title` rule. Subtitle differs
   (process `.process-subtitle` vs generate `.gen-chip`: different size,
   line-height AND color token) — family-scoped below. Padding/gap also
   differ by family: process's header carries its OWN 10px inset (the real
   header is independently padded, canvas-overlays.css:358-363 pattern);
   generate's header has NO own padding — the padding lives once on
   `.mg-inner` (generate-node-card.css:126-131) and every child (header,
   media, controls, GENERATE) shares one 8px flex gap. That structural
   difference is exactly the "buttons butt up against things" bug: v1/v2
   used ad-hoc per-element margins instead of either of these two real
   rhythms. */

.mg-head {
  display: flex;
  align-items: flex-start;
  gap: 0.75em;             /* 6px, both families */
}
.mg-proc .mg-head { padding: 1.25em; }             /* 10px, own inset */
.mg-gen  .mg-head { align-items: center; }         /* gen-header is items-center, not items-start */

.mg-head-txt { min-width: 0; flex: 1; }
.mg-gen .mg-head-txt { display: flex; flex-direction: column; gap: 0.125em; }  /* gen-title-block gap:1px */

.mg-title {
  margin: 0;
  font-size: 1.375em;      /* 11px */
  line-height: 1.5em;      /* 12px */
  font-weight: 600;
  letter-spacing: -0.01em; /* --tracking-snug */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mg-sub {
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* process-subtitle: 8px/9px, text-muted, margin-top 3px (own block, not a flex gap) */
.mg-proc .mg-sub { margin-top: 0.375em; font-size: 1em; line-height: 1.125em; font-weight: 400; color: var(--ink-soft); }
/* gen-chip: 9px/11px, text-faint (no site token — --mg-ink-faint) */
.mg-gen  .mg-sub { font-size: 1.125em; line-height: 1.375em; font-weight: 400; color: var(--mg-ink-faint); }

/* status badge — `.status-badge` + `.status-badge.completed` verbatim.
   Process only; DONE is the only state this demo can show without
   fabricating a number (the product's `running` badge reads a live
   percent this demo's fixed-timer run has no honest source for — IX-6:
   nothing is written that was not measured — so that variant is left out;
   see the report for the full list of what was deliberately not ported). */
.mg-badge {
  flex-shrink: 0;
  margin-left: 0.75em;      /* 6px */
  display: inline-flex;
  align-items: center;
  padding: 0.25em 0.75em;   /* 2px 6px */
  border-radius: 0.25em;    /* 2px = --radius-sm */
  border: 1px solid transparent;
  font-size: 1em;           /* 8px */
  line-height: 1.25em;      /* 10px */
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;   /* --tracking-wider */
  white-space: nowrap;
  background: color-mix(in srgb, var(--mg-success) 14%, transparent);
  color: var(--mg-success);
}
.mg-badge[hidden] { display: none; }

/* v2: the lock icon (per card) and gear icon (stage head) are GONE — they
   read as clickable and did nothing. Founder ruling. */

/* family glyph — process kinds ride the BARE stroke glyph (currentColor,
   no fill tile: `.process-kind-glyph`, 16px, margin-top 1px). Generate
   media ride a FILLED tile (`.gen-iconbox`: 22px, r6, the medium's real
   plotted 3-stop gradient, GraphWorkspaceSvgIcon size=13). These were two
   different recipes in the product; v1/v2 faked both off one shared
   `.mg-chip` box. Split for real here. */
.mg-proc .mg-chip {
  display: inline-flex;
  flex-shrink: 0;
  width: 2em; height: 2em;      /* 16px */
  margin-top: 0.125em;          /* 1px */
  color: var(--fam);
}
.mg-proc .mg-chip svg { width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }

.mg-gen .mg-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2.75em; height: 2.75em;   /* 22px */
  border-radius: 0.75em;           /* 6px = --radius-lg */
  box-shadow: inset 0 0 0 1px rgba(9, 12, 18, 0.22);  /* scrim-base 22% */
  color: #EFF4F9;                  /* --scrim-media-text */
  background: linear-gradient(120deg, var(--mg-gi-image-a) 0%, var(--mg-gi-image-b) 51%, var(--mg-gi-image-c) 99%);
}
.mg-gen[data-kind="generate-video"] .mg-chip {
  background: linear-gradient(206deg, var(--mg-gi-video-a) 0%, var(--mg-gi-video-b) 31%, var(--mg-gi-video-c) 99%);
}
.mg-gen .mg-chip svg { width: 1.625em; height: 1.625em; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }

/* ---------- preview well (generate cards only — `.gen-media` verbatim) --- */

.mg-well {
  position: relative;
  height: 9.5em;                /* 76px */
  border-radius: 0.75em;        /* 6px = --radius-lg */
  overflow: hidden;
  background: var(--mg-letterbox);
  display: grid;
  place-items: center;
}

.mg-well-glyph { position: relative; width: 2.75em; height: 2.75em; color: var(--mg-ink-faint); opacity: 0.5; }
.mg-well-glyph svg { width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
/* faint per-medium gradient wash on the empty slot — `.gen-media-empty::before` */
.mg-well:not([data-run]):not(:has(img))::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--mg-gi-image-a) 14%, transparent),
    color-mix(in srgb, var(--mg-gi-image-c) 14%, transparent));
  pointer-events: none;
}
.mg-gen[data-kind="generate-video"] .mg-well:not([data-run]):not(:has(img))::before {
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--mg-gi-video-a) 14%, transparent),
    color-mix(in srgb, var(--mg-gi-video-c) 14%, transparent));
}

.mg-well img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;     /* DS-22: real result is LETTERBOXED at native aspect, never cropped */
}

/* "N runs" pill — `.gen-count` verbatim */
.mg-count {
  position: absolute;
  right: 0.625em; bottom: 0.625em;   /* 5px */
  padding: 0.125em 0.75em;           /* 1px 6px */
  border-radius: 999px;
  background: rgba(9, 12, 18, 0.7);  /* --scrim-overlay-70 */
  color: #EFF4F9;
  font-family: var(--font-mono);
  font-size: 1em;                    /* 8px */
  font-weight: 600;
}
.mg-count[hidden] { display: none; }

/* running: one sweep across the well plus a hairline progress bar, which is
   the whole "something is happening" budget. No spinner, no percentage. */
.mg-well[data-run]::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 20%,
              color-mix(in srgb, var(--fam) 26%, transparent) 50%, transparent 80%);
  animation: mg-sweep 1s linear infinite;
}
.mg-well[data-run]::before {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 0.25em;
  background: var(--fam);
  transform-origin: 0 50%;
  animation: mg-fill 1.35s var(--ease) forwards;
  z-index: 2;
}
@keyframes mg-sweep { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
@keyframes mg-fill  { from { transform: scaleX(0.04); } to { transform: scaleX(1); } }

/* ---------- provider row (generate cards) — `.gen-provider` verbatim ----- */

.mg-provider { display: flex; align-items: center; gap: 0.75em; }  /* gen-controls: gap 6px */

.mg-seg {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 0.25em;              /* 2px */
  padding: 0.25em;           /* 2px */
  border: 1px solid var(--rule);
  border-radius: 0.75em;     /* 6px */
  background: var(--mg-well-canvas, var(--canvas-bg));
}
.mg-seg span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5em; height: 2em;      /* 20x16 */
  border-radius: 0.5em;           /* 4px */
  color: var(--mg-ink-faint);
}
.mg-seg span[data-on] { background: color-mix(in srgb, var(--fam) 22%, var(--mg-elevated)); color: var(--ink); }
/* v1.7.2 (founder 08-19): the segment is a real control now */
.mg-seg span { cursor: pointer; }
.mg-seg span:focus-visible { outline: 2px solid var(--accent, #70E8E4); outline-offset: 1px; }
.mg-seg svg { width: 1.375em; height: 1.375em; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
/* v2: the model-name dropdown (.mg-model — "FLUX 1.1 Pro" etc.) is GONE.
   Founder ruling: no model names anywhere on the marketing site. The
   home/cloud compute segment stays; it reads as product chrome without
   naming anything. */

/* ---------- action buttons — `.gen-generate` / `.run-btn` verbatim ------- */

.mg-act {
  width: 100%;
  padding: 0.375em 0.75em;   /* 3px 6px */
  border-radius: 0.5em;      /* 4px = --radius-md */
  border: 1px solid color-mix(in srgb, var(--fam) 30%, transparent);
  background: color-mix(in srgb, var(--fam) 12%, transparent);
  color: var(--fam);
  font: inherit;
  font-size: 1em;             /* 8px */
  font-weight: 600;
  letter-spacing: 0.08em;     /* --tracking-wider */
  text-transform: uppercase;
  line-height: 1.2;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  transition: background-color 0.12s var(--ease), opacity 0.12s var(--ease);
}
.mg-act:hover:not([aria-disabled="true"]) { background: color-mix(in srgb, var(--fam) 20%, transparent); }
.mg-act:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 0.5em; }
.mg-act[aria-disabled="true"] { opacity: 0.38; cursor: default; }
.mg-act svg { width: 1.5em; height: 1.5em; fill: none; stroke: currentColor; stroke-width: 1.9; }  /* process-run glyph, 12px */

/* ---------- process chip-section — `.chip-section` verbatim -------------- */
/* The bordered-top region housing the io-summary + run row. Flex COLUMN
   with `flex: 1 1 auto` so `.mg-runrow`'s `margin-top: auto` genuinely
   pins the button to the card's bottom inset in every state (the E20 fix
   quoted in canvas-overlays.css) — this is the mechanism, not a fixed
   margin, that keeps the RUN button off the chips above it regardless of
   whether a badge / meta line is present. */
.mg-chipwrap {
  border-top: 1px solid var(--rule-soft);
  padding: 1.125em 1.25em;   /* 9px 10px */
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
}

.mg-chips { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75em; }  /* io-summary: 6px */
.mg-chips b {
  padding: 0.375em 0.75em;    /* 3px 6px */
  border: 1px solid var(--rule-soft);
  background: var(--mg-inset);
  border-radius: 0.5em;       /* 4px = --radius-md */
  text-align: center;
  font-size: 1em;              /* 8px */
  line-height: 1.25em;         /* 10px */
  font-weight: 400;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
}
.mg-chips b.mg-src { color: var(--ink-soft); }   /* .summary-chip.no-output analog: text-muted */
.mg-chips b.mg-out { color: var(--fam); }        /* .summary-chip.has-output: currentColor */

/* founder ruling 08-18 ("unnecessary details on the process nodes, like
   showing CPU instead of just the loading progress bar"): the running
   message+device text row (`.run-meta .meta-row`) and the completed
   elapsed+device line (`.completed-meta`) are GONE — no "CPU" string
   renders anywhere on this card now. Replaced by `.mg-run-bar` below,
   which swaps in for the RUN button while running (mirrors the real
   product's run-row: Run button -> progress-track + Cancel). */
.mg-run-bar {
  position: relative;
  height: 0.25em;               /* 2px, matches the well's own fill-bar weight */
  border-radius: 999px;
  overflow: hidden;
  background: color-mix(in srgb, var(--fam) 16%, var(--mg-inset));
}
.mg-run-bar[hidden] { display: none; }
/* duration-driven fill, not a percent: reuses @keyframes mg-fill (defined
   above for `.mg-well[data-run]::before`) so the SAME honest mechanism —
   the bar's length is this demo's real RUN_MS timer, never an invented
   number — draws both cards' progress. 1.35s is RUN_MS/1000 verbatim
   (the well's own animation duplicates this same literal for the same
   reason: no shared CSS var for a JS timing constant in this module). */
.mg-run-bar-fill {
  position: absolute;
  inset: 0;
  width: 100%;
  background: var(--fam);
  transform-origin: 0 50%;
  animation: mg-fill 1.35s var(--ease) forwards;
}

/* run row — `.run-row` verbatim: flex column, 6px top pad, bottom-pinned
   by the parent's flex context (not a margin on the button itself). */
.mg-runrow { display: flex; flex-direction: column; padding-top: 0.75em; margin-top: auto; }
.mg-runrow .mg-act[hidden] { display: none; }

/* ---------- ports (measured off the 19 port-dot roster PNGs + the
   product's own GraphWorkspacePortConnector source) --------------------
   Real geometry: a 12px square hit-box, offset 7px off the card edge,
   1px border. Unconnected = a hollow circle (family-colour ring, card-well
   fill) with a 1px halo. Connected = the OUTER shape flattens to a 2px-
   radius transparent square and a nested 6x10 solid family-colour bar
   (::after here, since v1 has no real nested element) becomes the visible
   plug. The product notes hover/armed/snap states are DELIBERATELY
   transform- and filter-free (both were tried and pulled — GPU-layer
   re-rasterization on hover read as blur-up-close / jump-zoomed-out) so
   v2 drops the v1 scale-transform hover entirely and signals state with
   ring width/color only. Sizes are in em against the 8px node unit:
   12px = 1.5em, 7px offset = 0.875em, 1px border = 0.125em. */
.mg-port {
  position: absolute;
  top: 50%;
  width: 1.5em;
  height: 1.5em;
  margin: -0.75em 0 0;
  padding: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 0.125em solid var(--fam);
  background: var(--mg-well);
  box-shadow: 0 0 0 0.125em color-mix(in srgb, var(--mg-card) 35%, transparent);
  cursor: crosshair;
  touch-action: none;
  transition: box-shadow 0.16s var(--ease), border-color 0.16s var(--ease);
}
.mg-port-in  { left: -0.875em; }
.mg-port-out { right: -0.875em; }

/* connected: outer shape flattens, the nested bar (::after) is the plug */
.mg-port[data-linked] {
  border-radius: 0.25em;
  border-color: transparent;
  background: transparent;
  box-shadow: none;
}
.mg-port[data-linked]::after {
  content: "";
  width: 0.75em;   /* 6px  */
  height: 1.25em;  /* 10px */
  border-radius: 0.25em;
  background: var(--fam);
}

/* required input, nothing plugged in yet: the product's needs-connection
   pulse. Optional inputs (generate cards' reference slot) never carry
   data-required, so they stay a quiet ring — matches the optional vs.
   required port-dot PNGs on the roster. */
.mg-port[data-required]:not([data-linked]) {
  animation: mg-port-pulse 2.5s var(--ease) infinite;
}
@keyframes mg-port-pulse {
  0%, 100% { box-shadow: 0 0 0 0.125em color-mix(in srgb, var(--mg-card) 35%, transparent); }
  50%      { box-shadow: 0 0 0.55em 0.25em var(--fam); }
}

/* styles.css sets border-radius:3px on ANY button:focus-visible, which
   squares off a round port the moment it is focused. Every focusable control
   in here therefore restates its own radius. Static ring only — no scale,
   no drop-shadow filter (see note above). */
.mg-port:hover, .mg-port:focus-visible {
  outline: none;
  border-radius: 50%;
  box-shadow: 0 0 0 0.35em color-mix(in srgb, var(--fam) 26%, transparent);
}
.mg-port[data-linked]:hover, .mg-port[data-linked]:focus-visible { border-radius: 0.25em; }
.mg-port[data-armed] { box-shadow: 0 0 0 0.45em color-mix(in srgb, var(--fam) 34%, transparent); }
.mg-canvas[data-linking] .mg-port-in[data-ok] { box-shadow: 0 0 0 0.45em color-mix(in srgb, var(--fam) 30%, transparent); }
/* fix 3 — the CURRENT snap target (within SNAP_RADIUS of the live drag),
   distinct from the fainter data-ok "every compatible port" wash above:
   a brighter, wider ring on the port itself plus a matching glow on the
   card's outer ring (founder: "highlight the port/card"). Box-shadow /
   border-color only, same no-transform-no-filter discipline as every
   other port state here (see the FF7#7/#8 GPU-rasterization note ported
   from GraphWorkspacePortConnector.tsx above `.mg-port`). */
.mg-port[data-snap] { box-shadow: 0 0 0 0.5em color-mix(in srgb, var(--fam) 48%, transparent); }
.mg-node[data-snap-target]::before {
  border-color: var(--fam);
  box-shadow: var(--mg-menu-shadow), 0 0 0 0.125em color-mix(in srgb, var(--fam) 60%, transparent), 0 0 1.25em color-mix(in srgb, var(--fam) 30%, transparent);
}

/* ---------- stage card — `.maestro-card.stage` + StageBodyPreview.tsx ---- */

/* Stage is the ONE family whose real `.inner` clips (`overflow: hidden`,
   canvas-overlays.css:2634-2640 — the composition tiles genuinely paint
   past their own box). `.mg-shell` stays overflow:visible (canon, all
   families); only stage's `.mg-inner` gets the hidden + its own radius/bg,
   so the port (a sibling of `.mg-shell`, mounted on `.mg-node`, never
   inside `.mg-inner`) is never in the clipped subtree. This is the fix for
   the in-port clipping bug the v2 comment used to carry a bespoke
   `.mg-stage-inner` wrapper for — now it's just the shared `.mg-inner`
   with a per-family override, the same pattern every other family uses. */
.mg-stage .mg-shell { border-radius: 1.25em; }         /* 10px = stage's shell radius override */
.mg-stage .mg-inner {
  border-radius: 1.125em;   /* 9px = 10 - 1px border */
  background: var(--mg-stage-panel);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.mg-stage[data-fam]::before {
  border-color: var(--mg-stage-border);  /* stage ring uses the fixed --stage-border, not a currentColor mix */
  border-radius: var(--mg-radius-stage-ring);  /* fix 4: 13px, not the generic 17px ring */
}
/* fix 3: same specificity as the rule just above (class+attribute), so
   without this override source order would let the stage-border color
   above win over the generic `.mg-node[data-snap-target]::before` family-
   color highlight defined earlier in the file — this restates it after,
   scoped to stage, so a stage card snap-targeted mid wire-drag still
   glows in its own family color like every other family does. */
.mg-stage[data-snap-target]::before { border-color: var(--fam); }
.mg-stage .mg-shell { border-color: var(--mg-stage-border); }

.mg-stage-head {
  display: flex;
  align-items: center;
  gap: 1em;                    /* 8px */
  padding: 1em 1.25em;         /* 8px 10px */
  background: var(--mg-stage-timeline);
  box-shadow: inset 0 -1px 0 var(--mg-stage-border);
  flex-shrink: 0;
}
/* the house "Ae" identity chip — `.stage-target-chip[data-chip-size="20"]`
   verbatim: 20px, r5, bg-card-raised fill, border-subtle, family-tinted
   glyph only (dark-theme "glyph-tint" recipe — this site is dark-only). */
.mg-tile {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5em; height: 2.5em;      /* 20px */
  border-radius: 0.625em;           /* 5px */
  background: var(--mg-card);
  border: 1px solid var(--rule-soft);
  color: var(--fam);
  font-family: var(--font-mono);
}
.mg-tile b { font-size: 1.25em; line-height: 1; font-weight: 700; letter-spacing: -0.02em; }  /* 10px */
.mg-stage-head .mg-title { flex: 1; color: var(--mg-stage-text); }
.mg-layercount { flex: none; font-size: 1em; line-height: 1; color: var(--mg-stage-text-dim); }  /* 8px */

.mg-stage-body {
  display: grid;
  grid-template-columns: 9em 1fr;   /* 72px 1fr */
  gap: 0;
  box-shadow: inset 0 -1px 0 var(--mg-stage-border);
  flex-shrink: 0;
}
.mg-stage-body.mg-empty-grid { grid-template-columns: 1fr; }

/* real StageBodyPreview only renders the project rail at all once
   `hasLayers` — `display:none` here (default) drops the rail out of grid
   track allocation entirely, which is what lets `.mg-empty-grid`'s single
   1fr track hand the whole row to `.mg-comp` instead of squeezing it into
   grid column 1. */
.mg-rail { display: none; }
.mg-stage-body:not(.mg-empty-grid) .mg-rail {
  display: flex;
  flex-direction: column;
  padding: 0.75em;             /* 6px */
  box-shadow: inset -1px 0 0 var(--mg-stage-border);
  background: var(--mg-stage-timeline);
  min-width: 0;
}
.mg-label {
  margin: 0 0 0.625em;         /* 5px */
  font-size: 0.875em;          /* 7px */
  font-weight: 600;
  letter-spacing: 0.12em;      /* --tracking-widest */
  color: var(--mg-stage-text-dim);
  text-transform: uppercase;
}
.mg-rail-list { display: flex; flex-direction: column; gap: 0.5em; }  /* 4px */
.mg-rail p.mg-item {
  display: flex;
  align-items: center;
  gap: 0.5em;                  /* 4px */
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
}
.mg-item i { flex: none; width: 2em; height: 1.5em; border-radius: 0.375em; background: color-mix(in srgb, var(--fam) 22%, transparent); border: 1px solid color-mix(in srgb, var(--fam) 40%, transparent); }  /* thumb-mark: 16x12, r3 */
.mg-item span { font-size: 0.875em; line-height: 1em; color: var(--mg-stage-text); overflow: hidden; text-overflow: ellipsis; }  /* thumb-name: 7px/8px */

.mg-comp {
  position: relative;
  min-width: 0;
  padding: 0.75em;             /* .stage-composition-viewer padding: 6px */
  background: var(--mg-stage-timeline);
  display: flex;
  align-items: center;
  justify-content: center;
}
.mg-empty-grid .mg-comp { padding: 1em; box-shadow: inset 0 -1px 0 var(--mg-stage-border); }  /* .stage-empty-body: 8px pad + own bottom rule */

/* the composition FRAME itself — `.stage-composition-frame` verbatim:
   16:9, dashed inset-10% safe box, 1.5px crosshair (sub-pixel-safe weight
   per the FF-A zoom-rounding note in canvas-overlays.css), resolution
   label. This is a real child of `.mg-comp`, not `.mg-comp` itself — the
   product keeps the viewer (padding/bg) and the frame (border/aspect)
   as two nested elements, so the 6px/8px padding is never eaten by the
   frame's own 1px border. */
.mg-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: var(--mg-comp);
  border: 1px solid var(--mg-stage-border);
  border-radius: 0.75em;       /* 6px */
  overflow: hidden;
}
.mg-frame::before {
  content: "";
  position: absolute;
  inset: 10%;
  border: 1.5px dashed color-mix(in srgb, var(--mg-stage-border) 70%, transparent);
  pointer-events: none;
}
.mg-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to right, transparent calc(50% - 0.75px), color-mix(in srgb, var(--mg-stage-border) 60%, transparent) calc(50% - 0.75px), color-mix(in srgb, var(--mg-stage-border) 60%, transparent) calc(50% + 0.75px), transparent calc(50% + 0.75px)),
    linear-gradient(to bottom, transparent calc(50% - 0.75px), color-mix(in srgb, var(--mg-stage-border) 60%, transparent) calc(50% - 0.75px), color-mix(in srgb, var(--mg-stage-border) 60%, transparent) calc(50% + 0.75px), transparent calc(50% + 0.75px));
  pointer-events: none;
}
.mg-dim {
  position: absolute;
  bottom: 0.375em; right: 0.5em;    /* 3px 4px */
  font-family: var(--font-mono);
  font-size: 0.875em;               /* 7px */
  color: var(--mg-stage-text-dim);
  opacity: 0.6;
  z-index: 1;
}
/* v3: the v2 "No content yet - run the node" caption is REMOVED — the real
   empty StageCompositionPreview renders NOTHING beyond the dashed frame +
   resolution label (that copy is only ever a `title=""` tooltip on a
   PENDING timeline row once a wire lands with no content yet, not a
   permanent empty-state caption — confirmed by reading StageBodyPreview.tsx
   + StageCompositionPreview.tsx directly; the demo's stage always resolves
   real content synchronously on delivery, so that pending-row nuance has
   no honest trigger here and is left unimplemented, see report). */

.mg-plates { position: absolute; inset: 0; display: none; z-index: 2; }
.mg-stage[data-state="filled"] .mg-plates { display: block; }
/* stacked layer tiles — `.stage-layer-tile` + `.l1/.l2/.l3` verbatim. Real
   source (StageCompositionPreview.tsx) renders these as FLAT gradient
   color blocks, never a thumbnail image — the composition view is layer
   PRESENCE, not a content preview. v1/v2 painted the delivered frame as a
   background-image here; that was invented, not measured, and is dropped
   (JS updated to match — no more `pl.style.backgroundImage`). The 4th
   product tile, `.l4`, is unreachable here: the demo caps the visible
   rail at 3 deliveries same as it always has, JS untouched. */
.mg-plate {
  position: absolute;
  border-radius: 0.375em;      /* 3px */
  background: linear-gradient(135deg, color-mix(in srgb, var(--fam) 88%, #AEB4C3), color-mix(in srgb, var(--fam) 60%, #E7EBF4));
  box-shadow: 0 2px 4px rgba(0,0,0,0.32), inset 0 0 0 1px color-mix(in srgb, var(--fam) 40%, transparent);
}
.mg-plate:nth-child(1) { inset: 24%; transform: rotate(-1deg); }
.mg-plate:nth-child(2) { inset: 30% 18% 18% 30%; transform: rotate(2deg); opacity: 0.92; }
.mg-plate:nth-child(3) { inset: 18% 30% 30% 18%; transform: rotate(-3deg); opacity: 0.84; }
/* fix 6: the product's 4th tile (`.l4`) was unreachable at the old 3-cap;
   now that the demo can fan-in/re-run past 4, it's live — inset/rotate/
   opacity verbatim off canvas-overlays.css:2943-2945. */
.mg-plate:nth-child(4) { inset: 36% 12% 12% 36%; transform: rotate(4deg); opacity: 0.76; }

/* "+N more" overflow rows — fix 6, one per surface the product caps:
   rail (`.stage-layer-overflow`), timeline (`.stage-timeline-overflow`),
   composition tiles (`.stage-composition-overflow`, an absolutely
   positioned corner badge, not an inline row like the other two). */
.mg-rail-more {
  margin: 0.25em 0 0;           /* 2px */
  padding-top: 0.125em;         /* 1px */
  font-size: 0.875em;           /* 7px */
  line-height: 1em;             /* 8px */
  color: var(--mg-stage-text-dim);
}
.mg-tl-more {
  margin: 0.5em 0 0;            /* 4px */
  padding-top: 0.25em;          /* 2px */
  font-size: 0.875em;           /* 7px */
  line-height: 1em;             /* 8px */
  color: var(--mg-stage-text-dim);
}
.mg-plates-more {
  position: absolute;
  top: 0.5em; right: 0.5em;     /* 4px */
  z-index: 4;
  padding: 0.125em 0.625em;     /* 1px 5px */
  border: 1px solid var(--mg-stage-border);
  border-radius: 0.25em;        /* 2px = --radius-sm, per the .mg-badge note above */
  background: color-mix(in srgb, var(--mg-comp) 85%, transparent);
  font-family: var(--font-mono);
  font-size: 1em;                /* 8px */
  line-height: 1.2;
  color: var(--mg-stage-text);
  pointer-events: none;
}

/* fix 5c: the product draws this section's top divider as `.stage-body`'s
   OWN bottom-inset shadow (canvas-overlays.css:2727-2733) — but that
   shadow paints on `.stage-body`'s box, and its two children
   (.stage-project-panel / .stage-composition-viewer, same here as .mg-rail
   / .mg-comp) both carry solid backgrounds that tile edge-to-edge with no
   gap, painting OVER their parent's own inset shadow in normal DOM order
   (children always paint after their parent's background+box-shadow).
   The line is coded but invisible. Drawn here instead as `.mg-tl`'s OWN
   TOP-inset shadow: `.mg-tl` has its own padding, so nothing inside it
   ever touches that top edge to occlude it. */
.mg-tl { display: none; flex-direction: column; padding: 0.75em 1em; background: var(--mg-stage-timeline); box-shadow: inset 0 1px 0 var(--mg-stage-border), inset 0 -1px 0 var(--mg-stage-border); flex-shrink: 0; }  /* .stage-timeline-section: 6px 8px */
.mg-stage[data-state="filled"] .mg-tl { display: flex; }
.mg-tl-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5em; font-size: 0.75em; line-height: 1; color: var(--mg-stage-text-dim); letter-spacing: 0.08em; text-transform: uppercase; }  /* 6px, tracking-wider */
.mg-tl-rows { display: flex; flex-direction: column; gap: 0.5em; }   /* 4px */
.mg-lrow { display: grid; grid-template-columns: 10em 1fr; gap: 0.75em; align-items: center; }   /* 80px 1fr, gap 6px */
.mg-lrow-label { display: flex; align-items: center; gap: 0.5em; min-width: 0; }
.mg-lrow i { flex: none; width: 0.375em; height: 1.5em; border-radius: 999px; background: var(--fam); }   /* pill: 3x12 */
/* fix 5a: this used to be a bare `.mg-lrow span` selector, which (element+
   class specificity) out-ranked `.mg-lrow-dot { flex: none; ... }` below —
   the dot is ALSO a span inside .mg-lrow, so it inherited flex:1 and grew
   into a stretched ellipse between the name and the clip bar. Scoped to
   the name's own class (`.mg-lrow-name`, mirrors the product's `.row-
   label .name`) so `.mg-lrow-dot`'s `flex: none` is never shadowed. */
.mg-lrow-name {
  flex: 1;
  font-size: 1em;               /* 8px */
  line-height: 1.125em;         /* 9px */
  color: var(--mg-stage-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mg-lrow-dot { flex: none; width: 0.5em; height: 0.5em; border-radius: 50%; background: var(--mg-stage-text); opacity: 0.95; }
.mg-lrow u { height: 1.5em; border-radius: 0.375em; border: 1px solid color-mix(in srgb, var(--fam) 45%, transparent); background: color-mix(in srgb, var(--fam) 24%, transparent); text-decoration: none; }  /* .clip: 12px, r3 */

/* v2: the stage footer's "Open Stage" / "AE" buttons are GONE — neither
   did anything (no stage to open, no real After Effects handoff in the
   demo). Founder ruling. */

/* ---------- node picker -------------------------------------------------- */

.mg-scrim {
  position: fixed;
  inset: 0;
  z-index: 120;
  background: rgba(4, 6, 10, 0.62);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  display: grid;
  place-items: center;
  padding: var(--space-4);
  animation: mg-fade 0.16s var(--ease);
}
@keyframes mg-fade { from { opacity: 0; } to { opacity: 1; } }

/* opened from the canvas right-click menu, desktop only: a contextual
   popover near the pointer rather than a centred modal, so no dimmed
   backdrop — it still catches an outside pointerdown to close. JS sets
   .mg-picker's position inline (fixed, near the click), which lifts it
   out of this scrim's grid placement regardless of place-items. */
.mg-scrim.mg-scrim-bare { background: transparent; -webkit-backdrop-filter: none; backdrop-filter: none; }

/* ---- picker shell, measured off ui-picker.png / ui-picker-hover.png ---- */
.mg-picker {
  width: min(324px, 100%);
  max-height: min(26rem, 80vh);
  display: flex;
  flex-direction: column;
  padding: 3px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--mg-panel);
  box-shadow: var(--mg-menu-shadow);
  overflow: hidden;
}
.mg-picker-inner {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  border-radius: 5px;
  overflow: hidden;
  background: var(--mg-panel-well);
}

.mg-picker-search {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 4px;
  height: 26px;
  padding: 0 8px;
  border-radius: 6px;
  background: var(--canvas-bg);
}
.mg-picker-search svg { flex: 0 0 auto; width: 14px; height: 14px; fill: none; stroke: var(--ink-mute); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.mg-picker-search input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  font: 500 11px/16.5px var(--font-sans);
  color: var(--ink);
}
.mg-picker-search input:focus { outline: none; }
.mg-picker-search input::placeholder { color: var(--ink-mute); font-weight: 400; }

.mg-picker-body { flex: 1; min-height: 0; display: flex; }

.mg-picker-rail {
  flex: 0 0 86px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px;
  border-right: 1px solid var(--rule-soft);
  overflow: auto;
}
.mg-rail-btn {
  display: block;
  width: 100%;
  padding: 5px 8px;
  border: 0;
  border-radius: 4px;
  background: none;
  font: 500 10px/15px var(--font-sans);
  color: var(--ink-soft);
  text-align: left;
  cursor: pointer;
}
.mg-rail-btn:hover { color: var(--ink); }
.mg-rail-btn.is-active { background: color-mix(in srgb, var(--accent-bg) 16%, transparent); color: var(--ink); }
.mg-rail-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

.mg-picker-list { flex: 1; min-width: 0; overflow: auto; padding: 4px 4px 6px; }

.mg-group {
  margin: 0;
  padding: 6px 6px 3px;
  font-size: 9px;
  line-height: 13.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: var(--mg-panel-well);
  position: sticky;
  top: 0;
}
.mg-group:first-child { padding-top: 6px; }
.mg-group[hidden], .mg-pick[hidden] { display: none; }

.mg-pick {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 5px 6px;
  border: 0;
  border-radius: 4px;
  background: none;
  font: inherit;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  transition: background-color 0.12s var(--ease);
}
.mg-pick:hover, .mg-pick:focus-visible { outline: none; background: var(--mg-row-hover); }
.mg-pick i {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border-radius: 5px;
  background: color-mix(in srgb, var(--fam) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--fam) 30%, transparent);
  color: var(--fam);
}
.mg-pick svg { width: 11px; height: 11px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.mg-pick-title {
  flex: 1;
  min-width: 0;
  font-size: 11px;
  line-height: 16.5px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mg-pick-sub {
  flex: 0 0 auto;
  max-width: 9em;
  font-size: 9px;
  line-height: 13.5px;
  color: var(--ink-mute);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- bottom control bar — CanvasControlsCluster / CanvasControlButton
   (app/canvas-controls-cluster/CanvasControlsCluster.tsx) + the
   ZoomPercentControl pill (GraphWorkspaceCanvasControls.tsx) verbatim.
   Zoom is DEMO-only (no fit-to-view / reset-zoom — the product has those,
   the mini-graph doesn't have a camera to reset); add-node and clear-graph
   are the founder-required pair with no product equivalent on this bar,
   styled to match the same button/cluster chrome. */
.mg-ctrl {
  position: absolute;
  /* v3 / founder note: centered under the graph, not parked in the left
     corner. `translate`, not transform: the bar has no transform elsewhere
     and this keeps it available if one ever appears. */
  left: 50%;
  translate: -50% 0;
  bottom: 1rem;
  z-index: 30;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;                 /* GraphWorkspaceBottomBar: gap-[6px] between clusters */
  max-width: calc(100% - 2rem);
}
/* `CanvasControlsCluster`: inline-flex gap-[3px] rounded-[8px] border
   border-default bg-panel p-[3px] shadow-[--elevation-floating]. */
.mg-ctrl-group {
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 3px;
  border-radius: 8px;
  border: 1px solid var(--rule);
  background: var(--mg-panel);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03), 0 4px 16px rgba(0, 0, 0, 0.25);  /* --elevation-floating */
}
/* `CanvasControlButton`: h-[34px] w-[34px] (the canvas-controls sizeOverride)
   rounded-[6px] bg-base text-muted, hover bg-elevated text-primary. */
.mg-ctrl-btn {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  border: 0;
  border-radius: 6px;
  background: var(--mg-panel-well);
  color: var(--ink-soft);
  cursor: pointer;
  transition: color 0.15s var(--ease), background-color 0.15s var(--ease);
}
.mg-ctrl-btn:hover { color: var(--ink); background: var(--mg-elevated); }
.mg-ctrl-btn[data-danger]:hover { color: var(--mg-danger); }
.mg-ctrl-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
.mg-ctrl-btn svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* `ZoomPercentControl` (resting, non-editing state): inline-flex h-[34px]
   min-w-[48px] rounded-[6px] bg-base px-[8px] text-[12px] font-medium
   text-muted, hover bg-elevated text-primary — its own pill, not bare text
   floating between the two icon buttons. */
.mg-ctrl-readout {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 34px;
  min-width: 48px;
  padding: 0 8px;
  border-radius: 6px;
  background: var(--mg-panel-well);
  font-size: 0.75rem;         /* 12px */
  font-weight: 500;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
  transition: color 0.15s var(--ease), background-color 0.15s var(--ease);
}

/* ---------- cursor-following tooltip (mouse only) ------------------------ */
.mg-hint {
  position: absolute;
  z-index: 25;
  top: 0;
  left: 0;
  padding: 0.5em 0.75em;
  border-radius: 8px;
  border: 1px solid var(--rule);
  background: var(--mg-panel);
  font-size: 0.78rem;
  color: var(--ink-soft);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  will-change: opacity;
  transition: opacity 0.15s var(--ease);
}
.mg-hint[data-show] { opacity: 1; }

/* ---------- canvas right-click menu, measured off ui-context-menu.png --- */
.mg-ctxmenu {
  position: fixed;
  z-index: 130;
  min-width: 150px;
  padding: 3px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  /* LITERALS on purpose: this menu mounts on <body> (position:fixed would be
     trapped by .mg-canvas's `contain: paint` if mounted inside), so .mg-scoped
     vars like --mg-panel do NOT resolve here. That unresolved var was the
     founder's transparent ghost box. */
  background: #181C28;
  box-shadow:
    0 8px 20px rgba(0, 0, 0, 0.4),
    0 0 0 1px rgba(9, 12, 18, 0.35),
    inset 0 1px 0 rgba(255, 255, 255, 0.03),
    0 4px 16px rgba(0, 0, 0, 0.25);
  font-family: var(--font-sans);
}
.mg-ctxitem {
  display: block;
  width: 100%;
  padding: 6px 10px;
  border: 0;
  border-radius: 4px;
  background: none;
  font: 400 12px/18px var(--font-sans);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
/* LITERAL for the same body-mount reason as the panel above: --mg-row-hover
   is .mg-scoped and resolves EMPTY out here — that was the founder's
   "no hover on Add node / Clear graph" (08-19). #202638 = --mg-row-hover.
   :focus (not :focus-visible) so the JS hover-moves-focus unification
   below always shows exactly ONE lit row. */
.mg-ctxitem:hover, .mg-ctxitem:focus { outline: none; background: #202638; }
.mg-ctxitem-danger { color: #F15E5E; }
.mg-ctxrule { height: 1px; margin: 3px 6px; background: var(--rule-soft); }

/* ---------- mobile ------------------------------------------------------- */

@media (max-width: 767px) {
  .mg {
    /* v1.7.2 (founder 08-19): 44vw cards read cramped + oversized on the
       phone demo — down to ~35vw with the same tall canvas, so the story
       has air between its nodes. */
    --mg-u: 1.346vw;
    --mg-canvas-h: clamp(36rem, 148vw, 48rem);
  }
  /* touch: no cursor tooltip (mouse-only per the founder ruling); the
     control bar carries add/clear instead, which stays visible as-is */
  .mg-hint { display: none; }

  /* v1.7.13 (founder 08-19): ports read gigantic against the smaller
     phone cards - scaled down for the demo. */
  .mg-port { width: 1.1em; height: 1.1em; margin: -0.55em 0 0; }
  .mg-port-in  { left: -0.65em; }
  .mg-port-out { right: -0.65em; }

  /* bottom sheet — always, even for a right-click-triggered open, since a
     touch device has no meaningful "near the pointer" anchor */
  .mg-scrim { place-items: end stretch; padding: 0; }
  .mg-scrim.mg-scrim-bare { background: rgba(4, 6, 10, 0.62); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
  .mg-picker {
    width: 100% !important;
    max-width: none;
    max-height: 76vh;
    border-radius: 16px 16px 0 0;
    border-bottom: 0;
    padding: 3px 3px calc(3px + env(safe-area-inset-bottom, 0px));
    left: 0 !important;
    top: auto !important;
    animation: mg-rise 0.2s var(--ease);
  }
  .mg-pick { padding: 0.7rem 6px; }
  @keyframes mg-rise { from { transform: translateY(14%); } to { transform: none; } }
}

/* ---------- scripted demo (mobile, founder 08-19) ------------------------
   The canvas wears [data-demo] on phones: controls hidden, nodes inert
   (taps fall through to the canvas = replay), and arriving nodes get a
   drop-in on the SHELL (never the node root — `translate`/`transform`
   position the node, so animating them there would teleport it). */
.mg-canvas[data-demo] .mg-ctrl { display: none; }
.mg-canvas[data-demo] .mg-node { pointer-events: none; }
.mg-node[data-drop] .mg-shell { animation: mg-drop 0.34s var(--ease); }
@keyframes mg-drop {
  from { transform: scale(0.86); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
/* the scripted RUN press — brief accent ring so the tap reads as a press */
.mg-act[data-press] { box-shadow: 0 0 0 2px #70E8E4 inset; }

/* ---------- reduced motion ---------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .mg-node, .mg-proc, .mg-act, .mg-port, .mg-pick, .mg-ctrl-btn, .mg-hint { transition: none; }
  .mg-well[data-run]::after { display: none; }
  .mg-well[data-run]::before { animation: none; transform: none; }
  .mg-run-bar-fill { animation: none; transform: none; }
  .mg-node[data-refuse] { animation: none; outline: 2px solid var(--fam); outline-offset: 2px; }
  .mg-port[data-required]:not([data-linked]) { animation: none; box-shadow: 0 0 0 0.125em var(--fam); }
  .mg-scrim, .mg-picker { animation: none; }
}


/* ---------- wire draw-in (founder 08-19) --------------------------------- */
.mg-wire-in { stroke-dasharray: 100; animation: mg-wire-draw 0.5s ease forwards; }
@keyframes mg-wire-draw { from { stroke-dashoffset: 100; } to { stroke-dashoffset: 0; } }
@media (prefers-reduced-motion: reduce) {
  .mg-wire-in { animation: none; stroke-dasharray: none; }
}
