/* styles.css - Modern Design System */

/* Material Symbols Rounded (for Blueprint nodes) */
.material-symbols-rounded {
  font-family: 'Material Symbols Rounded';
  font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
  font-size: 24px;
  display: inline-block;
  vertical-align: middle;
}

body {
  margin: 0;
  overflow: hidden;
  background: var(--colors-background-primary, #0d1117);
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  color: var(--colors-text-primary, #f0f6fc);
  user-select: none;
  -webkit-user-select: none;
  font-weight: 400;
  line-height: 1.5;
  /* M3 R8 -- the single app-shell layout owner: top-bar / workspace-bar / diagram compose as three
     grid rows in normal document flow (auto / auto / 1fr), sized by their own REAL rendered height --
     not by a hand-authored pixel guess. Every other body child (context menus, dialogs, the settings/
     share/play panels, the debug console, the selection-instructions hint) is `position:fixed` or
     `position:absolute` and is therefore NOT a grid item (out-of-flow elements never participate in
     grid layout), so making body a grid container does not disturb any of them. 100vh is the universal
     fallback; 100dvh (where supported) additionally accounts for mobile browser chrome that 100vh does
     not, with no JS involved. */
  display: grid;
  grid-template-rows: auto auto 1fr;
  grid-template-columns: 1fr;
  height: 100vh;
  height: 100dvh;
}

/* Top Bar - Modern glassmorphism style */
.top-bar {
  display: grid;
  align-items: center;
  grid-template-columns: auto 1fr auto;
  /* M3 R8: explicit `min-width: 0` on a grid item is standard defensive practice -- without it, a grid
     item's automatic minimum size can override its own explicit `width` under `overflow: visible`
     (see the mobile-portrait media query below for the concrete case this round found and fixed). The
     base rule's own `overflow-x: auto` already suppresses this, but stating it explicitly here keeps
     the guarantee independent of any future overflow-value change at any breakpoint. */
  min-width: 0;
  background: rgba(13, 17, 23, 1);
  backdrop-filter: blur(10px);
  border-bottom: none;
  padding: 8px 16px;
  /* M3 R8: normal document flow, first grid row of the body shell (see body{} above) -- occupies its
     real computed (content-driven) height instead of `position:fixed;top:0`. z-index still applies:
     grid items (unlike normal static-position elements) participate in z-index stacking even without
     an explicit `position` value, so this still stacks above `#diagram` if anything were to overlap. */
  width: calc(100% - 32px);
  z-index: 1000;
  box-shadow: none;
  overflow-x: auto;
  overflow-y: hidden;
  white-space: nowrap;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE and Edge */
  column-gap: 24px;
}

.top-bar::-webkit-scrollbar {
  display: none; /* Chrome, Safari, Opera */
}

.top-bar .title {
  text-align: left;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.top-bar h2 {
  margin: 0;
  font-family: 'Permanent Marker', cursive;
  font-size: 1.6em;
  font-weight: 400;
  background: linear-gradient(135deg, #58a6ff 0%, #a855f7 50%, #06b6d4 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  letter-spacing: 1px;
  text-shadow: 0 0 20px rgba(88, 166, 255, 0.3);
}

.top-bar small {
  font-family: 'Permanent Marker', cursive;
  font-size: 0.7em;
  color: #06ffa5;
  text-shadow: 0 0 8px rgba(6, 255, 165, 0.6);
  letter-spacing: 1px;
  transform: rotate(0.5deg);
  display: inline-block;
  align-self: flex-end;
  position: relative;
  bottom: 0.6em;
  margin-left: 5px;
}

#project-name {
  position: static;
  transform: none;
  justify-self: stretch;
  width: min(100%, 38vw);
  max-width: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  pointer-events: none;
  color: #8b949e;
  font-size: 14px;
  font-weight: 500;
  min-width: 0;
}

.buttons {
  display: flex;
  gap: 18px;
  flex-shrink: 0;
  justify-self: end;
}

/* Pulsanti della top bar - Modern flat design */
.icon-button {
  background: transparent;
  border: none;
  padding: 8px;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.3s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  overflow: hidden;
}

.icon-button:hover {
  background: rgba(88, 166, 255, 0.1);
  color: #58a6ff;
  transform: scale(1.05);
}

.icon-button:active {
  transform: translateY(0);
}

/* M2 final fixes §5: a disabled icon-button (e.g. the future "Export Runtime…" action) must read as
 * visibly non-actionable -- dimmed, not-allowed cursor, and no hover affordance. */
.icon-button:disabled,
.icon-button[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: auto;
}

.icon-button:disabled:hover,
.icon-button[disabled]:hover {
  background: transparent;
  color: inherit;
  transform: none;
}

.icon-button:disabled:hover svg,
.icon-button[disabled]:hover svg {
  stroke: #8b949e;
  transform: none;
}

.icon-button svg {
  width: 18px;
  height: 18px;
  stroke: #8b949e;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: all 0.2s ease;
}

.icon-button:hover svg {
  stroke: #58a6ff;
  transform: scale(1.1);
}

/* Node Help Panel */
.node-help-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.35);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
  z-index: 1190;
}

.node-help-backdrop.show {
  opacity: 1;
  pointer-events: auto;
}

.node-help-panel {
  position: fixed;
  top: 60px;
  right: 0;
  width: 360px;
  max-width: 100vw;
  height: calc(100% - 80px);
  box-sizing: border-box;
  background: rgba(13, 17, 23, 0.96);
  border-left: 1px solid rgba(88, 166, 255, 0.25);
  box-shadow: -12px 0 28px rgba(0, 0, 0, 0.45);
  border-radius: 14px 0 0 14px;
  padding: 16px 18px;
  transition: transform 0.25s ease, opacity 0.25s ease;
  z-index: 1200;
  display: flex;
  flex-direction: column;
  gap: 12px;
  transform: translateX(130%);
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
}

.node-help-panel.show {
  transform: translateX(0);
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
}

.node-help-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.node-help-brand {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.node-help-logo {
  font-family: 'Permanent Marker', cursive;
  font-size: 18px;
  background: linear-gradient(135deg, #58a6ff 0%, #a855f7 50%, #06b6d4 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.node-help-node {
  font-weight: 600;
  font-size: 15px;
  color: #f0f6fc;
}

.node-help-close {
  background: transparent;
  border: none;
  color: #8b949e;
  font-size: 22px;
  cursor: pointer;
}

.node-help-media {
  display: flex;
  align-items: center;
  gap: 12px;
}

#nodeHelpIcon {
  width: 22px;
  height: 22px;
  fill: currentColor;
  stroke: none;
  color: #58a6ff;
}

#nodeHelpImage {
  width: 100%;
  max-height: 140px;
  object-fit: cover;
  border-radius: 10px;
  border: 1px solid rgba(88, 166, 255, 0.15);
  display: none;
}

.node-help-body {
  overflow-y: auto;
  padding-right: 6px;
}

.node-help-section h4 {
  margin: 8px 0 4px;
  font-size: 11px;
  color: #9da7b3;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.node-help-section p,
.node-help-section ul {
  margin: 0 0 10px;
  font-size: 13px;
  color: #e6edf3;
}

.node-help-section ul {
  padding-left: 18px;
}

#nodeHelpOptions li.node-help-group-title {
  list-style: none;
  margin-top: 8px;
  margin-left: -18px;
  color: #c9d1d9;
}

#nodeHelpOptions li.node-help-divider {
  list-style: none;
  margin: 6px 0;
  margin-left: -18px;
  height: 1px;
  background: rgba(139, 148, 158, 0.3);
}

/* Play Panel */
.play-panel-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.35);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
  z-index: 1180;
}

.play-panel-backdrop.show {
  opacity: 1;
  pointer-events: auto;
}

.play-panel {
  position: fixed;
  top: 60px;
  right: 0;
  width: 400px;
  max-width: 100vw;
  height: calc(100% - 80px);
  box-sizing: border-box;
  background: rgba(13, 17, 23, 0.96);
  border-left: 1px solid rgba(88, 166, 255, 0.25);
  box-shadow: -12px 0 28px rgba(0, 0, 0, 0.45);
  border-radius: 14px 0 0 14px;
  padding: 16px 18px;
  transition: transform 0.25s ease, opacity 0.25s ease;
  z-index: 1190;
  display: flex;
  flex-direction: column;
  gap: 12px;
  transform: translateX(130%);
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  --play-current-color: #58a6ff;
  --play-current-color-soft: rgba(88, 166, 255, 0.16);
  --play-current-color-soft-strong: rgba(88, 166, 255, 0.3);
  --play-current-text-color: #ffffff;
}

.play-panel.show {
  transform: translateX(0);
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
}

