@import url("/font/font.css");
/* fyzx — 2026 visual system. All color via circadian custom props. */

*, *::before, *::after { box-sizing: border-box; }
html, body, #root { height: 100%; margin: 0; }
:root {
  --r: 16px; --r-sm: 11px; --r-pill: 999px;
  --ff: "Fyzx", "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "Fyzx Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --rail-w: 48px;   /* one 32px tile: the column reads top to bottom as the order you use the app; 22 tiles + 3 rules fit a 900px screen unscrolled */
  background-color: var(--bg);
}
body {
  font-family: var(--ff);
  background-color: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  font-feature-settings: "cv05" 1, "ss01" 1, "tnum" 1;
  overflow: hidden;
}
button { font: inherit; color: inherit; cursor: pointer; background: none; border: none; }
input { font: inherit; color: inherit; }
kbd {
  font-family: var(--mono); font-size: 10px; letter-spacing: .02em;
  padding: 2px 5px; border-radius: 5px;
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text-faint);
}

.fz-app { height: 100%; display: flex; flex-direction: column; }

/* ---------- top bar ---------- */
/* env(safe-area-inset-*) is 0 in a desktop browser and the status bar on an
   iPad, where the app draws edge to edge (ios/PageRoot.swift). */
.fz-top {
  height: calc(52px + env(safe-area-inset-top)); flex: none; display: flex; align-items: center; gap: 18px;
  padding: env(safe-area-inset-top) 16px 0;
  background: var(--surface);
  border-bottom: 1px solid var(--hairline);
  z-index: 30;
}
.fz-brand { display: flex; align-items: center; gap: 12px; min-width: 0; }
.fz-logo {
  /* the modes button — an icon, not a wordmark: the app does not name itself inside itself */
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 8px;
  color: var(--text-dim);
  background: none; border: 0; padding: 0; cursor: pointer;
}
.fz-logo:hover { color: var(--text); background: var(--surface-2, rgba(127,127,127,.12)); }
.fz-logo::after { content: "·"; margin-left: 12px; color: var(--text-faint); }
.fz-run-name {
  font-size: 13px; color: var(--text-dim); font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 220px;
}

