/* M04 — minimal layout. Three columns (editor / stacks / heap), header, status,
   toolbar at the bottom. Light mode only. */

:root {
  --border: #d0d0d0;
  --header-bg: #f5f5f5;
  --text: #222;
  --muted: #888;
  --accent: #4a6fa5;
  --error: #b00020;
  --info: #2a6f97;
  --slot-bg: #fafafa;
  --frame-bg: #ffffff;
  --frame-border: #c0c0d0;
  --highlight: #ffe680;
  --button-bg: #ffffff;
  --button-hover: #eef2f7;
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
  color: var(--text);
  background: #fff;
}

/* M06.1: body as a vertical flex container so main absorbs remaining space
   and status appearing/disappearing doesn't push the toolbar off-screen. */
body {
  display: flex;
  flex-direction: column;
}

header {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.5rem 1rem;
  background: var(--header-bg);
  border-bottom: 1px solid var(--border);
}

header h1 {
  font-size: 1.1rem;
  font-weight: 600;
  margin: 0;
  margin-right: auto;
}

header label {
  font-size: 0.9rem;
  color: var(--muted);
}

header select {
  font: inherit;
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: white;
}

main {
  display: flex;
  /* M06.1: main flexes to fill remaining vertical space. When #status
     appears (or disappears) the body flex layout absorbs the height change
     here instead of pushing the toolbar past the viewport. */
  flex: 1;
  min-height: 0;
  border-bottom: 1px solid var(--border);
  position: relative; /* anchor for the absolute-positioned SVG overlay */
}

/* 020-foldable-resizable-panels: each top-level panel is wrapped in a
   `<div class="panel" data-panel="...">` container. The container drives
   the horizontal flex layout (replacing the legacy `main > section { flex:1 }`
   rules); the inner `<section>` keeps its existing id-scoped CSS for content
   layout (slot grids, heap-cell strips, etc.). */
.panel {
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
  border-right: 1px solid var(--border);
  /* **Proportional sizing via flex-grow.** `flex-basis: 0` + `flex-shrink: 0`
     means each panel takes ZERO baseline width and grows by `flex-grow` units
     to share the remaining row width. When a panel collapses to a sliver
     (`.is-folded` or auto-collapsed), its `flex-grow: 0` makes the OTHER
     panels' grow values absorb the freed space proportionally — no rigid
     percentages, no empty right-edge gap when panels collapse. */
  flex-shrink: 0;
  flex-basis: 0;
  /* Smooth fold/unfold + reset animations. Suppressed during a live drag
     via `body.is-resizing .panel { transition: none }`. */
  transition: flex-basis 120ms ease-out, flex-grow 120ms ease-out;
}

.panel:last-child {
  border-right: none;
}

.panel > section {
  flex: 1;
  min-width: 0;
  min-height: 0;
  overflow: auto;
  /* The legacy `main > section { border-right: 1px solid }` rule moved up
     to `.panel`; sections inside a panel must not double-paint the border. */
  border-right: none;
}

/* Default proportional widths. They're flex-grow values, NOT percentages —
   they represent each panel's SHARE of total layout. Sum is 100 by
   convention so a single value also reads as "approximate percent". When
   any panel slivers (grow→0), the remaining panels' grows redistribute. */
.panel[data-panel="editor"]  { flex-grow: 25; }
.panel[data-panel="stacks"]  { flex-grow: 30; }
.panel[data-panel="heap"]    { flex-grow: 25; }
.panel[data-panel="vtables"] { flex-grow: 10; }
.panel[data-panel="static"]  { flex-grow: 10; }

.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.3rem 0.5rem;
  font-size: 11px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  border-bottom: 1px solid var(--border);
  /* The header sits above the `<section>` content; don't shrink. */
  flex: 0 0 auto;
}

.panel-title {
  font-family: ui-monospace, SFMono-Regular, monospace;
}

.panel-fold-btn {
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted);
  cursor: pointer;
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 12px;
  line-height: 1;
}

.panel-fold-btn:hover {
  background: var(--frame-bg);
  color: var(--text);
  border-color: var(--border);
}

#btn-reset-layout {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 2px 8px;
  font-size: 11px;
  color: var(--muted);
  cursor: pointer;
}

#btn-reset-layout:hover {
  background: var(--frame-bg);
  color: var(--text);
}

/* 020-foldable-resizable-panels: drag-resize divider between adjacent
   panels. 6px hit-target with a 1px centered line; thickens + accent
   color on hover and active drag. */
.panel-divider {
  flex: 0 0 6px;
  cursor: col-resize;
  background: transparent;
  position: relative;
  /* No transition on flex-basis: the divider itself never resizes; only
     hover-recoloring of the inner pseudo-element animates. */
}

.panel-divider::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 1px;
  background: var(--border);
  transform: translateX(-50%);
  transition: background 120ms ease-out, width 120ms ease-out;
}

.panel-divider:hover::before,
.panel-divider.is-dragging::before {
  background: var(--accent);
  width: 3px;
}