.play-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.play-panel-brand {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.play-panel-logo {
  font-family: 'Permanent Marker', cursive;
  font-size: 18px;
  background: linear-gradient(135deg, #58a6ff 0%, #a855f7 50%, #06b6d4 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.play-panel-title {
  font-weight: 600;
  font-size: 15px;
  color: #f0f6fc;
}

.play-panel-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.play-panel-icon-btn {
  background: rgba(88, 166, 255, 0.15);
  border: 1px solid rgba(88, 166, 255, 0.4);
  color: #58a6ff;
  border-radius: 8px;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.play-panel-icon-btn .material-symbols-outlined {
  font-size: 18px;
  color: currentColor;
}

.play-panel-icon-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.play-panel-close {
  background: transparent;
  border: none;
  color: #8b949e;
  font-size: 22px;
  cursor: pointer;
}

.play-panel-status {
  font-size: 12px;
  color: #9da7b3;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--play-current-color-soft);
}

.play-panel-content {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-right: 6px;
}

.play-panel-section-title {
  font-size: 11px;
  color: #9da7b3;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.play-panel-node-text {
  font-size: 15px;
  color: #f1f5f9;
  line-height: 1.5;
}

.play-panel-current-line {
  background: linear-gradient(120deg, var(--play-current-color-soft), rgba(0, 0, 0, 0));
  border: 1px solid var(--play-current-color-soft-strong);
  border-radius: 10px;
  padding: 10px 12px;
}

.play-panel-transcript {
  display: flex;
  flex-direction: column;
  gap: 6px;
  border-bottom: 1px solid rgba(148, 163, 184, 0.18);
  padding-bottom: 8px;
}

.play-panel-transcript-item {
  border-left: 2px solid rgba(148, 163, 184, 0.45);
  padding: 4px 0 4px 10px;
  font-size: 14px;
  line-height: 1.45;
  color: #dbe6f3;
}

.play-panel-transcript-item.is-choice {
  border-left-color: rgba(34, 211, 238, 0.55);
  color: #f8fafc;
}

.play-panel-transcript-item.is-action {
  border-left-color: rgba(16, 185, 129, 0.55);
}

.play-panel-transcript-item.is-system {
  border-left-color: rgba(148, 163, 184, 0.45);
  color: #94a3b8;
  font-style: italic;
}

.play-panel-action {
  background: var(--play-current-color-soft);
  border: 1px solid var(--play-current-color-soft-strong);
  color: var(--play-current-text-color);
  border-radius: 10px;
  padding: 10px 12px;
  font-size: 13px;
  cursor: pointer;
  text-align: left;
}

.play-panel-action.selected {
  background: var(--play-current-color-soft-strong);
  border-color: var(--play-current-color);
}

.play-panel-action[data-action="true"] {
  border-color: var(--action-solid-color, rgba(88, 166, 255, 0.8));
  background: var(--action-solid-color, rgba(88, 166, 255, 0.35));
  color: var(--action-text-color, #ffffff);
  font-weight: 600;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
}

.play-panel-action[data-action="true"]:hover {
  filter: brightness(1.06);
}

.play-panel-action[data-action="true"]:active {
  border-color: var(--action-outline-color, rgba(88, 166, 255, 0.8));
  box-shadow: 0 0 0 2px var(--action-outline-color, rgba(88, 166, 255, 0.8));
}

.play-panel-action.action-pressed {
  border-color: var(--action-outline-color, rgba(88, 166, 255, 0.8));
  box-shadow: 0 0 0 2px var(--action-outline-color, rgba(88, 166, 255, 0.8));
}

.play-panel-input {
  display: flex;
  flex-direction: column;
  gap: 6px;
  background: rgba(15, 23, 42, 0.5);
  border: 1px solid rgba(88, 166, 255, 0.2);
  border-radius: 10px;
  padding: 10px 12px;
}

.play-panel-input label {
  font-size: 12px;
  color: #c9d1d9;
}

.play-panel-input input,
.play-panel-input select {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid rgba(16, 185, 129, 0.4);
  border-radius: 6px;
  padding: 6px 8px;
  font-size: 12px;
  color: #111827;
}

.play-variables-panel {
  display: none;
  border-top: 1px solid rgba(88, 166, 255, 0.15);
  padding-top: 10px;
  max-height: 200px;
  overflow-y: auto;
  font-size: 12px;
  color: #e6edf3;
}

.play-variables-panel.show {
  display: block;
}

.play-variables-group {
  margin-bottom: 10px;
}

.play-variables-group h4 {
  margin: 0 0 6px;
  font-size: 11px;
  color: #9da7b3;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.play-variable-item {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  padding: 4px 0;
  border-bottom: 1px solid rgba(88, 166, 255, 0.08);
}

.play-variable-name {
  color: #58a6ff;
  font-weight: 500;
}

.play-variable-value {
  color: #e6edf3;
  text-align: right;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.play-variable-type {
  color: #9da7b3;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.icon-button:focus {
  outline: none;
}

/* Workspace Bar - Tab system for different workspaces */
.workspace-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* M3 R8: same defensive `min-width: 0` as `.top-bar` above. */
  min-width: 0;
  background: rgba(22, 27, 34, 0.95);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid rgba(88, 166, 255, 0.1);
  padding: 4px 16px;
  /* M3 R8: normal document flow, second grid row of the body shell -- follows the top-bar at its real
     rendered height instead of a hard-coded `top: 56px` guess (see body{}/.top-bar{} above). */
  width: calc(100% - 32px);
  z-index: 999;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
  overflow-x: auto;
  overflow-y: hidden;
  white-space: nowrap;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.workspace-bar::-webkit-scrollbar {
  display: none;
}

.workspace-tabs {
  display: flex;
  gap: 4px;
  align-items: center;
  /* M3 R9: single ownership model for workspace-tab overflow at every breakpoint, replacing the old
     mobile-portrait-only `flex-wrap:wrap` patch that let tab pressure grow into up to 5 vertical rows
     (stealing canvas space -- see R8's own baseline evidence and the R9 handoff SS B/E). `.workspace-tabs`
     now shrinks to whatever space `.workspace-actions` (flex-shrink:0, below) doesn't need, then scrolls
     internally -- the same CSS Grid/flex "automatic minimum size" containment fix R8 already proved for
     `.top-bar`/`.workspace-bar` (min-width:0 + a non-`visible` overflow-x), applied one level deeper here
     since `.workspace-tabs` is itself the item that needs to shrink-then-scroll rather than force its
     container wider. */
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  white-space: nowrap;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.workspace-tabs::-webkit-scrollbar {
  display: none;
}

.workspace-tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 6px 16px;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.3s ease;
  border: 1px solid transparent;
  background: transparent;
  position: relative;
}

.workspace-tab:hover {
  background: rgba(88, 166, 255, 0.1);
  border-color: rgba(88, 166, 255, 0.3);
}

.workspace-tab.scene-workspace:hover {
  background: rgba(139, 92, 246, 0.12);
  border-color: rgba(139, 92, 246, 0.35);
}

.workspace-tab.active {
  background: rgba(88, 166, 255, 0.15);
  border-color: #58a6ff;
  color: #58a6ff;
}

.workspace-tab.scene-workspace.active {
  background: rgba(139, 92, 246, 0.18);
  border-color: #8b5cf6;
  color: #8b5cf6;
}

.workspace-tab.active .workspace-name {
  color: #58a6ff;
}

.workspace-tab.scene-workspace.active .workspace-name {
  color: #8b5cf6;
}

.workspace-icon {
  font-size: 16px;
  opacity: 0.8;
}

.workspace-tab.active .workspace-icon {
  opacity: 1;
}

.workspace-name {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.5px;
  color: #8b949e;
  text-align: center;
  width: 100%;
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 150px;
}

.workspace-actions {
  display: flex;
  gap: 8px;
  /* M3 R9: guaranteed non-shrinking -- the workspace add/create/trailing controls must never be
     squeezed or scrolled away by `.workspace-tabs`' own internal scroll above (R9 SS E invariant 9). */
  flex-shrink: 0;
}

.workspace-variables-btn,
.workspace-play-btn,
.workspace-add-btn {
  background: transparent;
  border: 1px solid #30363d;
  padding: 6px;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.3s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  min-width: 32px;
  min-height: 32px;
  outline: none !important;
  box-sizing: border-box;
  margin: 0;
  flex-shrink: 0;
}

.workspace-variables-btn:focus,
.workspace-play-btn:focus,
.workspace-add-btn:focus {
  outline: none !important;
  border-color: #30363d;
}

.workspace-variables-btn:active,
.workspace-play-btn:active,
.workspace-add-btn:active {
  outline: none !important;
  border-color: #30363d;
}

.workspace-variables-btn:hover,
.workspace-play-btn:hover,
.workspace-add-btn:hover {
  background: rgba(88, 166, 255, 0.1);
  border-color: #58a6ff;
  transform: scale(1.05);
}

.workspace-variables-btn svg,
.workspace-play-btn svg,
.workspace-add-btn svg {
  width: 16px;
  height: 16px;
}

.workspace-variables-btn svg path,
.workspace-play-btn svg path,
.workspace-add-btn svg path {
  stroke: #8b949e;
  fill: #8b949e;
  stroke-width: 2;
}

.workspace-variables-btn svg text,
.workspace-play-btn svg text,
.workspace-add-btn svg text {
  fill: #8b949e;
  stroke: none;
}

.workspace-variables-btn:hover svg path,
.workspace-play-btn:hover svg path,
.workspace-add-btn:hover svg path {
  stroke: #58a6ff;
  fill: #58a6ff;
}

.workspace-variables-btn:hover svg text,
.workspace-play-btn:hover svg text,
.workspace-add-btn:hover svg text {
  fill: #58a6ff;
}

/* Diagramma - Infinite pannable and zoomable grid */
#diagram {
  width: 100%;
  /* M3 R8: the third (1fr) grid row of the body shell -- fills whatever space remains after the
     top-bar's and workspace-bar's own real rendered heights, instead of a hard-coded
     `margin-top`/`height: calc(100vh - Npx)` pair that assumed a fixed 56px+36px shell. `min-height: 0`
     is required for a grid row to actually size to `1fr` rather than to its content's intrinsic size
     (an SVG's default intrinsic height can otherwise force the row -- and the whole grid -- taller than
     the viewport). */
  height: 100%;
  min-height: 0;
  background: var(--colors-canvas-background, #0d1117);
  touch-action: none;
  border-radius: 0;
  position: relative;
}

/* M3 Physical P5: the HTML overlay that hosts every live Blueprint node -- see
 * docs/handoffs/CLAUDE_TO_CHATGPT_M3_PHYSICAL_P5_FOREIGNOBJECT_EXIT_HTML_OVERLAY.md.
 *
 * Sizing history: the first two approaches tried here were CSS-only (co-locating `#nodeOverlay` in
 * `#diagram`'s own grid cell via `width/height: 100%`, then via `position: absolute` grid-area sizing)
 * and both produced a WRONG rect under direct measurement -- the first computed a real 900px width but a
 * 0px height (a CSS Grid percentage-height resolution quirk: `#diagram`'s SVG anchors the shared `1fr`
 * row's real 611px used height via its own intrinsic/replaced-element sizing, but a plain sibling
 * `<div>`'s OWN `height: 100%` does not reliably resolve against that same final track size); the second
 * (an explicit `grid-row: 3 / 4; grid-column: 1 / 2; position: absolute; inset: 0;`, which per spec
 * SHOULD size an abspos grid item to its grid area) still measured a 700px height instead of 611px in
 * this engine. Rather than keep chasing CSS Grid abspos/percentage edge cases, `#nodeOverlay` is
 * `position: fixed` here with NO width/height/inset of its own -- `main.js`'s `syncNodeOverlayRect()`
 * sets `left/top/width/height` directly, in px, from `svg.getBoundingClientRect()` (called once at
 * startup and from a `ResizeObserver` on `svg`), so its rect is ALWAYS exactly `#diagram`'s measured
 * rect, by direct construction rather than by hoping a CSS layout algorithm reproduces it. This is
 * explicitly sanctioned by the P5 handoff's own design property 8 ("overlay alignment must follow the
 * real diagram rect after resize/orientation/shell-height-changes/workspace-switch/reload... ResizeObserver
 * acceptable if causally necessary") -- this is exactly that causal necessity, demonstrated by two failed
 * CSS-only attempts, not a default reached for convenience.
 *
 * `z-index` paints it above `#diagram` -- SVG stays authoritative for background/grid, cables and Group
 * regions underneath; nodes paint on top, exactly matching the pre-P5 SVG paint order (`nodesGroup` was
 * the LAST child of `#diagram`, painting above `connectionsGroup`/`groupsGroup`). `pointer-events: none`
 * on this root lets a background pointerdown/click fall straight through to the real SVG background
 * beneath it (CanvasPointerInteraction/PointerLongPressInteraction keep working entirely unmodified --
 * see the P5 handoff's gesture-ownership audit); only actual node content re-enables `pointer-events`,
 * via `.node-blueprint`'s own host element below. `overflow: visible` matches the removed foreignObject's
 * own `overflow: visible` -- a node dragged partially outside the diagram's own rect must stay visible
 * and interactive, not get clipped. `touch-action: none` matches `#diagram`'s own rule (below) -- without
 * it, confirmed via CDP touch replay to cause a real regression: a node drag's `pointermove` sequence
 * would work for one or two synthetic steps, then the browser would fire an unrequested `pointercancel`
 * (Chromium's own native-scroll takeover) once the touch had moved far enough, freezing the drag. This
 * root and `#diagram` are SIBLINGS, not ancestor/descendant, so `touch-action` is not inherited between
 * them in the ordinary CSS sense -- but its EFFECTIVE value for a gesture is the union of the target
 * element's own value and every ancestor's up to the nearest one with default behavior, so setting it
 * here, on the shared ancestor of every node/port/resize-handle in the overlay, is sufficient without
 * repeating it on `.blueprint-node` itself.
 */
#nodeOverlay {
  position: fixed;
  overflow: visible;
  pointer-events: none;
  touch-action: none;
  z-index: 1;
}

/* The single canonical world-space root: `SvgCoordinateSpace.overlayWorldTransform()` sets this
 * element's own `transform` (translate + uniform scale) to exactly mirror the SVG's current
 * viewBox/preserveAspectRatio fit, once per `updateViewBox()` call -- not per node. Every node host
 * div is then placed via plain `left`/`top` in world-space px underneath this single transform, the
 * same "world position -> HTML painted client position" contract `worldToClient()` already defines,
 * reused rather than duplicated (see that file's header comment). `transform-origin: 0 0` matches the
 * translate-then-scale math (a scale about any other origin would need a different formula). */
#nodeOverlayWorld {
  position: absolute;
  left: 0;
  top: 0;
  transform-origin: 0 0;
}

/* M4 Group G3.2 physical review-blocker correction (docs/handoffs/
 * CHATGPT_TO_CLAUDE_M4_GROUP_G3_2_PHYSICAL_REVIEW_BLOCKER_INTERACTION.md): a plain SVG overlay root,
 * structurally parallel to #nodeOverlay/#nodeOverlayWorld immediately above (same fixed-rect-synced-
 * from-JS technique -- see dist/main.js's syncNodeOverlayRect()/updateNodeOverlayTransform()), stacked
 * ONE layer above it, hosting the REAL interactive collapsed-Group chip `<g>` while collapsed -- not a
 * decorative duplicate.
 *
 * Why this replaces the prior round's approach: that round used a second, `pointer-events:none`
 * DECORATIVE guard here, leaving the real interactive chip underneath in `#diagram`'s own `groupsGroup`.
 * ChatGPT's physical review correctly found this creates a visual/interaction mismatch -- the guard
 * paints on top (so the author SEES the collapsed Group in front of a Node), but being
 * `pointer-events:none` it is skipped during hit-testing, so a click at that same point still resolves
 * to the Node underneath instead of the Group the UI visibly shows in front. The binding fix is that
 * visual foreground and interaction foreground must be the SAME element, not two independently
 * maintained ones. Since `GroupPointerInteraction.js`'s own gesture/event wiring (`attach()`,
 * `setPointerCapture`, `dblclick`, `contextmenu`) is entirely DOM-generic -- no SVG-namespace dependency
 * beyond living inside SOME `<svg>` -- the REAL chip `<g>` (still built by
 * `createGroupCollapsedChipElement()`, still wired by the exact same `groupPointerInteraction.attach()`
 * call) is simply APPENDED here instead of into `groupsGroup` while `group.collapsed`, and back into
 * `groupsGroup` the moment it expands (both branches of `createGroupElement()`, which already fully
 * rebuilds every Group's `<g>` on every `refreshGroups()` pass -- no reparenting code needed, only a
 * different append target). Zero duplication, zero drift risk: there is exactly one live DOM node per
 * Group, exactly one set of event listeners, exactly where the author's eye already says it is.
 *
 * Why a whole second `<svg>`, not a CSS z-index tweak inside `#diagram`: `#nodeOverlay` has painted
 * above the ENTIRE `#diagram` SVG since M3 Physical P5 (`z-index: 1` on a sibling of `#diagram` -- see
 * that rule's own header comment), a deliberate, hard-won cross-platform text-rendering fix this
 * correction does not revisit or reverse. Nothing inside `#diagram` can out-paint an HTML sibling
 * already in a higher stacking context; only another layer stacked above `#nodeOverlay` itself can.
 * `pointer-events: none` on this root (mirroring `#nodeOverlay`'s own root) lets empty space here fall
 * straight through to `#nodeOverlay`'s Node content beneath it; `#collapsedGroupForegroundWorld .group`
 * below explicitly re-enables normal hit-testing for the one thing actually painted here, exactly
 * mirroring how `#nodeOverlay`'s own root note documents `.node-blueprint`'s host re-enabling it for
 * Node content. The net effect is precisely the gate's own binding product rule: a point inside the
 * visible collapsed chip's own painted footprint hits the Group; the same Node's screen area OUTSIDE
 * that footprint still hits the Node, exactly as it always has. */
#collapsedGroupForeground {
  position: fixed;
  overflow: visible;
  pointer-events: none;
  touch-action: none;
  z-index: 2;
}

/* Re-enables normal hit-testing (collapse-toggle click, header drag, context menu, rename) for the one
 * thing actually painted inside this layer -- the real collapsed-Group `<g class="group">` itself --
 * without touching `#collapsedGroupForeground`'s own root `pointer-events: none` above, which is what
 * lets the surrounding empty space keep falling through to Node content underneath. */
#collapsedGroupForegroundWorld .group {
  pointer-events: auto;
}

/* Menu contestuali - Futuristic dark theme */
#contextMenu, #contextMenuConnection, #svgContextMenu {
  /* M3 R8: `#svgContextMenu` was missing from this selector list -- unlike its sibling menus, it had no
     default `position:fixed; display:none`, so it rendered as an ordinary `position:static` block
     (browser default) that only ever *looked* hidden because the OLD `position:fixed` shell's document-
     flow math happened to push it below the visible viewport. Making body a grid container (see body{}
     above) means every `position:static` direct child becomes a real grid item and is laid out WITHIN
     the visible shell -- which surfaced this as a genuine, visible regression during R8's own smoke
     testing (a full-height "Create..." menu rendered open by default on every page load). This is the
     narrowest possible fix: give it the exact same default-hidden state its sibling menus already have,
     not a dialog/menu redesign (explicitly out of R8's scope). Confirmed via a stash/pop A-B comparison
     that this menu was never intentionally visible-by-default -- the pre-R8 code simply never surfaced
     the gap. */
  position: fixed;
  display: none;
  background: rgba(13, 17, 23, 0.98);
  backdrop-filter: blur(12px);
  color: #f0f6fc;
  border: 1px solid rgba(88, 166, 255, 0.3);
  border-radius: 12px;
  z-index: 10000;
  font-size: 0.85em;
  font-weight: 500;
  box-shadow: 
    0 8px 32px rgba(0, 0, 0, 0.4),
    0 0 24px rgba(88, 166, 255, 0.1),
    inset 0 1px 0 rgba(255, 255, 255, 0.1);
  overflow: hidden;
  min-width: 180px;
}