/* run menu — the auth panel's popover (the run switcher it served is in the lab) */
.fz-run-menu {
  position: absolute; top: calc(100% + 8px); left: 36px; z-index: 60;
  width: 290px; padding: 6px; border-radius: var(--r);
  background: var(--elev); border: 1px solid var(--hairline);
  box-shadow: var(--shadow);
}
.fz-run-menu-head {
  font-size: 10.5px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-faint); padding: 6px 10px 7px;
}
.fz-run-opt {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 10px; border-radius: 11px; transition: background .1s;
}
.fz-run-opt:hover { background: var(--surface-2); }
.fz-run-opt.is-active { background: var(--surface-2); }
.fz-run-opt-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.fz-run-opt-task { font-size: 13px; font-weight: 540; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fz-run-opt-meta { font-size: 11px; font-family: var(--mono); color: var(--text-faint); }
.fz-run-opt > svg { width: 16px; height: 16px; color: var(--accent); flex: none; }
.fz-surface-opt { text-decoration: none; }
.fz-surface-mark {
  width: 22px; height: 22px; flex: none; display: grid; place-items: center;
  font-size: 12px; font-family: var(--mono); color: var(--text-faint);
  border: 1px solid var(--hairline); border-radius: 7px; background: var(--surface-2);
}

.fz-search-trigger {
  margin-left: auto; display: flex; align-items: center; gap: 9px;
  height: 34px; padding: 0 13px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid transparent;
  color: var(--text-dim); font-size: 13px; min-width: 230px;
  transition: border-color .15s, background .15s;
}
.fz-search-trigger:hover { border-color: var(--accent-ring); background: var(--elev); }
.fz-search-trigger svg { width: 15px; height: 15px; opacity: .7; }
.fz-search-trigger span { flex: 1; text-align: left; }
.fz-search-trigger kbd { margin-left: auto; }

.fz-top-right { display: flex; align-items: center; gap: 14px; flex: none; }

/* Context inventory — the middle of the top bar, and the only place the app
   says what the chat can see. ONE row [user 2026-07-20: "every pixel matters"]:
   it scrolls sideways rather than wrapping, and fades at the right edge so a
   clipped chip reads as "more", not as a rendering bug. Replaced the four run
   counters that used to sit here — those were run state, not context. */
.fz-ctxbar {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 6px;
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 20px), transparent);
  mask-image: linear-gradient(90deg, #000 calc(100% - 20px), transparent);
}
.fz-ctxbar::-webkit-scrollbar { display: none; }
.fz-ctx-empty { flex: none; font-size: 11.5px; color: var(--text-faint); }
.fz-ctx-sep { flex: none; width: 1px; height: 15px; background: var(--hairline); margin: 0 3px; }
.fz-ctx-chip {
  flex: none; display: flex; align-items: center; gap: 5px;
  height: 24px; padding: 0 9px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid transparent;
  font-size: 11.5px; font-weight: 540; color: var(--text-dim);
  white-space: nowrap; max-width: 240px;
  transition: background .14s, border-color .14s, color .14s;
}
.fz-ctx-chip > svg { width: 13px; height: 13px; opacity: .6; flex: none; }
.fz-ctx-chip > span { overflow: hidden; text-overflow: ellipsis; }
.fz-ctx-chip em {
  font-style: normal; font-family: var(--mono); font-size: 10.5px; color: var(--text-faint);
  overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
button.fz-ctx-chip:hover { border-color: var(--accent-ring); color: var(--text); }
/* Could-be-added: same row, dashed and quiet — an offer, not a fact. */
.fz-ctx-chip.is-dim { background: transparent; border: 1px dashed var(--hairline); color: var(--text-faint); }
button.fz-ctx-chip.is-dim:hover { border-style: solid; border-color: var(--accent-ring); color: var(--text-dim); }

/* The addable chips collapsed into one "+" — was 4 permanent dashed chips
   eating header width for affordances used rarely. position: fixed on the
   menu escapes .fz-ctxbar's own overflow-y clip (needed for its edge fade). */
.fz-ctx-add { flex: none; position: relative; display: flex; }
.fz-ctx-add-btn { flex: none; width: 24px; height: 24px; border-radius: var(--r-pill); display: grid; place-items: center;
  color: var(--text-faint); border: 1px dashed var(--hairline); transition: background .14s, border-color .14s, color .14s; }
.fz-ctx-add-btn svg { width: 13px; height: 13px; }
.fz-ctx-add-btn:hover, .fz-ctx-add.is-open .fz-ctx-add-btn { border-style: solid; border-color: var(--accent-ring); color: var(--text); background: var(--surface-2); }
.fz-ctx-add-menu {
  position: fixed; z-index: 50; display: flex; flex-direction: column; gap: 4px; padding: 6px;
  background: var(--elev); border: 1px solid var(--hairline); border-radius: var(--r-sm); box-shadow: var(--shadow);
  animation: fz-slidein .14s ease;
}
.fz-ctx-add-menu .fz-ctx-chip { max-width: none; }

/* account / passkey (top-right) — quiet pill like the run switch; the panel
   reuses .fz-run-menu, right-anchored so it stays on screen. */
.fz-auth { position: relative; }
.fz-auth-switch {
  display: flex; align-items: center; gap: 7px;
  height: 30px; padding: 0 11px; border-radius: var(--r-pill);
  background: transparent; border: 1px solid transparent;
  font-size: 12.5px; font-weight: 540; color: var(--text-dim);
  transition: background .14s, border-color .14s;
}
.fz-auth-switch:hover, .fz-auth-switch.is-open { background: var(--surface-2); border-color: var(--hairline); }
.fz-auth-switch svg { width: 14px; height: 14px; opacity: .6; }
.fz-auth-menu { left: auto; right: 0; width: 252px; }
.fz-auth-input {
  width: 100%; height: 32px; padding: 0 10px; margin: 2px 0 6px;
  border-radius: 10px; background: var(--surface-2); border: 1px solid var(--hairline);
  font-size: 13px; color: var(--text);
}
.fz-auth-input:focus { outline: none; border-color: var(--accent-ring); }
.fz-auth-input::placeholder { color: var(--text-faint); }
.fz-auth-id { display: flex; align-items: baseline; gap: 8px; padding: 2px 10px 8px; font-size: 13px; font-weight: 560; color: var(--text); }
.fz-auth-id em { font-style: normal; font-size: 10.5px; font-family: var(--mono); color: var(--text-faint); }
.fz-auth-error { padding: 6px 10px 2px; font-size: 11.5px; line-height: 1.45; color: var(--err); }
.fz-auth-hint { padding: 6px 10px 4px; font-size: 11px; color: var(--text-faint); }

.fz-seg { display: flex; background: var(--surface-2); border: 1px solid transparent; border-radius: var(--r-pill); padding: 2px; gap: 2px; }
.fz-seg button {
  height: 28px; padding: 0 12px; border-radius: var(--r-pill); font-size: 11px; font-weight: 600;
  letter-spacing: .03em; color: var(--text-faint); font-family: var(--mono);
}
.fz-seg button.is-active { background: var(--elev); color: var(--text); box-shadow: var(--shadow); }

/* ---------- body / split ---------- */
.fz-body { flex: 1; display: flex; min-height: 0; }
.fz-left { position: relative; min-width: 0; overflow: hidden; }
/* The verbal column: the ladder alone, or a terminal stacked above it — chat
   and the shell are one surface [user 2026-09-07]. It is a COLUMN for the same
   reason the terminal splitter is (Terminal.jsx): at 620px two of anything side
   by side is two useless 300px panes. */
.fz-right { min-width: 320px; max-width: 620px; display: flex; flex-direction: column; min-height: 0; }
/* The ladder is written for a pane it fills alone (height:100%); in the stack
   it is one member of a column, so it takes what is left instead. */
.fz-right > .fz-ws { flex: 1 1 auto; min-height: 0; height: auto; }
.fz-verbal-term { display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
.fz-split {
  flex: none; width: 11px; margin: 0 -5px; z-index: 20; cursor: col-resize;
  display: flex; align-items: center; justify-content: center;
}
.fz-split span { width: 1px; height: 100%; background: var(--hairline); transition: background .15s, width .15s; }
.fz-split:hover span { background: var(--accent); width: 2px; }
/* A folded column leaves its edge: a raised lip with a grip, so closed is
   visible at a glance and one click opens it. */
.fz-split-closed { flex: none; width: 12px; min-height: 0; height: 100%; padding: 0; border: 0; cursor: pointer;
  background: var(--surface); box-shadow: var(--depth); display: flex; align-items: center; justify-content: center; }
.fz-split-closed span { width: 3px; height: 36px; border-radius: 2px; background: var(--text-faint); transition: background .15s, height .15s; }
.fz-split-closed:hover span { background: var(--accent); height: 56px; }

/* ---------- tree canvas ---------- */
.fz-canvas {
  position: absolute; inset: 0; overflow: hidden;
  background-color: var(--bg);
  background-image:
    radial-gradient(circle at 1px 1px, color-mix(in oklch, var(--text) 8%, transparent) 1px, transparent 0);
  background-size: 26px 26px;
  cursor: grab;
  touch-action: none;
}
.fz-canvas:active { cursor: grabbing; }
.fz-world { position: absolute; top: 0; left: 0; transform-origin: 0 0; }

.fz-links { position: absolute; top: 0; left: 0; pointer-events: none; }
/* edges are the agent's output, streaming from parent → child */
.fz-stream-track {
  fill: none; stroke: var(--text-faint); stroke-width: 1;
  opacity: .45; transition: opacity .25s, stroke .25s;
}
.fz-stream-text {
  font-family: var(--mono); font-size: 8px; letter-spacing: .01em;
  fill: var(--text-faint); opacity: .42;
  transition: fill .25s, opacity .25s;
}
.fz-edge.is-active .fz-stream-track { stroke: var(--accent); opacity: .55; }
.fz-edge.is-active .fz-stream-text { fill: var(--accent); opacity: 1; font-weight: 600; }
.fz-edge.is-live .fz-stream-track { stroke: var(--run); opacity: .42; }
.fz-edge.is-live .fz-stream-text { fill: var(--run); opacity: .72; }
.fz-edge.is-dim { opacity: .22; }

/* node = a status dot AT the edge-meeting point, with its label hanging off it.
   Dot carries status color; the word stays neutral & legible. */
.fz-node {
  position: absolute; cursor: pointer; user-select: none;
  transition: opacity .25s;
}
.fz-node:hover { z-index: 6; }
.fz-node.is-sel { z-index: 7; }
.fz-node.is-dim { opacity: .22; }

/* node = its NAME at the anchor where edges terminate. No dot — the name
   itself carries status via color. LR hangs the name to the right of the
   anchor; TB centers it on the anchor (centered hierarchy). */
.fz-lab { position: absolute; display: flex; flex-direction: column; align-items: flex-start; white-space: nowrap; left: 3px; top: 0; transform: translateY(-50%); }
.dir-TB .fz-lab { left: 0; transform: translate(-50%, -50%); align-items: center; text-align: center; }
/* Representative web image (Wikipedia/Commons via /api/microscope-image) for
   substance/material/compound nodes; derived element/particle nodes have none. */
.fz-node-img { width: 34px; height: 34px; object-fit: cover; border-radius: 7px; margin-bottom: 3px; border: 1px solid var(--hairline); background: var(--surface); }

.fz-cap {
  display: inline-block; max-width: 168px; overflow: hidden; text-overflow: ellipsis;
  vertical-align: middle; letter-spacing: -.006em;
  font-size: 12.5px; font-weight: 540; color: var(--text);
  text-shadow: 0 0 3px var(--bg), 0 0 3px var(--bg), 0 0 7px var(--bg), 0 1px 1px var(--bg);
  transition: color .15s;
}
/* kind → typographic treatment (agents read as headers, tools as code) */
.k-agent .fz-cap { font-size: 13.5px; font-weight: 650; letter-spacing: -.015em; }
.k-tool .fz-cap, .fz-arg { font-family: var(--mono); font-size: 11.5px; font-weight: 500; }
.fz-arg { color: var(--text-faint); font-style: normal; font-weight: 400; }
/* status → the name's color */
.s-done .fz-cap { color: var(--text); }
.s-running .fz-cap { color: var(--run); font-weight: 650; }
.s-queued .fz-cap { color: var(--text-faint); }
.s-error .fz-cap { color: var(--err); font-weight: 650; }

/* editable field — a node the file behind the graph will actually take a write
   for. The affordance is deliberately quiet (a dotted underline under the
   value, on hover) because most nodes are not writable and a canvas of edit
   boxes would read as a form. Double-click opens the input. */
.fz-cap.can-edit .fz-arg { border-bottom: 1px dotted var(--text-faint); cursor: text; }
.fz-cap.can-edit:hover .fz-arg { color: var(--accent); border-bottom-color: var(--accent-ring); }
.fz-edit {
  font-family: var(--mono); font-size: 11.5px; font-weight: 400;
  margin-left: 5px; padding: 1px 4px; min-width: 90px; max-width: 260px;
  color: var(--text); background: var(--bg);
  border: 1px solid var(--accent-ring); border-radius: 4px; outline: none;
}
.fz-edit:disabled { opacity: .6; }
.fz-edit.is-error { border-color: var(--err); }
/* The server's refusal, kept on screen with what was typed — a @LOCK exists so
   a person reads it, not so a write fails quietly. */
.fz-edit-err {
  display: block; max-width: 300px; margin-top: 3px;
  font-family: var(--mono); font-size: 10px; line-height: 1.35; color: var(--err);
  text-shadow: 0 0 3px var(--bg), 0 0 3px var(--bg);
}

/* scale-matched imagery — the real micrograph/photo pinned at this depth */
.fz-thumb {
  width: 34px; height: 34px; object-fit: cover; border-radius: 6px;
  margin-bottom: 3px; border: 1px solid var(--line, rgba(128,128,128,.35));
  box-shadow: 0 1px 4px rgba(0,0,0,.25);
}
.fz-tip-img { width: 100%; max-height: 120px; object-fit: cover; border-radius: 6px; margin-bottom: 5px; }
.fz-tip-cap { font-family: var(--mono); font-size: 9.5px; line-height: 1.3; color: var(--text-faint); margin-bottom: 7px; }

/* per-node metric line (elapsed · tokens) — the density layer */
.fz-meta {
  font-family: var(--mono); font-size: 9.5px; font-weight: 400; line-height: 1.2;
  color: var(--text-faint); letter-spacing: .01em; margin-top: 2px;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 3px var(--bg), 0 0 6px var(--bg);
}
.s-running .fz-meta { color: var(--run); }
.s-error .fz-meta { color: var(--err); }

.fz-node:hover .fz-cap { color: var(--accent); }
.fz-node.is-sel .fz-cap {
  color: var(--accent); font-weight: 720;
  text-decoration: underline; text-decoration-color: var(--accent-ring);
  text-underline-offset: 3px; text-decoration-thickness: 2px;
}

.fz-chevron {
  position: absolute; opacity: 0; left: calc(100% + 6px); top: 50%; margin-top: -8px;
  width: 16px; height: 16px; display: grid; place-items: center;
  border-radius: 50%; color: var(--text-dim);
  background: var(--surface); border: 1px solid var(--border); box-shadow: var(--shadow);
  transition: background .15s, color .15s, border-color .15s, opacity .15s; z-index: 8;
}
.fz-node:hover .fz-chevron { opacity: 1; }
.fz-chevron:hover { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.fz-chevron.is-collapsed { transform: rotate(-90deg); }
.fz-count {
  position: absolute; opacity: 1; left: calc(100% + 6px); top: 50%; margin-top: -8px;
  min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px;
  background: var(--accent-soft); color: var(--accent);
  font-size: 9.5px; font-weight: 700; display: grid; place-items: center;
  font-variant-numeric: tabular-nums; box-shadow: inset 0 0 0 1px var(--accent-ring);
  transition: opacity .12s; z-index: 7;
}
.fz-node:hover .fz-count { opacity: 0; }

.fz-canvas-hint {
  position: absolute; left: 14px; bottom: 12px; font-size: 11px; color: var(--text-faint);
  background: color-mix(in oklch, var(--surface) 70%, transparent); padding: 5px 12px;
  border-radius: var(--r-pill); pointer-events: none;
  letter-spacing: .01em;
}
.fz-canvas-controls {
  position: absolute; top: 14px; right: 14px; z-index: 20;
  display: flex; align-items: center; gap: 8px;
}

/* Artifacts — the session's decompositions, as pills (the lab's "runs" row).
   The active one is the tree on canvas. */
.fz-artifacts { display: flex; flex-wrap: wrap; gap: 8px; max-width: 100%; }
.fz-artifact {
  display: flex; align-items: center; gap: 7px; flex: 0 0 auto;
  padding: 5px 12px 5px 6px; border-radius: var(--r-pill); cursor: pointer;
  background: color-mix(in oklch, var(--surface) 82%, transparent);
  border: 1px solid var(--hairline); color: var(--text-dim);
  font-size: 12px; font-weight: 540; transition: border-color .12s, color .12s;
}
.fz-artifact:hover { border-color: var(--border); color: var(--text); }
.fz-artifact.is-active { border-color: var(--accent-ring); color: var(--text); background: var(--accent-soft); }
.fz-artifact img, .fz-artifact-ph {
  width: 22px; height: 22px; border-radius: 50%; object-fit: cover;
  background: var(--surface-2); flex: 0 0 auto;
}
.fz-canvas-controls .fz-seg {
  background: var(--surface);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow);
}

/* The home of the left pane: the live camera, and under it the newest still
   the person took. The camera takes the room; the still is a band, not a
   second pane competing with it. A run note sits on the camera and does not
   cover it — the section stays visible while a decomposition is in flight. */
.fz-around { position: absolute; inset: 0; display: flex; flex-direction: column; background: #000; min-height: 0; }
.fz-around-cam { position: relative; flex: 1 1 auto; min-height: 0; background: #000; }
.fz-around-cam video { width: 100%; height: 100%; object-fit: cover; display: block; background: #000; }
.fz-around-cam .fz-source-err { position: absolute; left: 12px; bottom: 12px; }
.fz-around-shot { flex: none; height: 22%; min-height: 84px; max-height: 168px; margin: 0; overflow: hidden; border-top: 1px solid rgba(255,255,255,.18); }
.fz-around-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fz-around-note {
  position: absolute; z-index: 4; left: 12px; top: 12px; max-width: min(420px, calc(100% - 24px));
  padding: 6px 10px; border-radius: 10px; background: rgba(0,0,0,.55); color: #fff;
  font-size: 12px; line-height: 1.4; pointer-events: none;
}
.fz-around-note.is-error { color: #ff8d8d; }

/* ---------- build pane — the artifact under construction (left) ---------- */
.fz-build-host { position: absolute; inset: 0; z-index: 15; display: flex; }
.fz-build { flex: 1; min-width: 0; display: flex; flex-direction: column; background: var(--bg); }
.fz-build-head {
  display: flex; align-items: center; gap: 10px; flex: none;
  padding: 8px 12px; border-bottom: 1px solid var(--hairline); background: var(--surface);
}
.fz-build-kind {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 600; color: var(--text-dim); text-transform: uppercase;
  letter-spacing: .06em;
}
.fz-build-kind svg { width: 15px; height: 15px; }
.fz-build-target {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 13px; font-weight: 560; color: var(--text);
}
.fz-build-live { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text-dim); }
.fz-build-live i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--ok, #3fb26f);
  animation: fz-build-pulse 1.6s ease-in-out infinite;
}
@keyframes fz-build-pulse { 50% { opacity: .35; } }
.fz-build-body { flex: 1; min-height: 0; display: flex; }
.fz-build-frame { flex: 1; border: 0; width: 100%; height: 100%; background: var(--bg); }
.fz-build-return {
  position: absolute; left: 14px; top: 14px; z-index: 21;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 13px; border-radius: var(--r-pill); cursor: pointer;
  background: color-mix(in oklch, var(--surface) 82%, transparent);
  border: 1px solid var(--hairline); color: var(--text-dim);
  font-size: 12px; font-weight: 540; transition: border-color .12s, color .12s;
}
.fz-build-return:hover { border-color: var(--border); color: var(--text); }
.fz-build-return svg { width: 14px; height: 14px; }

/* ---------- tooltip ---------- */
.fz-tip {
  position: absolute; width: 248px; z-index: 25; pointer-events: none;
  background: color-mix(in oklch, var(--elev) 96%, transparent);
  border: 1px solid var(--border); border-radius: var(--r);
  padding: 11px 12px; box-shadow: var(--shadow);
  animation: fz-tip-in .14s ease;
}
@keyframes fz-tip-in { from { opacity: 0; transform: translateY(3px); } }
.fz-tip-head { display: flex; align-items: center; gap: 7px; font-weight: 600; font-size: 13px; }
.fz-tip-meta { font-size: 11px; color: var(--text-faint); margin: 2px 0 7px 15px; text-transform: lowercase; }
.fz-tip-sum { font-size: 12px; line-height: 1.45; color: var(--text-dim); margin: 0 0 9px; text-wrap: pretty; }
.fz-tip-row { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: 11px; color: var(--text-dim); font-family: var(--mono); }
.fz-tip-row b { color: var(--text-faint); font-weight: 500; }

/* ---------- pinned node popup ----------
   The tip's twin that stays: same tokens, but it lives INSIDE .fz-world so it
   travels with its node, takes the pointer (selectable, scrollable summary),
   and is counter-scaled by Tree.jsx so its type never shrinks with the zoom. */
/* The world is a transformed stacking context, so nothing inside it can
   out-stack the run pills (.fz-artifacts, 20) on its own: while a popup is
   pinned the whole world steps above them, else a low node's text was drawn
   under the pill row. */
.fz-world.has-pop { z-index: 21; }
.fz-pop {
    position: absolute; width: 360px; max-width: 360px; box-sizing: border-box; z-index: 26;
  transform-origin: 0 0; cursor: default; user-select: text;
  background: color-mix(in oklch, var(--elev) 97%, transparent);
  border: 1px solid var(--border); border-radius: var(--r);
  padding: 11px 32px 11px 12px; box-shadow: var(--shadow);
  animation: fz-tip-in .14s ease;
}
.fz-pop .fz-tip-head { padding-right: 4px; }
.fz-pop .fz-tip-head .fz-arg { color: var(--text-faint); font-weight: 400; font-style: normal; }
.fz-pop-sum {
  max-height: 240px; overflow: auto; overscroll-behavior: contain;
  white-space: pre-wrap; overflow-wrap: anywhere; color: var(--text);
}
.fz-pop-sum:last-child { margin-bottom: 0; }
/* The global `button { min-height: 52px }` would turn a 22px × into a slab. */
.fz-pop-x {
  position: absolute; top: 6px; right: 6px;
  width: 22px; height: 22px; min-height: 0; min-width: 0; padding: 0;
  border: 0; border-radius: 6px; background: transparent;
  color: var(--text-faint); font-size: 16px; line-height: 22px; font-family: inherit; cursor: pointer;
}
.fz-pop-x:hover { background: color-mix(in oklch, var(--text) 10%, transparent); color: var(--text); }

/* ---------- right panel ---------- */
.fz-panel { display: flex; flex-direction: column; flex: 1; min-width: 0; border-left: 1px solid var(--hairline); background: var(--surface); box-shadow: var(--depth); }
.fz-panel-body { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.fz-panel-head {
  height: 52px; flex: none; display: flex; align-items: center; gap: 10px; padding: 0 14px;
  border-bottom: 1px solid var(--hairline);
}
.fz-icon-btn { width: 30px; height: 30px; border-radius: 10px; display: grid; place-items: center; color: var(--text-faint); transition: background .15s, color .15s; }
.fz-icon-btn:hover { background: var(--surface-2); color: var(--text); }
.fz-panel-title { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.fz-mode-name { font-size: 14px; font-weight: 600; letter-spacing: -.01em; }
.fz-panel-ctx { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--text-faint); }
.fz-status { width: 8px; height: 8px; border-radius: 50%; flex: none; display: inline-block; background: var(--text-faint); }
.fz-status.s-done { background: var(--ok); }
.fz-status.s-running { background: var(--run); }
.fz-status.s-queued { background: var(--queued); }
.fz-status.s-error { background: var(--err); }
.fz-panel-ctx .fz-status { width: 6px; height: 6px; }

.fz-panel-content { flex: 1; min-height: 0; display: flex; flex-direction: column; }
@keyframes fz-fade { from { opacity: 0; } }

/* chat */
.fz-chat { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.fz-msgs { flex: 1; overflow-y: auto; padding: 18px 16px 10px; display: flex; flex-direction: column; gap: 16px; }
.fz-ctx {
  align-self: flex-start; font-size: 11px; font-family: var(--mono); color: var(--text-faint);
  background: var(--surface-2); border: 1px solid transparent; padding: 5px 12px; border-radius: var(--r-pill);
}
.fz-msg.r-user { align-self: flex-end; max-width: 86%; }
.fz-msg.r-ai { align-self: flex-start; max-width: 92%; }
.fz-msg-role { font-size: 10.5px; text-transform: uppercase; letter-spacing: .08em; color: var(--text-faint); margin-bottom: 4px; font-weight: 600; }
.fz-msg.r-user .fz-msg-role { text-align: right; }
/* Read-aloud cursor: a thin amber (non-blue) vertical slice over the letter
   currently being spoken. min-width keeps it visible on spaces/thin glyphs. */
.fz-read-slice { background: rgba(255, 166, 0, .5); border-radius: 2px; display: inline-block; min-width: 4px; white-space: pre; }
.fz-msg p { margin: 0; font-size: 13.5px; line-height: 1.55; text-wrap: pretty; }
.fz-msg.r-user p { background: var(--accent); color: var(--accent-text); padding: 10px 15px; border-radius: 18px 18px 7px 18px; }
.fz-msg.r-ai p { color: var(--text); }
.fz-msg-model { text-transform: none; letter-spacing: 0; font-weight: 400; }

/* An assistant answer is markdown (/assets/markdown.js — the same renderer the
   client/ panels import). The renderer paints through --md-* so it inherits THIS
   palette instead of shipping a second one; everything else here is spacing, so
   a heading or a code block sits in the bubble the way a paragraph does. */
.fz-msg .markdown {
  --md-link: var(--accent);
  /* A code block has to read as a block at every hour, so it is mixed FROM the
     text colour rather than picked: the circadian palette swings the surface
     from near-black to near-white and a fixed tint would vanish at one end. */
  --md-code-bg: color-mix(in oklab, var(--text) 8%, var(--surface));
  --md-pre-bg: color-mix(in oklab, var(--text) 6%, var(--surface));
  --md-rule: var(--hairline);
  --md-rule-soft: var(--hairline);
  /* A heading inside a chat bubble is an emphasis, not a document title. */
  --md-h1: 15px;
  --md-h2: 14px;
  --md-h3: 13.5px;
  font-size: 13.5px;
}
.fz-msg .markdown > *:first-child { margin-top: 0; }
.fz-msg .markdown > *:last-child { margin-bottom: 0; }
.fz-msg .markdown p { font-size: 13.5px; line-height: 1.55; opacity: 1; color: var(--text); }
.fz-msg .markdown li { color: var(--text); }
.fz-msg .markdown h1, .fz-msg .markdown h2, .fz-msg .markdown h3 { margin: 10px 0 4px; color: var(--text); }
.fz-msg .markdown h1:first-child, .fz-msg .markdown h2:first-child { margin-top: 0; }
.fz-msg .markdown code, .fz-msg .markdown pre { font-family: var(--mono); }
.fz-msg .markdown pre { max-width: 100%; }
/* live reasoning stream — dim, mono, bounded; open while streaming, collapsible after */
.fz-think { margin: 0 0 6px; border-left: 2px solid var(--hairline); padding-left: 10px; }
.fz-think summary {
  cursor: pointer; list-style: none; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .08em; color: var(--text-faint); font-weight: 600; user-select: none;
}
.fz-think summary::-webkit-details-marker { display: none; }
.fz-think pre {
  margin: 4px 0 0; max-height: 180px; overflow-y: auto; white-space: pre-wrap; word-break: break-word;
  font-family: var(--mono); font-size: 11px; line-height: 1.5; color: var(--text-faint);
}
.fz-compose { flex: none; padding: 12px 14px 14px; border-top: 1px solid var(--hairline); display: flex; gap: 8px; }
.fz-compose input {
  flex: 1; height: 40px; padding: 0 16px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid transparent; outline: none; font-size: 13.5px;
  transition: border-color .15s;
}
.fz-compose input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
.fz-compose button { width: 40px; height: 40px; border-radius: 50%; background: var(--accent); color: var(--accent-text); display: grid; place-items: center; transition: filter .15s; }
.fz-compose button:hover { filter: brightness(1.08); }

/* shell */
.fz-shell { flex: 1; display: flex; flex-direction: column; min-height: 0; background: color-mix(in oklch, var(--bg) 70%, var(--surface)); }
.fz-term { flex: 1; overflow-y: auto; padding: 14px 15px; font-family: var(--mono); font-size: 12.5px; line-height: 1.65; }
.fz-term-meta { color: var(--text-faint); }
.fz-term-sep { height: 1px; background: var(--hairline); margin: 8px 0; }
.fz-term-cmd { color: var(--text); margin-top: 4px; }
.fz-term-out { color: var(--text-dim); white-space: pre-wrap; }
.fz-term-ok { color: var(--ok); }
.fz-term-err { color: var(--err); white-space: pre-wrap; }
.fz-prompt { color: var(--accent); font-weight: 700; }
.fz-cmdline { flex: none; display: flex; align-items: center; gap: 8px; padding: 10px 15px; border-top: 1px solid var(--hairline); font-family: var(--mono); }
.fz-cmdline input { flex: 1; background: none; border: none; outline: none; font-size: 12.5px; }

/* Proposed commands in chat — fenced shell is the control (click / Enter).
   Not terminal chrome: the block lives in the message, native-style. */
.fz-msg-body { display: flex; flex-direction: column; gap: 8px; }
.fz-msg-body > p { margin: 0; white-space: pre-wrap; text-wrap: pretty; }
.fz-code {
  border: 1px solid var(--hairline);
  border-radius: 11px;
  background: color-mix(in oklch, var(--bg) 55%, var(--surface));
  overflow: hidden;
}
.fz-code pre {
  margin: 0; padding: 10px 12px 12px;
  font-family: var(--mono); font-size: 12.5px; line-height: 1.5;
  white-space: pre-wrap; word-break: break-word; color: var(--text);
}
.fz-code-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 5px 10px;
  border-bottom: 1px solid var(--hairline);
  font-size: 10.5px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-faint); font-weight: 600;
}
.fz-code.is-runnable {
  cursor: pointer;
  transition: border-color .12s, background .12s;
}
.fz-code.is-runnable:hover,
.fz-code.is-runnable:focus-visible {
  border-color: var(--accent-ring);
  background: color-mix(in oklch, var(--accent) 8%, var(--surface));
  outline: none;
}
.fz-code-run { color: var(--ok); text-transform: none; letter-spacing: 0; font-weight: 650; }
.fz-code.is-runnable:hover .fz-code-run { color: var(--accent); }

/* inspector */
.fz-inspect { flex: 1; overflow-y: auto; padding: 16px; }
.fz-inspect-sum { font-size: 13px; line-height: 1.55; color: var(--text-dim); margin: 0 0 16px; text-wrap: pretty; }
.fz-kv { display: flex; flex-direction: column; }
.fz-kv-row { display: flex; justify-content: space-between; padding: 10px 2px; font-size: 12.5px; border-bottom: 1px solid var(--hairline); }
.fz-kv-row:last-child { border-bottom: none; }
.fz-kv-row span { color: var(--text-faint); font-family: var(--mono); }
.fz-kv-row b { font-weight: 560; font-family: var(--mono); }
.fz-stat-text.s-done { color: var(--ok); } .fz-stat-text.s-running { color: var(--run); }
.fz-stat-text.s-queued { color: var(--text-dim); } .fz-stat-text.s-error { color: var(--err); }
.fz-rel { margin-top: 16px; }
.fz-rel-h { font-size: 10.5px; text-transform: uppercase; letter-spacing: .08em; color: var(--text-faint); margin-bottom: 7px; font-weight: 600; }
.fz-rel-item { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; padding: 9px 11px; border-radius: 11px; font-size: 13px; transition: background .12s; margin-bottom: 2px; }
.fz-rel-item:hover { background: var(--surface-2); }
.fz-rel-item em { color: var(--text-faint); font-family: var(--mono); font-style: normal; font-size: 12px; }

/* history */
.fz-history { flex: 1; overflow-y: auto; padding: 12px; }
.fz-hist { display: flex; align-items: center; gap: 11px; width: 100%; text-align: left; padding: 10px 11px; border-radius: 11px; transition: background .12s; }
.fz-hist:hover { background: var(--surface-2); }
.fz-hist-time { font-family: var(--mono); font-size: 11.5px; color: var(--text-faint); }
.fz-hist-x { font-size: 13px; color: var(--text-dim); }

/* search palette */
.fz-search-scrim {
  position: fixed; inset: 0; z-index: 60; background: color-mix(in oklch, var(--bg) 50%, transparent);
  display: flex; justify-content: center; align-items: flex-start; padding-top: 13vh;
  animation: fz-fade .12s ease;
}
.fz-search {
  width: min(560px, 92vw); background: var(--elev); border: 1px solid var(--border);
  border-radius: 22px; box-shadow: var(--shadow); overflow: hidden;
  animation: fz-pop .16s cubic-bezier(.2,.8,.3,1);
}
@keyframes fz-pop { from { opacity: 0; transform: translateY(-8px) scale(.98); } }
.fz-search-bar { display: flex; align-items: center; gap: 11px; padding: 15px 16px; border-bottom: 1px solid var(--hairline); }
.fz-search-bar svg { width: 17px; height: 17px; color: var(--text-faint); }
.fz-search-bar input { flex: 1; background: none; border: none; outline: none; font-size: 15px; }
.fz-search-results { max-height: 50vh; overflow-y: auto; padding: 8px; }
.fz-search-empty { padding: 22px 14px; text-align: center; color: var(--text-faint); font-size: 13px; }
.fz-search-item { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; padding: 10px 11px; border-radius: 11px; transition: background .1s; }
.fz-search-item:hover { background: var(--surface-2); }
.fz-search-label { font-size: 13.5px; font-weight: 500; flex: 1; }
.fz-search-label em { color: var(--text-faint); font-family: var(--mono); font-style: normal; font-size: 12px; }
.fz-search-kind { font-size: 11px; color: var(--text-faint); font-family: var(--mono); }
/* ⌘K is where everything that is not an altitude lives now. */
.fz-cmd-kind { flex: none; width: 54px; font-family: var(--mono); font-size: 10px; text-transform: uppercase;
  letter-spacing: .06em; color: var(--text-faint); }
.fz-search-sep { height: 1px; margin: 7px 6px; background: var(--hairline); }

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 5px; border: 2px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background: var(--text-faint); background-clip: padding-box; }

/* motion off — kill decorative animation/transition (theme transition kept off too) */
[data-motion="off"] *, [data-motion="off"] *::before, [data-motion="off"] *::after {
  animation-duration: 0s !important; animation-delay: 0s !important;
  transition-duration: 0s !important; transition-delay: 0s !important;
}

/* ---------- top: camera / visual input (centered) ---------- */
.fz-top { position: relative; }
.fz-visual { position: absolute; left: 50%; top: 9px; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; gap: 7px; z-index: 31; }
.fz-visual-btn {
  display: flex; align-items: center; gap: 9px; height: 34px; padding: 0 16px 0 13px;
  border-radius: var(--r-pill); background: var(--surface-2); border: 1px solid transparent;
  color: var(--text-dim); font-size: 13px; font-weight: 500; transition: background .15s, color .15s, border-color .15s;
}
.fz-visual-btn:hover { border-color: var(--hairline); color: var(--text); }
.fz-visual-btn svg { width: 17px; height: 17px; }
.fz-visual-btn.is-on { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-ring); }
.fz-visual-feed {
  width: 240px; height: 116px; border-radius: 14px;
  background: var(--bg); border: 1px solid var(--accent-ring);
  display: grid; place-items: center; box-shadow: var(--shadow);
  overflow: hidden;
}
.fz-visual-feed video { width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); }
.fz-visual-feed span { position: absolute; font-family: var(--mono); font-size: 11px; color: var(--text-faint); letter-spacing: .04em; }

/* ---------- right panel: inspect toggle ---------- */
.fz-inspect-toggle {
  display: flex; align-items: center; gap: 6px; height: 30px; padding: 0 11px 0 9px;
  border-radius: var(--r-pill); font-size: 12px; font-weight: 560; color: var(--text-faint);
  border: 1px solid var(--hairline); transition: background .15s, color .15s, border-color .15s;
}
.fz-inspect-toggle svg { width: 15px; height: 15px; }
.fz-inspect-toggle:hover { color: var(--text); background: var(--surface-2); }
.fz-inspect-toggle.is-on { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-ring); }