/* Hide dividers adjacent to a folded panel — the sliver is a click-target
   for unfold, and a divider in between would be confusing. Uses CSS
   sibling + `:has()` for the both-sides cases. `:has()` is in all
   modern Chromium / Firefox / Safari (2023+). */
.panel.is-folded + .panel-divider,
.panel-divider:has(+ .panel.is-folded) {
  display: none;
}

/* Same for the auto-collapse sliver. */
.panel.panel-empty:not(.is-folded):not(.is-user-overridden) + .panel-divider,
.panel-divider:has(+ .panel.panel-empty:not(.is-folded):not(.is-user-overridden)) {
  display: none;
}

/* During an active drag, lock the global cursor + suppress text selection
   so the user never sees the cursor flicker between col-resize and the
   underlying panel's cursor when their pointer strays off the divider. */
body.is-resizing {
  cursor: col-resize !important;
  user-select: none;
}

/* Suppress flex-basis animation during a live drag — otherwise every
   mousemove kicks off a 120ms ease-out that lags behind the cursor. */
body.is-resizing .panel {
  transition: none !important;
}

/* M06: SVG overlay drawing borrow arrows. Absolutely positioned to overlap
   the panels, no pointer events so clicks pass through to the panels. */
#arrow-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 10;
}

/* M06: borrow arrows. Blue for shared (`&`), red for mutable (`&mut`).
   M07: hover highlight — thicker stroke + drop-shadow to bring the arrow
   to the foreground. `pointer-events: stroke` on each path lets the
   stroke catch hover events while the overlay's bounding box stays
   click-through (the overlay itself stays `pointer-events: none`). */
#arrow-overlay path {
  pointer-events: stroke;
  transition: stroke-width 120ms ease-out, filter 120ms ease-out;
}

/* **M07.2**: invisible wider hit-detection overlay. Each visible arrow
   has a sibling `.arrow-hit-target` path with the same geometry but a
   wider transparent stroke. SVG `pointer-events: stroke` uses the actual
   stroke width for hit-testing, so this wider stroke expands the
   detection zone (~5px total vs ~1.5px for the visible line) without
   thickening the rendered arrow. Hover events fire on the hit-target;
   the `:has()` selector below applies the visual hover style to the
   sibling visible path. */
#arrow-overlay .arrow-hit-target {
  stroke: transparent;
  stroke-width: 5;
  fill: none;
  pointer-events: stroke;
}

/* Visible arrows opt OUT of pointer-events — the hit-target catches
   everything for them so the wider detection zone applies uniformly. */
#arrow-overlay .arrow-shared,
#arrow-overlay .arrow-mut,
#arrow-overlay .arrow-owning {
  pointer-events: none;
}

/* Hover effect: when the hit-target is hovered, style the visible
   sibling path (which comes immediately after the hit-target in DOM
   order — see renderArrows). Uses CSS `:has()` for sibling selection. */
#arrow-overlay path:hover,
#arrow-overlay .arrow-hit-target:hover + path {
  stroke: #5eb1ff;       /* light blue highlight */
  stroke-width: 3.5;
  filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.45));
}

#arrow-overlay .arrow-shared {
  stroke: #2a6fa5;
  fill: none;
  stroke-width: 1.5;
  marker-end: url(#arrow-head-shared);
}

/* M07: owning arrows (Box/Vec/String -> heap). Black, thicker. */
#arrow-overlay .arrow-owning {
  stroke: #000;
  fill: none;
  stroke-width: 2;
  marker-end: url(#arrow-head-owning);
}

/* **M07.2**: transient "bytes copied" arrow. Orange dashed line + soft
   glow makes the data-flow direction obvious without competing with
   the permanent ownership/borrow arrows. Fades in via opacity (set by
   JS on render). Only present on the BytesCopy cursor step. */
#arrow-overlay .arrow-copy {
  stroke: #e07a00;
  fill: none;
  stroke-width: 2;
  stroke-dasharray: 5 3;
  marker-end: url(#arrow-head-copy);
  filter: drop-shadow(0 0 2px rgba(224, 122, 0, 0.55));
  pointer-events: none;
  animation: arrow-copy-fade-in 220ms ease-out;
}

@keyframes arrow-copy-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

#arrow-overlay .arrow-copy-label {
  font-size: 11px;
  fill: #b35f00;
  font-weight: 600;
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  user-select: none;
  pointer-events: none;
  animation: arrow-copy-fade-in 220ms ease-out;
}

/* **020-foldable-resizable-panels**: elided-arrow hint. Hidden by default,
   revealed when the source slot row is hovered (matching the project's
   "implicit relationship arrows reveal on hover" rule, see
   `feedback_arrow_viz_rules` memory). The L-shaped connector to the
   slot is drawn via `::before` / `::after` pseudo-elements — no SVG, so
   the text rendering uses the browser's normal font hinting and stays
   crisp at small sizes. Per-kind color via `currentColor` inheritance. */
/* Virtual elision targets are placeholder labels rendered into the source
   slot's row when the real target lives in a collapsed panel. The normal
   SVG arrow code then draws a regular arrow pointing at the label — same
   routing + styling vocabulary as when heap is open.

   Positioning context lives on `.frame-card`, NOT `.slot-row`: in
   single-thread mode slot-row uses `display: contents` and has no box,
   so `position: relative` on it is a no-op. Frame-card always has a real
   block-level box, giving a consistent containing block for the virt's
   absolute positioning in both single- and multi-thread modes. */