/* M4 Group G3.1 (docs/handoffs/CHATGPT_M4_GROUP_G3_1_HEADER_COLOR_INPUT_IMPLEMENTATION_GATE.md §D):
   the "Group Color..." popover -- GroupColorPicker.js's own small floating panel. Visually in the same
   dark-glassmorphism family as `#contextMenu` above (same background/border/blur/shadow language) but
   an entirely separate element/owner, per the gate's "reuse visual patterns, not ownership" instruction
   -- it does not share a class or a DOM parent with ThemeEditor.js's `.color-input-group`. */
.group-color-picker-popover {
  position: fixed;
  z-index: 10050;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  background: rgba(13, 17, 23, 0.98);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(88, 166, 255, 0.3);
  border-radius: 10px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), 0 0 24px rgba(88, 166, 255, 0.1);
  color: #f0f6fc;
  font-family: 'Inter', sans-serif;
  font-size: 0.85em;
}

.group-color-picker-label {
  white-space: nowrap;
  margin-right: 2px;
}

.group-color-picker-input {
  width: 36px;
  height: 28px;
  padding: 0;
  border: 1px solid rgba(88, 166, 255, 0.3);
  border-radius: 6px;
  background: transparent;
  cursor: pointer;
}

.group-color-picker-default {
  padding: 6px 10px;
  border: 1px solid rgba(88, 166, 255, 0.3);
  border-radius: 6px;
  background: rgba(88, 166, 255, 0.12);
  color: #f0f6fc;
  font-family: 'Inter', sans-serif;
  font-size: 0.95em;
  cursor: pointer;
  white-space: nowrap;
}

