/* ==========================================================================
   EDITOR.CSS — the node editor area: canvas, nodes, sockets, cables.

   .node                    positioned by the engine (transform)
     .node__card            the visible box
       .node__header        coloured bar
       .node__body          rows + preview (hidden on compact screens)
       .socket              absolutely placed on the card edge
   ========================================================================== */

.area--editor { background: var(--canvas); container-type: inline-size; }

.canvas {
  position: relative;
  min-height: 0;
  overflow: hidden;
  background-color: var(--canvas);
  background-image:
    radial-gradient(circle, var(--dot-major) 1.3px, transparent 1.7px),
    radial-gradient(circle, var(--dot) 1px, transparent 1.4px);
  background-size: 120px 120px, 24px 24px;
  background-position: center;
  touch-action: none;
}
.cables { position: absolute; z-index: 1; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.nodes { position: absolute; z-index: 3; inset: 0; pointer-events: none; }   /* click-through: cables below stay clickable */

/* ---------- Canvas overlays ---------- */
.canvas__hint {
  position: absolute; z-index: 2;
  top: 12px; left: 16px; right: 56px;
  margin: 0;
  font-size: 12px;
  color: var(--text-dim);
  pointer-events: none;
}
.canvas__btn {
  position: absolute; z-index: 2;
  top: 7px; right: 8px;
  display: grid; place-items: center;
  width: 30px; height: 28px;
  border: 0; border-radius: 6px;
  background: transparent; color: var(--text-dim);
}
.canvas__btn:hover { background: var(--field); color: var(--text); }
@media (pointer: coarse) { .canvas__btn { width: 40px; height: 36px; } }

/* "Simple version": in the empty space under the nodes */
.simple-btn {
  position: absolute; z-index: 2;
  left: 50%; bottom: 104px;
  transform: translateX(-50%);
  display: inline-flex; align-items: center; gap: 8px;
  height: 36px; padding: 0 16px 0 12px;
  border: 1px solid var(--node-edge);
  border-radius: 999px;
  background: var(--node);
  box-shadow: var(--node-shadow);
  color: var(--text);
  font-size: 13px; font-weight: 500;
  text-decoration: none; white-space: nowrap;
  transition: box-shadow 160ms var(--ease-out), transform 160ms var(--ease-out);
}
.simple-btn .icon { color: var(--text-dim); }
.simple-btn:hover { box-shadow: var(--node-shadow-lift); transform: translateX(-50%) translateY(-1px); }

/* Bottom-left flap: rises out of the status bar */
.flap--bl { z-index: 2; left: 0; bottom: 0; height: 64px; align-items: stretch; }
.flap--bl .flap__body { padding: 6px 4px 0 18px; max-width: 230px; }
.flap--bl .flap__body span:last-child { white-space: normal; }
.flap--bl .status-dot { margin-right: 2px; }

.is-tall .canvas__hint, .is-short .canvas__hint,
.is-tall .simple-btn, .is-short .simple-btn,
.is-tall .flap--bl, .is-short .flap--bl { display: none; }

/* ---------- Node metrics ---------- */
.canvas {
  --src-w: 184px;
  --personal-w: 212px;
  --hdr-h: 24px;
  --row-h: 22px;
  --fs: 11.5px;
  --sock: 12px;
  --sock-hit: 28px;
  --logo-h: 128px;
  --compact-h: 34px;
}
.is-short .canvas { --src-w: 138px; --personal-w: 150px; --logo-h: 92px; --compact-h: 38px; }
.is-tall .canvas  { --src-w: min(42vw, 210px); --personal-w: min(37vw, 176px); --logo-h: 92px; --compact-h: 40px; }
@media (min-width: 600px) { .is-tall .canvas { --logo-h: 124px; --compact-h: 48px; } }
/* Narrow editor on desktop (e.g. 1024px screens): smaller nodes so they never overlap */
@container (max-width: 500px) {
  :root:not(.is-tall):not(.is-short) .canvas { --src-w: 158px; --personal-w: 176px; --logo-h: 108px; }
}
@container (max-width: 430px) {
  :root:not(.is-tall):not(.is-short) .canvas { --src-w: 146px; --personal-w: 160px; --logo-h: 96px; }
}
@media (pointer: coarse) { .canvas { --sock-hit: 44px; } .is-tall .canvas, .is-short .canvas { --compact-h: 44px; } }

/* ---------- Node box ---------- */
.node {
  --hdr: var(--hdr-work);
  --cable: var(--cable-work);
  position: absolute;
  left: 0; top: 0;
  width: var(--src-w);
  will-change: transform;
  pointer-events: auto;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  outline: none;
}
.node--work     { --hdr: var(--hdr-work);     --cable: var(--cable-work); }
.node--cv       { --hdr: var(--hdr-cv);       --cable: var(--cable-cv); }
.node--contact  { --hdr: var(--hdr-contact);  --cable: var(--cable-contact); }
.node--personal { --hdr: var(--hdr-personal); --cable: var(--cable-idle); width: var(--personal-w); }

.node__card {
  position: relative;
  background: var(--node);
  border: 1px solid var(--node-edge);
  border-radius: 6px;
  box-shadow: var(--node-shadow);
  font-size: var(--fs);
  line-height: 1.25;
  color: var(--text);
  transition: box-shadow 160ms var(--ease-out), transform 160ms var(--ease-out);
}

/* Source nodes are big buttons: click / tap plugs them in */
.node--source { cursor: pointer; }
.node--source:hover .node__card { box-shadow: var(--node-shadow-lift); }
.node--source:active .node__card { transform: scale(.985); }
.node.is-live .node__card { box-shadow: 0 0 0 1.5px var(--active), var(--node-shadow); }
.node.is-live:hover .node__card { box-shadow: 0 0 0 1.5px var(--active), var(--node-shadow-lift); }
.node.is-dragging { cursor: grabbing; }
.node.is-dragging .node__card { box-shadow: 0 0 0 1px var(--active), var(--node-shadow-lift); transform: none; }
.node:focus-visible .node__card { box-shadow: 0 0 0 2px var(--accent), var(--node-shadow-lift); }

.node__header {
  display: flex;
  align-items: center;
  gap: 4px;
  height: var(--hdr-h);
  padding: 0 8px 0 4px;
  border-radius: 5px 5px 0 0;
  background: color-mix(in srgb, var(--hdr) var(--hdr-mix), var(--hdr-mix-base));
  color: var(--hdr-text);
  font-size: calc(var(--fs) + .5px);
  font-weight: 500;
  white-space: nowrap;
}
.node__header .icon { width: 13px; height: 13px; opacity: .75; }
.node__title { flex: 1; overflow: hidden; text-overflow: ellipsis; }
.node__state {
  display: none;
  align-items: center; gap: 5px;
  font-size: calc(var(--fs) - 1px);
  font-weight: 500;
  opacity: .9;
}
.node__state::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.node.is-live .node__state { display: inline-flex; }

.node__body { padding: 4px 0 8px; }
.row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  height: var(--row-h);
  padding: 0 12px;
}
.row--out { justify-content: flex-end; }
.row__value { color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Work: 3×2 mosaic of the project thumbnails */
.mosaic {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 3px;
  margin: 4px 8px 0;
}
.mosaic img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 3px; background: var(--field-sunken); }
.node__note {
  display: flex; align-items: center; gap: 6px;
  margin: 4px 8px 0;
  height: calc(var(--row-h) - 1px);
  padding: 0 8px;
  border-radius: 4px;
  background: var(--field-sunken);
  color: var(--text-dim);
  white-space: nowrap; overflow: hidden;
}
.node__note .icon { width: 13px; height: 13px; }