/* ---------- central bottom dock: voice / text input ---------- */
/* Clears the fixed rail on the left, the same way the panes do. */
.fz-dock { flex: none; padding: 6px 12px calc(6px + env(safe-area-inset-bottom)) calc(var(--rail-w, 0px) + 12px); display: flex; flex-direction: column; align-items: stretch; gap: 3px; }
.fz-dock-note { font-size: 11px; color: var(--text-faint); letter-spacing: .01em; }
/* A read that failed — the dropped file, the blocked microphone — reads as the note itself, in the error colour, not as a console line. */
.fz-dock-note .fz-dock-err { color: var(--err); }
/* The one input says out loud which MODE it will run in, live as you type, and
   teaches the key that overrides it. `is-auto` is the mode the text chose for
   itself — accent, because it changes under your hands; a rung you climbed to
   on purpose stays quiet grey. Same height as the mic and send buttons so the
   row reads as one control, not a chip parked beside one. */
@keyframes fz-listen { 0%,100% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--err) 45%, transparent); } 60% { box-shadow: 0 0 0 7px transparent; } }
.fz-dock-input {
  width: 100%; height: 34px; padding: 0 14px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid transparent; outline: none; font-size: 14px;
  transition: border-color .15s, box-shadow .15s;
}
.fz-dock-input.is-busy { opacity: .9; }
.fz-dock-field { position: relative; }
.fz-dock-field .fz-dock-input { padding-right: 38px; }
/* Inside the field, not beside it; min-height overrides the global 52px button floor. */
.fz-dock-mic { position: absolute; right: 4px; top: 50%; transform: translateY(-50%); width: 26px; height: 26px; min-height: 0;
  border-radius: 50%; display: grid; place-items: center; color: var(--text-faint); background: none; }