.group-color-picker-default:hover {
  background: rgba(88, 166, 255, 0.22);
}

#contextMenu div, #contextMenuConnection div {
  padding: 12px 18px;
  cursor: pointer;
  transition: all 0.2s ease;
  border-bottom: 1px solid rgba(88, 166, 255, 0.05);
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
}

#contextMenu div:last-child, #contextMenuConnection div:last-child {
  border-bottom: none;
}

#contextMenu div:hover, #contextMenuConnection div:hover {
  background: 
    linear-gradient(90deg, rgba(88, 166, 255, 0.15) 0%, rgba(124, 58, 237, 0.15) 100%);
  color: #58a6ff;
  transform: translateX(4px);
}

#contextMenu div:active, #contextMenuConnection div:active {
  background: rgba(88, 166, 255, 0.2);
  transform: translateX(2px);
}

/* Aggiungi effetti glow per alcuni elementi speciali */
#contextMenu div[data-type="delete"]:hover,
#contextMenuConnection div[data-type="delete"]:hover {
  background: linear-gradient(90deg, rgba(248, 113, 113, 0.15) 0%, rgba(239, 68, 68, 0.15) 100%);
  color: #f87171;
}

/* Modali - Futuristic glassmorphism with semi-transparent background */
.modal {
  display: none;
  position: fixed;
  z-index: 10001;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: auto;
  background: rgba(13, 17, 23, 0.6);
  backdrop-filter: blur(8px);
}

.modal-content {
  background: 
    linear-gradient(135deg, rgba(22, 27, 34, 0.98) 0%, rgba(13, 17, 23, 0.98) 100%);
  backdrop-filter: blur(20px);
  margin: 8% auto;
  padding: 28px;
  border: 1px solid rgba(88, 166, 255, 0.3);
  border-radius: 20px;
  width: 90%;
  max-width: 520px;
  text-align: center;
  color: #f0f6fc;
  position: relative;
  box-shadow: 
    0 20px 80px rgba(0, 0, 0, 0.5),
    0 0 40px rgba(88, 166, 255, 0.1),
    inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

.modal-content::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: 
    radial-gradient(circle at 20% 20%, rgba(88, 166, 255, 0.1) 0%, transparent 50%),
    radial-gradient(circle at 80% 80%, rgba(124, 58, 237, 0.1) 0%, transparent 50%);
  border-radius: 20px;
  pointer-events: none;
}

.close {
  color: #8b949e;
  position: absolute;
  top: 16px;
  right: 20px;
  font-size: 20px;
  font-weight: bold;
  cursor: pointer;
  transition: all 0.2s ease;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  background: rgba(88, 166, 255, 0.1);
  border: 1px solid rgba(88, 166, 255, 0.2);
}

.close:hover,
.close:focus {
  color: #58a6ff;
  background: rgba(88, 166, 255, 0.2);
  border-color: #58a6ff;
  transform: scale(1.1);
  text-decoration: none;
}

/* Debug Console - Futuristic terminal style */
#debugConsole {
  position: fixed;
  bottom: 20px;
  right: 20px;
  width: 340px;
  max-height: 280px;
  box-sizing: border-box;
  background:
    linear-gradient(135deg, rgba(13, 17, 23, 0.98) 0%, rgba(22, 27, 34, 0.95) 100%);
  backdrop-filter: blur(16px);
  border: 1px solid rgba(88, 166, 255, 0.3);
  border-radius: 16px;
  font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Fira Code', monospace;
  font-size: 11px;
  z-index: 999;
  overflow: hidden;
  box-shadow:
    0 8px 40px rgba(0, 0, 0, 0.4),
    0 0 20px rgba(88, 166, 255, 0.1),
    inset 0 1px 0 rgba(255, 255, 255, 0.1);
  transition: all 0.3s ease;
}

#debugConsole.collapsed {
  width: 120px;
  height: 40px;
  max-height: 40px;
}

#debugConsole.collapsed #debugContent {
  display: none;
}

#debugConsole::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(88, 166, 255, 0.5), transparent);
}

#toggleDebug {
  position: absolute;
  top: 10px;
  right: 12px;
  background: rgba(88, 166, 255, 0.15);
  border: 1px solid rgba(88, 166, 255, 0.4);
  color: #58a6ff;
  padding: 4px 10px;
  border-radius: 8px;
  font-size: 9px;
  cursor: pointer;
  transition: all 0.3s ease;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  z-index: 1000;
}

#debugConsole.collapsed #toggleDebug {
  position: static;
  margin: 8px;
  width: calc(100% - 16px);
}

/* Icon hidden by default (shown only on mobile collapsed) */
.debug-icon {
  display: none;
}

#toggleDebug:hover {
  background: rgba(88, 166, 255, 0.25);
  border-color: #58a6ff;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(88, 166, 255, 0.2);
}

#debugContent {
  padding: 36px 16px 16px 16px;
  max-height: 240px;
  overflow-y: auto;
  line-height: 1.5;
}

.debug-section {
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid rgba(88, 166, 255, 0.2);
}

.debug-section:last-child {
  border-bottom: none;
  margin-bottom: 0;
}

.debug-section h4 {
  margin: 0 0 8px 0;
  color: #58a6ff;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.debug-data {
  color: #c9d1d9;
  font-size: 10px;
  line-height: 1.6;
  padding: 8px;
  background: rgba(0, 0, 0, 0.2);
  border-radius: 6px;
  max-height: 80px;
  overflow-y: auto;
}

.debug-data::-webkit-scrollbar {
  width: 4px;
}

.debug-data::-webkit-scrollbar-track {
  background: rgba(0, 0, 0, 0.2);
  border-radius: 2px;
}

.debug-data::-webkit-scrollbar-thumb {
  background: rgba(88, 166, 255, 0.3);
  border-radius: 2px;
}

.debug-data::-webkit-scrollbar-thumb:hover {
  background: rgba(88, 166, 255, 0.5);
}

#debugContent::-webkit-scrollbar {
  width: 4px;
}

#debugContent::-webkit-scrollbar-track {
  background: rgba(88, 166, 255, 0.1);
  border-radius: 2px;
}

#debugContent::-webkit-scrollbar-thumb {
  background: rgba(88, 166, 255, 0.3);
  border-radius: 2px;
}

#debugContent p {
  margin: 0 0 6px 0;
  padding: 3px 0 3px 12px;
  border-left: 2px solid #58a6ff;
  font-size: 10px;
  position: relative;
  transition: all 0.2s ease;
}

#debugContent p::before {
  content: '▶';
  position: absolute;
  left: -8px;
  top: 3px;
  font-size: 8px;
  color: #58a6ff;
}

#debugContent p:nth-child(even) {
  border-left-color: #7c3aed;
}

#debugContent p:nth-child(even)::before {
  color: #7c3aed;
}

#debugContent p:nth-child(3n) {
  border-left-color: #059669;
}

#debugContent p:nth-child(3n)::before {
  color: #059669;
}

#debugContent p:hover {
  background: rgba(88, 166, 255, 0.05);
  transform: translateX(2px);
}

/* Render Modal */
#renderModal img {
  max-width: 100%;
  height: auto;
}

/* Connettori: forziamo dimensioni uguali per input e output */
.input-connector,
.output-connector {
  r: 5px;
}

