/* ═══════════════════════════════════════
   VISUALIZER — multi-pane layout
   ═══════════════════════════════════════ */

body { padding: 24px 18px; }

.app {
  display: flex;
  gap: 18px;
  max-width: 1900px;
  margin: 0 auto;
  align-items: flex-start;
}

/* ── Header / global toolbar ─────────────────────── */
.header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.header-row h1 {
  font-size: 0.95rem;
  letter-spacing: .18em;
  color: var(--accent);
  text-transform: uppercase;
}
.header-row .sub {
  font-size: 0.7rem;
  color: var(--muted);
}

.global-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  margin-bottom: 14px;
  font-size: 0.7rem;
  color: var(--muted);
}
.global-bar .scr { display: flex; align-items: center; gap: 8px; }
/* Dynamic container for the user's pinned shortcuts. Lays them out
   in the same row as the sync buttons / Present / ⚙ — we just delegate
   spacing to .global-bar's existing gap. */
.toolbar-shortcuts {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
/* Reserve a fixed-width slot for the slider's value readout so its
   numeric label doesn't reflow the buttons to its right while the
   user drags. Sized to fit the widest label each slider can show
   ("24px" / "1000x") plus a small buffer; tabular-nums keeps digits
   aligned across values. */
.global-bar #scv,
.global-bar #speedv {
  display: inline-block;
  text-align: left;
  font-variant-numeric: tabular-nums;
}
.global-bar #scv    { min-width: 4ch; }
.global-bar #speedv { min-width: 5ch; }

/* Square icon button used for the toolbar's glyph triggers. Overrides
   .btn's uppercase + letter-spacing + asymmetric padding (the trailing
   letter-spacing on a single char visually nudges it off-centre) and
   bumps the glyph size so it reads as an icon, not shrunken text. The
   ⚙ uses an inline SVG (font glyphs varied too much across systems —
   IBM Plex Mono's gear is tiny + slightly off-centre and the system
   fallbacks don't agree on metrics); ? stays a text char since it
   renders cleanly in the body font. */
.global-bar .btn-glyph {
  width: 36px;
  height: 36px;
  padding: 0;
  font-size: 1.1rem;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}
.global-bar .btn-glyph svg {
  width: 18px;
  height: 18px;
  display: block;
}

/* ── Play-arrow span (▶) ──────────────────────────────
   The ▶ glyph (U+25B6) sits below the optical centre of a text line
   because the character's baseline is at the bottom of the triangle.
   Wrapping it in <span class="pa"> + a small translateY pulls it up
   so it lines up with adjacent capital letters in buttons like
   "Step ▶", "Sync ▶ Step", "▶ Present". Slightly smaller too — the
   default size is visually heavier than the text next to it. */
.pa {
  display: inline-block;
  font-size: 0.85em;
  transform: translateY(-1px);
  /* Cancel the parent button's letter-spacing on this single glyph
     so it doesn't pick up an extra trailing gap. */
  letter-spacing: 0;
}

/* ── Icon-glyph button (history ⟲, etc.) ──────────────
   Used for in-pane buttons whose label is a single Unicode icon. The
   default .btn / .btn-sec uppercase + letter-spacing renders these
   icons tiny and off-centre. This modifier sizes them like real
   icons: bigger glyph, square-ish padding, centred via inline-flex. */
.btn-icon-glyph {
  font-size: 1.1rem;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  padding: 6px 12px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ── Custom <select> arrow ────────────────────────────
   Native select arrows (especially on Linux/Firefox) cram against the
   right edge. Strip the default arrow and paint our own SVG chevron
   with proper right-padding so the glyph has breathing room. */
.ctrl-bar select.strat-sel,
.global-bar select,
.settings-body .setting-select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'><path fill='none' stroke='%23a0e0ff' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M1.5 2l4.5 4.5L10.5 2'/></svg>");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 10px 7px;
  padding-right: 28px;
}