.frame-card {
  position: relative;
}

.elision-virtual-target {
  position: absolute;
  /* 60px above the frame card's top — short enough to stay inside the
     stacks panel, tall enough to be unmistakably "above" the slot. */
  bottom: calc(100% + 60px);
  /* Centered on the slot-name's x position (frame-card padding-left = 40px).
     `translateX(-50%)` shifts the virt left by half its own width so its
     center lands at left:40, matching where the SVG path's vertical line
     sits (src.left). */
  left: 40px;
  transform: translateX(-50%);
  background: rgba(255, 255, 255, 0.96);
  border: 1.5px solid currentColor;
  border-radius: 4px;
  padding: 2px 8px;
  font-size: 11px;
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  pointer-events: none;
  z-index: 5;
}

/* Per-kind label color matches the SVG arrow color the regular code emits
   for that kind. Dashed border for kinds whose normal arrow is dashed. */
.elision-virtual-target.arrow-shared  { color: #2a6fa5; }
.elision-virtual-target.arrow-mut     { color: #c62828; }
.elision-virtual-target.arrow-owning  { color: #111; }
.elision-virtual-target.arrow-arc     { color: #7a4eb5; border-style: dashed; }
.elision-virtual-target.arrow-copy    { color: #b35f00; border-style: dashed; }
.elision-virtual-target.arrow-vtable  { color: #b35f00; border-style: dashed; }

/* Elision arrows + virtual targets are HIDDEN by default, REVEALED when
   the source slot row is hovered. Matches the project's
   `feedback_arrow_viz_rules` convention: implicit-relationship arrows
   are hover-only; explicit ones are always-on. The relationship "slot's
   Arc points to a heap-block currently in a collapsed panel" is implicit
   — the user doesn't see the heap, so the arrow is contextual. */
#arrow-overlay .arrow-elision {
  opacity: 0;
  transition: opacity 120ms ease-out;
}
#arrow-overlay .arrow-elision.elision-visible {
  opacity: 1;
}

.elision-virtual-target {
  opacity: 0;
  visibility: hidden;
  transition: opacity 120ms ease-out, visibility 120ms ease-out;
}
.elision-virtual-target.elision-visible {
  opacity: 1;
  visibility: visible;
}

/* **M07.2**: source-side highlight for bytes/chars covered by a transient
   copy arrow. Orange-tinted to visually pair with the arrow itself; uses
   a distinct class from the yellow `.byte-slice-highlighted` so a hover-
   driven slice highlight and a copy-driven highlight don't conflict
   (different colors, both readable). Applied/cleared by renderCopyArrow. */
.byte-cell.byte-copy-source-highlighted {
  outline: 2px solid #e07a00;
  outline-offset: -1px;
  box-shadow: 0 0 4px rgba(224, 122, 0, 0.7);
  z-index: 1;
}

.elem-cell.elem-copy-source-highlighted {
  background: rgba(224, 122, 0, 0.3);
  outline: 1px solid #e07a00;
  border-radius: 2px;
  padding: 0 1px;
}

/* M07: heap panel. Each HeapAlloc creates a labeled box. Flex-wrap layout;
   CSS transition on transform handles realloc moves. */
#heap {
  display: flex;
  flex-wrap: wrap;
  column-gap: 0.5rem;
  /* M07: extra vertical row-gap leaves room for arrow lanes above each
     heap row (renderArrows() places lanes at heap.top - 12 - 6*i for the
     i-th arrow). The top padding gives the first row similar breathing
     room — large enough that even 5+ stacked lanes for arrows aimed at
     row-1 heap boxes never get clipped above the panel's visible area. */
  row-gap: 2.5rem;
  padding: 4rem 1rem 1rem 1rem;
  align-content: flex-start;
}

.heap-box {
  display: inline-flex;
  flex-direction: column;
  border: 1px solid var(--frame-border);
  border-radius: 4px;
  padding: 0.4rem 0.6rem;
  background: var(--frame-bg);
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 0.85rem;
  min-width: 80px;
  transition: transform 300ms ease-out, opacity 200ms;
}

.heap-box .heap-addr {
  font-size: 0.7rem;
  color: var(--muted);
}

.heap-box .heap-display {
  font-weight: 500;
}

/* **M07**: byte-level physical view. One small cell per byte of allocated
   capacity. Filled = used, empty = unused capacity. Makes per-type byte
   size differences visible (Box<f32> vs Box<f64>, Vec<i32> vs Vec<u8>). */
.heap-box .heap-cells {
  display: flex;
  flex-wrap: wrap;
  gap: 1px;
  margin-top: 4px;
  max-width: 280px;
}

.heap-box .byte-cell {
  width: 8px;
  height: 8px;
  border: 1px solid #bbb;
  background: #fff;
  box-sizing: border-box;
}

.heap-box .byte-cell.byte-used {
  background: #4a90c8;
  border-color: #2a6fa5;
}

/* **M07.3**: a slot-value holding an array's inline cells needs to allow
   the cells + labels to stack vertically inside it. Convert the
   originally-inline `.slot-value` span into a column-flex container when
   it contains stack-inline-cells. */
.slot-row .slot-value:has(.stack-inline-cells) {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}

/* **M07.4**: struct view inside a stack slot's value area. Per
   research R-016 Proposal A — vertical labeled rows. Each field
   gets a row with `name: ty` label, byte-cells, and the value.
   Pedagogically: makes the byte layout + named composition visible
   together. */
.slot-row .slot-value:has(.struct-view) {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
}

.struct-view {
  display: flex;
  flex-direction: column;
  gap: 2px;
  border: 1px solid #999;
  padding: 4px 6px;
  background: #fafafa;
  border-radius: 3px;
  max-width: 260px;
}

.struct-field {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 8px;
  align-items: center;
  font-size: 11px;
  padding: 2px 4px;
  border-radius: 2px;
}

.struct-field-label {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  color: var(--muted);
}

.struct-field-cells {
  display: flex;
  gap: 1px;
}

.struct-field-cells .byte-cell {
  width: 8px;
  height: 8px;
  border: 1px solid #999;
  background: #c8c8c6;
  box-sizing: border-box;
}

.struct-field-value {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  color: inherit;
}

/* M07.4 — per-field hover-highlight (US2 wires this up next). */
.struct-field.field-borrow-highlighted {
  background: #fffde7;
  outline: 2px solid #fbc02d;
}

/* **M07.3**: inline byte-cells for stack-allocated arrays. Rendered
   inside the slot's value area (NOT in the heap panel). Gray-tinted
   to distinguish from heap blocks' blue cells — pedagogically conveys
   "this memory lives in the stack, not the heap". Wraps to multiple
   rows when there are too many bytes to fit (e.g. an array of 9
   i32s = 36 cells). */
.stack-inline-cells {
  display: flex;
  flex-wrap: wrap;
  gap: 1px;
  max-width: 200px;
}

.stack-inline-cells .byte-cell {
  width: 8px;
  height: 8px;
  border: 1px solid #999;
  background: #fff;
  box-sizing: border-box;
}

.stack-inline-cells .byte-cell.byte-used {
  background: #c8c8c6;
  border-color: #999;
}

/* **M07.3**: element labels for stack-allocated arrays. Default
   horizontal layout for short arrays; when the array has more elements
   than the inline limit, first N labels show inline followed by a
   clickable `+M more` toggle that expands to a vertical stack. */
.stack-elem-labels {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 11px;
  color: var(--muted);
  max-width: 220px;
}

/* Collapsed mode: hide labels beyond the inline limit (CSS-driven so
   the toggle expansion is just a class flip). The limit (4) matches
   the JS-side INLINE_ELEM_LIMIT — keep them in sync. */
.stack-elem-labels.collapsed .elem-cell:nth-of-type(n+5) {
  display: none;
}

/* Expanded mode: vertical stack of all elements. */
.stack-elem-labels.expanded {
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
}

/* The +N more / collapse toggle: looks like an inline link, no button
   chrome. Cursor pointer to invite the click. */
.stack-elem-toggle {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: #4d8fcd;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.stack-elem-toggle:hover {
  color: #2a6fa5;
}

/* **M07.1**: byte-cells covered by a slice get a bright yellow outline + a
   soft glow when the user hovers the slice's arrow. Makes the slice's
   "view of these specific bytes" tangible — `&v[1..3]` lights up the
   second and third elements when you hover the slice arrow. */
.heap-box .byte-cell.byte-slice-highlighted {
  outline: 2px solid #f5b800;
  outline-offset: -1px;
  box-shadow: 0 0 4px rgba(245, 184, 0, 0.7);
  z-index: 1;
}

/* **M07.1**: element-span labels inside a Vec's display text — segmented
   per element so the slice-arrow hover handler can highlight the labels
   covered by `[elem_start, elem_start + len)`. The base style is invisible
   (no decoration) so the display reads as before; the highlight class is
   what makes the covered elements pop.
   **M07.2**: same mechanism applies to per-byte spans inside a static
   block's display (`"hello"` → segmented as `h`, `e`, `l`, `l`, `o`).
   Selector is broadened to match `.elem-cell` regardless of parent so
   both Vec-element labels and static-byte labels light up uniformly. */
.elem-cell {
  /* no baseline decoration — invisible until highlighted */
}

.elem-cell.elem-slice-highlighted {
  background: rgba(245, 184, 0, 0.35);
  outline: 1px solid #f5b800;
  border-radius: 2px;
  padding: 0 1px;
}

.heap-box.heap-freed .byte-cell {
  border-style: dashed;
  background: repeating-linear-gradient(
    45deg,
    transparent, transparent 2px,
    rgba(0,0,0,0.1) 2px, rgba(0,0,0,0.1) 4px
  );
}

/* M07: freed heap blocks stay visible (allocator "free list" pedagogy)
   but render grayed-out to convey they're available for reuse. */
.heap-box.heap-freed {
  opacity: 0.45;
  border-style: dashed;
  background: repeating-linear-gradient(
    45deg,
    var(--frame-bg),
    var(--frame-bg) 4px,
    rgba(0, 0, 0, 0.04) 4px,
    rgba(0, 0, 0, 0.04) 8px
  );
}

.heap-box.heap-freed .heap-display {
  text-decoration: line-through;
  color: var(--muted);
}

#arrow-overlay .arrow-mut {
  stroke: #c62828;
  fill: none;
  stroke-width: 1.5;
  marker-end: url(#arrow-head-mut);
}

/* **M07.1**: slice arrows show a `[len: N]` annotation near the arrowhead.
   Small monospace, blue to match the shared-borrow color, unselectable so
   it doesn't interfere with text selection in adjacent panels.
   **M07.2**: hidden by default; revealed only when the corresponding
   arrow is hovered. Keeps the visualization clean when many slices
   coexist (labels would otherwise stack and crowd adjacent UI). */
#arrow-overlay .arrow-len-label {
  font-size: 10px;
  fill: #4d8fcd;
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  user-select: none;
  pointer-events: none;
  opacity: 0;
  transition: opacity 100ms ease-out;
}

#arrow-overlay .arrow-len-label.label-visible {
  opacity: 1;
}

/* **M07.4**: field-borrow annotation (`.x`) on `&p.x` arrows. Same
   hover-revealed pattern as the slice [len: N] label. */
#arrow-overlay .arrow-field-label {
  font-size: 10px;
  fill: #4d8fcd;
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  user-select: none;
  pointer-events: none;
  opacity: 0;
  transition: opacity 100ms ease-out;
}