.output-connector {
  fill: black !important;
}

.input-connector {
  fill: white !important;
}

/* Cursore a manina per nodi, gruppi e linee */
.node, .group, #connectionsGroup line {
  cursor: pointer;
}

/* M4 Group G2: the canonical Group resize handle (bottom-right corner). Diagonal resize cursor,
   consistent with the node resize handle's own `nwse-resize` (blueprint-nodes.css's `.resize-handle`). */
.group-resize-handle {
  cursor: nwse-resize;
}

/* M4 Group G3.1 (docs/handoffs/CHATGPT_M4_GROUP_G3_1_HEADER_COLOR_INPUT_IMPLEMENTATION_GATE.md §B):
   the header/ribbon is the primary Group drag surface -- same "grab" affordance as the Group body/
   outline above, not a distinct cursor that would visually suggest a different kind of control. */
.group-header-ribbon, .group-header-title {
  cursor: pointer;
}

/* M4 Group G3.2: the collapse/expand chevron is a discrete click/tap target, explicit rule rather than
   relying on inheritance from `.group`/`.group-header-ribbon` above so it stays correct independent of
   any future change to those. */
.group-collapse-toggle {
  cursor: pointer;
}

/* M4 Group G3.1.1 (docs/handoffs/CHATGPT_M4_GROUP_G3_1_1_PHYSICAL_UX_POLISH_IMPLEMENTATION_GATE.md):
   the trailing "..." overflow/menu affix and its dedicated `context-menu`-cursor rule are retired --
   the Group context menu is reachable via right-click/long-press only, with no replacement affix. */

/* M4 Group G3.1.2: the Group header's inline double-click/double-tap rename editor (and this
   `.group-header-rename-input` rule that styled its `<input>`) is retired -- header rename now opens
   the same Rename dialog the context-menu command uses (main.js's requestGroupRename()). See
   docs/handoffs/CHATGPT_M4_GROUP_G3_1_2_PHYSICAL_RENAME_RESIZE_LABEL_GATE.md §A. */

/* M4 Group G3.1.1: the resize handle's glyph is the SAME Material Symbols Rounded icon Node's own
   `.resize-handle::before` uses (blueprint-nodes.css) -- this rule only prevents accidental text
   selection/dragging of the glyph, matching that rule's own `user-select: none`. */
.group-resize-glyph {
  user-select: none;
}

/* Dropdown menu for add node button */
.dropdown {
  position: relative;
  display: inline-block;
}

.dropdown-content {
  display: none;
  position: absolute;
  background: rgba(22, 27, 34, 0.95);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(88, 166, 255, 0.2);
  border-radius: 8px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  z-index: 1001;
  min-width: 180px;
  top: 100%;
  right: 0;
  padding: 8px 0;
}

.dropdown-content.show {
  display: block;
  animation: dropdownFadeIn 0.2s ease-out;
}

@keyframes dropdownFadeIn {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.dropdown-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  color: var(--colors-text-primary, #f0f6fc);
  cursor: pointer;
  transition: all 0.2s ease;
  font-size: 14px;
  border: none;
  background: none;
  width: 100%;
  text-align: left;
}

.dropdown-item:hover {
  background: rgba(88, 166, 255, 0.1);
  color: #58a6ff;
}

.dropdown-item svg {
  width: 16px;
  height: 16px;
  opacity: 0.8;
}

.dropdown-item:hover svg {
  opacity: 1;
}

/* Links styling - fucsia/green without underline */
a {
  color: #e879f9; /* Fucsia bright */
  text-decoration: none;
  transition: color 0.2s ease, text-shadow 0.2s ease;
  font-weight: 500;
}

a:hover {
  color: #22d3ee; /* Verde acqua on hover */
  text-shadow: 0 0 8px rgba(34, 211, 238, 0.4);
}

a:visited {
  color: #c084fc; /* Viola più tenue per i visited */
}

/* Enhanced context menus with proper readability */
.context-menu {
  background: rgba(22, 27, 34, 0.95);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(88, 166, 255, 0.2);
  border-radius: 8px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  color: #f0f6fc;
  font-family: 'Inter', sans-serif;
}

/* M4 SDU post-deploy visual-contract gate (docs/handoffs/CHATGPT_TO_CLAUDE_M4_SDU_POST_DEPLOY_
   PHYSICAL_KO_CSP_ICONS_CONTEXT_MENU_VISUAL_CONTRACT_GATE.md, requirement D), corrected per
   `docs/handoffs/CHATGPT_TO_CLAUDE_M4_SDU_CSP_VISUAL_CONTRACT_CORRECTION_GATE.md` §B: root menus
   previously had NO scrollbar styling at all -- `ContextMenu.js`'s `applyThemeToMenus()` sets
   `overflow-y: auto` but nothing here ever styled the resulting scrollbar, so browsers painted their
   native gray one (the physical defect Antonio reported). The color family is derived from the
   workspace-bar/menu's own dark background (`rgba(22, 27, 34, ...)`) with the app's existing blue
   accent for the thumb -- not fuchsia. ONE shared token set below (custom properties) plus ONE
   combined selector list is the single source of truth for BOTH `.context-menu` and `.context-submenu`
   scrollbars, so the two tracks/thumbs cannot silently drift apart again the way the submenu's own
   `rgba(255, 255, 255, 0.06)` track previously did (blocked by the correction gate's review).
   `#svgContextMenu`'s own border stays fuchsia untouched -- only the scrollbar family is unified here,
   not the whole menu palette. */
.context-menu.force-scrollbar,
.context-submenu.force-scrollbar {
  --menu-scrollbar-track: rgba(22, 27, 34, 0.6);
  --menu-scrollbar-thumb: rgba(88, 166, 255, 0.45);
  --menu-scrollbar-thumb-hover: rgba(88, 166, 255, 0.65);
  overflow-y: scroll !important;
  scrollbar-width: thin;
  scrollbar-color: var(--menu-scrollbar-thumb) var(--menu-scrollbar-track);
}

.context-menu.force-scrollbar::-webkit-scrollbar,
.context-submenu.force-scrollbar::-webkit-scrollbar {
  width: 10px;
}

.context-menu.force-scrollbar::-webkit-scrollbar-track,
.context-submenu.force-scrollbar::-webkit-scrollbar-track {
  background: var(--menu-scrollbar-track);
  border-radius: 8px;
}

.context-menu.force-scrollbar::-webkit-scrollbar-thumb,
.context-submenu.force-scrollbar::-webkit-scrollbar-thumb {
  background: var(--menu-scrollbar-thumb);
  border-radius: 8px;
}

.context-menu.force-scrollbar::-webkit-scrollbar-thumb:hover,
.context-submenu.force-scrollbar::-webkit-scrollbar-thumb:hover {
  background: var(--menu-scrollbar-thumb-hover);
}

.context-menu div {
  font-family: 'Inter', sans-serif;
  font-weight: 400;
  color: #f0f6fc;
  padding: 8px 16px;
  cursor: pointer;
  transition: all 0.2s ease;
  font-size: 14px;
  display: flex;
  align-items: center;
  gap: 12px;
}

.context-menu div svg {
  width: 16px;
  height: 16px;
  opacity: 0.8;
  flex-shrink: 0;
}

.context-menu div .material-symbols-rounded {
  font-size: 16px;
  opacity: 0.8;
  width: 16px;
  height: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.context-menu div:hover {
  background: rgba(88, 166, 255, 0.1);
  color: #58a6ff;
}

.context-menu div:hover svg {
  opacity: 1;
}

.context-menu div:hover .material-symbols-rounded {
  opacity: 1;
}

/* Submenu support for nested menus */
.context-menu-item.has-submenu {
  position: relative;
}

.context-menu-item.has-submenu .submenu-arrow {
  margin-left: auto;
  width: 16px;
  height: 16px;
}

.context-submenu {
  /* M3 R10: `position`/`left`/`top`/`max-width`/`max-height` below are only the pre-JS fallback --
     `ContextMenu.js`'s `ensureSubmenuInViewport()` is the real source of truth and always sets
     `position: fixed` plus explicit viewport-coordinate `left`/`top`/`max-width`/`max-height` inline
     at open time (safe-rect-derived, not the fixed `100vh`/`100%` values here), so these values are
     only ever visible for the one CSS-only `:hover` frame before JS's own `mouseenter` handler runs. */
  display: none !important;
  position: absolute;
  left: 100%;
  top: 0;
  min-width: 180px;
  max-height: calc(100vh - 24px);
  overflow-y: auto;
  background: rgba(22, 27, 34, 0.95);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(232, 121, 249, 0.3);
  border-radius: 8px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  /* M4-P1 build-06 (docs/handoffs/CHATGPT_TO_CLAUDE_M4_P1_BUILD06_SUBMENU_TOUCH_STACKING_PLACEMENT.md):
     was `10001`, unrelated to and lower than the root menus' own inline `z-index: 12050`
     (applyThemeToMenus() in ContextMenu.js). Once R10 reparents an OPEN submenu to a direct child of
     `<body>`, it becomes a stacking SIBLING of its own open root menu instead of a contained
     descendant, so the two numbers are compared directly -- 10001 < 12050 let the submenu paint UNDER
     its own root whenever their rects overlap (root near a viewport edge, submenu flipped to the
     opposite side of its parent item). `ContextMenu.js`'s own `SUBMENU_Z_INDEX` constant is now the
     single source of truth (kept just above the root menus' 12050, not an unrelated magic number) and
     always applies it inline with `!important` at open time -- inline `!important` beats this
     stylesheet rule's own `!important`, same precedence relationship the position/size properties
     above already rely on. This value is kept numerically coherent with that constant purely so the
     one CSS-only pre-JS frame documented above is never wrong either, not because this rule is load-
     bearing once JS has run. */
  z-index: 12060;
}

.context-menu-item.has-submenu:hover .context-submenu {
  display: block !important;
}

.context-submenu div {
  padding: 8px 16px;
  cursor: pointer;
  display: block !important;
  font-size: 14px;
  color: #f0f6fc;
  white-space: nowrap;
  gap: 0 !important;
}

.context-submenu div:hover {
  background: rgba(232, 121, 249, 0.1);
  color: #e879f9;
}

.context-submenu svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

/* M4 SDU CSP visual-contract correction gate §B: the submenu's own dedicated `.force-scrollbar` block
   (was fuchsia, then a pale `rgba(255, 255, 255, 0.06)` overlay track that still didn't match the root
   menus' dark workspace-bar family) has been folded into the single shared `.context-menu.force-
   scrollbar, .context-submenu.force-scrollbar` rule set above -- both element kinds now read the exact
   same `--menu-scrollbar-*` custom properties, so there is no second declaration site left to drift.
   §C is unaffected: every other submenu color (background, command text, hover) stays exactly as
   before, only the scrollbar family moved. */

/* M4 SDU fixed-edge overflow indicator gate (docs/handoffs/CHATGPT_TO_CLAUDE_M4_SDU_MENU_OVERFLOW_
   FIXED_EDGE_INDICATOR_GATE.md), corrected to be event-driven (docs/handoffs/CHATGPT_TO_CLAUDE_M4_SDU_
   MENU_OVERFLOW_FIXED_EDGE_INDICATOR_CORRECTION_GATE.md): replaces the previous `.context-menu.
   has-overflow`/`.context-submenu.has-overflow` `::before`/`::after` pseudo-elements. Those were
   generated INSIDE the same element that owns `overflow-y: scroll`, so their `position: absolute`
   resolved against that element's own padding box and physically scrolled together with the menu's
   real content instead of staying anchored to the visible menu edge -- the proven root cause recorded
   in the gate handoff (`position: fixed` would not have helped either: `backdrop-filter` already
   establishes a fixed-position containing block on these same menus). These are now real elements,
   reparented to `document.body` by `ContextMenu.js`'s `getOrCreateEdgeIndicators()` and repositioned
   by `syncEdgeIndicators()` from the host menu's own `getBoundingClientRect()`, called solely from
   `updateMenuOverflowIndicators()` -- the sole owner of overflow/direction state and indicator
   visibility, itself already run on open/scroll/resize/reposition, so no per-frame polling loop is
   needed. This is the same containing-block escape already used for open submenus (see
   `ensureSubmenuInViewport()`). Visual language (blue for root menus, fuchsia for submenus, thin
   gradient strip, small arrow, pointer-events:none) is unchanged from the previous pseudo-element
   contract. */
.context-menu-edge-indicator {
  position: fixed;
  height: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 120ms ease;
  z-index: 12070;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.2px;
  line-height: 1;
  box-sizing: border-box;
  color: rgba(88, 166, 255, 0.9);
}

.context-menu-edge-indicator.is-visible {
  opacity: 1;
}

.context-menu-edge-indicator--submenu {
  color: rgba(232, 121, 249, 0.9);
}

.context-menu-edge-indicator--top::before {
  content: "▲";
}

.context-menu-edge-indicator--top {
  background: linear-gradient(to bottom, rgba(13, 17, 23, 0.95), rgba(13, 17, 23, 0));
}

.context-menu-edge-indicator--bottom::before {
  content: "▼";
}

.context-menu-edge-indicator--bottom {
  background: linear-gradient(to top, rgba(13, 17, 23, 0.95), rgba(13, 17, 23, 0));
}

/* Specific styling for SVG context menu with fucsia outline */
#svgContextMenu {
  border: 1px solid rgba(232, 121, 249, 0.3) !important; /* Fucsia like links */
}

#svgContextMenu div:hover {
  background: rgba(232, 121, 249, 0.1) !important;
  color: #e879f9 !important;
}