.fz-dock-mic:hover { color: var(--text); }
.fz-dock-mic.is-on { background: var(--err); color: #fff; }
.fz-dock-mic svg { width: 15px; height: 15px; }
.fz-dock-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }

/* ---------- load / empty / error overlay over the tree canvas ---------- */
.fz-load-overlay {
  position: absolute; inset: 0; z-index: 8; display: grid; place-items: center;
  pointer-events: none; padding: 24px;
  background: color-mix(in oklch, var(--bg) 62%, transparent);
  animation: fz-fade .18s ease;
}
.fz-load-overlay.is-empty { background: transparent; }
.fz-load-card {
  pointer-events: auto; max-width: 380px; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 22px 26px; border-radius: 16px;
  background: var(--surface); border: 1px solid var(--hairline);
  box-shadow: 0 8px 30px color-mix(in oklch, var(--text) 14%, transparent);
  color: var(--text); font-size: 14px; line-height: 1.5;
}
.fz-load-card b { font-size: 15px; font-weight: 680; }
.fz-load-card span { color: var(--text-dim); }
.fz-load-overlay.is-error .fz-load-card b { color: var(--err); }
.fz-load-hint { font-size: 12.5px; }
.fz-seed-why { color: var(--text-dim); }
.fz-load-spin {
  width: 26px; height: 26px; border-radius: 50%;
  border: 2.5px solid var(--accent-ring); border-top-color: var(--accent);
  animation: fz-spin .8s linear infinite;
}
@keyframes fz-spin { to { transform: rotate(360deg); } }

/* ---------- window-level image drop target ---------- */
.fz-drop-overlay {
  position: fixed; inset: 0; z-index: 80; display: grid; place-items: center;
  background: color-mix(in oklch, var(--bg) 55%, transparent);
  outline: 2px dashed var(--accent); outline-offset: -8px;
  pointer-events: none; animation: fz-fade .12s ease;
}
.fz-drop-overlay span {
  padding: 10px 18px; border-radius: var(--r-pill);
  background: var(--surface); border: 1px solid var(--accent-ring);
  color: var(--text); font-size: 13px; font-weight: 550; box-shadow: var(--shadow);
}

/* Summoned 3D molecule (Inspect mode) */
.fz-summon3d { margin: 10px 0 2px; padding: 6px 14px; border-radius: var(--r-pill); border: 1px solid var(--hairline); background: var(--surface-2); color: var(--text-dim); font-size: 12.5px; font-weight: 500; }
.fz-summon3d:hover { color: var(--text); border-color: var(--text-faint); }
.fz-mol3d { width: 100%; height: 230px; margin: 10px 0 2px; border-radius: 10px; background: var(--surface-2); cursor: grab; touch-action: none; }
.fz-mol3d:active { cursor: grabbing; }
.fz-mol3d-err { margin: 10px 0 2px; font-size: 12px; color: var(--text-faint); }


/* ==== ported from origin/main: workspace / rail / mail / auth / artifacts ==== */

.fz-run-stats { display: flex; align-items: center; gap: 12px; }
.fz-stat { display: flex; align-items: center; gap: 6px; font-size: 12px; font-variant-numeric: tabular-nums; color: var(--text-dim); }
.fz-stat i { width: 7px; height: 7px; border-radius: 50%; }
.fz-stat.s-done i { background: var(--ok); }
.fz-stat.s-running i { background: var(--run); }
.fz-stat.s-queued i { background: var(--queued); }
.fz-stat.s-error i { background: var(--err); }
/* account / passkey (top-right) — quiet pill like the run switch; the panel
   reuses .fz-run-menu, right-anchored so it stays on screen. */
.fz-auth { position: relative; }
.fz-auth-switch {
  display: flex; align-items: center; gap: 7px;
  height: 30px; padding: 0 11px; border-radius: var(--r-pill);
  background: transparent; border: 1px solid transparent;
  font-size: 12.5px; font-weight: 540; color: var(--text-dim);
  transition: background .14s, border-color .14s;
}
.fz-auth-switch:hover, .fz-auth-switch.is-open { background: var(--surface-2); border-color: var(--hairline); }
.fz-auth-switch svg { width: 14px; height: 14px; opacity: .6; }
.fz-auth-menu { left: auto; right: 0; width: 252px; }
.fz-auth-input {
  width: 100%; height: 32px; padding: 0 10px; margin: 2px 0 6px;
  border-radius: 10px; background: var(--surface-2); border: 1px solid var(--hairline);
  font-size: 13px; color: var(--text);
}
.fz-auth-input:focus { outline: none; border-color: var(--accent-ring); }
.fz-auth-input::placeholder { color: var(--text-faint); }
.fz-auth-id { display: flex; align-items: baseline; gap: 8px; padding: 2px 10px 8px; font-size: 13px; font-weight: 560; color: var(--text); }
.fz-auth-id em { font-style: normal; font-size: 10.5px; font-family: var(--mono); color: var(--text-faint); }
.fz-auth-error { padding: 6px 10px 2px; font-size: 11.5px; line-height: 1.45; color: var(--err); }
.fz-auth-hint { padding: 6px 10px 4px; font-size: 11px; color: var(--text-faint); }
/* scale-matched imagery — the real micrograph/photo pinned at this depth */
.fz-thumb {
  width: 34px; height: 34px; object-fit: cover; border-radius: 6px;
  margin-bottom: 3px; border: 1px solid var(--line, rgba(128,128,128,.35));
  box-shadow: 0 1px 4px rgba(0,0,0,.25);
}
.fz-tip-img { width: 100%; max-height: 120px; object-fit: cover; border-radius: 6px; margin-bottom: 5px; }
.fz-tip-cap { font-family: var(--mono); font-size: 9.5px; line-height: 1.3; color: var(--text-faint); margin-bottom: 7px; }
.fz-msg-model { text-transform: none; letter-spacing: 0; font-weight: 400; }
/* live reasoning stream — dim, mono, bounded; open while streaming, collapsible after */
.fz-think { margin: 0 0 6px; border-left: 2px solid var(--hairline); padding-left: 10px; }
.fz-think summary {
  cursor: pointer; list-style: none; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .08em; color: var(--text-faint); font-weight: 600; user-select: none;
}
.fz-think summary::-webkit-details-marker { display: none; }
.fz-think pre {
  margin: 4px 0 0; max-height: 180px; overflow-y: auto; white-space: pre-wrap; word-break: break-word;
  font-family: var(--mono); font-size: 11px; line-height: 1.5; color: var(--text-faint);
}
/* ============================ v2 workspace ============================ */
.fz-body { padding-left: var(--rail-w); }      /* clear the fixed far-left rail */


.fz-ws { display: flex; flex-direction: column; flex: 1; min-width: 0; height: 100%;
  border-left: 1px solid var(--hairline); background: var(--surface); box-shadow: var(--depth); }
/* Only Terminal.jsx uses this header (the ladder's own strip is .fz-levels)
   — a slim strip, not a full-width text row: the surface is already named by
   the rail icon that opened it, so this repeats only the live bits (status
   dot, cwd, session id). */
.fz-ws-head { flex: none; height: 34px; display: flex; align-items: center; gap: 7px; padding: 0 8px 0 6px; border-bottom: 1px solid var(--hairline); }
.fz-ws-ctx { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-dim); font-weight: 540; }
.fz-ws-ctx svg { width: 14px; height: 14px; }
.fz-ws-ctx .fz-status { width: 7px; height: 7px; }
/* The shell's reported cwd (OSC 7) — the language pane's directory context. */
.fz-ws-cwd,
.fz-coder-tools {
  margin-left: auto; font-family: var(--mono);
  font-size: 11px; color: var(--text-faint); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; max-width: 45%;
}

/* Input sources — one row directly above the dock, and only while one is on.
   They used to be a resident tool strip inside the right column; every button
   in it already had a live duplicate in the top context bar or the rail. */
