/* nodes.css — TapNow-style visual restyle for workflow nodes.
 *
 * SPRINT-B SCOPE NOTE
 * The original spec called for a separate prompt panel rendered below
 * every node. That model breaks our pipeline-style canvas (5–15
 * connected nodes) because every panel would overlap the next node and
 * occlude connection edges. This file delivers the visual restyle of
 * the node CARD, sockets, label, empty state, and upload pill — and
 * restyles the existing right props panel as the "prompt panel" so
 * the form lives in a stable, reachable surface keyed to the selected
 * node. The DOM restructure for an always-visible per-node panel is a
 * separate sprint after this one ships and can be observed live.
 *
 * Selectors deliberately overlap the existing class names from
 * workflow.html (.workflow-node, .node-header, .node-preview, .port)
 * so this stylesheet is purely additive — no JS changes required to
 * see the visual update. The legacy rules in workflow.html's <style>
 * block still load first; nodes.css comes after and takes precedence
 * via cascade order on shared properties.
 */

/* ── Node card ────────────────────────────────────── */
/* The .workflow-node element is the card. We don't need an outer
   wrapper — the card itself takes the spec's bg/border/radius. */
.workflow-node {
  background: var(--node-card-bg, var(--node-bg, #1f1f1f)) !important;
  border: 1px solid var(--node-card-border, rgba(255,255,255,0.08)) !important;
  border-radius: 16px !important;
  box-shadow: none !important;
  transition: border-color 0.18s ease, transform 0.12s ease;
}
.workflow-node:hover {
  border-color: var(--node-card-border-hover, rgba(255,255,255,0.16)) !important;
}
.workflow-node.selected {
  border: 1.5px solid var(--accent, #7c6dfa) !important;
  box-shadow: 0 0 0 1px var(--accent, #7c6dfa) !important;
}
.workflow-node.running {
  border-color: var(--cyan, #22d4fd) !important;
  box-shadow: 0 0 0 1px var(--cyan, #22d4fd), 0 0 24px rgba(34, 212, 253, 0.18) !important;
}
.workflow-node.done {
  border-color: var(--success, var(--green, #06f7a1)) !important;
}
.workflow-node.failed {
  border-color: var(--danger, var(--err, #ff6b81)) !important;
}

/* ── Type label rendered ABOVE the card ──────────── */
/* The existing .node-header is currently the FIRST child of the card.
   We re-position it absolutely so it floats 28px above the card,
   matching the TapNow layout. The card itself loses the in-card
   header bar, freeing the card surface for content/empty-state. */
.workflow-node .node-header {
  position: absolute !important;
  top: -28px !important;
  left: 0 !important;
  right: auto !important;
  margin-bottom: 0 !important;
  padding: 0 !important;
  border: none !important;
  border-bottom: none !important;
  background: transparent !important;
  font-size: 14px !important;
  font-weight: 500 !important;
  color: var(--text-secondary, var(--text2, rgba(240,242,255,0.7))) !important;
  letter-spacing: 0 !important;
  cursor: grab;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 7px;
}
.workflow-node .node-header:active { cursor: grabbing; }
.workflow-node .node-header .n-icon {
  background: transparent !important;
  width: 16px !important;
  height: 16px !important;
  font-size: 14px !important;
  color: var(--text-secondary, var(--text2, rgba(240,242,255,0.7))) !important;
}
/* The status dot (added in legacy code) moves to the right of the label. */
.workflow-node .node-header .status-dot {
  margin-left: 6px !important;
  width: 6px !important;
  height: 6px !important;
}

/* The card needs top padding to compensate for the moved header. */
.workflow-node {
  padding-top: 16px !important;
}

/* ── Empty-state glyph ────────────────────────────── */
/* Shown when the node has no .node-preview content yet — i.e. the
   canvas hasn't generated anything for this node. We synthesize the
   empty state purely from CSS by rendering a faded glyph as a
   ::before pseudo-element on the .node-body when it has no preview
   sibling. Kept separate per node-type via type-specific glyph CSS. */
.cg-empty-state {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  min-height: 80px;
  color: var(--text-muted, var(--text3, rgba(240,242,255,0.45)));
  opacity: 0.3;
  pointer-events: none;
  font-size: 64px;
  line-height: 1;
}
.cg-empty-state svg { width: 64px; height: 64px; stroke: currentColor; fill: none; stroke-width: 1.5; }

/* When a node has both an empty-state and a populated preview, hide
   the empty state. The render layer toggles a class — see workflow.html. */
.workflow-node.has-output .cg-empty-state { display: none !important; }

/* ── Sockets (the + buttons on left/right edges) ──── */
/* Renderer wraps all input port-rows in <div class="port-rail input"> and
   outputs in <div class="port-rail output">. The rail is absolutely
   positioned on the edge and spans the full height; port-rows flow
   normally inside via flex-column with justify-content:space-evenly so
   N inputs land at evenly distributed y-positions instead of stacking.
   This is the fix for the coincident-ports bug — Image generate's
   "prompt" + "reference" inputs now occupy distinct drop zones. */
.workflow-node .port-rail {
  position: absolute;
  top: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
  align-items: center;
  pointer-events: none;        /* port-rows re-enable below */
  z-index: 2;
}
.workflow-node .port-rail.input  { left:  -22px; }
.workflow-node .port-rail.output { right: -22px; }
.workflow-node .port-rail > .port-row { pointer-events: auto; }

.workflow-node .port-row.input,
.workflow-node .port-row.output {
  position: relative;          /* override any legacy absolute */
  top: auto;
  left: auto;
  right: auto;
  transform: none;
  width: auto;
  padding: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  color: var(--text-muted, var(--text3, rgba(240,242,255,0.45)));
  white-space: nowrap;
}
.workflow-node .port-row.output {
  flex-direction: row-reverse;
}

/* Port labels (Sprint-D-Labels) — rendered via ::before on the port
   span (the "+" glyph already lives in ::after, so labels go on the
   other pseudo). Sits OUTSIDE the card on the left/right edge. The
   renderer no longer emits a sibling <span> for the label; data-label
   is the single source of truth. Color tracks --this-port-color so
   labels match the port + line color (Sprint 2 coding). */
.workflow-node .port[data-label]::before {
  content: attr(data-label);
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--this-port-color, var(--text-secondary));
  opacity: 0.85;
  white-space: nowrap;
  pointer-events: none;
}
.workflow-node .port.input[data-label]::before  { right: calc(100% + 10px); text-align: right; }
.workflow-node .port.output[data-label]::before { left:  calc(100% + 10px); text-align: left;  }
.workflow-node .port:hover::before { opacity: 1; font-weight: 600; }

/* Hide labels on nodes with only one port of that direction — single
   socket needs no naming, the icon + position are unambiguous. */
.workflow-node.single-input  .port.input::before  { display: none; }
.workflow-node.single-output .port.output::before { display: none; }
.workflow-node .port {
  width: 36px !important;
  height: 36px !important;
  border-radius: 50% !important;
  background: var(--socket-bg, rgba(0,0,0,0.6)) !important;
  border: 1px solid var(--socket-border, rgba(255,255,255,0.15)) !important;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  transition: transform 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease;
  position: relative;
  cursor: crosshair;
}
.workflow-node .port::after {
  content: '+';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  font-weight: 400;
  color: var(--text-secondary, var(--text2, rgba(240,242,255,0.7)));
  line-height: 1;
}
.workflow-node .port:hover {
  transform: scale(1.1);
  border-color: var(--accent, #7c6dfa) !important;
}
.workflow-node .port:hover::after {
  color: var(--accent, #7c6dfa);
}
.workflow-node .port.dragging,
.workflow-node .port.connecting {
  transform: scale(1.2);
  border-color: var(--accent, #7c6dfa) !important;
  animation: cgSocketPulse 1s ease-in-out infinite;
}
@keyframes cgSocketPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(124, 109, 250, 0.4); }
  50%      { box-shadow: 0 0 0 8px rgba(124, 109, 250, 0); }
}

/* ── Upload pill (above node, when applicable) ──── */
/* Generated by JS only on nodes that accept uploads. CSS-only here
   so when the JS flips a class on the card, the pill appears. */
.cg-upload-pill {
  position: absolute;
  top: -52px;
  left: 50%;
  transform: translateX(-50%);
  padding: 8px 16px;
  background: var(--upload-pill-bg, rgba(0,0,0,0.8));
  border: 1px solid var(--upload-pill-border, rgba(255,255,255,0.1));
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-radius: 9999px;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 500;
  color: var(--text, #f0f2ff);
  cursor: pointer;
  z-index: 3;
  transition: background 0.12s, border-color 0.12s, transform 0.12s;
}
.cg-upload-pill:hover {
  background: var(--surface-hover, rgba(255, 255, 255, 0.12));
  transform: translateX(-50%) translateY(-1px);
}

/* ── Aspect-ratio variants ───────────────────────── */
/* Applied as data attributes by the renderer when known; falls back
   to free-form sizing otherwise. The legacy node width is 220px;
   the spec wants 480/270 for 16:9 video. We respect the data
   attribute if present, else leave width: auto. */
.workflow-node[data-aspect="16:9"] { width: 480px !important; }
.workflow-node[data-aspect="16:9"] .node-body,
.workflow-node[data-aspect="16:9"] .node-preview { aspect-ratio: 16 / 9; }
.workflow-node[data-aspect="9:16"] { width: 270px !important; }
.workflow-node[data-aspect="9:16"] .node-body,
.workflow-node[data-aspect="9:16"] .node-preview { aspect-ratio: 9 / 16; }
.workflow-node[data-aspect="1:1"]  { width: 320px !important; }
.workflow-node[data-aspect="1:1"]  .node-body,
.workflow-node[data-aspect="1:1"]  .node-preview { aspect-ratio: 1 / 1; }
.workflow-node[data-aspect="4:5"]  { width: 320px !important; }
.workflow-node[data-aspect="4:5"]  .node-body,
.workflow-node[data-aspect="4:5"]  .node-preview { aspect-ratio: 4 / 5; }
.workflow-node[data-aspect="3:4"]  { width: 320px !important; }
.workflow-node[data-aspect="3:4"]  .node-body,
.workflow-node[data-aspect="3:4"]  .node-preview { aspect-ratio: 3 / 4; }
.workflow-node[data-aspect="4:3"]  { width: 400px !important; }
.workflow-node[data-aspect="4:3"]  .node-body,
.workflow-node[data-aspect="4:3"]  .node-preview { aspect-ratio: 4 / 3; }
.workflow-node[data-aspect="audio"] { width: 480px !important; }
.workflow-node[data-aspect="audio"] .node-body { min-height: 96px; }

/* Aspect-change animation (Sprint-D-Body-Strip). Width transitions when
   data-aspect flips. .node-body / .node-preview animate via the same
   transition since aspect-ratio is the controlling property. transform
   is excluded so node-drag positioning stays instant. */
.workflow-node { transition: width 200ms ease, height 200ms ease; }
.workflow-node .node-body,
.workflow-node .node-preview { transition: aspect-ratio 200ms ease; }

/* ── Prompt panel (re-skin of the existing right props panel) ── */
/* The legacy props panel is .props in workflow.html (grid-area: props,
   width 200px). For the prompt-panel design we promote it to 360px
   wide and restyle interior. */
.props {
  background: var(--prompt-panel-bg, rgba(20, 20, 20, 0.85)) !important;
  border-left: 1px solid var(--prompt-panel-border, rgba(255, 255, 255, 0.08)) !important;
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
}
/* Wider props column when the prompt-panel mode is on. */
body.cg-prompt-panel-mode .app {
  grid-template-columns: 160px 1fr 360px;
}

.props h3 {
  font-size: 11px !important;
  font-weight: 700 !important;
  color: var(--text-muted, var(--text3, rgba(240,242,255,0.5))) !important;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* Header icon-button cluster + textarea + footer chips. The renderer
   in workflow.html populates #propsBody; we wrap whatever's there in
   the new design via JS injection of a header bar + footer. CSS hooks
   shipped here for the renderer to use. */
.cg-prompt-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 0 12px;
}
.cg-prompt-header .cg-prompt-spacer { flex: 1; }
.cg-prompt-icon-btn {
  width: 32px;
  height: 32px;
  border-radius: 10px;
  background: var(--prompt-icon-btn-bg, rgba(255,255,255,0.05));
  border: none;
  color: var(--text, #f0f2ff);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.1s;
  font-family: inherit;
}
.cg-prompt-icon-btn:hover { background: var(--prompt-icon-btn-hover, rgba(255,255,255,0.1)); }
.cg-prompt-icon-btn svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; }

.cg-prompt-input {
  width: 100%;
  background: transparent !important;
  border: none !important;
  padding: 8px 0 !important;
  font-size: 15px !important;
  color: var(--text, #f0f2ff) !important;
  font-family: inherit;
  resize: none;
  min-height: 48px;
  max-height: 200px;
  outline: none;
}
.cg-prompt-input::placeholder { color: var(--text-muted, var(--text3, rgba(240,242,255,0.45))); }

.cg-prompt-footer {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-top: 8px;
}
.cg-prompt-footer-left, .cg-prompt-footer-right {
  display: flex;
  align-items: center;
  gap: 6px;
}
.cg-prompt-footer-left { flex: 1; min-width: 0; }
.cg-vsep {
  width: 1px;
  height: 16px;
  background: var(--border, var(--line, rgba(255,255,255,0.1)));
  margin: 0 4px;
}
.cg-model-badge,
.cg-format-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 8px;
  background: transparent;
  border: none;
  color: var(--text, #f0f2ff);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  font-family: inherit;
  transition: background 0.1s;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cg-model-badge:hover,
.cg-format-chip:hover { background: var(--prompt-icon-btn-bg, rgba(255,255,255,0.05)); }
.cg-model-badge svg,
.cg-format-chip svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 2; flex-shrink: 0; }

.cg-count-pill {
  padding: 4px 10px;
  border-radius: 9999px;
  background: var(--prompt-icon-btn-bg, rgba(255,255,255,0.05));
  border: none;
  color: var(--text, #f0f2ff);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  font-family: inherit;
  font-variant-numeric: tabular-nums;
}
.cg-count-pill:hover { background: var(--prompt-icon-btn-hover, rgba(255,255,255,0.1)); }

/* Combined credit + send pill. The white circle on the right IS the
   send button. Click triggers executeNode for the selected node. */
.cg-send-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 4px 4px 12px;
  border-radius: 9999px;
  background: var(--send-pill-bg, rgba(255,255,255,0.08));
  border: none;
  font-family: inherit;
  cursor: default;
  color: var(--text, #f0f2ff);
}
.cg-send-pill .cg-credit-count {
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.cg-send-pill .cg-coin {
  width: 14px; height: 14px;
  color: var(--accent, var(--violet, #7c6dfa));
}
.cg-send-pill .cg-send-circle {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--accent-text, #ffffff);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: transform 0.12s, box-shadow 0.12s;
}
.cg-send-pill .cg-send-circle:hover {
  transform: scale(1.05);
  box-shadow: 0 4px 12px rgba(255, 255, 255, 0.2);
}
.cg-send-pill .cg-send-circle svg {
  width: 14px; height: 14px;
  stroke: var(--bg, #05060f);
  stroke-width: 2.5;
  fill: none;
}
.cg-send-pill.running .cg-send-circle {
  animation: cgSendSpin 0.8s linear infinite;
}
@keyframes cgSendSpin { to { transform: rotate(360deg); } }
.cg-send-pill.insufficient {
  animation: cgSendShake 0.4s;
}
@keyframes cgSendShake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-4px); }
  75% { transform: translateX(4px); }
}

/* On a successful generation, briefly pulse the card border in accent. */
.workflow-node.just-completed {
  animation: cgNodeCompleted 1s ease-out;
}
@keyframes cgNodeCompleted {
  0%   { box-shadow: 0 0 0 0 var(--accent, #7c6dfa); }
  50%  { box-shadow: 0 0 0 6px rgba(124, 109, 250, 0.3); }
  100% { box-shadow: 0 0 0 0 rgba(124, 109, 250, 0); }
}

/* ── Error-code chip on a failed-node preview ───────────────────
   Surfaces the CG-XXXXXXXX code returned by the API + workflow
   webhook. Clicking copies the code to the clipboard (handler in
   workflow.html). Uses the existing failed-state palette. */
.cg-node-error {
  padding: 16px;
  text-align: center;
  color: var(--err, #ff6b81);
}
.cg-node-error-msg {
  margin-bottom: 8px;
}
.cg-node-error-code {
  display: inline-block;
  padding: 4px 10px;
  margin: 6px 0 10px;
  background: var(--field-bg, rgba(8, 10, 28, 0.85));
  border: 1px solid var(--line, rgba(255,255,255,0.1));
  border-radius: 6px;
  font-family: 'JetBrains Mono', 'SF Mono', Menlo, monospace;
  font-size: 12px;
  color: var(--mute, rgba(240,242,255,0.7));
  cursor: pointer;
  letter-spacing: 0.5px;
  transition: background 0.15s, color 0.15s;
}
.cg-node-error-code:hover {
  background: var(--glass-strong, rgba(255,255,255,0.07));
  color: var(--text, #f0f2ff);
}
.cg-node-error-code:active {
  color: var(--violet, #7c6dfa);
}