#arrow-overlay .arrow-field-label.label-visible {
  opacity: 1;
}

/* **M07.4**: method-self arrow (calling-convention borrow). Hidden by
   default; revealed on hover of the `self` slot row. Same fade timing
   as the slice/field labels — visually consistent reveal-on-hover. */
#arrow-overlay .arrow-hover-only {
  opacity: 0;
  transition: opacity 100ms ease-out;
}

#arrow-overlay .arrow-hover-only.arrow-visible {
  opacity: 1;
}

#editor {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 13px;
}

#editor .cm-editor {
  height: 100%;
}

#editor .cm-current-span {
  background: var(--highlight);
  border-radius: 2px;
}

/* M05 / US2: red wavy underline at a compile-error span. Lives at a
   different visual layer than `.cm-current-span` (background) and
   `.cm-current-fn` (border) so they compose cleanly. */
#editor .cm-error-span {
  text-decoration: red wavy underline;
  text-decoration-thickness: 1px;
}

/* M03.1: red border around the currently-executing function's body span.
   Paired with the red `.frame-current` styling in the stacks panel so the
   learner sees the call site/active function highlighted consistently across
   editor + stacks. */
#editor .cm-current-fn {
  border: 1px solid #c62828;
  border-radius: 3px;
  background: rgba(198, 40, 40, 0.06);
}