/* ── Right column = panes container ─────────────── */
.right-col {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* Default layout: panes stack vertically so each one gets the full
   available width (the diagram is the dominant zone, and a 360-px-wide
   pane crowds the diagram). The legacy `.stack` class flips back to a
   wrapping flex grid for users who still want side-by-side comparison. */
.panes-host {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding-bottom: 6px;
}
.panes-host.stack {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: stretch;
}
.panes-host.stack .pane { flex: 1 1 480px; }
.add-pane-btn {
  background: transparent;
  border: 1px dashed var(--border);
  color: var(--muted);
  cursor: pointer;
  font-family: inherit;
  font-size: 0.78rem;
  padding: 14px 18px;
  letter-spacing: .12em;
  text-transform: uppercase;
  transition: border-color .15s, color .15s;
}
/* In stacked (side-by-side) mode the add button stretches vertically;
   in default vertical mode it spans the full row width and stays short. */
.panes-host.stack .add-pane-btn { align-self: stretch; padding: 0 18px; min-width: 60px; }
.add-pane-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* ── A single pane ─────────────────────────────── */
/* Layout favours the diagram: the editor + controls collapse to compact
   rows, and the diagram window keeps `flex: 1` so it grows with the
   pane. The min-height keeps the default size short enough that the
   "+ Add another" button stays visible without scrolling — users who
   want a taller diagram can resize the window or fullscreen the pane. */
.pane {
  width: 100%;
  min-width: 0;
  min-height: 520px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 12px;
}
.pane.focused { border-color: var(--accent-2); }

.pane-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.pane-title {
  font-size: 0.7rem;
  letter-spacing: .14em;
  color: var(--accent);
  text-transform: uppercase;
}
.pane-actions { display: flex; gap: 4px; margin-left: auto; }
.pane-close, .pane-fold {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  width: 22px; height: 22px;
  cursor: pointer;
  font-size: 0.75rem;
  font-family: inherit;
}
.pane-close:hover { color: var(--danger); border-color: var(--danger); }
.pane-fold:hover { color: var(--accent); border-color: var(--accent); }

/* ── Editor row ─────────────────────────────────── */
.pane .ir {
  display: flex;
  gap: 8px;
  align-items: stretch;
}
.pane .editor-wrap {
  position: relative;
  flex: 1;
  display: flex;
  min-width: 0;
}

/* Foldable code editor (contenteditable) */
.editor {
  flex: 1;
  min-height: 44px;
  max-height: 320px;
  overflow-y: auto;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.92rem;
  line-height: 1.55;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 10px 13px;
  white-space: pre-wrap;
  word-break: break-word;
  outline: none;
  transition: border-color .15s;
}
.editor:focus { border-color: var(--accent-2); }
.editor.invalid { border-color: var(--danger); }
.editor[placeholder]:empty::before {
  content: attr(placeholder);
  color: var(--muted);
  pointer-events: none;
}

/* Keyboard focus ring inside the visualizer — green to stay consistent
   with the .pane.focused border. Only applied here (not via a global
   theme rule) to avoid the Opera-side rendering quirk that was painting
   the left/right edges of focused panes white.
   Form fields (input, select, textarea) and the contenteditable editor
   are excluded because they ALREADY show focus via their own
   border-color change — adding an outline on top would draw two parallel
   rings. Only buttons / links / other "borderless" interactive elements
   actually need the focus ring for keyboard accessibility. */
.app :focus-visible {
  outline: 1px solid var(--accent-2);
  outline-offset: 2px;
}
.app input:focus-visible,
.app select:focus-visible,
.app textarea:focus-visible,
.app .editor:focus-visible {
  outline: none;
}
.app :focus:not(:focus-visible) { outline: none; }

/* Foldable λ-bodies */
.editor .fold {
  display: inline;
  cursor: default;
}
.editor .fold-toggle {
  display: inline-block;
  cursor: pointer;
  color: var(--accent);
  user-select: none;
  font-weight: 500;
  margin-right: 2px;
  padding: 0 2px;
  border-radius: 2px;
}
.editor .fold-toggle:hover { background: var(--surface-2); }
.editor .fold.collapsed > .fold-body { display: none; }
.editor .fold.collapsed > .fold-placeholder { display: inline; color: var(--muted); font-style: italic; }
.editor .fold:not(.collapsed) > .fold-placeholder { display: none; }

/* Autocomplete dropdown */
.autocomplete-list {
  position: absolute;
  top: 100%;
  left: 0; right: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: none;
  max-height: 240px;
  overflow-y: auto;
  z-index: 100;
  display: none;
}
.autocomplete-list.open { display: block; }
.autocomplete-item {
  padding: 8px 13px;
  cursor: pointer;
  font-size: 0.82rem;
  display: flex;
  gap: 10px;
  align-items: center;
  border-bottom: 1px solid #181820;
}
.autocomplete-item:last-child { border-bottom: none; }
.autocomplete-item:hover, .autocomplete-item.selected { background: #1a1a25; }
.autocomplete-name { color: var(--accent); font-weight: 500; min-width: 60px; }
.autocomplete-expr {
  color: var(--muted);
  font-size: 0.72rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
}

.input-error {
  font-size: 0.7rem;
  color: var(--danger);
  margin-top: 4px;
  min-height: 1em;
}
.input-error.suggestion { color: var(--warn); }
/* Soft confirmation that the autosaved draft just came back. Held for
   ~1.9s then faded so it doesn't sit there permanently — the JS clears
   the text after the animation finishes. */
.input-error.draft-hint {
  color: var(--muted);
  animation: draft-hint-fade 2500ms forwards;
}
@keyframes draft-hint-fade {
  0%, 76% { opacity: 1; }
  100% { opacity: 0; }
}

/* ── Find bar ─────────────────────────────────── */
.find-bar {
  display: none;
  gap: 6px;
  align-items: center;
  padding: 8px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  flex-wrap: wrap;
}
.find-bar.open { display: flex; }
.find-bar input {
  flex: 1;
  min-width: 100px;
  padding: 6px 10px;
  font-size: 0.8rem;
}
.find-bar button { padding: 6px 10px; font-size: 0.66rem; }
.find-bar .count { color: var(--muted); font-size: 0.7rem; min-width: 50px; }

/* ── Per-pane control bar (single row) ─────────── */
.ctrl-bar {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
  font-size: 0.78rem;
  color: var(--muted);
}
.ctrl-bar .btn { padding: 7px 13px; font-size: 0.78rem; }
/* Strategy dropdown sits inline with the rest of the controls. */
.ctrl-bar .strat-sel {
  font-size: 0.78rem;
  padding: 6px 10px;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
}
.ctrl-sep {
  width: 1px;
  height: 22px;
  background: var(--border);
  margin: 0 3px;
}
.ctrl-max {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.72rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .08em;
}
.ctrl-max input.max-steps {
  width: 64px;
  padding: 6px 8px;
  font-size: 0.78rem;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
}
/* Icon button: square, shows a single glyph; padded to roughly match
   .btn height so the row stays visually aligned. */
.ctrl-icons {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-left: 2px;
}
/* Icon button: square box with a single SVG centered inside. The flex
   centering handles SVG positioning, so we don't need line-height tricks
   that would also crush the tooltip's text. */
.icon-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  font-family: inherit;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
  padding: 0;
}
.icon-btn svg {
  width: 16px;
  height: 16px;
  display: block;
  flex-shrink: 0;
}
.icon-btn:hover { color: var(--accent); border-color: var(--accent); }
.icon-btn[data-act="rec"] { color: #f08080; }
.icon-btn[data-act="rec"].active { background: rgba(240, 128, 128, 0.18); }
/* Record-button states.
   - Idle (default): show the filled circle, hide the square.
   - .recording:     swap to the filled square, hold the red border,
     gently pulse so it's obvious capture is in progress.
   The shape swap stays inside one SVG so the icon box and surrounding
   button geometry never shift between states. */
.icon-btn[data-act="rec"] .rec-active { display: none; }
.icon-btn[data-act="rec"].recording .rec-idle { display: none; }
.icon-btn[data-act="rec"].recording .rec-active { display: block; }
.icon-btn[data-act="rec"].recording {
  color: #f08080;
  border-color: #f08080;
  background: rgba(240, 128, 128, 0.12);
  animation: rec-pulse 1.4s ease-in-out infinite;
}
.icon-btn[data-act="rec"].recording:hover {
  /* Override .icon-btn:hover so hover doesn't drag the colour back to
     accent-cyan while a recording is active. */
  color: #f08080;
  border-color: #f08080;
}
@keyframes rec-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}
@media (prefers-reduced-motion: reduce) {
  .icon-btn[data-act="rec"].recording { animation: none; }
}

.scr { display: flex; align-items: center; gap: 6px; }
input[type=range] { accent-color: var(--accent); width: 90px; }

.status { font-size: 0.7rem; color: var(--muted); margin-left: auto; }
.status.nf { color: var(--accent-2); }
.status.max { color: var(--warn); }
.status.running { color: var(--accent); }

/* ── Code display ─────────────────────────────── */
.code-display {
  font-size: 0.88rem;
  padding: 10px 14px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  letter-spacing: .02em;
  overflow-x: auto;
  white-space: nowrap;
  position: relative;
}
/* When the code-display contains a .code-inner wrapper, the outer becomes
   a non-scrolling frame and the inner handles horizontal scrolling. That
   keeps the absolute .copy-btn anchored to the visible top-right corner
   instead of drifting with the scrolled content. */
.code-display:has(.code-inner) {
  overflow: hidden;
  padding: 0;
}
.code-display .code-inner {
  padding: 10px 14px;
  overflow-x: auto;
  white-space: nowrap;
  /* Reserve room on the right so very long expressions don't run under
     the copy button when scrolled to the start. */
  padding-right: 56px;
}
.code-display .copy-btn {
  position: absolute;
  top: 4px; right: 4px;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  font-family: inherit;
  font-size: 0.6rem;
  padding: 2px 6px;
  cursor: pointer;
  letter-spacing: .08em;
  text-transform: uppercase;
  opacity: 0;
  transition: opacity .15s, color .15s;
}
.code-display:hover .copy-btn { opacity: 1; }
.code-display .copy-btn:hover { color: var(--accent); border-color: var(--accent); }
.code-display .var-occ.highlight {
  background: rgba(255, 128, 96, 0.25);
  outline: 1px solid #ff8060;
  border-radius: 2px;
}

/* ── Diagram window ─────────────────────────────── */
/* Diagram keeps flex:1 so it eats whatever vertical space is left after
   the controls + code display. The min-height is intentionally modest:
   tall enough to read a typical reduction at a glance, short enough
   that the page-level "+ Add another visualizer" button stays in view
   on a normal laptop. */
.dw {
  background: var(--surface-2);
  border: 1px solid var(--border);
  padding: 14px;
  min-height: 280px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  position: relative;
  user-select: none;
  flex: 1;
}
.dw.panning { cursor: grabbing; }
.dw:not(.panning) { cursor: grab; }
.dw svg { transition: width var(--dur) var(--ease), height var(--dur) var(--ease); }
/* No `opacity` transition here — JS RAF (animateLine) drives line opacity
   directly, and a CSS transition on the same property fights with it and
   makes fade-outs invisible. Stroke can still transition (color changes are
   rare with stable hashing, but smooth when they do happen). */
.dw svg line { transition: stroke 200ms linear; }
.dw svg line.highlighted { stroke: #ff8060 !important; filter: drop-shadow(0 0 3px #ff8060); }
.dw svg .redex-zone {
  fill: transparent;
  cursor: pointer;
  transition: fill 150ms;
}
.dw svg .redex-zone:hover { fill: rgba(255, 128, 96, 0.08); }

/* Brief flash on the lines of a redex right before β-reduction kicks in.
   Pure-CSS keyframe so it doesn't interfere with the position/opacity RAF. */
@keyframes redex-flash {
  0%   { filter: drop-shadow(0 0 0 transparent); }
  40%  { filter: drop-shadow(0 0 8px #ffd060) brightness(1.6); }
  100% { filter: drop-shadow(0 0 0 transparent); }
}
.dw svg line.redex-flash { animation: redex-flash 220ms ease-out; }

.zoom-controls {
  position: absolute;
  bottom: 6px; right: 6px;
  display: flex;
  gap: 4px;
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 3px;
  z-index: 10;
  font-size: 0.7rem;
}
.zoom-btn {
  background: transparent;
  border: none;
  color: var(--muted);
  width: 24px; height: 24px;
  cursor: pointer;
  font-size: 0.95rem;
  line-height: 1;
}
.zoom-btn:hover { color: var(--accent); }
.zoom-level {
  color: var(--muted);
  padding: 0 6px;
  align-self: center;
  min-width: 40px;
  text-align: center;
}
.fs-btn {
  position: absolute;
  top: 6px; right: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--muted);
  width: 26px; height: 26px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
  z-index: 10;
}
.fs-btn:hover { color: var(--accent); border-color: var(--accent); }

/* Fullscreen for an individual pane */
.dw.fullscreen {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  z-index: 1000;
  padding: 60px 24px 24px 24px;
  min-height: 100vh;
  max-height: 100vh;
  border: none;
  display: block;
  overflow: auto;
  /* Match the SVG's internal background <rect> color (#0a0a12) so the
     SVG box blends seamlessly with the rest of the fullscreen surface. */
  background: #0a0a12;
}
.dw.fullscreen .fs-btn { top: 16px; right: 16px; }
/* When any pane is in fullscreen, hide the burger nav so it doesn't cover
   the ⛶ exit button at top-right. Esc or the ⛶ button still exit. */
body.has-fullscreen-pane .topnav,
body.has-fullscreen-pane .nav-drawer,
body.has-fullscreen-pane .nav-overlay { display: none !important; }
.dw.fullscreen svg {
  position: absolute;
  top: 60px; left: 24px;
  transform-origin: 0 0;
  /* The SVG's intrinsic width/height stay sized to the diagram. autoFit
     scales the inner <g> up to fill the viewport, so without this the
     enlarged content gets clipped by the SVG's default overflow:hidden
     (showing only a tiny rect in the top-left corner). */
  overflow: visible;
}

/* ── Sync controls (multi-pane) ─────────────── */
.sync-bar {
  display: flex;
  gap: 10px;
  padding: 8px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  align-items: center;
  font-size: 0.7rem;
  color: var(--muted);
}
.sync-bar .label { letter-spacing: .12em; text-transform: uppercase; }

/* ── Presentation mode ─────────────────────────── */
body.presentation { padding: 0; overflow: hidden; }
body.presentation .app > .sidebar,
body.presentation .global-bar,
body.presentation .header-row,
body.presentation .topnav,
body.presentation .sync-bar,
body.presentation .add-pane-btn,
body.presentation .pane-header,
body.presentation .pane .ir,
body.presentation .pane .find-bar,
body.presentation .pane .input-error,
body.presentation .pane .ctrl-bar,
body.presentation .info {
  display: none !important;
}
body.presentation .app {
  flex-direction: column;
  gap: 0;
  max-width: none;
  padding: 0;
  margin: 0;
  height: 100vh;
}
body.presentation .right-col {
  flex: 1;
  height: 100vh;
  padding: 60px 80px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 20px;
}
body.presentation .panes-host {
  flex: 1;
  overflow: visible;
  padding: 0;
  gap: 30px;
}
body.presentation .pane {
  background: transparent;
  border: none;
  padding: 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
body.presentation .pane .code-display {
  text-align: center;
  /* Long expressions hurt at 1.4rem and 22-char lines wrap weirdly at
     huge sizes; clamp() shrinks the font on long terms so it stays
     readable without pushing the diagram off the bottom of the screen. */
  font-size: clamp(0.85rem, 1.6vw, 1.4rem);
  padding: 14px 24px;
  background: transparent;
  border: none;
  /* Hard cap on how much vertical room the prettifier can claim, so a
     5-line expression doesn't squeeze the diagram into a sliver. The
     residual scroll keeps the rest reachable. */
  max-height: 30vh;
  overflow-y: auto;
  /* Reset the .code-inner outer-clipping rule from the visualiser base
     so the inner wrapper can lay out normally below. */
  overflow-x: visible;
}
body.presentation .pane .code-display:has(.code-inner) {
  padding: 14px 24px;
  overflow: visible;
  overflow-y: auto;
  max-height: 30vh;
}
/* The .code-inner wrapper exists for the desktop copy-button anchoring
   trick; in presentation we want plain wrapping text instead, no
   horizontal scrollbar, no padding-right reservation for a button
   that's already hidden below. */
body.presentation .pane .code-display .code-inner {
  white-space: normal;
  word-break: break-word;
  overflow: visible;
  padding: 0;
  line-height: 1.45;
}
body.presentation .pane .code-display .copy-btn { display: none !important; }
body.presentation .pane .dw {
  flex: 1;
  padding: 0;
  border: none;
  background: transparent;
  min-height: 0;
  display: block;
  position: relative;
  /* Anything sticking past the SVG box during the post-step refit looks
     like a "the diagram escaped" bug; clip it to the diagram window. */
  overflow: hidden;
}
body.presentation .pane .dw svg {
  position: absolute;
  top: 0; left: 0;
  transform-origin: 0 0;
  overflow: visible;
  /* In normal mode the SVG width/height CSS-transition over var(--dur)
     so the box grows smoothly. In presentation we re-autoFit after every
     step and that fit reads the *attribute* (final size); if the visible
     SVG box is still in flight, the fitted viewport would briefly clip.
     Snap the resize instead so fit + content always agree. */
  transition: none;
}
body.presentation .pane .fs-btn,
body.presentation .pane .zoom-controls { display: none; }

/* Single-pane presentation: hide every pane that wasn't chosen on
   enterPresentation. Multi-pane present mode is deferred — for now
   only the focused / first-drawn pane is shown so we can keep the
   layout (expression + diagram + corner HUD) clean. */
body.presentation .pane { display: none !important; }
body.presentation .pane.presented { display: flex !important; }

/* God-output in presentation — only kicks in while a reduction is
   actually mid-flight (.pane.reducing). Previously this rule was
   unconditional, which meant the loader showed up in presentation
   even when god mode was idle (no Run pressed yet).

   Loader + caption are pinned to the VIEWPORT centre with
   position: fixed. Trying to centre via the layout (flex/grid inside
   .god-output) was unreliable because the surrounding right-col
   padding + flex tree shifted the visual centre subtly. position:
   fixed bypasses that whole stack and just puts the orb at 50/50 of
   the window. No ancestor of the loader has a transform, so fixed
   resolves against the viewport as expected. */
body.presentation .pane.reducing .god-output {
  background: transparent !important;
  border: none !important;
  padding: 0 !important;
  min-height: 0 !important;
}
body.presentation .pane.reducing .loader {
  position: fixed;
  top: 50%;
  left: 50%;
  /* The .loader has no explicit width (its size comes from .box =
     100×100). Shift by -50px so the box centre lands on viewport
     centre. */
  margin: -50px 0 0 -50px;
  z-index: 100;
}
body.presentation .pane.reducing .god-status {
  position: fixed;
  top: 50%;
  left: 50%;
  /* x: horizontally centre on its own width; y: 80px below the
     viewport centre, which clears the orb's bottom edge. */
  transform: translate(-50%, 80px);
  margin: 0;
  z-index: 100;
  white-space: nowrap;
}

/* Diagram window: give it a minimum height so it can't collapse, and
   flex-shrink:0 on the prettifier so the code-display always shows. */
body.presentation .pane .code-display { flex-shrink: 0; }
body.presentation .pane .dw {
  flex: 1 1 0;
  min-height: 160px;
}

/* ── Corner stats HUD (presentation only) ─────────────
   Top-right badge showing live step count + wall-time of the
   presented pane. Hidden outside presentation and toggled by the
   H key (same hud-hidden body class as the bottom keyboard HUD). */
.presentation-stats {
  display: none;
  position: fixed;
  top: 22px;
  right: 22px;
  z-index: 2000;
  background: rgba(16, 16, 26, 0.85);
  border: 1px solid var(--border);
  padding: 8px 14px;
  font-size: 0.74rem;
  letter-spacing: .08em;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  align-items: center;
  gap: 8px;
  backdrop-filter: blur(8px);
}
body.presentation .presentation-stats { display: inline-flex; }
body.presentation.hud-hidden .presentation-stats { display: none; }
.presentation-stats .ps-sep { color: var(--muted); }
.presentation-stats .ps-time { color: var(--text); }
body.presentation .presentation-hud { display: flex !important; }
/* "Hide HUD" (H key) only hides the HUD itself — the keyboard hints
   strip at the bottom and the step-counter badge at the top right.
   The expression / diagram stay visible because those *are* the
   content of the presentation, not overlay chrome. */
body.presentation.hud-hidden .presentation-hud { display: none !important; }

.presentation-hud {
  display: none;
  position: fixed;
  bottom: 24px; left: 50%;
  transform: translateX(-50%);
  background: rgba(16, 16, 26, 0.85);
  border: 1px solid var(--border);
  padding: 10px 20px;
  font-size: 0.7rem;
  color: var(--muted);
  gap: 18px;
  align-items: center;
  border-radius: 3px;
  z-index: 2000;
  backdrop-filter: blur(8px);
}
.presentation-hud kbd {
  background: #0a0a12;
  border: 1px solid var(--border);
  padding: 2px 6px;
  border-radius: 2px;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.65rem;
  color: var(--accent);
}

/* ── Settings modal ─────────────────────────── */
.settings-overlay {
  position: fixed;
  inset: 0;
  background: rgba(10, 10, 18, 0.78);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 9500;
  padding: 24px;
}
.settings-overlay.open { display: flex; animation: shortcuts-fade 180ms ease-out; }
.settings-card {
  background: var(--surface);
  border: 1px solid var(--border);
  max-width: 640px;
  width: 100%;
  max-height: calc(100vh - 80px);
  display: flex;
  flex-direction: column;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.55);
}
.settings-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
  font-size: 0.78rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent);
}
.settings-close {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  width: 28px; height: 28px;
  cursor: pointer;
  font-size: 1rem;
  font-family: inherit;
  line-height: 1;
}
.settings-close:hover { color: var(--danger); border-color: var(--danger); }
.settings-body {
  padding: 18px 22px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.settings-body section h4 {
  font-size: 0.66rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 10px;
  font-weight: 500;
}
.setting-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 0;
  border-top: 1px solid var(--border);
  cursor: pointer;
}
.setting-row:first-of-type { border-top: none; padding-top: 4px; }
/* Block layout (not flex) so inline children like the BETA badge keep
   their intrinsic width instead of being stretched into a full-row
   flex item. Description gets its own line via display:block + margin. */
.setting-label {
  font-size: 0.82rem;
  color: var(--text);
  min-width: 0;
  flex: 1 1 auto;
  line-height: 1.4;
}
.setting-desc {
  display: block;
  margin-top: 4px;
  font-size: 0.72rem;
  color: var(--muted);
  font-weight: 300;
  line-height: 1.45;
}
.setting-beta {
  display: inline-block;
  background: rgba(240, 192, 96, 0.16);
  color: var(--warn);
  border: 1px solid rgba(240, 192, 96, 0.45);
  font-size: 0.58rem;
  letter-spacing: .14em;
  padding: 1px 6px;
  margin-left: 6px;
  vertical-align: 1px;
}
/* Toggle switch: a pill-shaped button whose ::before is the knob.
   Driven by the .on class (set by syncSettingToggleUI) — no JS
   animation; the transition lives in CSS. */
.setting-toggle {
  position: relative;
  flex: 0 0 auto;
  width: 38px;
  height: 22px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  cursor: pointer;
  padding: 0;
  transition: background .18s ease, border-color .18s ease;
  align-self: center;
}
.setting-toggle::before {
  content: '';
  position: absolute;
  top: 2px; left: 2px;
  width: 16px; height: 16px;
  background: var(--muted);
  border-radius: 50%;
  transition: transform .18s var(--ease), background .18s ease;
}
.setting-toggle.on {
  background: rgba(160, 224, 255, 0.18);
  border-color: var(--accent);
}
.setting-toggle.on::before {
  transform: translateX(16px);
  background: var(--accent);
}
.setting-toggle:focus-visible {
  outline: 1px solid var(--accent-2);
  outline-offset: 2px;
}
/* Rows whose right-hand control is a select or text input instead of a
   toggle pill. The control sits on the right at a fixed width so labels
   line up vertically with the toggle rows above and below. */
.setting-row-input {
  align-items: center;
}
.setting-select,
.setting-input {
  flex: 0 0 auto;
  width: 150px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  font-family: inherit;
  font-size: 0.78rem;
  padding: 6px 8px;
  outline: none;
  transition: border-color .18s;
}
.setting-input { width: 100px; text-align: right; }
.setting-select:focus,
.setting-input:focus { border-color: var(--accent); }

.settings-foot {
  padding: 12px 20px;
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 10px;
}
/* Pushes the destructive "Reset to defaults" button to the far right
   so the safer export/import pair sits on the left side of the row. */
.settings-foot-spacer { flex: 1 1 auto; }

/* ── Pin counter pill in section header ────────────── */
.settings-body section h4 {
  display: flex;
  align-items: center;
  gap: 10px;
}
.pin-count {
  font-size: 0.62rem;
  letter-spacing: .08em;
  color: var(--muted);
  background: var(--surface-2);
  border: 1px solid var(--border);
  padding: 2px 8px;
  font-variant-numeric: tabular-nums;
  text-transform: none;
  font-weight: 400;
}
.pin-help {
  font-size: 0.72rem;
  color: var(--muted);
  margin: 0 0 12px;
  line-height: 1.5;
}

/* ── Per-row pin button ───────────────────────────────
   A small square button at the start of each pinnable .setting-row,
   containing a pushpin SVG. Both pinned and unpinned states stay in
   the muted gray palette so the modal doesn't light up with cyan
   accents — only the pin's fill changes (outline vs solid) to signal
   state. Direct hover lifts the colour to accent. .disabled appears
   when the toolbar is already at MAX. */
.setting-pin {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: center;
  transition: color .15s, border-color .15s, background .15s;
}
.setting-pin svg {
  width: 14px;
  height: 14px;
  fill: none;
  transition: fill .15s;
}
/* Pinned = filled pushpin, same gray as unpinned (deliberately not
   accent-coloured so the modal stays calm at rest). */
.setting-pin.pinned svg { fill: currentColor; }
.setting-pin:hover:not(.disabled) {
  color: var(--accent);
  border-color: var(--accent);
}
.setting-pin.disabled {
  opacity: 0.3;
  cursor: not-allowed;
}

/* Tighten the setting-row layout to absorb the new pin column at the
   start. The gap already separates pin from label. */
.setting-row { gap: 12px; }

/* Slider-row variant — same layout as a toggle row but the right slot
   holds the slider + its readout instead of the toggle pill. */
.setting-row-slider { align-items: center; }
.setting-slider {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
}
.setting-slider input[type=range] { width: 140px; }
.setting-slider-val {
  display: inline-block;
  min-width: 4ch;
  text-align: left;
  font-variant-numeric: tabular-nums;
  font-size: 0.74rem;
  color: var(--muted);
}

/* ── God mode — fast headless reduction ─────────────────
   Under .app.god-mode, panes hide everything diagram / pretty-printer /
   secondary-control related and show only the god-output area which
   carries the loader (during reduction) or the final reduced
   expression (after). The point of the mode is to chew through huge
   terms without paying for rendering, so most UI noise is suppressed.
*/

/* Permanent god-mode hides: only the controls / outputs that have no
   meaning without a step-by-step animation. Everything else (BLC,
   Recognize, Find, Copy, fold, fullscreen, strategy, history…) keeps
   working on the start / end AST. Sync ▶ Step also goes — stepping is
   the one sync action that doesn't fit god mode; Sync ▶ Run and
   Sync ▶ Reset still make sense (kick off all panes' fast reductions
   at once, or wipe them all back to start). */
.app.god-mode .pane [data-act="back"],
.app.god-mode .pane [data-act="step"],
.app.god-mode .pane [data-act="runN"],
.app.god-mode .pane [data-act="png"],
.app.god-mode .pane [data-act="rec"],
.app.god-mode .pane .ctrl-max,
.app.god-mode #syncStepBtn {
  display: none !important;
}
/* God-output container is invisible by default and only flips on while
   a reduction is mid-flight (the pane wears .reducing). During that
   window we also collapse the diagram + pretty-printer + the BLC /
   Recognize side-rows because their AST is churning at top speed and
   re-rendering them would defeat the whole point of god mode. */