/* System dialog styling overrides */
dialog {
  background: rgba(22, 27, 34, 0.98) !important;
  backdrop-filter: blur(12px) !important;
  border: 2px solid #e879f9 !important; /* Fucsia outline to match links */
  border-radius: 12px !important;
  padding: 24px !important;
  color: #f0f6fc !important;
  font-family: 'Inter', sans-serif !important;
  box-shadow: 0 16px 64px rgba(0, 0, 0, 0.4) !important;
}

dialog::backdrop {
  background: rgba(13, 17, 23, 0.6) !important;
  backdrop-filter: blur(8px) !important;
}

/* Input fields in dialogs */
dialog input[type="text"],
dialog input[type="url"],
dialog textarea {
  background: rgba(13, 17, 23, 0.8) !important;
  border: 1px solid #22d3ee !important; /* Verde acqua for inputs */
  border-radius: 6px !important;
  padding: 8px 12px !important;
  color: #f0f6fc !important;
  font-family: 'Inter', sans-serif !important;
  font-size: 14px !important;
  outline: none !important;
}

dialog input[type="text"]:focus,
dialog input[type="url"]:focus,
dialog textarea:focus {
  border-color: #58a6ff !important;
  box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.2) !important;
}

/* Buttons in dialogs */
dialog button {
  background: #58a6ff !important;
  border: none !important;
  border-radius: 6px !important;
  padding: 8px 16px !important;
  color: white !important;
  font-family: 'Inter', sans-serif !important;
  font-weight: 500 !important;
  cursor: pointer !important;
  transition: background 0.2s ease !important;
  margin: 0 4px !important;
}

dialog button:hover {
  background: #1f6feb !important;
}

dialog button[type="button"]:last-child {
  background: #22d3ee !important; /* Verde acqua for OK/Confirm buttons */
}

dialog button[type="button"]:last-child:hover {
  background: #0891b2 !important;
}

/* Connection Animations */
@keyframes pulse-flow {
  0% {
    stroke-dashoffset: 0;
    opacity: 0.7;
  }
  50% {
    opacity: 1;
  }
  100% {
    stroke-dashoffset: -20;
    opacity: 0.7;
  }
}

/* Enhanced connection styles with node color and animated pulse */
svg line[stroke-dasharray] {
  /* Color will be set dynamically based on fromNode color */
  animation: pulse-flow 2s linear infinite;
}

/* Solid line overlay for connections */
.connection-base {
  stroke-width: 3;
  opacity: 0.6;
}

/* Animated pulse overlay */
.connection-pulse {
  stroke: #ffffff;
  stroke-width: 2;
  stroke-dasharray: 8,12;
  opacity: 0.9;
  animation: pulse-flow 1.5s linear infinite;
}

.connection-travel-dot {
  opacity: 0.9;
  filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.6));
}

/* Box Selection Styles */
.selection-box {
  pointer-events: none;
  fill: rgba(88, 166, 255, 0.3);
  stroke: #58a6ff;
  stroke-width: 2.5px;
  stroke-dasharray: 4, 2;
  vector-effect: non-scaling-stroke;
  z-index: 9999;
  animation: selection-pulse 2s ease-in-out infinite alternate;
  display: block !important;
  visibility: visible !important;
  opacity: 1 !important;
}

@keyframes selection-pulse {
  0% {
    fill-opacity: 0.2;
    stroke-opacity: 0.8;
  }
  100% {
    fill-opacity: 0.4;
    stroke-opacity: 1;
  }
}

/* Crosshair cursor for box selection */
svg.box-selecting {
  cursor: crosshair !important;
}