#stacks {
  padding: 0.5rem;
  display: flex;
  flex-direction: column-reverse; /* innermost frame visually on top */
  gap: 0.5rem;
}

/* **M08**: multi-thread layout. When `state.threads` is non-empty,
   `renderStacks` builds a `.thread-columns` container as the only child
   of `#stacks`, with one `.thread-column` per live thread. Each column
   keeps the existing `flex-direction: column-reverse` rule so frames
   inside a column behave like the pre-M08 single-column rendering
   (frames anchored to the BOTTOM, innermost frame on top). The
   `align-items: stretch` on the row container forces each column to
   fill the full height — without it, columns size to content and
   frames appear at the TOP rather than anchored to the bottom. */
#stacks .thread-columns {
  display: flex;
  flex-direction: row;
  gap: 0.75rem;
  align-items: stretch;
  overflow-x: auto;
  height: 100%;
}

.thread-column {
  /* **Post-M08**: widen the cap (was 360px which clipped MutexGuard /
     fat-pointer value cells) and let columns shrink only as far as their
     content demands. The outer `.thread-columns` container scrolls
     horizontally past 3 columns so wider cards never compress. */
  flex: 1 1 auto;
  min-width: 280px;
  max-width: 520px;
  display: flex;
  flex-direction: column-reverse; /* innermost frame on top (pre-M08 behavior) */
  gap: 0.5rem;
  padding: 0.5rem;
  border: 1px dashed transparent;
  border-radius: 4px;
  /* Allow frame cards inside to overflow vertically (long value cells
     wrap rather than truncate). */
  min-height: 0;
}

/* **Post-M08**: inside narrow thread columns, the pre-M08 single-line
   3-column grid (name | type | value) crushes the value cell when types
   are long (`Arc<Mutex<i32>>`, `MutexGuard<i32>`, etc). Switch to a
   STACKED layout per slot — `name : type` on line 1, value on a second
   line indented under it. Gives the value the full column width and
   reads more naturally for fat-pointer / heap-owning values. The
   single-thread fall-back path keeps the legacy grid layout untouched. */