.pane .god-output { display: none; }
.app.god-mode .pane.reducing .god-output { display: flex; }
.app.god-mode .pane.reducing .dw,
.app.god-mode .pane.reducing .code-display { display: none; }

/* During an in-progress god-mode reduction, lock every button on the
   pane EXCEPT Share — per spec. Inputs / select / editor also lock so
   the user can't change strategy or expression mid-flight. Reset
   relies on max-steps as the safety valve (default 1000 stops fast);
   if a user sets max=0 (∞) on a runaway term they can still refresh
   the page. The runToken in runGodMode would also pick up a Reset if
   we later relaxed this, so the contract holds either way. */
.app.god-mode .pane.reducing button:not([data-act="share"]) {
  pointer-events: none;
  opacity: 0.4;
}
.app.god-mode .pane.reducing input,
.app.god-mode .pane.reducing select,
.app.god-mode .pane.reducing textarea,
.app.god-mode .pane.reducing .editor {
  pointer-events: none;
  opacity: 0.55;
}

/* God-output panel: holds the loader + "Reducing…" caption. Fills the
   same vertical slot the diagram normally occupies so the pane height
   doesn't collapse to a stub mid-reduction. */
.god-output {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  min-height: 320px;
  padding: 30px 20px;
  background: var(--surface);
  border: 1px solid var(--border);
}
.god-status {
  color: var(--accent);
  font-size: 0.78rem;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.god-status .dots > span {
  display: inline-block;
  opacity: 0.25;
  animation: god-dot 1.2s infinite ease-in-out;
}
.god-status .dots > span:nth-child(2) { animation-delay: 0.18s; }
.god-status .dots > span:nth-child(3) { animation-delay: 0.36s; }
@keyframes god-dot {
  0%, 80%, 100% { opacity: 0.25; }
  40%           { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .god-status .dots > span { animation: none; opacity: 0.7; }
}

/* ── Loader (god mode) ───────────────────────────────
   From Uiverse.io by andrew-manzyk (see Ressources/test.html).
   Reproduced verbatim — only the @keyframes names are namespaced
   (loader-rotation / loader-roundness / loader-colorize) to avoid
   clashing with any future site-wide @keyframes. */
.loader {
  --color-one: #ffbf48;
  --color-two: #be4a1d;
  --color-three: #ffbf4780;
  --color-four: #bf4a1d80;
  --color-five: #ffbf4740;
  --time-animation: 2s;
  --size: 1;
  position: relative;
  border-radius: 50%;
  /* translateZ(0) is the classic "promote to its own compositor layer"
     trick — combined with will-change, it tells the browser the loader
     will be repainting continuously, so isolate it on the GPU rather
     than reflowing into the main page raster. */
  transform: scale(var(--size)) translateZ(0);
  will-change: filter, transform;
  box-shadow:
    0 0 25px 0 var(--color-three),
    0 20px 50px 0 var(--color-four);
  animation: loader-colorize calc(var(--time-animation) * 3) ease-in-out infinite;
}
.loader .box,
.loader svg #clipping,
.loader svg #clipping polygon {
  will-change: transform, filter;
}
.loader::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  border-top: solid 1px var(--color-one);
  border-bottom: solid 1px var(--color-two);
  background: linear-gradient(180deg, var(--color-five), var(--color-four));
  box-shadow:
    inset 0 10px 10px 0 var(--color-three),
    inset 0 -10px 10px 0 var(--color-four);
}
.loader .box {
  width: 100px;
  height: 100px;
  background: linear-gradient(
    180deg,
    var(--color-one) 30%,
    var(--color-two) 70%
  );
  mask: url(#clipping);
  -webkit-mask: url(#clipping);
}
.loader svg {
  position: absolute;
}
.loader svg #clipping {
  filter: contrast(15);
  animation: loader-roundness calc(var(--time-animation) / 2) linear infinite;
}
.loader svg #clipping polygon {
  filter: blur(7px);
}
.loader svg #clipping polygon:nth-child(1) {
  transform-origin: 75% 25%;
  transform: rotate(90deg);
}
.loader svg #clipping polygon:nth-child(2) {
  transform-origin: 50% 50%;
  animation: loader-rotation var(--time-animation) linear infinite reverse;
}
.loader svg #clipping polygon:nth-child(3) {
  transform-origin: 50% 60%;
  animation: loader-rotation var(--time-animation) linear infinite;
  animation-delay: calc(var(--time-animation) / -3);
}
.loader svg #clipping polygon:nth-child(4) {
  transform-origin: 40% 40%;
  animation: loader-rotation var(--time-animation) linear infinite reverse;
}
.loader svg #clipping polygon:nth-child(5) {
  transform-origin: 40% 40%;
  animation: loader-rotation var(--time-animation) linear infinite reverse;
  animation-delay: calc(var(--time-animation) / -2);
}
.loader svg #clipping polygon:nth-child(6) {
  transform-origin: 60% 40%;
  animation: loader-rotation var(--time-animation) linear infinite;
}
.loader svg #clipping polygon:nth-child(7) {
  transform-origin: 60% 40%;
  animation: loader-rotation var(--time-animation) linear infinite;
  animation-delay: calc(var(--time-animation) / -1.5);
}
@keyframes loader-rotation {
  0%   { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
@keyframes loader-roundness {
  0%   { filter: contrast(15); }
  20%  { filter: contrast(3); }
  40%  { filter: contrast(3); }
  60%  { filter: contrast(15); }
  100% { filter: contrast(15); }
}
@keyframes loader-colorize {
  0%   { filter: hue-rotate(0deg); }
  20%  { filter: hue-rotate(-30deg); }
  40%  { filter: hue-rotate(-60deg); }
  60%  { filter: hue-rotate(-90deg); }
  80%  { filter: hue-rotate(-45deg); }
  100% { filter: hue-rotate(0deg); }
}
@media (prefers-reduced-motion: reduce) {
  .loader,
  .loader svg #clipping,
  .loader svg #clipping polygon { animation: none; }
}

/* ── Settings-driven layout hooks ─────────────────────── */
/* Hide-sidebar mode: drop the Definitions column entirely so the diagram
   gets the full width. Stays toggleable from the modal even on small
   screens (where the responsive breakpoint already collapses the
   sidebar). */
.app.no-sidebar .sidebar { display: none; }

/* Record button is hidden by default — it's a beta feature that fires
   only when explicitly enabled in Settings. Each pane carries the
   button in its toolbar; we gate via the parent .app class so a single
   toggle drives every existing and future pane. */
.app:not(.show-record) .pane .ctrl-icons [data-act="rec"] { display: none; }

/* ── Keyboard shortcuts modal ─────────────────────────── */
.shortcuts-overlay {
  position: fixed;
  inset: 0;
  background: rgba(10, 10, 18, 0.78);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 9500;
  padding: 24px;
}
.shortcuts-overlay.open { display: flex; animation: shortcuts-fade 180ms ease-out; }
@keyframes shortcuts-fade { from { opacity: 0; } to { opacity: 1; } }
.shortcuts-card {
  background: var(--surface);
  border: 1px solid var(--border);
  max-width: 700px;
  width: 100%;
  max-height: calc(100vh - 80px);
  display: flex;
  flex-direction: column;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.55);
}
.shortcuts-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
  font-size: 0.78rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent);
}
.shortcuts-close {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  width: 28px; height: 28px;
  cursor: pointer;
  font-size: 1rem;
  font-family: inherit;
  line-height: 1;
}
.shortcuts-close:hover { color: var(--danger); border-color: var(--danger); }
.shortcuts-body {
  padding: 18px 22px 20px;
  overflow-y: auto;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 14px 26px;
}
.shortcuts-body section h4 {
  font-size: 0.66rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 10px;
  font-weight: 500;
}
.shortcuts-body dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: 14px;
  row-gap: 7px;
  font-size: 0.78rem;
  margin: 0;
}
.shortcuts-body dt {
  display: flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
  color: var(--text);
}
.shortcuts-body dd {
  margin: 0;
  color: var(--muted);
  line-height: 1.5;
}
.shortcuts-body kbd {
  background: var(--bg);
  border: 1px solid var(--border);
  padding: 2px 7px;
  border-radius: 2px;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.74rem;
  color: var(--accent);
  line-height: 1;
}