.fz-sources { width: min(720px, 100%); display: flex; flex-direction: column; gap: 8px; padding-bottom: 4px; }
.fz-source { position: relative; display: flex; flex-direction: column; gap: 6px; }
.fz-source-off { position: absolute; top: 6px; right: 6px; width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center; background: var(--surface); border: 1px solid var(--hairline); color: var(--text-faint); }
.fz-source-off:hover { color: var(--text); }
.fz-source-off svg { width: 13px; height: 13px; }
.fz-source-link .fz-source-off, .fz-source .fz-source-btn + .fz-source-off { position: static; flex: none; }
.fz-source-video video { width: 100%; max-height: 180px; object-fit: cover; border-radius: var(--r-sm); background: #000; }
/* Live camera readout. In-frame, not in a control bar: the settle trigger
   answers without a click, so the answer belongs where the eye already is. */
.fz-live { position: absolute; top: 6px; left: 6px; display: flex; align-items: center; gap: 5px;
  padding: 3px 8px; border-radius: 999px; background: rgba(0,0,0,.55); backdrop-filter: blur(6px);
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .02em; pointer-events: none; }
.fz-live-dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
.fz-live-aiming { color: rgba(255,255,255,.6); }
.fz-live-reading { color: #7fd1a0; }
.fz-live-reading .fz-live-dot { animation: fz-live-pulse 1s ease-in-out infinite; }
.fz-live-held { color: rgba(255,255,255,.72); }
@keyframes fz-live-pulse { 0%,100% { opacity: 1; } 50% { opacity: .25; } }

.fz-live-reads { position: absolute; left: 6px; right: 6px; bottom: 6px; display: flex; flex-wrap: wrap; gap: 5px; }
.fz-live-read { padding: 4px 10px; border-radius: 999px; background: rgba(0,0,0,.6); backdrop-filter: blur(6px);
  border: 1px solid rgba(255,255,255,.18); color: #fff; font-size: 11.5px; line-height: 1.3; }
.fz-live-read:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-text, #fff); }
/* Two lines in one pill: the name the settle proposes, and what it is probably
   made of. The parts line is dimmer because tapping still commits to the name. */
.fz-live-read { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; border-radius: 12px; }
.fz-live-read-name { font-weight: 600; }
.fz-live-read-parts { font-size: 10.5px; opacity: .72; }

.fz-source-btn { display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px 12px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px dashed var(--border); color: var(--text-dim); font-size: 12.5px; text-align: left; }
.fz-source-btn:hover:not(:disabled) { border-color: var(--accent-ring); color: var(--text); }
.fz-source-btn:disabled { opacity: .6; cursor: default; }
.fz-source-thumb { width: 34px; height: 34px; object-fit: cover; border-radius: 7px; }
.fz-source-link { flex-direction: row; align-items: center; gap: 6px; }
.fz-source-link input { flex: 1; height: 36px; padding: 0 12px; border-radius: var(--r-sm); background: var(--surface-2); border: 1px solid var(--hairline); font-size: 13px; }
.fz-source-link input:focus { outline: none; border-color: var(--accent-ring); }
.fz-source-go { width: 36px; height: 36px; border-radius: var(--r-sm); display: grid; place-items: center; background: var(--accent); color: var(--accent-text, #fff); }
.fz-source-go svg { width: 17px; height: 17px; }
.fz-source-err { font-size: 11.5px; color: var(--err); font-family: var(--mono); }

/* Ink — the drawing pad (public/Ink.jsx). Taller than the camera preview on
   purpose: a stroke needs room the way a frame does not, and on a phone this
   is the surface the thumb actually works on. `touch-action: none` is what
   stops the browser from reading the first 10 px of a stroke as a scroll and
   swallowing it — without it a drawn line begins by dragging the page. */
.fz-source-ink { gap: 6px; }
.fz-ink-pad { position: relative; width: 100%; height: min(42vh, 340px);
  border-radius: var(--r-sm); background: var(--surface-2); border: 1px solid var(--hairline); overflow: hidden; }
.fz-ink-canvas { display: block; width: 100%; height: 100%; touch-action: none; cursor: crosshair; color: var(--text); }
.fz-ink-hint { position: absolute; inset: 0; display: grid; place-items: center; pointer-events: none;
  color: var(--text-faint); font-size: 12px; font-family: var(--mono); letter-spacing: .01em; text-align: center; padding: 0 18px; }
.fz-ink-bar { display: flex; align-items: center; gap: 6px; }
.fz-ink-sp { flex: 1; }
.fz-ink-act { padding: 5px 10px; border-radius: 999px; background: var(--surface-2); border: 1px solid var(--hairline);
  color: var(--text-dim); font-family: var(--mono); font-size: 11.5px; }
.fz-ink-act:hover:not(:disabled) { color: var(--text); border-color: var(--accent-ring); }
.fz-ink-act:disabled { opacity: .4; cursor: default; }
.fz-ink-use { padding: 6px 14px; border-radius: 999px; background: var(--accent); color: var(--accent-text, #fff);
  font-size: 12px; border: 1px solid var(--accent); }
.fz-ink-use:disabled { opacity: .4; cursor: default; }
.fz-source-ink .fz-source-off { position: static; flex: none; }

/* The column's own strip: drop-rung and collapse, right-aligned. Rungs are
   not tabs here — the header's altitude chip and ⌘0…⌘9 move between them. */
.fz-levels { flex: none; display: flex; align-items: center; gap: 2px; padding: 7px 8px 0; }
.fz-levels-spacer { flex: 1; }
.fz-ws-view { flex: 1; min-height: 0; display: flex; flex-direction: column; background: var(--surface-2); }

/* Rung 0 — the object in the left pane, said in words. */
.fz-ground { flex: 1; min-height: 0; overflow-y: auto; padding: 22px 18px; display: flex; flex-direction: column; gap: 16px; }
.fz-ground-subject { font-size: 21px; font-weight: 600; letter-spacing: -.01em; }
.fz-ground-empty { color: var(--text-faint); font-size: 13px; }
.fz-ground-facts { display: flex; flex-direction: column; gap: 7px; margin: 0; }
.fz-ground-facts > div { display: flex; gap: 12px; font-size: 12.5px; }
.fz-ground-facts dt { width: 62px; flex: none; color: var(--text-faint); }
.fz-ground-facts dd { margin: 0; color: var(--text-dim); font-family: var(--mono); font-size: 12px; }
/* The mode is highlighted here, once — not drawn as a node in the tree. */
.fz-ground-mode { background: var(--accent-ring); color: var(--accent); padding: 1px 7px; border-radius: var(--r-pill); }
.fz-ground-hint { margin: 0; font-size: 12px; line-height: 1.55; color: var(--text-faint); }
.fz-ground-hint kbd { font-family: var(--mono); font-size: 11px; padding: 1px 5px; border-radius: 5px;
  background: var(--surface-2); border: 1px solid var(--hairline); }

/* chat */
.fz-chat { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.fz-ctx-err { color: var(--err); }
.fz-typing { display: flex; gap: 4px; align-items: center; }
.fz-typing i { width: 6px; height: 6px; border-radius: 50%; background: var(--text-faint); animation: fz-blink 1.2s infinite; }
.fz-typing i:nth-child(2) { animation-delay: .2s; }
.fz-typing i:nth-child(3) { animation-delay: .4s; }
@keyframes fz-blink { 0%,60%,100% { opacity: .25; } 30% { opacity: 1; } }

/* shell terminal — ShellView removed with it (shell tab is the real pty now);
   only the status dot + auth prompt styles survive below. */

/* ---------- far-left rail + slide-overs ----------
   Modes sit at the very left: the row reads Modes | Graphics | Text
   [user 2026-10-01: "I've moved Modes to very left"]. It was the far right
   until then. */
.fz-rail { position: fixed; top: calc(52px + env(safe-area-inset-top)); left: 0; bottom: 0; width: var(--rail-w); z-index: 40;
  display: grid; grid-template-columns: 32px; grid-auto-rows: max-content;
  justify-content: center; align-content: start; gap: 3px; padding: 8px 0;
  overflow-y: auto; scrollbar-width: none;
  background: var(--surface); border-right: 1px solid var(--hairline); }
.fz-rail::-webkit-scrollbar { display: none; }
.fz-rail-btn { width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center; color: var(--text-faint); transition: background .14s, color .14s; }
.fz-rail-btn:hover { background: var(--surface-2); color: var(--text); }
.fz-rail-btn.is-active { background: var(--accent); color: var(--accent-text, #fff); }
/* Sound & haptics toggle: muted reads as absence. */
.fz-rail-btn.is-muted { opacity: .45; }
.fz-rail-btn svg { width: 19px; height: 19px; }

/* app menu bar (the surfaces) */
.fz-rail-app {
  width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center;
  color: var(--text-faint); text-decoration: none; border: 1px solid transparent;
  transition: background .14s, color .14s, border-color .14s;
}
.fz-rail-app:hover { background: var(--surface-2); color: var(--text); }
.fz-rail-app.is-here { background: var(--surface-2); color: var(--text); border-color: var(--accent-ring); }
.fz-rail-app svg { width: 18px; height: 18px; }
.fz-rail-mark { font-family: var(--mono); font-size: 12px; line-height: 1; letter-spacing: -.02em; }
/* Unavailable surfaces stay visible (when shown) but read as inert — no
   hover lift, no pointer, roughly half strength. */
.fz-rail-app.is-unavailable { opacity: .38; cursor: default; pointer-events: none; }
.fz-rail-toggle svg { width: 14px; height: 14px; }
.fz-rail-sep { grid-column: 1 / -1; width: 100%; height: 1px; background: var(--hairline); margin: 3px 0; flex: none; }

/* a legacy place: the analyzer bundle in the left pane, said so in its header */
.fz-legacy { display: flex; flex-direction: column; height: 100%; }
.fz-legacy-head { gap: 10px; }
.fz-legacy-mark { font-family: var(--mono); font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  padding: 2px 6px; border-radius: 5px; border: 1px solid var(--border); color: var(--text-faint); }
.fz-legacy-title { font-size: 13px; font-weight: 600; }
.fz-legacy-note { font-size: 12px; color: var(--text-faint); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fz-legacy-frame { flex: 1; width: 100%; border: 0; background: var(--bg); }

/* ---------- network: the tailnet in the left pane (Network.jsx) ---------- */
.fz-net-body { flex: 1; min-height: 0; overflow-y: auto; padding: 12px 14px; font-family: var(--mono); font-size: 12.5px; line-height: 1.5; }
.fz-net-row { display: grid; gap: 10px; align-items: center; padding: 6px 0; border-top: 1px solid var(--hairline); }
.fz-net-peer { grid-template-columns: minmax(0, 1fr) 130px 150px 84px; }
.fz-net-route { grid-template-columns: 18px minmax(0, 1fr) 130px 150px 84px; }
.fz-net-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fz-net-path { white-space: nowrap; }
.fz-net-name > span, .fz-net-name > b + span { margin-left: 6px; }
.fz-net-dim { color: var(--text-faint); }
.fz-net-ok { color: var(--ok, #3fb950); }
.fz-net-warn { color: var(--warn, #d29922); }
.fz-net-err { color: var(--err); }
.fz-net-tag { color: var(--accent); }
.fz-net-ip { background: none; border: 0; padding: 0; font: inherit; color: var(--text-dim); text-align: left; cursor: pointer; }
.fz-net-ping { text-align: right; display: flex; gap: 4px; justify-content: flex-end; }
.fz-net-btn { font: inherit; font-size: 12px; padding: 1px 8px; border-radius: 5px; border: 1px solid var(--hairline); background: var(--surface-2); color: var(--text-dim); cursor: pointer; }
.fz-net-btn:hover:not(:disabled) { color: var(--text); }
.fz-net-btn:disabled { opacity: .35; cursor: default; }
.fz-net-btn.is-on { background: var(--accent); color: var(--accent-text); border-color: transparent; }
.fz-net-section { margin-top: 18px; }
.fz-net-acts { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.fz-net-reason { margin: 6px 0 8px; }
.fz-net-reason code { font: inherit; color: var(--text-dim); }

/* ---------- visualize: the discussion drawn (Visualize.jsx) ---------- */
.fz-visualize .fz-ws-head { gap: 8px; }
.fz-visualize .fz-ws-ctx { display: flex; align-items: baseline; gap: 10px; min-width: 0; overflow: hidden; }
.fz-viz-body { flex: 1; min-height: 0; overflow-y: auto; padding: 10px 14px; display: flex; flex-direction: column; gap: 8px; font-size: 12px; line-height: 1.5; }
.fz-viz-dim { color: var(--text-faint); font-size: 11px; }
.fz-viz-empty { font-size: 12px; }
.fz-viz-summary { color: var(--text-dim); }
.fz-viz-unavailable { border: 1px solid var(--hairline); border-radius: 4px; padding: 8px 10px; }
.fz-viz-err { color: var(--err); margin-top: 4px; word-break: break-word; }
.fz-viz-btn { margin-left: auto; font: inherit; font-size: 11px; padding: 1px 8px; border-radius: 5px; border: 1px solid var(--hairline); background: var(--surface-2); color: var(--text-dim); cursor: pointer; }
.fz-viz-btn:hover:not(:disabled) { color: var(--text); }
.fz-viz-btn:disabled { opacity: .4; cursor: default; }
.fz-viz-canvas { overflow: auto; min-height: 0; flex: 1; }
.fz-viz-svg { display: block; max-height: 100%; transition: opacity 120ms; }
.fz-viz-svg.is-drawing { opacity: .45; }
.fz-viz-legend { display: flex; gap: 10px; flex-wrap: wrap; font-size: 10px; color: var(--text-faint); }
.fz-viz-key { display: inline-flex; align-items: center; gap: 4px; }
.fz-viz-swatch { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }
.fz-viz-grounding { font-size: 10px; color: var(--text-faint); line-height: 1.5; }

/* ---------- terminal surface (right pane) ---------- */
.fz-surface-app { display: flex; flex-direction: column; flex: 1; min-width: 0; height: 100%; border-left: 1px solid var(--hairline); background: var(--surface); box-shadow: var(--depth); }
.fz-xterm { flex: 1; min-height: 0; position: relative; background: #0b0b0d; padding: 8px 6px 6px 10px; overflow: hidden; }
.fz-xterm .xterm { height: 100%; }
.fz-xterm-status { position: absolute; left: 12px; bottom: 8px; font-family: var(--mono); font-size: 11px; color: var(--text-faint); pointer-events: none; }
.fz-term-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--text-faint); }
.fz-term-dot.s-connected { background: var(--ok); }
.fz-term-dot.s-connecting { background: var(--queued); }
.fz-term-dot.s-disconnected, .fz-term-dot.s-error { background: var(--err); }
/* terminal splitter — stacked panes, one session each, draggable dividers */
.fz-term-split { display: flex; flex-direction: column; flex: 1; min-width: 0; height: 100%; }
.fz-term-pane { display: flex; min-height: 0; overflow: hidden; }
.fz-term-pane .fz-surface-app { height: 100%; }
.fz-term-pane .fz-ws-cwd { margin-left: 0; }        /* the actions take the right edge instead */
.fz-term-split .fz-ws-head { height: 34px; }        /* four 46px headers would eat the pane */
.fz-term-acts { margin-left: auto; display: flex; align-items: center; gap: 2px; }
.fz-term-divider {
  flex: none; height: 11px; margin: -5px 0; z-index: 20; cursor: row-resize;
  display: flex; align-items: center; justify-content: center;
  /* Without this the divider cannot be dragged by a finger AT ALL: iOS Safari
     claims a vertical drag on it as a page pan, fires pointercancel, and the
     pointermove stream we resize from simply stops. The gesture is ours, so we
     have to say so before it starts. */
  touch-action: none;
}
.fz-term-divider span { height: 1px; width: 100%; background: var(--hairline); transition: background .15s, height .15s; }
.fz-term-divider:hover span { background: var(--accent); height: 2px; }
/* Dragging is the one affordance a touch screen cannot discover by hovering, so
   on a coarse pointer the divider gets a grab strip a finger can actually land
   on and a grip it can see. The negative margin keeps its contribution to the
   column at 1px, so widening the target moves no pane. */
@media (pointer: coarse) {
  .fz-term-divider { height: 28px; margin: -13.5px 0; }
  .fz-term-divider span { height: 2px; width: 44px; border-radius: 2px; background: var(--text-faint); }
}
.fz-term-divider.is-dragging span { background: var(--accent); height: 3px; }

.fz-term-auth { flex: none; padding: 12px 14px; border-top: 1px solid var(--hairline); background: var(--surface); display: flex; flex-direction: column; gap: 8px; }
.fz-term-auth-msg { font-size: 12px; color: var(--text-dim); }
/* The remote-access token flow, de-prioritized behind a disclosure for a
   local target — sign-in is the primary CTA in the message above it. */
.fz-term-auth-toggle { align-self: flex-start; font-size: 11.5px; color: var(--text-faint); text-decoration: underline; text-underline-offset: 2px; }
.fz-term-auth-toggle:hover { color: var(--text-dim); }
.fz-term-auth-row { display: flex; gap: 6px; }
.fz-term-auth-row input,
.fz-coder-bar input { flex: 1; height: 34px; padding: 0 12px; border-radius: var(--r-sm); background: var(--surface-2); border: 1px solid var(--hairline); font-size: 13px; font-family: var(--mono); }
.fz-term-auth-row input:focus,
.fz-coder-bar input:focus { outline: none; border-color: var(--accent-ring); }
.fz-term-auth-row button { height: 34px; padding: 0 16px; border-radius: var(--r-sm); background: var(--accent); color: var(--accent-text, #fff); font-size: 12.5px; font-weight: 560; }

/* The coder — the agent loop's own surface. It sits where the xterm sits, so it
   inherits .fz-surface-app's column and lays out only its own two rows: the
   transcript, and the ask bar pinned under it. Its text input is not defined
   here; it joins the one rule above that every input on this page uses. */
.fz-coder-log { flex: 1; min-height: 0; overflow-y: auto; padding: 10px 12px; font-family: var(--mono); font-size: 12.5px; line-height: 1.5; }
.fz-coder-hint { color: var(--text-faint); font-size: 12.5px; padding: 18px 4px; }
/* content-visibility keeps a long run cheap on a phone: an 8 KB tool result is
   a few hundred lines of monospace, and every appended line would otherwise
   re-measure all of them. Offscreen lines skip layout and paint entirely. */
.fz-coder-line { display: flex; gap: 8px; padding: 3px 0; white-space: pre-wrap; word-break: break-word;
  content-visibility: auto; contain-intrinsic-size: auto 20px; }
.fz-coder-mark { flex: none; color: var(--text-faint); }
/* A tool's own output is the one thing here that is not prose, and it is the
   thing an operator reads most carefully — so it keeps its shape and scrolls
   sideways rather than reflowing into a paragraph. */
.fz-coder-line code,
.fz-coder-line pre { color: var(--text-dim); }
.fz-coder-line pre { margin: 0; flex: 1; min-width: 0; overflow-x: auto; }
.fz-coder-line.k-ask { font-weight: 600; }
.fz-coder-line.k-said { color: var(--text-dim); font-style: italic; }
.fz-coder-line.k-stop { color: var(--danger, #d9534f); }
.fz-coder-model { font-family: var(--mono); font-size: 11px; color: var(--text-faint); white-space: nowrap; }
.fz-coder-bar { flex: none; display: flex; gap: 6px; padding: 10px 12px; border-top: 1px solid var(--hairline); background: var(--surface); }

.fz-slide { position: fixed; top: calc(52px + env(safe-area-inset-top)); left: var(--rail-w); bottom: 0; width: 330px; z-index: 45;
  display: flex; flex-direction: column; background: var(--elev); border-right: 1px solid var(--hairline); box-shadow: var(--shadow);
  animation: fz-slidein .18s ease; }
@keyframes fz-slidein { from { transform: translateX(-12px); opacity: 0; } to { transform: none; opacity: 1; } }
.fz-slide-head { flex: none; height: 46px; display: flex; align-items: center; justify-content: space-between; padding: 0 8px 0 16px; border-bottom: 1px solid var(--hairline); font-size: 13px; font-weight: 600; }
.fz-slide-body { flex: 1; min-height: 0; overflow-y: auto; padding: 10px; display: flex; flex-direction: column; gap: 4px; }
.fz-slide-empty { padding: 22px 12px; text-align: center; font-size: 12.5px; color: var(--text-faint); }
.fz-slide-add { display: flex; gap: 6px; padding: 4px 4px 8px; }
.fz-slide-add input { flex: 1; height: 36px; padding: 0 12px; border-radius: var(--r-sm); background: var(--surface-2); border: 1px solid var(--hairline); font-size: 13px; }
.fz-slide-add input:focus { outline: none; border-color: var(--accent-ring); }
.fz-slide-add button { width: 36px; height: 36px; border-radius: var(--r-sm); display: grid; place-items: center; background: var(--accent); color: var(--accent-text, #fff); }
.fz-slide-add button svg { width: 17px; height: 17px; }
.fz-slide-item { display: flex; align-items: center; gap: 10px; padding: 10px 11px; border-radius: var(--r-sm); text-decoration: none; color: inherit; text-align: left; width: 100%; }
.fz-slide-item + .fz-slide-item { }
.fz-slide-btn:hover, .fz-slide-link:hover { background: var(--surface-2); }
.fz-slide-item-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.fz-slide-item-title { font-size: 13px; font-weight: 540; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fz-slide-item-meta { font-size: 11px; font-family: var(--mono); color: var(--text-faint); }

/* Signals: a capability name, then what will be built for it. A read row stays
   in place rather than vanishing — you read it here, and the same row is what
   the phone and the Mac just withdrew. */
.fz-slide-item-mark {
  font-family: var(--mono); font-size: 10px; letter-spacing: .02em; color: var(--accent);
  border: 1px solid var(--border); border-radius: var(--r-sm); padding: 2px 6px;
  max-width: 92px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: none;
}
.fz-slide-item.is-read { opacity: .5; }
/* Two lines of why, never twelve: the evidence is a reason to trust the row,
   not the row itself. */
.fz-signal { align-items: flex-start; }
.fz-signal .fz-slide-item-meta {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; line-height: 1.45;
}
.fz-signal .fz-slide-item-title { white-space: normal; }
/* The row a banner click landed on. */
.fz-signal.is-target { border-color: var(--accent-ring); box-shadow: inset 2px 0 0 var(--accent); }
/* The row's links: the item's own page and the page it came from. Underlined
   because a link that only looks like a link on hover is a link you do not
   know is there. NOT .fz-slide-item-meta — that class line-clamps with
   `display:-webkit-box`, which beat the flex row on specificity and ran the two
   links together with no gap. */
.fz-signal-links {
  display: flex; gap: 12px; margin-top: 4px; align-items: baseline;
  font-size: 11px; font-family: var(--mono); min-width: 0;
}
.fz-signal-links a {
  color: var(--text-faint); text-decoration: underline; text-underline-offset: 2px;
  /* One line each: a source URL is arbitrarily long and must not push the
     panel's width around. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.fz-signal-links a:first-child { flex: none; }
.fz-signal-links a + a { flex: 1 1 auto; }
.fz-signal-links a:hover { color: var(--accent); }
.fz-slide-item.is-read .fz-slide-item-mark { color: var(--text-faint); }
.fz-slide-btn2 {
  font-size: 12px; padding: 5px 10px; border-radius: var(--r-sm);
  border: 1px solid var(--border); color: var(--text-dim); background: transparent;
}
.fz-slide-btn2:hover { background: var(--surface-2); color: var(--text); }
/* The count has to be legible with the panel closed — that is the only time a
   notification is doing any work. */
.fz-rail-btn { position: relative; }
.fz-rail-badge {
  position: absolute; top: 1px; right: 1px; min-width: 14px; height: 14px; padding: 0 3px;
  border-radius: 7px; background: var(--accent); color: var(--accent-text, #fff);
  font-family: var(--mono); font-size: 9px; line-height: 14px; text-align: center;
}

/* ---------- subject hero (in-world, anchored to the root node) ---------- */
.fz-world-subject {
  position: absolute; transform: translate(-50%, -100%);
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  pointer-events: none;
}
.dir-LR .fz-world-subject { transform: translate(-100%, -50%); }
.fz-world-subject img {
  width: 132px; height: 132px; object-fit: contain;
  filter: drop-shadow(0 8px 22px color-mix(in oklch, var(--text) 22%, transparent));
  animation: fz-subject-in .32s ease;
}
@keyframes fz-subject-in { from { opacity: 0; transform: translateY(-6px) scale(.96); } to { opacity: 1; } }
.fz-qty {
  height: 28px; padding: 0 13px; border-radius: var(--r-pill); font-size: 11.5px; letter-spacing: .01em;
  color: var(--text-faint); background: var(--surface-2);
  border: 1px solid var(--hairline); transition: color .14s, background .14s, border-color .14s;
}
.fz-qty:hover { color: var(--text-dim); border-color: var(--hairline); }
.fz-qty.is-on { color: var(--accent); background: var(--surface); border-color: var(--accent-ring); box-shadow: var(--shadow); }
/* ---------- email mode (chat-ladder rung 1) ---------- */
.fz-mail { display: flex; flex-direction: column; gap: 14px; padding: 14px; overflow-y: auto; height: 100%; }
.fz-mail-compose { display: flex; flex-direction: column; gap: 8px; }
.fz-mail-compose input, .fz-mail-compose textarea {
  width: 100%; padding: 9px 12px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text); font-family: var(--ff); font-size: 13px; resize: vertical;
}
.fz-mail-compose input:focus, .fz-mail-compose textarea:focus { outline: none; border-color: var(--accent-ring); }
.fz-mail-actions { display: flex; align-items: center; justify-content: flex-end; gap: 10px; }
.fz-mail-notice { font-size: 12px; color: var(--text-dim); }
.fz-mail-notice.is-ok { color: var(--accent); }
.fz-mail-notice.is-err { color: #c0524d; }
.fz-mail-send {
  display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 14px;
  border-radius: var(--r-pill); background: var(--accent-soft); color: var(--accent);
  border: 1px solid var(--accent-ring); font-size: 13px; font-weight: 600;
}
.fz-mail-send:disabled { opacity: .45; }
.fz-mail-send svg { width: 15px; height: 15px; }
.fz-mail-inbox { display: flex; flex-direction: column; gap: 6px; border-top: 1px solid var(--hairline); padding-top: 10px; }
.fz-mail-inbox-head {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 12px; font-weight: 600; color: var(--text-faint);
  text-transform: uppercase; letter-spacing: .05em;
}
.fz-mail-item { padding: 8px 10px; border-radius: var(--r-sm); background: var(--surface-2); border: 1px solid var(--border); }
.fz-mail-item-top { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; color: var(--text-dim); }
.fz-mail-item-top b { color: var(--text); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fz-mail-item-top span { flex-shrink: 0; font-size: 11px; color: var(--text-faint); }
.fz-mail-item-subj { font-size: 13px; color: var(--text); margin-top: 2px; }
.fz-mail-empty { font-size: 13px; color: var(--text-faint); padding: 8px 2px; }
.fz-mail-setup { display: flex; flex-direction: column; gap: 8px; font-size: 13px; color: var(--text-dim); }
.fz-mail-setup b { color: var(--text); }
.fz-mail-setup code {
  display: block; padding: 8px 10px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px solid var(--border);
  font-family: var(--mono); font-size: 11px; overflow-x: auto; white-space: nowrap;
}

/* The comms ladder, rendered as the strip above the composer. Rung order is
   the server's (core/comms-ladder.js) — the strip never sorts, so what the
   operator reads left-to-right IS the order a message is tried in. A filled
   dot means configured; the ring means this is the rung the current recipient
   would take. */
.fz-ladder-strip { display: flex; gap: 8px; flex-wrap: wrap; }
.fz-rung {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 9px; border-radius: 999px; font-size: 12px;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text-faint);
}
.fz-rung.is-ready { color: var(--text-dim); }
.fz-rung.is-chosen { border-color: var(--accent-ring); color: var(--text); }
.fz-rung em { font-style: normal; font-size: 11px; color: var(--text-faint); font-family: var(--mono); }
.fz-rung-dot { width: 7px; height: 7px; border-radius: 50%; border: 1px solid currentColor; }
.fz-rung.is-ready .fz-rung-dot { background: var(--accent); border-color: var(--accent); }
.fz-route { font-size: 12px; color: var(--text-dim); padding: 0 2px; }
.fz-route b { color: var(--text); font-weight: 600; }
.fz-route.is-err { color: #c0524d; }
.fz-mail-item.is-preferred { border-left: 2px solid var(--accent); }

/* Unread is WEIGHT plus one dot, not colour: the pane already spends its one
   accent colour on the preferred rung, and a second meaning on the same hue is
   how a legend becomes necessary. */
.fz-mail-item.is-openable { cursor: pointer; }
.fz-mail-item.is-openable:hover { border-color: var(--accent-ring); }
.fz-mail-item.is-unread .fz-mail-item-subj { font-weight: 650; }
.fz-mail-item.is-unread .fz-mail-item-top b { font-weight: 700; }
.fz-mail-unread-dot {
  display: inline-block; width: 6px; height: 6px; margin-right: 6px;
  border-radius: 50%; background: var(--accent); vertical-align: middle;
}
.fz-mail-back {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; color: var(--text); text-align: left;
}

/* Conversations.

   The count pill is the whole visible payoff of threading, so it sits where
   the eye already goes for the date and reads as one glyph: four rows of
   "Re: 7361 Motherboard" became one row with a 4 on it. The unread pill is
   separate and coloured, because "this conversation has messages in it" and
   "some of them are waiting for you" are different facts and one number
   cannot say both. */
.fz-mail-view {
  background: none; border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 1px 7px; margin-left: auto; margin-right: 6px; cursor: pointer;
  font: 11px/1.6 inherit; color: var(--text-dim);
}
.fz-mail-view:hover { border-color: var(--accent-ring); color: var(--text); }
.fz-thread-count, .fz-thread-unread {
  display: inline-block; margin-right: 6px; padding: 0 5px;
  border-radius: 8px; font-style: normal; font-size: 10px; font-weight: 650;
}
.fz-thread-count { background: var(--surface-3, var(--surface-2)); border: 1px solid var(--border); color: var(--text-dim); }
.fz-thread-unread { background: var(--accent); color: var(--surface-1, #fff); }
.fz-thread { display: flex; flex-direction: column; gap: 6px; }
.fz-thread-head {
  display: flex; flex-direction: column; gap: 3px;
  padding-bottom: 6px; border-bottom: 1px solid var(--border);
}
.fz-thread-meta { font-size: 11px; color: var(--text-faint); }
/* Every participant, wrapped rather than truncated: who is in a conversation
   is the thing the row could only summarise, and this is where it is answered. */
.fz-thread-people { font-size: 11px; color: var(--text-dim); word-break: break-word; }
/* Search sits above the list it filters, not in the pane header: it belongs to
   the inbox, and a field in the header would still be there while a message is
   open, where it filters nothing. */
.fz-mail-search { display: flex; gap: 6px; align-items: center; }
.fz-mail-search input {
  flex: 1; min-width: 0; padding: 7px 10px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px solid var(--border);
  font: 13px/1.4 inherit; color: var(--text);
}
.fz-mail-search input:focus { outline: none; border-color: var(--accent-ring); }

/* The provenance line. Dimmer than a message and above every hit, because it
   qualifies the whole list rather than belonging to any row in it. */
.fz-mail-scope {
  display: flex; flex-direction: column; gap: 6px;
  padding: 7px 10px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px dashed var(--border);
  font-size: 12px; color: var(--text-dim);
}
.fz-mail-scope b { color: var(--text); font-weight: 650; }
.fz-mail-scope.is-cache { border-style: dashed; }
.fz-mail-scope-dsl {
  font: 11px/1.4 var(--mono, ui-monospace, monospace);
  color: var(--text-faint); word-break: break-word;
}
/* What the grouping covered, under the counts it qualifies. Faint on purpose:
   it is a bound on the numbers above, so it must be readable and must not
   compete with them for the eye. */
.fz-mail-scope-note { font-size: 11px; color: var(--text-faint); }

/* A message inside a conversation that MATCHED the search that found it. The
   left edge is the marker rather than a background wash, so a matched message
   that is also unread still shows its unread state — two facts, two channels,
   the same mistake the thread/unread pills avoid. `is-focus` is the one the
   search landed on and it is scrolled to on open. */
.fz-mail-item.is-match { border-left: 2px solid var(--accent-ring, var(--accent)); }
.fz-mail-item.is-focus { background: var(--surface-3, var(--surface-2)); }
.fz-mail-widen {
  align-self: flex-start; cursor: pointer; padding: 4px 8px;
  border-radius: var(--r-sm); border: 1px solid var(--border);
  background: none; font: inherit; font-size: 12px; color: var(--text);
}
.fz-mail-widen:hover { border-color: var(--accent-ring); }

/* A sign-in code, ahead of the message it came from. The value is text
   (a screen reader reads it, a selection copies it) and it does not inherit
   the inbox head's uppercase — an alphanumeric code is case-sensitive. */
.fz-mail-code {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding: 12px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px solid var(--border);
}
.fz-mail-code-value {
  font: 600 28px/1.1 var(--mono, ui-monospace, monospace);
  letter-spacing: 0.18em; color: var(--text);
  text-transform: none; user-select: all;
}
.fz-mail-code-from { flex-basis: 100%; font-size: 12px; color: var(--text-dim); text-transform: none; }
.fz-mail-code button:focus-visible,
.fz-mail-view:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }

.fz-mail-read { display: flex; flex-direction: column; gap: 10px; }
.fz-mail-read-subj { font-size: 15px; font-weight: 650; color: var(--text); }
.fz-mail-read-meta { display: flex; flex-direction: column; gap: 2px; font-size: 12px; color: var(--text-dim); }
.fz-mail-read-meta b { color: var(--text-faint); font-weight: 600; margin-right: 6px; }
.fz-mail-read-mid { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fz-mail-read-actions { display: flex; justify-content: flex-end; }
/* The files on a message. A row, not a sentence: each one is its own click
   target, so "save that spreadsheet" is one press rather than a filename to
   read out of a comma-separated list and then hunt for. */
.fz-mail-atts { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 4px; }
.fz-mail-att {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 8px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px solid var(--border);
  font-size: 12px; color: var(--text); text-decoration: none;
}
.fz-mail-att svg { width: 14px; height: 14px; }
.fz-mail-att:hover { border-color: var(--accent-ring); }
/* A part named by a body cached before parts had ids — it can be shown, not
   fetched, and it must not look like a link that will work. */
.fz-mail-att.is-stale { color: var(--text-dim); border-style: dashed; }
.fz-mail-read-body {
  margin: 0; padding: 10px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px solid var(--border);
  font: 13px/1.5 var(--mono, ui-monospace, monospace); color: var(--text);
  white-space: pre-wrap; word-break: break-word; max-height: 60vh; overflow-y: auto;
}

/* ---------- DIFFUZOOM: the descent (web/DIFFUZOOM.md) ----------
   The pane is a single frame plus a rung rail. Every mark that says an image is
   ESTIMATED is ACHROMATIC on purpose: colourised electron micrographs are an
   established publishing convention meaning "real image, prettified", so a
   tinted badge would read as *photograph, tidied up* to the one audience that
   matters. Dashed border + burned footer, no chroma. Real anchors are
   undecorated — a science app owes evidence that. */
.dz { position: absolute; inset: 0; display: flex; flex-direction: column; background: var(--bg); }
.dz-head {
  flex: none; display: flex; align-items: center; gap: 14px; height: 44px; padding: 0 14px;
  border-bottom: 1px solid var(--hairline); background: var(--surface);
}
.dz-exit {
  height: 26px; padding: 0 10px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--hairline);
  color: var(--text-dim); font-size: 12px;
}
.dz-exit:hover { color: var(--text); border-color: var(--border); }
.dz-title { font-size: 13px; color: var(--text-dim); }
.dz-title b { color: var(--text); font-weight: 620; }
.dz-readout { font-family: var(--mono); font-size: 12px; color: var(--text-dim); margin-left: auto; }
.dz-readout b { color: var(--text); font-weight: 600; }
.dz-count { font-family: var(--mono); font-size: 11px; color: var(--text-faint); }

.dz-stage { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 14px; gap: 8px; }
.dz-frame {
  position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column;
  border-radius: var(--r-sm); overflow: hidden; background: var(--surface-2);
  border: 1px solid var(--border);
}
.dz-img { flex: 1; min-height: 0; width: 100%; object-fit: contain; background: #0b0b0c; }
.dz-canvas { flex: 1; min-height: 0; width: 100%; touch-action: none; cursor: grab; }
.dz-frame-mol { background: var(--surface-2); }
/* An estimated frame never appears without its border and its footer. */
.dz-frame-est { border: 4px dashed rgba(150,150,150,.75); }
.dz-foot {
  flex: none; display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 7px 10px; font-size: 11.5px; color: var(--text-dim);
  background: var(--surface); border-top: 1px solid var(--hairline);
}
.dz-foot-est { background: #1a1a1b; color: #d8d8d8; border-top: 1px solid #2c2c2e; }
.dz-foot-est a, .dz-foot-est .dz-dim { color: #a9a9ac; }
.dz-foot a { color: inherit; text-decoration: underline; text-decoration-style: dotted; }
.dz-tag {
  font-family: var(--mono); font-size: 10px; letter-spacing: .06em; font-weight: 700;
  padding: 2px 6px; border-radius: 4px;
}
.dz-tag.t-real { background: var(--surface-2); border: 1px solid var(--border); color: var(--text-dim); }
.dz-tag.t-est { background: #d8d8d8; color: #17171a; }
.dz-tag.t-render { background: var(--surface-2); border: 1px solid var(--border); color: var(--text-dim); }
.dz-dim { color: var(--text-faint); }
.dz-warn { color: var(--err); }
.dz-loading { padding: 14px; font-size: 12px; color: var(--text-faint); font-family: var(--mono); }
.dz-also { flex: none; font-size: 11px; color: var(--text-faint); }

.dz-card {
  flex: 1; min-height: 0; overflow: auto; padding: 18px;
  border-radius: var(--r-sm); background: var(--surface-2);
  border: 1px dashed var(--border); font-size: 13px; color: var(--text-dim);
}
.dz-card b { display: block; color: var(--text); font-size: 14px; margin-bottom: 6px; }
.dz-card p { margin: 0 0 8px; }
.dz-card ul { margin: 0; padding-left: 18px; }
.dz-card li { margin-bottom: 4px; }
.dz-card-unavail { border-color: var(--err); }
.dz-spec { margin-top: 10px; }
.dz-spec pre {
  max-height: 220px; overflow: auto; font-family: var(--mono); font-size: 10.5px;
  background: var(--bg); border: 1px solid var(--hairline); border-radius: var(--r-sm); padding: 8px;
}
.dz-neighbours { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.dz-neighbours button {
  height: 26px; padding: 0 10px; border-radius: var(--r-pill);
  background: var(--surface); border: 1px solid var(--hairline);
  font-size: 11.5px; color: var(--text-dim);
}
.dz-neighbours button:hover { color: var(--text); border-color: var(--border); }

.dz-rail { flex: none; display: flex; gap: 2px; padding: 0 14px 6px; overflow-x: auto; }
.dz-rung {
  flex: 1 0 auto; display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 4px 2px; border-radius: 6px; font-family: var(--mono); font-size: 9.5px;
  color: var(--text-faint); background: none; border: 1px solid transparent;
}
.dz-rung i { display: block; width: 100%; height: 5px; border-radius: 2px; background: var(--border); }
.dz-rung.k-photo i { background: var(--ok); }
.dz-rung.k-estimated i { background: #b9b9bd; }
.dz-rung.k-unavailable i { background: var(--err); }
.dz-rung.k-molecule i { background: var(--accent); }
.dz-rung.is-refused i { background: repeating-linear-gradient(45deg, var(--border) 0 3px, transparent 3px 6px); }
.dz-rung.is-at { color: var(--text); border-color: var(--border); background: var(--surface-2); }
.dz-hint { flex: none; padding: 0 14px 10px; font-size: 11px; color: var(--text-faint); }

/* ---------- result card: what just happened, where you are looking ----------
   Fed by the `result` event on the ui-live channel, rendered straight from the
   payload (no refetch, no reload) [user 2026-07-25]. */
.fz-result {
  width: 100%; display: flex; flex-direction: column; gap: 7px; padding: 11px 12px;
  border-radius: var(--r-sm); background: var(--elev); border: 1px solid var(--border);
  font-size: 12.5px; color: var(--text-dim);
}
.fz-result.s-unavailable { border-color: var(--err); }
.fz-result-head { display: flex; align-items: center; gap: 8px; }
.fz-result-kind { font-weight: 640; color: var(--text); }
.fz-result-status {
  font-family: var(--mono); font-size: 10px; letter-spacing: .05em; text-transform: uppercase;
  padding: 2px 6px; border-radius: 4px; background: var(--surface-2); border: 1px solid var(--hairline);
  color: var(--text-faint); margin-right: auto;
}
.fz-result.s-unavailable .fz-result-status { color: var(--err); border-color: var(--err); }
.fz-result-sum { color: var(--text); }
.fz-result-img { width: 100%; max-height: 180px; object-fit: contain; border-radius: var(--r-sm); background: #0b0b0c; }
.fz-result-why { margin: 0; padding-left: 16px; font-size: 11.5px; color: var(--text-faint); }
.fz-result-out {
  margin: 0; max-height: 120px; overflow: auto; font-family: var(--mono); font-size: 10.5px;
  color: var(--text-faint); background: var(--bg); border: 1px solid var(--hairline);
  border-radius: var(--r-sm); padding: 7px;
}
.fz-result-open {
  align-self: flex-start; height: 26px; padding: 0 11px; border-radius: var(--r-pill);
  background: var(--accent-soft); color: var(--accent); border: 1px solid var(--accent-ring);
  font-size: 12px; font-weight: 600;
}

/* ---------- the lab: every part that is not the composition flow ----------
   [user 2026-08-27] "Such parts need to be baked out in a seperate right
   section for us to test it works". One column between the ladder and the
   rail: a 30px spine when collapsed (the default), 360px of rows when open.
   The rows hold the widgets the main area used to float — the pills, the qty
   toggle, the result card. Those rules now describe only the widget; the lab
   row places it, so nothing here needs un-floating. */
.fz-lab-col {
  flex: none; display: flex; flex-direction: row; min-height: 0;
  border-left: 1px solid var(--hairline); background: var(--surface);
}
.fz-lab-toggle {
  flex: none; width: 30px; display: flex; flex-direction: column; align-items: center;
  gap: 8px; padding: 14px 0; background: transparent; border: 0; cursor: pointer;
  color: var(--text-faint); transition: color .14s, background .14s;
}
.fz-lab-toggle:hover { color: var(--text); background: var(--surface-2); }
.fz-lab-col.is-open .fz-lab-toggle { color: var(--accent); border-right: 1px solid var(--hairline); }
.fz-lab-toggle-name {
  writing-mode: vertical-rl; transform: rotate(180deg);
  font-family: var(--mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
}
.fz-lab-toggle-chev { font-size: 10px; }
.fz-lab-body {
  width: 360px; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; padding: 10px 12px 24px;
}
.fz-lab-row { display: flex; flex-direction: column; gap: 8px; padding: 10px 0 12px; border-bottom: 1px solid var(--hairline); }
.fz-lab-row:last-child { border-bottom: 0; }
.fz-lab-row-head { display: flex; flex-direction: column; gap: 2px; }
.fz-lab-row-label { font-family: var(--mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--text); }
.fz-lab-row-hint { font-size: 11.5px; color: var(--text-faint); }
.fz-lab-row-body { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.fz-lab-runs, .fz-lab-surfaces { display: flex; flex-direction: column; width: 100%; gap: 2px; }
.fz-lab-surfaces { padding-top: 6px; border-top: 1px dashed var(--hairline); }
.fz-lab-check { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--text-dim); width: 100%; cursor: pointer; }
.fz-lab-check input { accent-color: var(--accent); margin: 0; }
.fz-lab-empty { font-size: 12px; color: var(--text-faint); }
.fz-ctx-chip:disabled { opacity: .5; cursor: default; }
/* DIFFUZOOM's root is `position: absolute; inset: 0` — the stage is the box
   that gives it a frame of its own inside the column. */
.fz-lab-stage { position: relative; width: 100%; height: 440px; border-radius: var(--r-sm); overflow: hidden; border: 1px solid var(--hairline); }
/* ---------- phones ---------------------------------------------------------
   [user 2026-08-16] "make it work on mobile … deploy to my phone". Until now
   this stylesheet had no media query at all: every width got the desktop
   layout — a 46px fixed rail on the right, a draggable vertical split, and two
   panes side by side inside 390 CSS pixels.

   The three moves, and why each is forced rather than chosen:
   · the split becomes a STACK. A col-resize handle has no gesture on a
     touchscreen, and 620px of right pane inside a 390px screen is not a pane.
   · the rail becomes a BOTTOM BAR. It is the app menu bar, so it cannot be
     hidden — and a thumb reaches the bottom edge, not a 46px column down one
     side.
   · an in-place surface takes the WHOLE body — but WHICH pane it is differs,
     so App.jsx sets two classes: `.fz-app.has-surface` for a surface the left
     pane holds (mail, network, legacy) and `.fz-app.has-verbal-surface` when a
     terminal is stacked on the ladder in the right column [user 2026-09-07].
     Either way a shell sharing the screen with the chat column leaves ~30
     columns, which is not a terminal.

   820px, not 768: this is where the two panes stop both being usable, and it
   catches a tablet in portrait as well as every phone. */
@media (max-width: 820px) {
  /* dvh, not vh: iOS counts the URL bar in vh, so 100vh is taller than the
     screen and the dock ends up under the fold. */
  html, body, #root { height: 100dvh; }

  .fz-top {
    height: calc(48px + env(safe-area-inset-top));
    padding: env(safe-area-inset-top) 10px 0;
    gap: 10px;
  }
  .fz-run-name { max-width: 92px; }
  .fz-top-right { gap: 8px; }

  /* The rail is no longer a column beside the body, so the body stops clearing
     it on the left — and the whole app clears it at the bottom. The
     reservation belongs on .fz-app, not .fz-body: the dock (the input row) is
     .fz-body's SIBLING, so padding on the body leaves the one control you
     always need sitting under the bar. */
  .fz-app { padding-bottom: calc(50px + env(safe-area-inset-bottom)); }
  .fz-body {
    flex-direction: column;
    padding-left: 0;
  }
  /* flex-basis is an INLINE percentage from the drag handle (App.jsx sets
     .fz-left/.fz-right from `split`). In a column that percentage silently
     becomes a height, so it has to be dropped, and only !important reaches an
     inline style. */
  .fz-left, .fz-right {
    flex-basis: auto !important;
    width: 100%; min-width: 0; max-width: none;
  }
  .fz-left  { flex: 1 1 46%; }
  .fz-right { flex: 1 1 54%; border-top: 1px solid var(--hairline); }
  .fz-panel { border-left: 0; }
  .fz-split { display: none; }

  .fz-app.has-surface .fz-left  { flex: 1 1 100%; }
  .fz-app.has-surface .fz-right { display: none; }
  /* …unless the surface IS the right column: a terminal stacks on the ladder
     there [user 2026-09-07], so on a phone that column takes the body and the
     graph steps aside — the same trade the rule above makes, one pane over. */
  .fz-app.has-verbal-surface .fz-left  { display: none; }
  .fz-app.has-verbal-surface .fz-right { display: flex; flex: 1 1 100%; }

  .fz-rail {
    top: auto; left: 0; right: 0; bottom: 0;
    width: auto; height: auto;
    display: flex; flex-direction: row; align-items: center; justify-content: flex-start;
    gap: 6px;
    padding: 6px 10px calc(6px + env(safe-area-inset-bottom));
    border-right: 0; border-top: 1px solid var(--hairline);
    overflow-x: auto; -webkit-overflow-scrolling: touch;
  }
  .fz-rail::-webkit-scrollbar { display: none; }
  .fz-rail-btn, .fz-rail-app { flex: none; width: 38px; height: 38px; }
  .fz-rail-sep { width: 1px; height: 22px; margin: 0 4px; }

  .fz-slide {
    top: calc(48px + env(safe-area-inset-top));
    right: 0; left: 0;
    bottom: calc(50px + env(safe-area-inset-bottom));
    width: auto;
  }

  .fz-dock { padding: 4px 8px calc(4px + env(safe-area-inset-bottom)); }
  /* Below 16px iOS Safari zooms the whole page when the field takes focus, and
     the layout never zooms back. */
  .fz-dock-input { font-size: 16px; }

  .fz-xterm { padding: 6px 4px 4px 6px; }
  .fz-ws-head { height: 38px; }
}

/* ⌘K beyond this tree: the groups /api/search answers with. */
.fz-search-group { padding: 6px 11px 2px; font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--text-faint); font-family: var(--mono); }
/* How far a source looked, under its results — see searchScopeLine in App.jsx.
   Quieter than a result row on purpose: it is the frame around the answer, not
   another answer. */
.fz-search-scope { padding: 4px 11px 8px; font-size: 11px; color: var(--text-faint); line-height: 1.5; }
.fz-search-ignored { color: var(--warn, #d08b3c); }
.fz-search-widen { display: block; margin-top: 4px; padding: 0; border: 0; background: none; color: var(--accent, #6aa9ff); font-size: 11px; cursor: pointer; font-family: inherit; }
.fz-search-widen[disabled] { opacity: .6; cursor: default; }
.fz-found { flex-direction: column; align-items: flex-start; gap: 3px; }
.fz-found .fz-search-kind, .fz-found .fz-search-label { max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fz-search-web { opacity: .8; }
.fz-search-web:disabled { opacity: .5; }

/* ---------- Hosted by a native app (window.fyzxNative) ----------
   The phone keeps its own input bar, mic and camera under this page and posts
   into it, so the page's dock would be a second bar drawn over the first. The
   rail stays: it is the page's, and the host has no copy of it. */
/* The Mac app hides its title bar: the traffic lights sit on this header, so
   the header clears them and is the window's grab bar (App.jsx). */
.fz-app.is-native.is-macos .fz-top { padding-left: 84px; -webkit-user-select: none; }
.fz-app.is-native .fz-dock { display: none; }
.fz-app.is-native .fz-dock-note { display: none; }

/* ---------- the nest — containment drawn, not indicated ----------
   [user 2026-09-07] "the tree is a ssstuuupppid version of just tryiing to show
   what's within something elsee by zoommming". Depth is DEPTH: a child paints
   over its parent (z-index = its level), so the stack of borders you see is the
   chain of things you are inside. Everything is positioned in screen px by
   Nest.jsx — no CSS transform — because a scaled container would scale the
   1px borders and the type with it, and at 400x the frame would be all border. */
.fz-nest {
  position: absolute; inset: 0; overflow: hidden; cursor: grab;
  background: var(--bg); touch-action: none; user-select: none;
}
.fz-nest:active { cursor: grabbing; }

.fz-nest-box {
  position: absolute; border-radius: 4px; overflow: hidden;
  border: 1px solid var(--hairline);
  background: color-mix(in oklch, var(--surface) 82%, transparent);
  transition: border-color .12s, box-shadow .12s;
}
.fz-nest-box:hover { border-color: var(--accent-ring, var(--accent)); }
.fz-nest-box.is-sel { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
/* A leaf has nothing inside it — so it is the one box drawn as a solid area
   rather than as a container waiting to be opened. */
.fz-nest-box.is-leaf { background: color-mix(in oklch, var(--elev) 70%, transparent); }
/* Areas here are an equal split, not a measurement: at least one member of the
   group carries no quantity, so the picture must not read as a proportion. */
.fz-nest-box.is-unsized { border-style: dashed; }
.fz-nest-box.k-agent { background: color-mix(in oklch, var(--accent) 9%, var(--surface)); }

.fz-nest-head {
  display: flex; align-items: baseline; gap: 6px; padding: 3px 6px 0;
  white-space: nowrap; overflow: hidden; line-height: 1.25;
}
/* A narrow box gets an ellipsis, not a word cut mid-letter: "electron" sheared
   to "electro" reads as a different word, and the box is telling you its
   identity — the one thing it must not garble. */
.fz-nest-name { color: var(--text); font-weight: 600; letter-spacing: -.01em; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.fz-nest-arg, .fz-nest-qty { color: var(--text-faint); font-family: var(--mono); font-size: .82em; }

/* Where you are. A nest gives depth for free and loses the path — indentation's
   one gift — so the chain of containers is written out. */
.fz-nest-trail {
  position: absolute; top: 14px; left: 14px; z-index: 9000;
  display: flex; align-items: center; gap: 5px; flex-wrap: wrap; max-width: 60%;
  padding: 5px 10px; border-radius: var(--r-pill);
  background: color-mix(in oklch, var(--surface) 88%, transparent);
  border: 1px solid var(--hairline); box-shadow: var(--shadow);
  font-size: 11px;
}
.fz-nest-trail button { color: var(--text-faint); font-size: 11px; }
.fz-nest-trail button:hover { color: var(--text); }
.fz-nest-trail button.is-here { color: var(--text); font-weight: 600; }
.fz-nest-sep { color: var(--text-faint); opacity: .6; }

/* Senses — the five sensors this machine has, each reading live (Senses.jsx).
   Left pane, so it inherits the surface palette rather than carrying one. */
.fz-senses {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  min-height: 0;
  overflow-y: auto;
  padding: 14px 16px;
  gap: 10px;
}
.fz-senses .senses-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.fz-senses .senses-head h2 { margin: 0; font-size: 15px; font-weight: 600; letter-spacing: .01em; }
.fz-senses .senses-count { font-size: 12px; opacity: .55; font-variant-numeric: tabular-nums; }
.fz-senses .senses-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.fz-senses .sense {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) auto;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid currentColor;
  border-color: color-mix(in srgb, currentColor 14%, transparent);
  border-radius: 10px;
}
/* The state is the border: a live row has to be findable at arm's length,
   which is the whole job of a sensor panel. */
.fz-senses .sense-live { border-color: color-mix(in srgb, #3fb950 60%, transparent); }
.fz-senses .sense-asking { border-color: color-mix(in srgb, #d29922 60%, transparent); }
.fz-senses .sense-refused,
.fz-senses .sense-absent { border-color: color-mix(in srgb, #f85149 45%, transparent); }
.fz-senses .sense-name { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fz-senses .sense-what { font-size: 11px; opacity: .5; }
.fz-senses .sense-reading { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fz-senses .sense-value { font-size: 14px; font-variant-numeric: tabular-nums; }
.fz-senses .sense-detail { font-size: 11px; opacity: .5; overflow-wrap: anywhere; }
.fz-senses .sense-button {
  padding: 5px 12px;
  border-radius: 8px;
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.fz-senses .sense-button:hover { background: color-mix(in srgb, currentColor 8%, transparent); }
.fz-senses .senses-video { width: 100%; max-height: 46vh; border-radius: 10px; object-fit: cover; }
@media (max-width: 640px) {
  .fz-senses .sense { grid-template-columns: minmax(0, 1fr) auto; }
  .fz-senses .sense-reading { grid-column: 1 / -1; }
}

/* Hosted by a native app that carries its own input (ios/TerminalScreen.swift
   writes this attribute at document start). The dock is the page's composer;
   inside a screen whose bar at the thumb already types into this terminal it is
   a SECOND input under the first, which is the thing that screen exists to
   stop. Hidden rather than removed: the same page in a browser is unchanged,
   and a host that stops setting the attribute gets its composer back. */
html[data-fz-host="native"] .fz-dock { display: none; }
/* Inside the native terminal screen the shell IS the screen. The ladder under
   it invited the reader to "type a subject below" into the composer hidden
   just above, and held two thirds of a phone's height to say so. Measured
   2026-09-23 in the simulator: terminal ~170pt of ~600. The inline flexBasis is
   the desk's remembered share, so the override has to outrank it. */
html[data-fz-host="native"] .fz-app.has-verbal-surface .fz-right > .fz-ws,
html[data-fz-host="native"] .fz-app.has-verbal-surface .fz-term-divider { display: none; }
html[data-fz-host="native"] .fz-app.has-verbal-surface .fz-verbal-term { flex: 1 1 100% !important; }

/* A terminal-style prompt in the reading flow, above the viewport edge. */
.fz-msgs.has-composer { overflow-anchor: none; }
.fz-msgs > .fz-msg, .fz-msgs > .fz-ctx { flex-shrink: 0; }
.fz-msgs > .fz-dock { padding: 0; flex: none; }
.fz-reading-space { flex: none; height: min(30vh, 240px); pointer-events: none; }