.thread-column .frame-card .slots {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.thread-column .slot-row {
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
  padding-left: 0.1rem;
  /* `slot-row::before` from the grid-mode siblings doesn't apply here;
     the row is now a normal block. */
}

.thread-column .slot-row .slot-name {
  font-weight: 600;
  display: inline;
}

.thread-column .slot-row .slot-ty {
  color: var(--muted);
  display: inline;
  margin-left: 0.1rem;
}

.thread-column .slot-row .slot-value {
  display: block;
  margin-left: 1rem;
  color: var(--text);
  overflow-wrap: anywhere;
  word-break: break-word;
  min-width: 0;
}

/* Hide the value line entirely when there's nothing to display (pending
   placeholder is rendered via ::before — the empty cell would otherwise
   reserve a row of vertical space). Keep .slot-pending visible because
   it carries the `?` indicator. */
.thread-column .slot-row .slot-value:empty:not(.slot-pending) {
  display: none;
}

/* M08: only animate on first appearance. JS adds .thread-new for one
   render after creating the column DOM, then removes on animationend. */
.thread-column.thread-new {
  animation: thread-slide-in 280ms ease-out;
}

.thread-column .thread-header {
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-size: 11px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 0.2rem 0;
  border-bottom: 1px dashed var(--border);
  /* The column uses `flex-direction: column-reverse`, which reverses the
     flex MAIN AXIS: first DOM child renders at the BOTTOM, last at the
     TOP. To put the header at the visual TOP we need it to be the LAST
     item in flex order — set `order: 99` (any value > frames' default 0)
     to push it to the END of flex order, which equals the TOP with
     column-reverse. */
  order: 99;
}

.thread-column.thread-current .thread-header {
  color: var(--accent);
  border-bottom-color: var(--accent);
  font-weight: 600;
}

.thread-column.thread-joined {
  opacity: 0.5;
  filter: grayscale(0.4);
}

.thread-column.thread-ready {
  opacity: 0.7;
}

@keyframes thread-slide-in {
  from {
    transform: translateX(30%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* **M07.2**: static-memory region (read-only data segment). Holds blocks
   for string literals; persists for the whole trace. Visually distinct
   from heap — gray gradient background + muted gray byte-cells. */
#static {
  padding: 0.5rem 1rem;
  background: linear-gradient(to bottom, #f1f1f0, #e7e7e6);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-height: 60px;
  /* 020-foldable-resizable-panels: the legacy `flex: 0.6` lived here when
     `<section>` was the panel itself. The `.panel[data-panel="static"]`
     wrapper now drives horizontal sizing — defaults to 10% in T004. */
}

/* 020-foldable-resizable-panels: the post-M08 auto-collapse heuristic
   becomes SUBORDINATE to explicit user state:
     - `.panel.is-folded`              → user explicitly folded (sliver)
     - `.panel.is-user-overridden`     → user explicitly unfolded an empty
                                         panel (full width even when empty)
     - `.panel.panel-empty`            → auto-collapse hint from renderUi()
                                         based on snapshot content
   The auto-collapse only fires when neither user-state class is present.
   Both `.is-folded` and the auto-collapse render identically as a 28px
   sliver. `flex-grow: 0` zeros the panel's share of leftover space; the
   OTHER panels absorb the freed space proportionally to their own grow
   values. No empty right-edge gap when STATIC + VTABLES collapse. */
.panel.is-folded,
.panel.panel-empty:not(.is-folded):not(.is-user-overridden) {
  flex-grow: 0 !important;
  flex-basis: 28px !important;
  min-width: 28px;
  overflow: hidden;
  cursor: pointer; /* whole sliver is click-target for unfold */
}

/* Vertical-text header in sliver mode. `writing-mode: vertical-rl` reads
   bottom-to-top; the title is then rotated 180° so it reads top-to-bottom. */
.panel.is-folded .panel-header,
.panel.panel-empty:not(.is-folded):not(.is-user-overridden) .panel-header {
  writing-mode: vertical-rl;
  padding: 0.5rem 0;
  justify-content: flex-start;
  gap: 0.5rem;
  border-bottom: none;
  border-right: 1px dashed var(--border);
  height: 100%;
}

.panel.is-folded .panel-title,
.panel.panel-empty:not(.is-folded):not(.is-user-overridden) .panel-title {
  transform: rotate(180deg);
  font-size: 10px;
  opacity: 0.6;
}

/* Hide content in sliver mode — only the vertical header strip shows.
   `!important` needed because the inner sections have id-scoped rules
   (`#heap { display: flex }`, `#static { display: flex }`,
   `#vtables { display: flex }`) with higher specificity. Without
   `!important` the section stays visible at 28px and `getBoundingClientRect`
   on heap-box / static-block returns a non-empty rect, which breaks the
   `getEffectiveRect` arrow fallback that depends on the descendant's
   rect being zero. */
.panel.is-folded > section,
.panel.panel-empty:not(.is-folded):not(.is-user-overridden) > section {
  display: none !important;
}

/* In USER fold mode, rotate the `−` glyph so it reads as a vertical bar
   (signaling "I'm folded, click to expand"). Auto-collapse keeps the
   original `−` to differentiate UX intent. */
.panel.is-folded .panel-fold-btn {
  transform: rotate(90deg);
}

#static .static-label {
  font-style: italic;
  font-size: 11px;
  color: var(--muted);
  letter-spacing: 0.5px;
  text-transform: uppercase;
}

#static .static-blocks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-content: flex-start;
}

.static-block {
  display: inline-flex;
  flex-direction: column;
  border: 1px solid #b5b5b3;
  border-radius: 4px;
  padding: 0.3rem 0.5rem;
  background: #fafafa;
  font-size: 12px;
}

.static-block .static-addr {
  font-size: 10px;
  color: var(--muted);
  font-family: ui-monospace, SFMono-Regular, monospace;
}

.static-block .static-display {
  font-family: ui-monospace, SFMono-Regular, monospace;
  margin: 2px 0;
}

.static-block .static-cells {
  display: flex;
  gap: 1px;
}

.static-block .byte-cell {
  width: 8px;
  height: 8px;
  border: 1px solid #999;
  background: #c8c8c6;
  box-sizing: border-box;
}

/* **M07.7**: VTABLES panel. One box per `(trait, type)` pair; persists for
   the trace's lifetime. Visually distinct from static — warm cream background
   + orange accent (matches the dispatch-arrow color). */
#vtables {
  padding: 0.5rem 1rem;
  background: linear-gradient(to bottom, #fbf6ee, #f0e8d8);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-height: 60px;
  /* 020-foldable-resizable-panels: legacy `flex: 0.6` removed; the
     `.panel[data-panel="vtables"]` wrapper drives horizontal sizing. */
}

#vtables .vtables-label {
  font-style: italic;
  font-size: 11px;
  color: var(--muted);
  letter-spacing: 0.5px;
  text-transform: uppercase;
}

#vtables .vtable-blocks {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  align-content: flex-start;
}