.err { color: var(--danger); font-size: 0.82rem; }
.info { font-size: 0.65rem; color: var(--muted); line-height: 1.7; padding: 0 4px; }

/* ── Sidebar ─────────────────────────────────── */
.sidebar {
  width: 300px;
  flex-shrink: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 16px 14px;
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  position: sticky;
  top: 24px;
  font-size: 0.78rem;
}
.sb-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.sb-title {
  font-size: 0.72rem;
  letter-spacing: .15em;
  color: var(--accent);
  text-transform: uppercase;
}
.sb-filter { width: 100%; padding: 7px 10px; font-size: 0.76rem; margin-bottom: 12px; }

.def-cat { margin-bottom: 16px; }
.def-cat h3 {
  font-size: 0.62rem;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 6px;
  font-weight: 400;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
}
.def-cat h3 .cat-name { flex: 1; }
.def-cat h3 .cat-actions {
  display: flex;
  gap: 2px;
  opacity: 0;
  transition: opacity .15s;
}
.def-cat:hover h3 .cat-actions { opacity: 1; }
.def-cat h3 .cat-action {
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted);
  font-family: inherit;
  font-size: 0.7rem;
  cursor: pointer;
  padding: 1px 5px;
  line-height: 1;
  transition: color .12s, border-color .12s;
}
.def-cat h3 .cat-action:hover {
  color: var(--accent);
  border-color: var(--border);
}
.def-cat h3 .cat-action[data-act="delete"]:hover {
  color: var(--danger);
  border-color: var(--danger);
}
.def-list { display: flex; flex-direction: column; gap: 2px; }
.def-entry {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  border-radius: 3px;
  cursor: pointer;
  transition: background .12s;
  position: relative;
}
.def-entry:hover { background: #1a1a25; }
.def-name {
  color: var(--accent);
  font-weight: 500;
  font-size: 0.76rem;
  min-width: 50px;
}
.def-expr {
  color: var(--muted);
  font-size: 0.7rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
}
.def-actions { display: none; gap: 4px; }
.def-entry.user:hover .def-actions { display: flex; }
.def-entry.user:hover .def-expr { display: none; }
.def-act-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  font-family: inherit;
  font-size: 0.6rem;
  padding: 2px 6px;
  cursor: pointer;
  border-radius: 2px;
}
.def-act-btn:hover { color: var(--accent); border-color: var(--accent); }
.def-act-btn.del:hover { color: var(--danger); border-color: var(--danger); }