/* Node highlighting during box selection - only target the main background */
.node-intersects-selection > .node-bg {
  filter: drop-shadow(0 0 8px #58a6ff) !important;
  stroke-width: 3px !important;
  stroke: #58a6ff !important;
  transition: all 0.1s ease-out;
}

/* Node selected state - handled by NodeFactory.setNodeState() */

/* Node dropdown styling */
/* Node CSS - Global Styles */
.node-label {
  color: #e2e8f0 !important;
  font-size: 12px !important;
  font-weight: 500 !important;
}

.node-select {
  background: transparent !important;
  color: #ffffff !important;
  border-radius: 4px !important;
  padding: 2px 6px !important;
  font-size: 12px !important;
  cursor: pointer !important;
}

.node-select option {
  background: #1a1f2e !important;
  color: #ffffff !important;
}

/* Instructions overlay for box selection */
.selection-instructions {
  /* M4 SDU physical retest residual UI coherence gate (docs/handoffs/CHATGPT_TO_CLAUDE_M4_SDU_PHYSICAL_
     RETEST_RESIDUAL_UI_COHERENCE_GATE.md, Finding A): the M3 R8 grid-item placement this comment used to
     describe ("shared with #diagram... instead of position:fixed") is the PROVEN root cause of a real
     physical bug, not merely a stale comment. `body`'s own grid-shell comment claims every non-top-bar/
     workspace-bar/diagram child (this one explicitly named) is "fixed/absolute and therefore not a grid
     item" -- but `grid-row:3; grid-column:1` WITHOUT an explicit `position` here made it a REAL grid item
     sharing #diagram's own row/track. Measured directly (CDP, `getBoundingClientRect()` before/after this
     element's `display:none -> block` toggle): the instant box-selection starts, this element's own
     ~42px intrinsic box (32px content + 10px margin-top) perturbs row 3's track, and `#diagram`'s (the
     `<svg>`'s) own rendered box shifts (top +42px, height -42px) as a synchronous, native CSS Grid
     layout consequence. The SVG's own internal content (Groups/Connections/cables, mapped via its
     `viewBox`) reprojects to that shift IMMEDIATELY, natively, same frame. `#nodeOverlay` (the HTML
     Blueprint-node world root's own fixed-position ancestor) does NOT -- it only re-syncs to `#diagram`'s
     rect via `syncNodeOverlayRect()` (main.js), fired from a `ResizeObserver` callback that measurably
     lags by at least one JS task/frame behind the synchronous grid layout. For that window, SVG-drawn
     content (cables, Group outlines) sits at the NEW (shifted) position while every HTML Blueprint node
     is still painted at the OLD one -- exactly Antonio's "Nodes/cables visibly change position, canvas
     looks disconnected" report. Root-caused and fixed at the layout-participation level, not by chasing
     the reprojection lag itself (no manual cable-refresh spam, no compensating offset): this element is
     now `position:fixed`, out of grid flow entirely (so it can never again perturb `#diagram`'s own box,
     confirmed via the same CDP measurement: `#diagram`'s rect is now byte-identical before/after the
     `display:none -> block` toggle, at scale 1 AND scale 0.5+pan), with its on-screen position derived
     from `#diagram`'s own LIVE rect in `syncNodeOverlayRect()` (main.js) -- the SAME established,
     ResizeObserver-driven technique `#nodeOverlay`/`#collapsedGroupForeground` already use, not a new,
     independent, hand-picked pixel guess (the exact category of fragility M3 R8's own original comment
     was trying to avoid by moving to grid-row in the first place; this fix gets the "no magic number"
     property WITHOUT the grid-track side effect). See `syncNodeOverlayRect()`'s own header comment. */
  position: fixed;
  /* `left`/`top` themselves are written live by syncNodeOverlayRect() (main.js), matching the M3 R8
     `justify-self:center; align-self:start; margin-top:10px` visual placement this replaces: `left` is
     `#diagram`'s own horizontal center, `translateX(-50%)` here re-centers this element's own box around
     that point (equivalent to the old `justify-self:center`), and `top` is `#diagram`'s own top edge + 10px
     (equivalent to the old `align-self:start; margin-top:10px`). */
  transform: translateX(-50%);
  background: rgba(13, 17, 23, 0.9);
  color: #58a6ff;
  padding: 6px 14px;
  border-radius: 6px;
  font-size: 12px;
  border: 1px solid #30363d;
  backdrop-filter: blur(10px);
  z-index: 998; /* Just below workspace-bar */
  opacity: 0.9;
  max-width: 100%;
  white-space: nowrap;
  text-align: center;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
  pointer-events: none; /* Ensure it doesn't interfere with mouse events */
}
/* Settings Panel */
.settings-panel {
  position: fixed;
  top: 0;
  right: 0;
  /* M3 R13: a fixed 400px width clipped ~11px off-screen to the left at 390px-wide phone viewports
     (measured live: rect.left:-11, rect.right:390) -- min() caps it to the viewport width with no
     effect at desktop/iPad/tablet (all >=768px, already well above 400px). */
  width: min(400px, 100vw);
  /* M3 R13.1: `width` above sets the CONTENT box under the default content-box sizing -- the 1px
     `border-left` below was then added OUTSIDE that width, making the true rendered outer box 391px
     at a 390px viewport (measured live post-R13: rect.left:-1, rect.right:390 -- ~1px of the panel's
     own border hanging off-screen; not "contained with 1px tolerance", an actual box-model bug).
     border-box sizing makes `width` the OUTER box (content+border) instead, so the border is drawn
     INSIDE the declared width -- with `right:0` already anchoring the right edge to the viewport
     edge, this makes left:0/right:<=viewportWidth exact and structural, not a rounding coincidence.
     No calc()/offset/breakpoint/UA-sniff added -- same width formula, transitions, and 400px/100vw
     caps as R13. */
  box-sizing: border-box;
  height: 100vh;
  background: linear-gradient(135deg, rgba(22, 27, 34, 0.98) 0%, rgba(13, 17, 23, 0.98) 100%);
  backdrop-filter: blur(20px);
  border-left: 1px solid rgba(88, 166, 255, 0.3);
  box-shadow: -10px 0 40px rgba(0, 0, 0, 0.5);
  z-index: 10002;
  transform: translateX(100%);
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.settings-panel.show {
  transform: translateX(0);
}

.settings-content {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.settings-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px;
  border-bottom: 1px solid rgba(88, 166, 255, 0.2);
}

.settings-header h2 {
  color: #f0f6fc;
  margin: 0;
  font-size: 24px;
  font-weight: 600;
}

.settings-close {
  background: rgba(88, 166, 255, 0.1);
  border: 1px solid rgba(88, 166, 255, 0.2);
  color: #8b949e;
  font-size: 28px;
  width: 36px;
  height: 36px;
  border-radius: 8px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s ease;
}

.settings-close:hover {
  background: rgba(88, 166, 255, 0.2);
  border-color: rgba(88, 166, 255, 0.4);
  color: #f0f6fc;
}

.settings-body {
  flex: 1;
  overflow-y: auto;
  padding: 20px;
}

/* M3 R13: `.share-panel` had NO dedicated CSS of its own before this round -- confirmed live at both
   390x844 and 1440x900 that opening it rendered a `position:static`, full-width, in-flow block sitting
   wherever `#sharePanel` happens to fall in the document body (near the end, right before the debug
   console), not a floating panel at all: no backdrop, no fixed positioning, no centering, no contained
   width. The class names (`.share-panel-header/body/close`) were clearly modeled after Settings'
   (`.settings-header/body/close`) but the actual panel rules were never written. This mirrors Settings'
   already-hardened fixed/slide-in/internal-scroll contract as ITS OWN independent CSS block -- not a
   shared rule, not a JS merge -- per R11's classification of Settings/Share as legitimately distinct
   primitives (kept distinct here too: this is a narrow, mechanical parity fix to close a genuine
   containment gap, not a forced architecture consolidation). `toggleSharePanel()` in main.js was updated
   to match, using the identical two-phase show/hide timing Settings.js already uses. */
.share-panel {
  position: fixed;
  top: 0;
  right: 0;
  width: min(400px, 100vw);
  /* M3 R13.1: see the matching note on `.settings-panel` above -- the same content-box/border
     interaction made Share's outer box 1px wider than the declared width too. */
  box-sizing: border-box;
  height: 100vh;
  background: linear-gradient(135deg, rgba(22, 27, 34, 0.98) 0%, rgba(13, 17, 23, 0.98) 100%);
  backdrop-filter: blur(20px);
  border-left: 1px solid rgba(88, 166, 255, 0.3);
  box-shadow: -10px 0 40px rgba(0, 0, 0, 0.5);
  z-index: 10002;
  transform: translateX(100%);
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  flex-direction: column;
}

.share-panel.show {
  transform: translateX(0);
}

.share-panel-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 20px;
  border-bottom: 1px solid rgba(88, 166, 255, 0.2);
  flex: 0 0 auto;
}

.share-panel-header h3 {
  color: #f0f6fc;
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  flex: 1;
}

.share-panel-close {
  background: rgba(88, 166, 255, 0.1);
  border: 1px solid rgba(88, 166, 255, 0.2);
  color: #8b949e;
  font-size: 22px;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s ease;
  flex: 0 0 auto;
}

.share-panel-close:hover {
  background: rgba(88, 166, 255, 0.2);
  border-color: rgba(88, 166, 255, 0.4);
  color: #f0f6fc;
}

.share-panel-body {
  flex: 1;
  overflow-y: auto;
  padding: 20px;
}

.settings-section {
  margin-bottom: 30px;
}

.settings-section h3 {
  color: #58a6ff;
  margin: 0 0 15px 0;
  font-size: 18px;
  font-weight: 500;
}

.setting-item {
  margin-bottom: 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.setting-item label {
  color: #c9d1d9;
  font-size: 14px;
  font-weight: 500;
}

.setting-item input[type="number"] {
  background: rgba(13, 17, 23, 0.6);
  border: 1px solid rgba(88, 166, 255, 0.3);
  border-radius: 6px;
  padding: 10px;
  color: #f0f6fc;
  font-size: 14px;
  transition: all 0.2s ease;
}

.setting-item input[type="number"]:focus {
  outline: none;
  border-color: rgba(88, 166, 255, 0.6);
  background: rgba(13, 17, 23, 0.8);
}

.setting-hint {
  color: #8b949e;
  font-size: 12px;
  font-style: italic;
}

/* Developer Tools Buttons */
.btn-tester {
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
  transition: all 0.2s;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.btn-tester .material-symbols-outlined {
  font-size: 14px;
  vertical-align: middle;
}

.btn-tester.btn-blueprint {
  background: rgba(245, 158, 11, 0.2);
  color: #f59e0b;
  border: 1px solid #f59e0b;
}

.btn-tester.btn-rules {
  background: rgba(102, 126, 234, 0.2);
  color: #667eea;
  border: 1px solid #667eea;
}

.btn-tester.btn-color-test {
  background: rgba(139, 92, 246, 0.2);
  color: #8b5cf6;
  border: 1px solid #8b5cf6;
}

.btn-tester.btn-danger {
  background: rgba(239, 68, 68, 0.2);
  color: #ef4444;
  border: 1px solid #ef4444;
}

.btn-tester:hover {
  filter: brightness(1.2);
}

/* Custom scrollbar for foreignObject divs */
.dialogue-text::-webkit-scrollbar,
.choice-text::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

.dialogue-text::-webkit-scrollbar-track,
.choice-text::-webkit-scrollbar-track {
  background: rgba(0, 0, 0, 0.2);
  border-radius: 3px;
}

.dialogue-text::-webkit-scrollbar-thumb,
.choice-text::-webkit-scrollbar-thumb {
  background: rgba(88, 166, 255, 0.5);
  border-radius: 3px;
}

.dialogue-text::-webkit-scrollbar-thumb:hover,
.choice-text::-webkit-scrollbar-thumb:hover {
  background: rgba(88, 166, 255, 0.7);
}

/* Firefox scrollbar */
.dialogue-text,
.choice-text {
  scrollbar-width: thin;
  scrollbar-color: rgba(88, 166, 255, 0.5) rgba(0, 0, 0, 0.2);
}

/* Mobile Portrait Mode - Multi-row layout */
@media (max-width: 768px) and (orientation: portrait) {
  /* ── Top bar: compact single-row header ── */
  .top-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    padding: 6px 10px;
    width: calc(100% - 20px);
    /* M3 R8: kept as `auto` (contained horizontal scroll, matching the base/desktop rule), NOT
       `visible` -- `.top-bar` is now a body-grid item (see body{} above), and CSS Grid's "automatic
       minimum size" only applies when the relevant overflow axis computes to `visible`. With
       `overflow-x: visible` here, `.buttons{flex-wrap:nowrap}`'s non-shrinking icon row (11 buttons
       that cannot get smaller than their own intrinsic width) forced `.top-bar`'s OWN box to grow to
       fit them regardless of the explicit `width` above -- discovered as a real R8 regression via this
       round's own evidence suite (`.top-bar`'s measured width was ~826-846px at EVERY narrow viewport
       tested, 390 through 768, independent of the actual viewport width -- the unmistakable signature
       of this exact CSS Grid sizing gotcha). `overflow-x: auto` (like the desktop rule already uses)
       suppresses the automatic-minimum-size behavior and restores containment: the icon row scrolls
       horizontally WITHIN the bar on very narrow phones instead of expanding the bar itself past the
       viewport. This does not hide, remove, reorder or reinterpret any toolbar action -- every button
       remains present and reachable, exactly per R8's own binding constraint. */
    overflow-x: auto;
    overflow-y: visible;
    white-space: normal;
    height: auto;
    column-gap: 0;
  }

  /* Logo stays left, doesn't take full width */
  .top-bar .title {
    width: auto;
    flex: 0 0 auto;
    justify-content: flex-start;
    margin-bottom: 0;
    order: 0;
    gap: 4px;
  }

  .top-bar h2 {
    font-size: 1.15em;
  }

  .top-bar small {
    font-size: 0.55em;
    bottom: 0.35em;
    margin-left: 2px;
  }

  /* Filename goes right on the same row */
  #project-name {
    position: static !important;
    transform: none !important;
    width: auto;
    max-width: none;
    flex: 1;
    text-align: right;
    order: 1;
    margin: 0;
    pointer-events: none;
    font-size: 12px;
  }

  /* M3 R9: the action row is its own deterministic second topbar row (bounded at exactly 2 rows
     total with the title/project-name row above), per the binding product guardrail addendum --
     portrait multi-row topbar is intentional, and buttons must NOT be shrunk to force a single row.
     Previously this cammed all 11 icon buttons into one non-wrapping row via `gap:0` and a reduced
     `.icon-button{padding:6px}` (~30px touch target, and as small as ~22px effective width once
     flex-shrink squeezed individual buttons below their own intrinsic size on narrow phones -- see
     the R9 hand-back's baseline evidence). Buttons now keep their base/desktop size (8px padding +
     18px icon = 34px touch target, matching `[tw-4b]`) and the row scrolls internally via contained
     horizontal overflow instead of compressing -- exactly the "internal horizontal strip" mechanism
     SS B calls for for narrow portrait action reachability. */
  .buttons {
    width: 100%;
    justify-content: flex-start;
    flex-wrap: nowrap;
    gap: 10px;
    order: 2;
    margin-top: 4px;
    min-width: 0;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    -ms-overflow-style: none;
  }

  /* M3 R9: `.icon-button`'s own base rule sets `overflow: hidden` (for its hover-ripple effect),
     which per the flex spec makes ITS OWN automatic minimum size 0 rather than its min-content size
     -- so without this, the default `flex-shrink: 1` let individual buttons compress far below their
     34px touch target inside this scrollable row (measured as low as ~19px) instead of hitting a
     content floor and triggering the intended horizontal scroll. Pinning `flex-shrink: 0` here keeps
     every button at its true base/desktop size; the row scrolls instead of the buttons shrinking. */
  .buttons .icon-button {
    flex-shrink: 0;
  }

  .buttons::-webkit-scrollbar {
    display: none;
  }

  /* ── Workspace bar: follows the (now possibly multi-row) top-bar naturally via the body grid's
     `auto` row sizing -- M3 R8 removed the `top: 80px` hard-coded guess here. ── */
  .workspace-bar {
    padding: 4px 8px;
    width: calc(100% - 16px);
  }

  /* M3 R9: the old `.workspace-tabs{width:100%;justify-content:center;flex-wrap:wrap}` +
     `.workspace-actions{width:100%;justify-content:center;margin-top:4px}` portrait-only patch is
     retired -- it let workspace-tab pressure wrap into up to 5 vertical rows here (measured in R8's
     own baseline evidence), stealing canvas space. `.workspace-tabs`/`.workspace-actions` now have
     ONE coherent ownership model defined once in the base rules above (shrink-then-internal-scroll
     for tabs, guaranteed-unshrinkable for actions), applied uniformly at every breakpoint instead of
     patched per breakpoint -- so no portrait-specific override is needed here at all. */

  /* M3 R8: #diagram's `height: 100%; min-height: 0;` (base rule, above) already fills whatever the
     body grid's 1fr row leaves after the (now taller, wrapped) top-bar/workspace-bar rows -- no
     compact-breakpoint override needed; the old `margin-top: 155px` / `height: calc(100vh - 155px)`
     pair (a second independent guess-chain on top of the base 92px one) is removed entirely. */

  /* ── Debug console: collapse to small icon circle ── */
  #debugConsole.collapsed {
    width: 36px;
    height: 36px;
    max-height: 36px;
    border-radius: 50%;
    bottom: 12px;
    right: 12px;
    padding: 0 !important;
    overflow: hidden;
  }

  #debugConsole.collapsed::before {
    display: none;
  }

  /* Hide label, show icon */
  #debugConsole.collapsed .debug-label {
    display: none;
  }

  #debugConsole.collapsed #toggleDebug {
    position: absolute;
    top: 0;
    left: 0;
    width: 36px;
    height: 36px;
    margin: 0;
    padding: 0;
    border: none;
    background: transparent;
    cursor: pointer;
  }

  #debugConsole.collapsed .debug-icon {
    display: flex !important;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    font-size: 20px;
    color: #58a6ff;
  }

  /* When expanded: show label, hide icon */
  #debugConsole:not(.collapsed) .debug-icon {
    display: none;
  }
}