.vtable-box {
  display: inline-flex;
  flex-direction: column;
  border: 1px solid #c9a96a;
  border-radius: 4px;
  padding: 0.3rem 0.5rem;
  background: #fdfaf2;
  font-size: 12px;
  max-width: 100%;
}

.vtable-box .vtable-header {
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-weight: 600;
  font-size: 11px;
  color: #b46e16;
  margin-bottom: 2px;
}

.vtable-box .vtable-methods {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.vtable-box .vtable-method {
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-size: 11px;
  color: #5a4a2a;
  padding: 1px 2px;
  border-radius: 2px;
  transition: background 120ms ease-out, color 120ms ease-out;
}

/* **M07.7**: transient highlight on a vtable method row when dispatch
   resolved to that method. Set by `renderDispatchArrow` at the FrameEnter
   cursor step; cleared on the next step. The visual is "this row was
   looked up" — the indirection step made concrete without a second arrow. */
.vtable-box .vtable-method.vtable-method-highlighted {
  background: #f4d398;
  color: #6a3a08;
  font-weight: 600;
}

/* **M07.7**: trait-object slot rendering — fat-pointer cells inside the
   slot's value column. Two stacked labeled rows (`data:` and `vtable:`)
   make the 16-byte fat-pointer composition tangible. Mirrors the
   struct-view's labeled-row pattern but with orange accent (matches
   vtable visual language). */
.dyn-fat-pointer {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 2px 0;
  padding: 3px 6px;
  border: 1px solid #c9a96a;
  border-radius: 3px;
  background: #fdfaf2;
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-size: 11px;
}

.dyn-cell {
  display: flex;
  gap: 6px;
  align-items: baseline;
}

.dyn-cell-label {
  color: var(--muted);
  font-size: 10px;
  min-width: 40px;
}

.dyn-cell-target {
  color: #5a4a2a;
}

/* **M07.7**: trait-object dispatch arrows. Dashed orange to convey
   "runtime indirection" — visually distinct from M06-7's solid palette
   (blue=shared, red=mut, black=owning, orange-solid=copy). Transient —
   visible only at the dispatch cursor step. */
.arrow-vtable-dispatch {
  stroke: #b46e16;
  stroke-width: 2;
  stroke-dasharray: 4 3;
  fill: none;
}

.arrow-vtable-dispatch-label {
  fill: #b46e16;
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-size: 10px;
  font-weight: 600;
}

/* **M08**: Arc shared-ownership arrows. Dashed purple to distinguish
   from black owning (Box) and blue/red borrows. Hover-only per the
   post-M07.7 arrow-viz rule (see [[feedback-arrow-viz-rules]]). */
.arrow-arc {
  stroke: #8a4fb4;
  stroke-width: 2;
  stroke-dasharray: 4 3;
  fill: none;
}

/* **M08**: refcount suffix on Arc heap blocks. Small muted text appended
   to the heap-block addr line. */
.heap-box .heap-refcount {
  font-size: 9px;
  color: #8a4fb4;
  font-weight: 600;
  margin-left: 0.3rem;
}

/* **Post-M08 polish**: moved bindings stay on the frame card with
   grayed-out styling. Rust's move semantics: the stack bytes physically
   persist; only the binding becomes unusable (compiler prevents further
   references; the new owner runs Drop in their scope). Matches the
   project's M03.1 "memory persists until reused" principle that already
   grays out returned frames and freed-heap pointers. */
.slot-row.slot-moved .slot-name,
.slot-row.slot-moved .slot-ty {
  color: var(--muted);
  text-decoration: line-through;
  opacity: 0.6;
}

.slot-row.slot-moved .slot-value.slot-moved-value {
  color: var(--muted);
  font-style: italic;
  opacity: 0.7;
}

/* **Post-M08 polish**: Mutex lock-state badge on Mutex / Arc<Mutex<T>>
   heap blocks. Green pill when free; red when locked by a thread.
   Appended to the heap-block addr line alongside the refcount. */
.heap-box .heap-mutex-state {
  display: inline-block;
  margin-left: 0.3rem;
  padding: 0 5px;
  border-radius: 8px;
  font-size: 9px;
  font-weight: 700;
  font-family: ui-monospace, SFMono-Regular, monospace;
  letter-spacing: 0.3px;
  vertical-align: 1px;
}

.heap-box .heap-mutex-state.mutex-free {
  background: #d8f5d8;
  color: #2a8a3f;
  border: 1px solid #8fce96;
}

.heap-box .heap-mutex-state.mutex-locked {
  background: #f7d8d8;
  color: #b53030;
  border: 1px solid #d88a8a;
}

#heap {
  /* M07: the later #heap rule above defines padding for the active heap
     panel (4rem top to give arrow lanes room). This rule only contributes
     the muted color (carried from the M04 placeholder era). */
  color: var(--muted);
}