.add-def-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px;
  background: #0d0d15;
  border: 1px dashed var(--border);
  border-radius: 3px;
  margin-top: 6px;
}
.add-def-form input { padding: 7px 10px; font-size: 0.74rem; }
.add-def-form .row { display: flex; gap: 6px; }
.add-def-form .btn { padding: 6px 12px; font-size: 0.64rem; flex: 1; }
.add-def-form .err-msg { color: var(--danger); font-size: 0.66rem; min-height: 1em; }

.user-empty { font-size: 0.7rem; color: var(--muted); font-style: italic; padding: 6px 8px; }

/* ── Drag-and-drop: move user defs between user categories ──────────── */
.def-entry.user[draggable="true"] { cursor: grab; }
.def-entry.user[draggable="true"]:active { cursor: grabbing; }
.def-entry.user.dragging { opacity: 0.4; }
.def-cat.drop-target-pending h3 {
  color: var(--accent);
  transition: color 150ms;
}
.def-cat.drop-target {
  background: rgba(160, 224, 255, 0.06);
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
  border-radius: 3px;
}
/* "Drop here to create a new category" zone */
.def-cat.new-cat-zone {
  border: 1px dashed var(--border);
  border-radius: 3px;
  padding: 12px 8px;
  margin-top: 4px;
  text-align: center;
  transition: border-color 150ms, background 150ms;
}
.def-cat.new-cat-zone .new-cat-hint {
  font-size: 0.66rem;
  color: var(--muted);
  letter-spacing: .04em;
  font-style: italic;
}
.def-cat.new-cat-zone.drop-target {
  border-color: var(--accent);
  border-style: dashed;
  background: rgba(160, 224, 255, 0.06);
  box-shadow: 0 0 0 1px var(--accent);
}