/* Personal */
.personal__logo {
  display: grid;
  place-items: center;
  margin: 2px 8px 4px;
  padding: 12px 8px;
  border-radius: 4px;
  background: var(--field-sunken);
}
.personal__logo img { height: var(--logo-h); width: auto; filter: var(--logo-filter); }
.personal__logo img.no-invert { filter: none; }

/* ---------- Sockets ---------- */
.socket {
  position: absolute;
  z-index: 2;
  width: var(--sock);
  height: var(--sock);
  border-radius: 50%;
  background: var(--cable);
  border: 1px solid var(--sock-edge);
  transition: transform 140ms var(--ease-out), background-color 200ms, box-shadow 140ms;
}
.socket::before {           /* larger invisible hit area */
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: var(--sock-hit); height: var(--sock-hit);
  transform: translate(-50%, -50%);
  border-radius: 50%;
}
/* Output socket of a source node: right edge, on the first row */
.socket--out {
  right: calc(var(--sock) / -2);
  top: calc(var(--hdr-h) + 4px + var(--row-h) / 2 - var(--sock) / 2);
  cursor: crosshair;
  touch-action: none;
}
.node:not(.is-live) .socket--out { background: var(--node); border: 2px solid var(--cable); }
.socket--out:hover, .node.is-wiring .socket--out { transform: scale(1.3); box-shadow: 0 0 0 3px color-mix(in srgb, var(--cable) 30%, transparent); }