@media (max-width: 1024px) and (orientation: landscape) {
  /* M3 R9: `#project-name{max-width:30vw}` is the sole width/layout owner for this compact-landscape
     tier (base rule's `width:min(100%,38vw)` is the desktop tier below it); the touch-landscape
     capability query below no longer redeclares `width`/`max-width`, so there is one coherent chain
     instead of a patch-on-patch stack across three queries for the same property. */
  #project-name {
    max-width: 30vw;
    font-size: 12px;
  }

  /* M3 R9: removed a dead `.buttons{gap:18px}` here -- identical to the base rule's value (18px),
     so this declaration changed nothing and only added a misleading extra "owner" of the property. */
}

/* Tablet/mobile landscape (touch): compact top bar to prevent project-name overlap */
@media (hover: none) and (pointer: coarse) and (orientation: landscape) {
  .top-bar {
    column-gap: 12px;
    padding: 8px 12px;
    width: calc(100% - 24px);
  }

  .top-bar .title {
    gap: 6px;
  }

  .top-bar h2 {
    font-size: clamp(1.2em, 2vw, 1.45em);
  }

  .top-bar small {
    font-size: 0.62em;
    bottom: 0.45em;
    margin-left: 2px;
  }

  /* M3 R9: genuinely cosmetic for this input-density tier (font-size/padding only) -- `width`/
     `max-width` are deliberately NOT redeclared here anymore. They used to override the 1024px-
     landscape query's `max-width:30vw` for every touch-landscape device in that width range (a
     patch-on-patch chain for the same property across two competing queries); layout/width ownership
     for `#project-name` now lives solely in the base rule + the width-keyed 1024px-landscape query
     above, per SS C ("capability queries...must not become competing layout owners"). */
  #project-name {
    font-size: 12px;
    padding-inline: 4px;
  }

  .buttons {
    gap: 10px;
  }

  .icon-button {
    padding: 7px;
  }
}

/* Extra-compact layout for narrower landscape tablets (e.g. iPad Pro 11) */
@media (hover: none) and (pointer: coarse) and (orientation: landscape) and (max-width: 1200px) {
  .buttons {
    gap: 8px;
  }
}