#heap .placeholder {
  margin: 0;
  font-style: italic;
}

.frame-card {
  background: var(--frame-bg);
  border: 1px solid var(--frame-border);
  border-radius: 4px;
  padding: 0.4rem 0.6rem;
  /* M06.1: extra left padding gives borrow-arrow gutters room without
     overlapping the slot text. */
  padding-left: 2.5rem;
}

.frame-card .frame-header {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.25rem;
}

.frame-card .frame-name {
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--accent);
}

/* M03.1: transient return-value annotation on a frame card, shown for one
   cursor step between body completion and FrameLeave. */
.frame-card .frame-return-value {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 12px;
  font-weight: 600;
  color: #2a8a3f;
}

/* M03.1: the innermost active frame — the one whose body is currently
   executing. Other active frames are paused callers waiting for their
   callee to return. A red frame name + red left border make "where
   execution is right now" instantly readable. */
.frame-card.frame-current {
  border-left: 3px solid #c62828;
}

.frame-card.frame-current .frame-name {
  color: #c62828;
}

/* M03.1: frame card lingers after FrameLeave to convey that stack bytes
   persist until reused. The header (frame name + return-value annotation)
   stays at full opacity so the return value is still legible; only the slots
   area is grayed to signal "no longer the active frame's working memory". */
.frame-card.frame-grayed {
  background: #f5f5f7;
}

.frame-card.frame-grayed .frame-name {
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  color: var(--muted);
}

.frame-card.frame-grayed .slots {
  opacity: 0.45;
}

.frame-card .slots {
  display: grid;
  grid-template-columns: auto auto 1fr;
  gap: 0.15rem 0.6rem;
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 12px;
}

.slot-row {
  display: contents;
}

.slot-row .slot-name {
  font-weight: 600;
}

.slot-row .slot-ty {
  color: var(--muted);
}

.slot-row .slot-value {
  color: var(--text);
}

.slot-row .slot-value.slot-pending::before {
  content: "?";
  color: var(--muted);
  font-style: italic;
}

#status {
  padding: 0.5rem 1rem;
  border-top: 1px solid var(--border);
  font-size: 0.9rem;
}

#status.status-error {
  background: #fff0f3;
  color: var(--error);
  font-weight: 600;
}

#status.status-info {
  background: #f0f7fa;
  color: var(--info);
}

footer#toolbar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  background: var(--header-bg);
  border-top: 1px solid var(--border);
}

footer#toolbar button {
  font: inherit;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--button-bg);
  cursor: pointer;
}

footer#toolbar button:hover {
  background: var(--button-hover);
}

footer#toolbar button:disabled {
  opacity: 0.5;
  cursor: default;
}

#step-indicator {
  margin-left: auto;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* 021-randomized-scheduler: seed input + re-roll button. Lives in the
   toolbar between the play controls and the step indicator. */
.seed-label {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 11px;
  color: var(--muted);
  margin-left: 1rem;
}

#seed-input {
  width: 6em; /* fits 10-digit u32 max (4294967295) */
  padding: 2px 6px;
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 12px;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: white;
}

#seed-input:disabled {
  opacity: 0.5;
  cursor: default;
}

#btn-reroll-seed {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 2px 6px;
  font-size: 14px;
  cursor: pointer;
  line-height: 1;
}

#btn-reroll-seed:hover {
  background: var(--frame-bg);
}

#btn-reroll-seed:disabled {
  opacity: 0.5;
  cursor: default;
}