/* ── History dropdown ───────────────────────────────────
   Pinned to fill the input row's full width — earlier versions used
   `right: 0` + `min-width: 280px`, which let long λ-expressions push
   the popup's *left* edge off the viewport. Now the popup never
   exceeds its parent's width, and each entry truncates with an
   ellipsis instead. */
.history-pop {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  max-height: 320px;
  overflow-y: auto;
  /* Belt-and-braces: the parent (.ir) is the positioning context, so
     left:0 + right:0 already pins the width to it, but capping max-width
     keeps a stray min-content child from blowing the popup open. */
  max-width: 100%;
  box-sizing: border-box;
  z-index: 200;
  display: none;
  margin-top: 4px;
}
.history-pop.open { display: block; }
.history-item {
  padding: 8px 12px;
  font-size: 0.74rem;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 8px;
  /* `min-width: 0` lets the inner expression span shrink below its
     content size, which is what makes its `text-overflow: ellipsis`
     actually kick in inside a flex row. Without this the long
     expression forces the row wider than the popup. */
  min-width: 0;
  /* Reserve a little extra right padding so the delete button (which
     fades in on hover) doesn't visually crowd the expression text. */
  padding-right: 8px;
}
.history-item:hover { background: var(--surface-2); }
.history-item .history-expr {
  /* Inner span: takes whatever space remains after the ✕ button,
     truncates the rest with an ellipsis. Pointer-events:none lets
     clicks pass through to the parent row, which is what wires up
     "click anywhere on the line to restore". */
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: 'IBM Plex Mono', monospace;
  pointer-events: none;
}
.history-item .del-h {
  /* Bigger, square button that appears only when the row is hovered.
     pointer-events toggled too so when invisible it can't swallow a
     click meant for the row; when visible it traps the click and
     stopPropagation in JS prevents the row's restore from firing. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  flex: 0 0 auto;
  margin-left: auto;
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted);
  font-size: 0.9rem;
  font-family: inherit;
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s, color .15s, border-color .15s, background .15s;
}
.history-item:hover .del-h {
  opacity: 1;
  pointer-events: auto;
}
.history-item .del-h:hover {
  color: var(--danger);
  border-color: var(--danger);
  background: rgba(240, 128, 128, 0.08);
}
.history-item.empty { color: var(--muted); font-style: italic; cursor: default; }
.history-item.empty:hover { background: transparent; }
.history-item.history-clear {
  border-top: 1px solid var(--border);
  color: var(--danger);
}

@media (max-width: 1100px) {
  .app { flex-direction: column; align-items: stretch; }
  .sidebar { width: 100%; position: static; max-height: none; }
  .pane { min-width: 0; }
  /* When the sidebar stacks vertically the user shouldn't have to scroll
     past 50+ definitions before reaching the editor. The right column
     (header + toolbar + panes) comes first; the sidebar lives below as
     a reference panel. Tab order is unchanged (still source order). */
  .right-col { order: 1; }
  .sidebar   { order: 2; }
}