/* Personal: input on the left (last row), viewer output on the right (first row) */
.socket--in, .socket--view { pointer-events: none; background: var(--live-cable, var(--cable-idle)); }
.socket--in { left: calc(var(--sock) / -2); bottom: calc(8px + var(--row-h) / 2 - var(--sock) / 2); }
.socket--view { right: calc(var(--sock) / -2); top: calc(var(--hdr-h) + 4px + var(--row-h) / 2 - var(--sock) / 2); }
.node--personal:not(.has-input) .socket--in { background: var(--node); border: 2px solid var(--cable-idle); }
.node--personal.is-target .node__card { box-shadow: 0 0 0 1.5px var(--accent), var(--node-shadow-lift); }
.node--personal.is-target .socket--in { transform: scale(1.35); box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 35%, transparent); }

/* ---------- Compact nodes (phones, landscape phones) ----------
   Source nodes collapse to their coloured header, like a collapsed node
   in Blender. Personal keeps only the logo. */
.is-tall .node__body .row,
.is-short .node__body .row,
.is-tall .node--source .node__body,
.is-short .node--source .node__body { display: none; }
.is-tall .node--source .node__header,
.is-short .node--source .node__header {
  height: var(--compact-h);
  border-radius: 5px;
  padding: 0 14px 0 8px;
  font-size: 13px;
}
.is-tall .socket--out, .is-short .socket--out { top: calc(50% - var(--sock) / 2); }
/* On phones the whole node is the tap target, so the socket keeps a small
   hit area and taps next to it reach the cable (tap a cable to unplug). */
.is-tall .socket--out::before, .is-short .socket--out::before { width: calc(var(--sock) + 6px); height: calc(var(--sock) + 6px); }
.is-tall .node--personal .node__body, .is-short .node--personal .node__body { padding: 4px 0 6px; }
.is-tall .personal__logo, .is-short .personal__logo { margin: 0 6px; padding: 8px 6px; }
.is-tall .socket--in, .is-short .socket--in { bottom: auto; top: calc(50% + var(--hdr-h) / 2 - var(--sock) / 2); }
.is-short .socket--view { top: calc(50% + var(--hdr-h) / 2 - var(--sock) / 2); }
/* Tall: the viewer is above, so Personal's output points up */
.is-tall .socket--view { top: calc(var(--sock) / -2); right: auto; left: calc(50% - var(--sock) / 2); }

/* ---------- Cables ---------- */
.cable { display: none; }
.cable.is-visible { display: inline; }
.cable[data-kind="work"]    { --c: var(--cable-work); }
.cable[data-kind="cv"]      { --c: var(--cable-cv); }
.cable[data-kind="contact"] { --c: var(--cable-contact); }
.cable[data-kind="idle"]    { --c: var(--cable-idle); }
.cable__hit { fill: none; stroke: transparent; stroke-width: 18; pointer-events: stroke; cursor: pointer; }
@media (pointer: coarse) { .cable__hit { stroke-width: 32; } }
.cable__shade { fill: none; stroke: var(--cable-shade); stroke-width: 5.5; stroke-linecap: round; }
.cable__line {
  fill: none;
  stroke: color-mix(in srgb, var(--c) var(--cable-mix), #000);
  stroke-width: 2.6;
  stroke-linecap: round;
  transition: stroke-width 140ms var(--ease-out);
}
.cable:hover .cable__line { stroke-width: 3.8; }
.cable:hover .cable__shade { stroke-width: 7; }
.cable.is-draft .cable__hit, .cable.is-tween .cable__hit { pointer-events: none; }
.jack { fill: var(--c); stroke: var(--sock-edge); stroke-width: 1; }

/* ---------- Reaction when a node is plugged in ----------
   The node jiggles and four little spark marks pop at its corners.
   JS adds .is-reacting: the source first, then Personal, then the viewer,
   so the signal visibly travels along the cable.                          */
.node__fx { position: absolute; inset: 0; pointer-events: none; }
.node__fx i { position: absolute; width: 22px; height: 22px; opacity: 0; color: var(--fx, var(--cable)); }
.node__fx svg { display: block; width: 100%; height: 100%; }
.node__fx i:nth-child(1) { top: -22px; left: -22px;   --dx: -5px; --dy: -5px; }
.node__fx i:nth-child(2) { top: -22px; right: -22px;  --dx: 5px;  --dy: -5px; }
.node__fx i:nth-child(3) { bottom: -22px; right: -22px; --dx: 5px; --dy: 5px; }
.node__fx i:nth-child(4) { bottom: -22px; left: -22px;  --dx: -5px; --dy: 5px; }
.node__fx i:nth-child(1) svg { transform: scaleX(-1); }
.node__fx i:nth-child(3) svg { transform: scaleY(-1); }
.node__fx i:nth-child(4) svg { transform: scale(-1, -1); }
.node--personal { --fx: var(--live-cable, var(--cable-idle)); }

.node.is-reacting .node__card { animation: jiggle 560ms cubic-bezier(.3, .7, .4, 1); }
.node.is-reacting .node__fx i { animation: spark 620ms var(--ease-out); }
.node.is-reacting .node__fx i:nth-child(2), .node.is-reacting .node__fx i:nth-child(4) { animation-delay: 40ms; animation-fill-mode: backwards; }

@keyframes jiggle {
  0%   { transform: none; }
  18%  { transform: scale(1.05) rotate(-1.4deg); }
  38%  { transform: scale(.985) rotate(1deg); }
  58%  { transform: scale(1.012) rotate(-.5deg); }
  78%  { transform: scale(.998) rotate(.2deg); }
  100% { transform: none; }
}
@keyframes spark {
  0%   { opacity: 0; translate: 0 0; scale: .4; }
  30%  { opacity: 1; scale: 1; }
  100% { opacity: 0; translate: var(--dx) var(--dy); scale: 1; }
}
.jack { transform-box: fill-box; transform-origin: center; }
.cable.is-reacting .jack { animation: jack 520ms var(--ease-out); }
@keyframes jack { 40% { transform: scale(1.8); } }

/* ---------- 3D nodes ---------- */
.node--model { --hdr: var(--hdr-model); --cable: var(--cable-model); }
.cable[data-kind="model"] { --c: var(--cable-model); }
.node__close {
  display: grid; place-items: center; flex: none;
  width: 18px; height: 18px;
  margin: 0 -4px 0 2px;
  border: 0; border-radius: 4px;
  background: transparent; color: inherit;
  opacity: .7;
}
.node__close:hover { opacity: 1; background: rgba(0, 0, 0, .25); }
.node__close .icon { width: 12px; height: 12px; opacity: 1; }
@media (pointer: coarse) { .node__close { width: 30px; height: 30px; opacity: .9; } }
.model-thumb { position: relative; margin: 4px 8px 0; border-radius: 4px; overflow: hidden; }
.model-thumb img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.model-thumb span {
  position: absolute; left: 6px; bottom: 6px;
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 6px; border-radius: 4px;
  background: rgba(10, 10, 10, .72); color: #fff;
  font-size: 10.5px; font-weight: 600;
}
.model-thumb .icon { width: 11px; height: 11px; opacity: 1; color: var(--cable-model); }

/* A fresh 3D node invites you to plug it in */
.node.is-new:not(.is-live) .socket--out { animation: invite 1.2s ease-in-out 3; }
@keyframes invite { 50% { transform: scale(1.5); box-shadow: 0 0 0 6px color-mix(in srgb, var(--cable) 30%, transparent); } }

/* Drop zone while a project image is dragged over the editor */
.canvas.is-drop::after {
  content: attr(data-drop);
  position: absolute; z-index: 4; inset: 10px;
  display: flex; align-items: flex-start; justify-content: center;
  padding-top: 36px;
  border: 2px dashed var(--cable-model);
  border-radius: 10px;
  background: color-mix(in srgb, var(--cable-model) 7%, transparent);
  color: var(--text);
  font-size: 13px; font-weight: 500;
  pointer-events: none;
}

/* Phones: a bit more room in the node area while a 3D node exists */
.is-tall .workspace.has-model { --editor-h: 216px; }
@media (min-width: 600px) { .is-tall .workspace.has-model { --editor-h: 264px; } }

/* Crowded canvas (small laptop screens): the 3D node drops its thumbnail */
.node--model.is-slim .model-thumb { display: none; }

/* Compact 3D node: keep the name readable (the outline already shows it's live) */
.is-tall .node--model .node__state, .is-short .node--model .node__state { font-size: 0; gap: 0; }
/* Small node area: the dragged ghost carries the drop label instead */
.is-tall .canvas.is-drop::after, .is-short .canvas.is-drop::after { content: ""; }

/* ---------- Material nodes (desktop) ---------- */
.node--material { --hdr: var(--hdr-material); --cable: var(--cable-material); width: min(var(--src-w), 156px); }
.is-tall .node--material, .is-short .node--material { display: none; }
.cable[data-kind="material"] { --c: var(--cable-material); }
.mat-preview { display: grid; place-items: center; padding: 8px 0 4px; }
.mat-preview .ball { width: 38px; height: 38px; }
.socket--min {                          /* the material's Geometry input */
  --cable: var(--cable-model);
  left: calc(var(--sock) / -2);
  bottom: calc(8px + var(--row-h) / 2 - var(--sock) / 2);
  pointer-events: none;
  background: var(--node); border: 2px solid var(--cable-model);
}
.node--material.is-live .socket--min { background: var(--cable-model); border: 1px solid var(--sock-edge); }
.node--material.is-target .node__card { box-shadow: 0 0 0 1.5px var(--cable-model), var(--node-shadow-lift); }
.node--material.is-target .socket--min { transform: scale(1.35); box-shadow: 0 0 0 4px color-mix(in srgb, var(--cable-model) 35%, transparent); }
.node--model.is-feeding .node__card { box-shadow: 0 0 0 1.5px var(--cable-model), var(--node-shadow); }
.cable.is-feed .cable__line { stroke-dasharray: none; }

/* ---------- G: grab ---------- */
.node.is-grabbing { cursor: move; }
.node.is-grabbing .node__card { box-shadow: 0 0 0 1.5px var(--accent), var(--node-shadow-lift); transform: scale(1.03) rotate(-.6deg); transition: transform 160ms var(--ease-out); }
body.is-grab-mode, body.is-grab-mode * { cursor: move !important; }

/* ---------- X: delete (implode) and "can't" shake ---------- */
.node.is-removing { pointer-events: none; }
.node.is-removing .node__card { animation: implode 300ms cubic-bezier(.5, 0, .75, 0) forwards; }
@keyframes implode { 30% { transform: scale(1.06); } 100% { transform: scale(.2) rotate(8deg); opacity: 0; } }
.node__card.is-shaking { animation: shake 420ms cubic-bezier(.36, .07, .19, .97); }
@keyframes shake { 20%, 80% { transform: translateX(-2px); } 40%, 60% { transform: translateX(5px); } 50% { transform: translateX(-6px); } }

/* ---------- Signal arrives at Personal: socket clicks in, logo glows ---------- */
.node--personal.is-reacting .socket--in { animation: sock-pop 420ms var(--ease-out); }
@keyframes sock-pop { 35% { transform: scale(1.9); box-shadow: 0 0 0 6px color-mix(in srgb, var(--live-cable, var(--accent)) 30%, transparent); } }
.node--personal.is-reacting .personal__logo { animation: logo-glow 700ms var(--ease-out); }
@keyframes logo-glow { 30% { box-shadow: inset 0 0 0 1.5px var(--live-cable, var(--accent)), inset 0 0 26px color-mix(in srgb, var(--live-cable, var(--accent)) 30%, transparent); } }

/* ---------- Keyboard hints in the canvas ---------- */
.canvas__hint { display: flex; flex-wrap: wrap; gap: 4px 14px; align-items: center; }
.canvas__hint .keys { display: inline-flex; align-items: center; gap: 5px; color: var(--text-faint); }
.canvas__hint kbd {
  font: 600 10.5px var(--font-ui);
  padding: 1px 5px; border-radius: 4px;
  background: var(--field-sunken); color: var(--text-dim);
  box-shadow: inset 0 -1px 0 var(--node-edge);
}
.canvas__hint kbd:not(:first-child) { margin-left: 6px; }

/* ---------- Toast ---------- */
.toast {
  position: absolute; z-index: 6; left: 50%; top: 44px;
  max-width: calc(100% - 32px);
  margin: 0; padding: 8px 14px;
  border-radius: 999px;
  background: var(--node); color: var(--text);
  border: 1px solid var(--node-edge);
  box-shadow: var(--node-shadow-lift);
  font-size: 12.5px; text-align: center;
  opacity: 0; transform: translate(-50%, -8px);
  transition: opacity 200ms, transform 260ms var(--ease-out);
  pointer-events: none;
}
.toast.is-visible { opacity: 1; transform: translate(-50%, 0); }

/* ---------- Shift+A: Add menu ---------- */
.addmenu {
  position: fixed; z-index: 250;
  width: 250px; max-height: min(420px, calc(100vh - 16px));
  display: flex; flex-direction: column;
  overflow: hidden;
  border-radius: 7px;
  background: var(--node); color: var(--text);
  border: 1px solid var(--node-edge);
  box-shadow: var(--node-shadow-lift);
  font-size: 12.5px;
  transform-origin: 20px 18px;
  animation: menu-in 160ms var(--ease-out);
}
.addmenu.is-closing { animation: menu-out 140ms ease-in forwards; }
@keyframes menu-in { from { opacity: 0; transform: scale(.92) translateY(-4px); } }
@keyframes menu-out { to { opacity: 0; transform: scale(.95); } }
.addmenu__head { display: flex; align-items: center; gap: 6px; height: 26px; padding: 0 10px; background: var(--area-header); font-weight: 600; font-size: 12px; }
.addmenu__head .icon { width: 13px; height: 13px; color: var(--text-dim); }
.addmenu__search {
  margin: 6px; height: 26px; padding: 0 9px;
  border: 1px solid var(--node-edge); border-radius: 5px;
  background: var(--field-sunken); color: var(--text); font-size: 12.5px;
}
.addmenu__search:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
.addmenu__list { overflow-y: auto; padding: 0 4px 6px; }
.addmenu__group { margin: 8px 8px 4px; font-size: 11px; font-weight: 600; color: var(--text-faint); }
.addmenu__item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  min-height: 30px; padding: 4px 8px;
  border: 0; border-radius: 5px;
  background: transparent; color: var(--text);
  font-size: 12.5px; text-align: left;
  animation: item-in 220ms var(--ease-out) backwards;
  animation-delay: calc(var(--n) * 18ms);
}
@keyframes item-in { from { opacity: 0; transform: translateX(-6px); } }
.addmenu__item img { width: 34px; height: 20px; object-fit: cover; border-radius: 3px; flex: none; }
.addmenu__item .ball--tiny { width: 18px; height: 18px; margin: 0 8px; flex: none; }
.addmenu__item small { margin-left: auto; color: var(--text-faint); font-size: 10.5px; }
.addmenu__item.is-active { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.addmenu__item[aria-disabled="true"] { opacity: .5; }
.addmenu__empty { margin: 10px; color: var(--text-dim); }