/* ── Phones ───────────────────────────────────────────────────────────
   The visualizer at narrow widths: shrink the body padding, let the
   global toolbar wrap properly, give the editor its own row, and
   make every control row work as a vertical stack. */
@media (max-width: 700px) {
  body { padding: 14px 10px 24px; }

  .header-row { gap: 8px; margin-bottom: 12px; }
  .header-row h1 { font-size: 0.86rem; letter-spacing: .14em; }
  .header-row .sub { font-size: 0.66rem; }

  /* Toolbar: every control gets a fair share of the row. The sliders'
     fixed-width range inputs were the main culprit forcing a horizontal
     scroll on narrow phones. */
  .global-bar {
    gap: 8px;
    padding: 8px 10px;
    font-size: 0.66rem;
  }
  .global-bar .scr { flex: 1 1 calc(50% - 4px); min-width: 0; }
  .global-bar .scr input[type=range] { flex: 1 1 auto; width: auto; min-width: 60px; }
  .global-bar .btn-toggle, .global-bar .btn-sec { font-size: 0.66rem; padding: 7px 9px; }

  /* Sidebar shrinks its definition rows so a 360px screen can list
     names + abbreviated expressions. The filter input gets full width. */
  .sidebar { padding: 10px; }
  .sb-header { gap: 4px; flex-wrap: wrap; }
  .def-entry { padding: 6px 8px; min-width: 0; }
  .def-name { font-size: 0.78rem; min-width: 36px; flex-shrink: 0; }
  /* `min-width: 0` lets flex children shrink below their content size,
     which lets text-overflow:ellipsis actually kick in instead of the
     long `Y` / `Z` / `insert` definitions blowing out the row. */
  .def-expr { font-size: 0.68rem; min-width: 0; }

  /* ── A single pane ─── */
  .pane { padding: 10px; min-height: 0; gap: 6px; }
  .pane-header { padding-bottom: 4px; gap: 6px; }
  .pane-title { font-size: 0.66rem; }

  /* Editor row: editor takes the full width, action buttons drop to
     their own row underneath. The History (⟲) button is icon-only,
     Draw button stretches across the remainder. */
  .pane .ir {
    flex-wrap: wrap;
    gap: 6px;
  }
  .pane .editor-wrap { flex: 1 1 100%; min-width: 0; }
  .pane .ir > .btn,
  .pane .ir > .btn-sec { flex: 1 1 auto; min-height: 40px; font-size: 0.72rem; }
  .editor { font-size: 16px; line-height: 1.5; padding: 9px 11px; min-height: 40px; }

  /* Control bar: keep every button at >=32px so they're tappable. The
     icon-buttons (find / copy / share / png / rec) get a tiny bit more
     padding to compensate for thumb size. */
  .ctrl-bar { gap: 5px; font-size: 0.72rem; }
  .ctrl-bar .btn { padding: 7px 10px; font-size: 0.72rem; min-height: 32px; }
  .ctrl-bar .strat-sel { font-size: 0.72rem; padding: 6px 8px; min-height: 32px; }
  .ctrl-bar .ctrl-icons { gap: 4px; margin-left: auto; }
  .icon-btn { width: 34px; height: 34px; }
  .ctrl-bar .ctrl-sep { display: none; }
  .ctrl-bar .ctrl-max { font-size: 0.66rem; }
  .ctrl-max input.max-steps { width: 56px; }
  .status { font-size: 0.66rem; }

  /* Code display: smaller padding so it doesn't waste horizontal room
     under the diagram. */
  .code-display { font-size: 0.78rem; }
  .code-display .code-inner { padding: 8px 11px; padding-right: 48px; }
  .code-display .copy-btn { opacity: 1; }  /* hover-to-reveal isn't useful on touch */

  /* Diagram viewport shrinks accordingly. The min-height of 280px keeps
     simple shapes (I, K, 0) from collapsing to a tiny strip. */
  .dw { min-height: 220px; padding: 8px; }
  .zoom-controls { font-size: 0.66rem; padding: 2px; }
  .zoom-btn { width: 28px; height: 28px; font-size: 0.85rem; }
  .fs-btn { width: 30px; height: 30px; font-size: 0.78rem; }

  /* Find bar full-width on its own row. */
  .find-bar { padding: 6px; gap: 5px; }
  .find-bar input { flex: 1 1 100%; font-size: 0.78rem; }
  .find-bar button { font-size: 0.66rem; padding: 6px 9px; }

  /* Fullscreen pane on a phone: smaller top inset so the back button
     and the diagram both fit. */
  .dw.fullscreen { padding: 50px 12px 12px; }
  .dw.fullscreen .fs-btn { top: 10px; right: 10px; }

  /* Shortcut overlay was sized for desktop dialogs. */
  .shortcuts-card { width: 100%; max-height: 88vh; overflow-y: auto; padding: 18px; }
  .shortcuts-body { display: block; }
  .shortcuts-body section { margin-bottom: 16px; }

  /* Info paragraph at the bottom stays in a compact muted block. */
  .info { font-size: 0.72rem; }
}
