:root {
  /* Orthoticon — Clinical White, light-first. Accent = Clinical Blue.
     A calm clinical scheme: bright cool-white grounds, a trustworthy
     medical blue for active/interactive state, leaf-green for success
     (kept off the accent hue), and a warm coral for danger. */
  --bg: #eef2f6;
  --ink: #172029;
  --muted: #5d6b78;
  --line: #e3e9f0;
  --panel: #ffffff;
  --panel-sunken: #f4f7fb;
  --sidebar-bg: #ffffff;
  --accent: #1f74c4;
  --accent-quiet: #e4eff9;
  --accent-ink: #0f4c86;
  --accent-shadow: rgba(31, 116, 196, 0.22);
  --hover-border: #b7c3cf;
  --hover-bg: #eef5fb;
  --success-border: #1f9d55;
  --success-ink: #136e3b;
  --success-bg: #e4f6ea;
  --danger: #d1402f;
  /* The rail's starting pin. Its own tokens rather than a reuse of the accent:
     accent means "the stage you are looking at", and one colour cannot carry
     two meanings. Dark mode is chosen, not an automatic flip - a light amber
     inverted mechanically comes out muddy against the dark sidebar. */
  --home-bg: #fdf3d3;
  --home-line: #e8cf8a;
  --home-ink: #7a5a10;
  --home-mark: #d99a2b;
  /* 3D viewport background (CSS container fallback behind the GL canvas).
     The actual WebGL clear color is theme-aware via viewerBackground() in
     app.js and kept in sync with these values. */
  --viewer: #cdd6e0;
}

/* Dark theme override, toggled via the button in the sidebar header
   (see themeToggle in app.js). Only the app chrome (sidebar, toolbar,
   panels, buttons) is themed here - --viewer and the .tool-panel family
   are intentionally left alone, see the comment above. */
:root[data-theme="dark"] {
  /* Softer, less-inky dark than the old scheme (user found the previous
     dark too heavy). Clinical Blue lifts to a brighter tint for contrast. */
  --bg: #171c22;
  --ink: #e7edf2;
  --muted: #93a1ad;
  --line: #2a323b;
  --panel: #1f262e;
  --panel-sunken: #242c35;
  --sidebar-bg: #1b2129;
  --accent: #5aa8ec;
  --accent-quiet: #1b3450;
  --accent-ink: #bcdcf8;
  --accent-shadow: rgba(90, 168, 236, 0.30);
  --hover-border: #3a444f;
  --hover-bg: #22303d;
  --success-border: #35c46a;
  --success-ink: #8fe6ab;
  --success-bg: #173a24;
  --danger: #ff7a6e;
  --home-bg: #3a3016;
  --home-line: #5f4d1f;
  --home-ink: #f2d98d;
  --home-mark: #e0b155;
  --viewer: #0e1a24;
}

* {
  box-sizing: border-box;
}

[hidden] {
  display: none !important;
}

body {
  margin: 0;
  color: var(--ink);
  font-family: "Segoe UI", Arial, sans-serif;
  background: var(--bg);
  transition: background-color 0.15s ease, color 0.15s ease;
}

button,
input {
  font: inherit;
}

button {
  min-height: 36px;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--ink);
  background: var(--panel);
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

button:hover {
  border-color: var(--hover-border);
  background: var(--hover-bg);
}

button.is-active {
  border-color: var(--accent);
  color: var(--accent-ink);
  background: var(--accent-quiet);
  box-shadow: inset 0 0 0 1px var(--accent-shadow);
}

button.is-busy {
  border-color: var(--success-border);
  color: var(--success-ink);
  background: var(--success-bg);
}

button:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

svg {
  width: 18px;
  height: 18px;
  stroke-width: 1.8;
}

/* Four columns: sidebar | viewer | drag handle | document dock. The last two
   collapse to zero unless a document is open, so the default layout is exactly
   the two-column one it has always been. */
.app-shell {
  display: grid;
  grid-template-columns: 384px minmax(0, 1fr) 0 0;
  height: 100vh;
}

.app-shell.doc-open {
  grid-template-columns: 384px minmax(0, 1fr) 6px var(--doc-dock-w, 520px);
}

/* Full-screen loader shown until the workflow rails are built, so the sidebar
   never flashes its pre-reorganized (flat) layout while vtk.js/lucide load. */
.app-loading {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  background: var(--bg);
  transition: opacity 0.3s ease;
}

.app-loading.done {
  opacity: 0;
  pointer-events: none;
}

.app-loading-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
}

/* An insole silhouette drawing itself on a loop. */
.load-insole {
  width: 46px;
  height: auto;
}

.load-insole path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 6.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* The path carries pathLength="1250", so the total is normalized to 1250
     regardless of the real outline perimeter - the dash math below stays put
     even if the insole shape is swapped again. */
  stroke-dasharray: 1250;
  animation: insole-draw 2.6s ease-in-out infinite;
}

@keyframes insole-draw {
  0% { stroke-dashoffset: 1250; }
  42% { stroke-dashoffset: 0; }
  58% { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: -1250; }
}

/* "Orthoticon" with the letters doing a staggered wave-jump. */
.load-word {
  display: flex;
  gap: 1px;
  font-size: 22px;
  font-weight: 680;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.load-word span {
  display: inline-block;
  animation: letter-jump 1.5s ease-in-out infinite;
}

.load-word span.ac {
  color: var(--accent);
}

.load-word span:nth-child(1) { animation-delay: 0s; }
.load-word span:nth-child(2) { animation-delay: 0.07s; }
.load-word span:nth-child(3) { animation-delay: 0.14s; }
.load-word span:nth-child(4) { animation-delay: 0.21s; }
.load-word span:nth-child(5) { animation-delay: 0.28s; }
.load-word span:nth-child(6) { animation-delay: 0.35s; }
.load-word span:nth-child(7) { animation-delay: 0.42s; }
.load-word span:nth-child(8) { animation-delay: 0.49s; }
.load-word span:nth-child(9) { animation-delay: 0.56s; }
.load-word span:nth-child(10) { animation-delay: 0.63s; }

@keyframes letter-jump {
  0%, 55%, 100% { transform: translateY(0); }
  25% { transform: translateY(-9px); }
}

@media (prefers-reduced-motion: reduce) {
  .load-insole path { animation: none; stroke-dashoffset: 0; }
  .load-word span { animation: none; }
}

/* ---- Settings modal ---- */
.settings-modal {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: grid;
  place-items: center;
}

.settings-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(10, 18, 28, 0.45);
}

.settings-card {
  position: relative;
  width: min(960px, calc(100% - 32px));
  max-height: calc(100% - 48px);
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 30px 70px -30px rgba(10, 20, 35, 0.6);
}

.settings-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--line);
}

.settings-head h2 {
  margin: 0;
  font-size: 17px;
  font-weight: 660;
}

.settings-close {
  width: 30px;
  height: 30px;
  min-height: 0;
  padding: 0;
  display: inline-grid;
  place-items: center;
  border: 1px solid transparent;
  border-radius: 8px;
  background: none;
  color: var(--muted);
  cursor: pointer;
}

.settings-close:hover {
  background: var(--hover-bg);
  color: var(--ink);
}

.settings-close svg {
  width: 16px;
  height: 16px;
}

.settings-body {
  padding: 4px 18px 16px;
  display: flex;
  flex-direction: column;
}

.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 16px 0;
  border-top: 1px solid var(--line);
}

.settings-row:first-child {
  border-top: 0;
}

.settings-label {
  flex: 1;
}

.settings-title {
  font-size: 14px;
  font-weight: 600;
}

.settings-desc {
  margin-top: 2px;
  font-size: 12px;
  color: var(--muted);
  /* Was a flat 40ch, which left a wide dialog mostly empty. */
  max-width: 72ch;
}

/* Tool-dialog dock side (set from Settings; default right). Info/help panel
   docks to the opposite side so it never overlaps an open tool dialog. */
:root[data-dialog-side="left"] .tool-float,
:root[data-dialog-side="left"] .tool-panel {
  left: 12px;
  right: auto;
}

:root[data-dialog-side="left"] .info-panel {
  left: auto;
  right: 12px;
}

.sidebar {
  display: flex;
  flex-direction: column;
  gap: 12px;
  height: 100vh;
  overflow: hidden;
  padding: 14px 12px;
  border-right: 1px solid var(--line);
  background: var(--sidebar-bg);
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

.brand-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 4px 2px 8px;
}

.brand-actions {
  display: flex;
  gap: 8px;
}

.brand-row h1 {
  margin: 0;
  font-size: 24px;
  line-height: 1;
}

.brand-row p {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: 13px;
}

.icon-button {
  display: inline-grid;
  place-items: center;
  width: 38px;
  padding: 0;
}

/* ---- Workflow rail: the sidebar's six stages, driven by a slim icon rail.
   buildWorkflowRail() (app.js) re-parents the data-stage panels into
   .stage-deck; only the active stage is shown. ---- */
.workflow {
  display: flex;
  gap: 10px;
  flex: 1;
  min-height: 0;
}

.rail {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: none;
  width: 62px;
  overflow-y: auto;
}

.rail-btn {
  /* Positioned so .rail-btn-home can put its underline marker on it. */
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 9px 2px 7px;
  border: 1px solid transparent;
  border-radius: 9px;
  background: none;
  color: var(--muted);
  font-size: 10px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.12s ease, color 0.12s ease;
}

.rail-btn > i,
.rail-btn svg {
  width: 20px;
  height: 20px;
}

.rail-btn:hover {
  background: var(--hover-bg);
  color: var(--ink);
}

.rail-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.rail-btn[aria-selected="true"] {
  color: var(--accent-ink);
  background: var(--accent-quiet);
  box-shadow: inset 2px 0 0 var(--accent);
}

/* The stage a session starts on. CT/MR and Docs sit above it because they are
   the other two ways data arrives, so Import is no longer the first pin - and
   without a mark, "where do I begin" stops being obvious the moment it stops
   being the top one.
   A light panel, not an accent: accent already means "this is the stage you
   are looking at", and two things competing for the same signal would leave
   neither meaning anything. So this reads as a lit doorway that stays lit
   whichever stage is open, and the selection highlight still wins on top. */
.rail-btn-home {
  background: var(--home-bg);
  border-color: var(--home-line);
  color: var(--home-ink);
}

.rail-btn-home::after {
  content: "";
  position: absolute;
  left: 6px;
  right: 6px;
  bottom: 2px;
  height: 2px;
  border-radius: 1px;
  background: var(--home-mark);
}


/* Selected still beats home: the underline stays as a reminder of where the
   start is, but the panel itself has to show which stage is open. */
.rail-btn-home[aria-selected="true"] {
  background: var(--accent-quiet);
  color: var(--accent-ink);
}

.rail-btn-home[aria-selected="true"]::after {
  background: var(--accent);
  opacity: 1;
}

.stage-deck {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}

.stage {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.stage-head {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 2px 2px 4px;
}

.stage-head .st-ico {
  display: inline-flex;
  color: var(--accent);
  flex: none;
}

.stage-head .st-ico > i,
.stage-head .st-ico svg {
  width: 20px;
  height: 20px;
}

.stage-head h2 {
  margin: 0;
  font-size: 16px;
  font-weight: 660;
  line-height: 1.1;
}

.stage-head p {
  margin: 2px 0 0;
  font-size: 12px;
  color: var(--muted);
}

/* Relocated top-toolbar groups, shown as labelled icon rows inside a stage.
   The .tool-group / button styling is global, so the buttons keep their look
   and active-state highlight. */
.stage-tools {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.stage-tools .tool-group {
  flex-wrap: wrap;
}

/* --- Second rail (tools of the active stage) + floating tool menu --- */
.toolrail {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.toolrail-head {
  padding: 2px 2px 8px;
}

.toolrail-head h3 {
  margin: 0;
  font-size: 16px;
  font-weight: 660;
}

.toolrail-head .th-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.expand-all {
  min-height: 0;
  padding: 4px 8px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--panel);
  color: var(--muted);
  font-size: 11px;
  font-weight: 560;
  cursor: pointer;
  white-space: nowrap;
}

.expand-all:hover {
  border-color: var(--hover-border);
  background: var(--hover-bg);
  color: var(--ink);
}

.toolrail-head p {
  margin: 2px 0 0;
  font-size: 12px;
  color: var(--muted);
}

.toolrail-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}

.toolrail-list {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding-bottom: 8px;
}

/* Import stage: a persistent inline panel (drop-zones) instead of tool buttons. */
.import-panel {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-bottom: 8px;
}

/* Reference and CT stages: same flat, always-visible treatment as Analyze -
   each is one way in plus the controls that follow from it, with nothing worth
   collapsing. */
/* Same look as .field-val, but a separate class on purpose - see the comment on
   this row in index.html: anything inside .field gets the app's generic slider
   readout attached, which cannot render "43 / 85". */
.pressure-field {
  display: grid;
  grid-template-columns: 72px 1fr 52px;
  align-items: center;
  gap: 10px;
  color: var(--muted);
  font-size: 13px;
}

.pressure-field output {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  font-size: 12px;
}

/* ---- Scene list -----------------------------------------------------------
   Models, the background image and the pressure map in one list, grouped by
   category. The rows themselves are plain .actor-row - the point is that all
   three kinds look and behave identically once they are in the scene. */
/* The three categories used to be spelled out in headings, which cost three
   lines of a panel that is mostly list. The icon in the name cell and the
   colour stripe down the left say the same thing inside the row the object was
   already taking up - and the row's tooltip still names the category in words,
   so nothing depends on remembering which colour is which. */
/* Light is the base and [data-theme="dark"] overrides it - the same shape as
   every other variable in this file. index.html stamps data-theme on <html>
   before first paint, so there is no prefers-color-scheme fallback to write.

   Model keeps the app's own accent blue, since it is the thing being designed;
   the other two take a violet and a warm orange that read as clearly different
   at a 13px icon without competing with it. */
:root {
  --kind-mesh: #1f74c4;
  --kind-image: #7d5bd0;
  --kind-pressure: #d9822b;
}

:root[data-theme="dark"] {
  --kind-mesh: #5aa8ec;
  --kind-image: #a68ae8;
  --kind-pressure: #eda158;
}

/* Anchor for the opacity pop-up, which is absolutely positioned so opening it
   never reflows the rows below - the list must not move under the pointer
   while it is being used to compare two things drawn on top of each other. */
.scene-entry {
  position: relative;
}

/* One row, four columns: remove | name | eye | move.

   Remove is on its own at the far left, with the whole filename between it and
   the two controls that actually get used - it used to end a run of four
   buttons on the right, which is where a slightly-long press aimed at "move"
   landed, and removing a model cannot be undone. */
/* remove | name + % | eye | move. Four columns, no per-row kind icon and no
   Mirror button - the stripe says what it is, and Mirror / Inverse live in the
   move/rotate panel, which is already open on exactly one object. */
.actor-row.scene-row {
  grid-template-columns: 26px minmax(0, 1fr) 26px 24px;
  gap: 3px;
  padding: 5px 6px;
  /* Wide enough to read as a colour rather than a hairline - at 3px it was a
     detail you had to go looking for, which is no use as the only thing saying
     what kind of object the row is. */
  border-left: 6px solid var(--kind-mesh);
}

.actor-row.scene-row-image { border-left-color: var(--kind-image); }
.actor-row.scene-row-pressure { border-left-color: var(--kind-pressure); }

.scene-name {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}

.scene-name-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Always in the layout, blank at 100%: the number can then be written straight
   into it while the eye is being dragged without anything moving. */
.scene-percent {
  flex: 0 0 auto;
  margin-left: auto;
  min-width: 30px;
  text-align: right;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}

.scene-eye.is-fading {
  border-color: var(--accent);
  color: var(--accent-ink);
  cursor: ns-resize;
}

/* The colour key, once, under the list. */
.scene-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  padding: 2px 2px 0;
  font-size: 11px;
  color: var(--muted);
}

.scene-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.scene-legend-item::before {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: 2px;
  background: var(--kind-mesh);
}

.scene-legend-image::before { background: var(--kind-image); }
.scene-legend-pressure::before { background: var(--kind-pressure); }

/* The per-object settings dialog opens bottom-right, not top-right like every
   other tool panel: the move/rotate gizmo's panel is very likely to be open at
   the same time, and two panels in one corner cover each other. */
.tool-panel.tool-panel-br {
  top: auto;
  bottom: 12px;
}

.scene-settings-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 56vh;
  overflow: auto;
}

/* The blocks it borrows were laid out for the 384px rail; inside a 300px panel
   they need to give up their own outer padding. */
.scene-settings-body > * {
  margin: 0;
}

/* Mirror / Inverse in the move/rotate panel: a label and three axis buttons. */
.manip-flip-row {
  display: grid;
  grid-template-columns: 52px repeat(3, 1fr);
  align-items: center;
  gap: 6px;
}

.manip-flip-row + .manip-flip-row {
  margin-top: 6px;
}

.manip-flip-row > span {
  font-size: 12px;
  color: var(--muted);
}

.manip-flip-row .tool-btn {
  min-width: 0;
  padding: 4px 0;
}

/* The shared 30px sizing would overflow these columns. */
.scene-row .visibility-button,
.scene-row .delete-button {
  width: 100%;
  min-height: 26px;
  border-radius: 6px;
}

/* Visible but faded reads as neither "shown" nor "hidden", so the eye says so
   itself - otherwise the only clue is inside a pop-up nobody has open. */
.scene-eye.is-faded {
  color: var(--accent);
}

/* Hold-to-fade must not start a text selection or fire the long-press menu on
   a touch screen half a second into the gesture. */
.scene-eye {
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

.scene-icon {
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  cursor: pointer;
}

.scene-icon:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.scene-icon.is-active {
  border-color: var(--accent);
  background: var(--accent-quiet);
  color: var(--accent-ink);
}

.scene-icon svg {
  width: 14px;
  height: 14px;
}

/* Min / Max / Frame: three mutually exclusive readings, so one segmented
   control. .segmented already lays out equal columns and lights .active. */
.pressure-modes button {
  padding: 6px 4px;
  font-size: 13px;
}

/* The play button shares the label's 72px column rather than taking one of its
   own, so the slider keeps every pixel it had. */
.pressure-frame-label {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.pressure-play {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel-sunken);
  color: var(--ink);
  cursor: pointer;
}

.pressure-play:disabled {
  opacity: 0.45;
  cursor: default;
}

.pressure-play:not(:disabled):hover {
  border-color: var(--accent);
  color: var(--accent);
}

.pressure-play.is-playing {
  border-color: var(--accent);
  background: var(--accent-quiet);
  color: var(--accent-ink);
}

.pressure-play svg {
  width: 13px;
  height: 13px;
}

.reference-panel,
.ct-panel {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-bottom: 8px;
}

/* Analyze stage: a flat, always-visible control panel (its wf-inline blocks
   are shown whole - no collapsible tool-btn2 groups), so every coloring and
   appearance control stays reachable at once. */
.analyze-panel {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-bottom: 8px;
}

/* Analyze reads as a control page, so give its mode buttons a consistent,
   roomier look - equal size in the 2-col grid, a touch larger and rounder than
   the dense toolbar buttons. */
.analyze-panel .tool-btn {
  min-height: 32px;
  font-size: 12px;
  border-radius: 8px;
}

/* Separate the Appearance and Coloring blocks with a divider and extra
   breathing room so the panel reads as two distinct, tidy sections. */
.analyze-panel .wf-inline + .wf-inline {
  border-top: 1px solid var(--line);
  margin-top: 4px;
  padding-top: 18px;
}

/* Each Analyze section header (Appearance / Coloring) reads like the group
   headers in the other stages - a muted section icon + an ink label - so the
   two blocks are clearly separated and consistent with the rest of the app. */
.analyze-panel .panel-subtitle {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 2px;
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
}

.analyze-panel .panel-subtitle > i,
.analyze-panel .panel-subtitle > svg {
  width: 18px;
  height: 18px;
  flex: none;
  color: var(--muted);
}

/* Indent the controls under each Analyze section header behind a left rule -
   the same sub-item indentation the stage sublists (Cut & clip -> box / plane /
   sphere / lasso) use, so Appearance/Coloring read as grouped sub-content. */
.analyze-subcontent {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-left: 14px;
  padding-left: 10px;
  border-left: 1px solid var(--line);
}

/* Unify the Representation segmented (Surface / Wire / Points) with the coloring
   buttons - same height and weight so they read as one button family. */
.analyze-panel .segmented button {
  min-height: 32px;
  font-size: 12px;
  font-weight: 650;
}

.wf-inline {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Persistent footer: Actors + Background Images, visible on every stage. */
.toolrail-footer {
  flex: none;
  max-height: 46%;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}

.build-stamp {
  flex: none;
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--line);
  font-size: 10px;
  letter-spacing: 0.02em;
  color: var(--muted);
  opacity: 0.7;
  text-align: right;
  font-variant-numeric: tabular-nums;
  cursor: default;
}

.toolrail-footer-bar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
}

/* Buttons pinned to the RIGHT, grip pushed left by the auto margin below.
   The floating panel is anchored by its right edge, so right-aligned buttons
   keep the same screen position when the list collapses and the panel narrows.
   With space-between they sat wherever the current width put them, so the
   collapse button slid sideways under the pointer the moment it was used - and
   a second click landed on nothing. */
.toolrail-footer.floating .toolrail-footer-bar {
  justify-content: flex-end;
  cursor: move;
  margin: -4px -4px 4px;
  padding: 3px 4px;
  border-radius: 7px;
  touch-action: none;
}

.toolrail-footer.floating .footer-grip {
  margin-right: auto;
}

.toolrail-footer.floating .toolrail-footer-bar:hover {
  background: var(--hover-bg);
}

.footer-grip {
  display: none;
  align-items: center;
  color: var(--muted);
}

.toolrail-footer.floating .footer-grip {
  display: inline-flex;
}

.footer-grip svg,
.footer-grip i {
  width: 15px;
  height: 15px;
}

.measure-modes {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
}

.footer-dock-toggle {
  align-self: flex-end;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--panel);
  color: var(--muted);
  cursor: pointer;
}

.footer-dock-toggle:hover {
  color: var(--ink);
  border-color: var(--hover-border);
  background: var(--hover-bg);
}

.footer-dock-toggle svg,
.footer-dock-toggle i {
  width: 15px;
  height: 15px;
}

/* Popped-out Actors / Background-images list: a floating panel parked at a fixed
   spot over the viewer (toggled by the footer's dock button). */
/* Bottom-LEFT: the per-object settings dialog opens bottom-right, and the two
   would land on top of each other. The inline style set when it pops out says
   the same thing - this is only the starting point before any drag. */
.toolrail-footer.floating {
  position: fixed;
  left: 18px;
  bottom: 18px;
  width: 300px;
  max-height: 62vh;
  z-index: 40;
  margin-top: 0;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: 0 18px 44px -18px rgba(15, 30, 50, 0.45);
}

.tool-btn2 {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 0;
  text-align: left;
  padding: 9px 10px;
  border-radius: 9px;
  border: 1px solid transparent;
  background: none;
  color: var(--ink);
  cursor: pointer;
  font-size: 13px;
  font-weight: 550;
}

.tool-btn2 > i,
.tool-btn2 svg {
  flex: none;
}

.tool-btn2 > i:first-child,
.tool-btn2 > svg:first-child {
  width: 18px;
  height: 18px;
  color: var(--muted);
}

.tool-btn2 > span {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tool-btn2 .chev {
  flex: none;
  width: 16px;
  height: 16px;
  opacity: 0.5;
  transition: transform 0.15s ease;
}

.tool-btn2[aria-expanded="true"] .chev {
  transform: rotate(90deg);
}

.tool-btn2:hover {
  background: var(--hover-bg);
  border-color: var(--line);
}

/* Active action (a tool that's toggled on) keeps the accent "selected" look. */
.tool-btn2[aria-pressed="true"] {
  background: var(--accent-quiet);
  color: var(--accent-ink);
  box-shadow: inset 0 0 0 1px var(--accent-shadow);
}

/* An expanded group is not a selection - it's just opened. Keep the header
   fully neutral (no fill; only the rotated chevron and the revealed sub-list
   mark it open), so only actually-active actions read as selected. */
.tool-btn2[aria-expanded="true"] {
  background: none;
}

.tool-btn2[aria-pressed="true"] > i:first-child,
.tool-btn2[aria-pressed="true"] > svg:first-child {
  color: var(--accent);
}

/* Inline expandable sub-actions (e.g. Cut & clip -> box/plane/sphere/lasso, or
   the Shape stage's Mirror / Smoothing / Sculpt items). Default ("compact")
   layout: an equal-column grid of pills, so a group of 6-8 items becomes a few
   short rows instead of one full-width row each. Every pill in a section shares
   the same width (the grid's 1fr tracks), which keeps a set like Mirror X/Y/Z,
   Inverse X/Y/Z reading as a tidy block. The column count adapts to the sidebar
   width. The "list" layout below (a Settings option) restores one row per item. */
.tool-sublist {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 4px;
  margin: 3px 0 6px 14px;
  padding-left: 10px;
  border-left: 1px solid var(--line);
}

/* Reconstruct item-tree: a shared slider (Surface normals' K) or an inline
   toggle (Show normals) spans the full sub-list width, not one pill cell. */
.tool-sublist > .field,
.tool-sublist > .toggle {
  grid-column: 1 / -1;
}

/* Reconstruct groups expand their real controls (buttons + sliders) inline
   under the header, behind the same left rule the pill sublists use. */
.tool-inline-sublist {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 3px 0 6px 14px;
  padding-left: 10px;
  border-left: 1px solid var(--line);
}

.tool-subrow {
  display: flex;
  align-items: center;
  gap: 6px;
  width: auto;
  min-width: 0;
  min-height: 0;
  text-align: left;
  padding: 5px 10px;
  border-radius: 7px;
  border: 1px solid transparent;
  background: var(--panel-sunken);
  color: var(--ink);
  cursor: pointer;
  font-size: 12.5px;
  white-space: nowrap;
}

.tool-subrow > span {
  overflow: hidden;
  text-overflow: ellipsis;
}

.tool-subrow > i,
.tool-subrow svg {
  flex: none;
  width: 15px;
  height: 15px;
  color: var(--muted);
}

.tool-subrow:hover {
  background: var(--hover-bg);
  border-color: var(--line);
}

.tool-subrow[aria-pressed="true"] {
  background: var(--accent-quiet);
  border-color: var(--accent-shadow);
  color: var(--accent-ink);
}

.tool-subrow[aria-pressed="true"] > i:first-child,
.tool-subrow[aria-pressed="true"] > svg:first-child {
  color: var(--accent);
}

/* "List" layout (Settings -> Tool item layout): one full-width row per item,
   as before the compact grid. Set via data-item-layout on the root element. */
[data-item-layout="list"] .tool-sublist {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

[data-item-layout="list"] .tool-subrow {
  width: 100%;
  gap: 9px;
  padding: 7px 9px;
  border-radius: 8px;
  background: none;
}

[data-item-layout="list"] .tool-subrow:hover {
  background: var(--hover-bg);
  border-color: transparent;
}

.viewer-band {
  position: relative;
}

.tool-float-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 8;
}

.tool-float {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 274px;
  max-height: calc(100% - 28px);
  display: flex;
  flex-direction: column;
  pointer-events: auto;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 13px;
  box-shadow: 0 22px 50px -22px rgba(15, 30, 50, 0.5);
  overflow: hidden;
}

.tool-float-head {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 11px 12px;
  border-bottom: 1px solid var(--line);
}

.tool-float-head .tf-ico {
  display: inline-flex;
  color: var(--accent);
}

.tool-float-head .tf-ico svg {
  width: 18px;
  height: 18px;
}

.tool-float-head h4 {
  margin: 0;
  font-size: 14px;
  font-weight: 640;
  flex: 1;
}

.tf-close {
  width: 28px;
  height: 28px;
  min-height: 0;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 7px;
  background: none;
  color: var(--muted);
  display: inline-grid;
  place-items: center;
  cursor: pointer;
}

.tf-close:hover {
  background: var(--hover-bg);
  color: var(--ink);
}

.tf-close svg {
  width: 15px;
  height: 15px;
}

.tool-float-body {
  padding: 12px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Foam-style dialog structure: params grouped, then a divider, then actions. */
.tf-params {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.tf-actions {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 2px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}

.tf-desc {
  margin: 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--muted);
}

.tf-reset {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-top: 2px;
  min-height: 30px;
  font-size: 12px;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
}

.tf-reset:hover {
  color: var(--ink);
  border-color: var(--hover-border);
  background: var(--hover-bg);
}

.tf-reset svg {
  width: 14px;
  height: 14px;
}

.tf-param-slot:empty {
  display: none;
}

.tf-run {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 34px;
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  font-weight: 620;
}

.tf-run:hover {
  background: var(--accent);
  border-color: var(--accent);
}

.panel {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

.panel-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 650;
  font-size: 14px;
}

.drop-zone {
  display: grid;
  grid-template-columns: 24px 1fr;
  align-items: center;
  gap: 10px;
  min-height: 74px;
  padding: 14px;
  border: 1px dashed var(--hover-border);
  border-radius: 8px;
  color: var(--muted);
  background: var(--panel-sunken);
  cursor: pointer;
}

.drop-zone.dragging {
  border-color: var(--accent);
  background: var(--accent-quiet);
}

.drop-zone input {
  display: none;
}

.drop-zone .dz-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.drop-zone .dz-title {
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
}

.drop-zone .dz-ext {
  color: var(--muted);
  font-size: 11px;
}

.actor-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 240px;
  overflow: auto;
}

.actor-row {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) 36px;
  align-items: center;
  gap: 6px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel-sunken);
}

.actor-row.active {
  border-color: var(--accent);
  background: var(--accent-quiet);
}

.actor-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
}

.visibility-button,
.delete-button {
  display: inline-grid;
  place-items: center;
  width: 30px;
  min-height: 30px;
  padding: 0;
}

.bg-image-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel-sunken);
  cursor: pointer;
}

.bg-image-row.active {
  border-color: var(--accent);
  background: var(--accent-quiet);
}

.bg-image-move {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
}

/* Shared compact tool-button look - used by both the Background Images
   rows and the actor Mirror/Inverse controls in the Display panel, so the
   two areas read as one consistent design rather than two different
   button styles. */
.tool-btn-row {
  display: grid;
  gap: 4px;
  margin-top: 4px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}

.tool-btn-row-2 {
  grid-template-columns: repeat(2, 1fr);
}

.tool-btn-row-3 {
  grid-template-columns: repeat(3, 1fr);
}

.tool-btn-row-4 {
  grid-template-columns: repeat(4, 1fr);
}

/* Same grid, no top divider - for a row that directly follows another
   tool-btn-row/tool-btn-grid in the same group (only the first one in a
   group should draw the divider line). */
.tool-btn-grid {
  display: grid;
  gap: 4px;
}

.tool-btn-grid-2 {
  grid-template-columns: repeat(2, 1fr);
}

.tool-btn-grid-3 {
  grid-template-columns: repeat(3, 1fr);
}

.tool-btn-grid-4 {
  grid-template-columns: repeat(4, 1fr);
}

.tool-btn {
  display: inline-grid;
  place-items: center;
  min-height: 26px;
  padding: 0 4px;
  font-size: 11px;
  font-weight: 650;
}

/* Visually fences off a cluster of buttons plus the one shared parameter
   slider that applies to all of them (Torsion/Convexity/Curv. Radius, and
   separately the 7 Smoothing filter buttons) as one connected unit - the
   slider only means something in relation to those buttons, so a shared
   border/background makes that obvious at a glance instead of relying on
   the slider's label text alone. */
.radius-tools-group {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 4px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel-sunken);
}

.bg-image-reset-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  min-height: 26px;
  font-size: 12px;
  font-weight: 500;
}

.bg-image-mouse-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  min-height: 28px;
  font-size: 12px;
}

.field {
  display: grid;
  grid-template-columns: 72px 1fr;
  align-items: center;
  gap: 10px;
  color: var(--muted);
  font-size: 13px;
}

.field input[type="color"] {
  width: 100%;
  height: 32px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel);
}

/* Let the range slider shrink to fit its grid cell instead of forcing the
   row wider than the panel (which clipped the value column after the sidebar
   was narrowed by the workflow rail). */
.field input[type="range"] {
  width: 100%;
  min-width: 0;
}

/* A .field that also shows the slider's current value on the right. */
.field-val {
  grid-template-columns: 72px 1fr 34px;
}

.field-val output {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  font-size: 12px;
}

/* A readout that carries a CONVERTED value as well as the raw one - the
   Poisson trim shows both the multiple it is set to and what that works out to
   in millimetres on this model. 34px fits "8" and wraps "8x - 0.54mm" onto a
   second line, which pushes the row taller than its neighbours and reads as a
   layout bug. The unit is small and grey so the number still leads. */
.field-val-wide {
  grid-template-columns: 72px 1fr 86px;
}

.field-val-wide output {
  white-space: nowrap;
}

.field-val-wide output .converted {
  color: var(--muted);
  font-size: 11px;
}

/* Small explanatory note under a panel subtitle (e.g. the normals notice). */
.panel-note {
  margin: 2px 0 4px;
  color: var(--muted);
  font-size: 11px;
  line-height: 1.4;
}

/* A full-width action button sitting directly above its own parameter
   slider(s) - used to pair each triangulation method with its controls. */
.tool-btn-lead {
  margin-top: 6px;
}
.tool-btn-lead:first-child {
  margin-top: 0;
}

.segmented {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 4px;
}

.segmented button {
  white-space: nowrap;
}

.segmented button.active {
  border-color: var(--accent);
  color: var(--accent-ink);
  background: var(--accent-quiet);
}

.side-selector {
  grid-template-columns: repeat(2, 1fr);
}

.side-switch {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-size: 13px;
  color: var(--muted);
}

.side-switch-label {
  cursor: pointer;
  user-select: none;
}

.side-switch-label.is-active {
  color: var(--ink);
  font-weight: 650;
}

.switch {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 22px;
  flex: none;
}

.switch input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.switch-track {
  position: absolute;
  inset: 0;
  background: var(--line);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s ease;
}

.switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  background: #fff;
  border-radius: 50%;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
  transition: transform 0.15s ease;
}

.switch input:checked ~ .switch-track {
  background: var(--accent);
}

.switch input:checked ~ .switch-track .switch-knob {
  transform: translateX(18px);
}

.foot-diagram {
  display: flex;
  justify-content: center;
  padding: 4px 0;
}

.foot-diagram-svg {
  width: 84px;
  height: auto;
}

.foot-diagram-outline {
  fill: none;
  stroke: var(--muted);
  stroke-width: 3;
  stroke-linejoin: round;
}

.foot-diagram-point {
  stroke: #fff;
  stroke-width: 1.5;
}

.foot-diagram-label {
  font-size: 13px;
  font-weight: 700;
  fill: #fff;
  text-anchor: middle;
}

.button-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.button-grid button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}

.panel-subtitle {
  color: var(--muted);
  font-size: 12px;
  font-weight: 650;
}

/* Sub-group label under a panel-subtitle (e.g. the two Triangulation groups) -
   smaller and uppercase so it reads as a subdivision, not a new section. */
.tool-group-caption {
  color: var(--muted);
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-top: 4px;
}

.toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--muted);
  font-size: 13px;
}

.sub-panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel-sunken);
}

.sub-panel-title {
  color: var(--muted);
  font-size: 12px;
  font-weight: 650;
  text-transform: uppercase;
}

#exportThreePoints,
#applyFastFoam,
#exportModelVtk,
#exportModelStl {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}

#reduceModel {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}

/* The explicit minmax(0, 1fr) COLUMN matters as much as the rows: without it
   the single implicit column is auto-sized, so the toolbar's min-content width
   becomes a floor and the viewer band stretches past the workspace box. That
   overflow used to be invisible (nothing sat to the right of the viewer); with
   the document dock there, the WebGL canvas spilled over the drag handle and
   swallowed its pointer events. */
.workspace {
  display: grid;
  grid-template-rows: 54px minmax(0, 1fr) 32px;
  grid-template-columns: minmax(0, 1fr);
  min-width: 0;
  height: 100vh;
}

/* Consequence of the above: when the workspace is squeezed (a wide dock on a
   small screen) the toolbar scrolls instead of forcing the viewer wider. */
.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
  overflow-x: auto;
  overflow-y: hidden;
}

.toolbar.is-busy,
.sidebar.is-busy,
.tool-panel.is-busy {
  pointer-events: none;
  opacity: 0.6;
  cursor: wait;
}

.tool-group {
  display: flex;
  gap: 6px;
}

.tool-group button {
  display: inline-grid;
  place-items: center;
  width: 38px;
  padding: 0;
}

/* Visible separator inside a toolbar group (a trailing margin is invisible here
   because .toolbar uses justify-content: space-between). */
.tool-divider {
  width: 1px;
  align-self: stretch;
  margin: 5px 10px;
  background: var(--line);
}

.viewer-band {
  position: relative;
  min-height: 0;
  min-width: 0;
  /* The WebGL canvas is sized in pixels by vtk.js and only catches up on the
     next resize() - clip it so a frame of lag can never paint (or take clicks)
     outside the viewer. */
  overflow: hidden;
  background: var(--viewer);
}

.viewer {
  position: relative;
  z-index: 0;
  width: 100%;
  height: 100%;
}

.empty-state {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 14px;
  /* Darker than the old placeholder grey - the mark was almost lost against
     the light viewport background. */
  color: #8d99ab;
  pointer-events: none;
}

.empty-state svg {
  width: 44px;
  height: 44px;
}

/* Shared floating tool panel: every interactive tool (clip widgets, move
   widget, foam extractor, and future tools like spline selector / surface
   spray) uses this same panel, always at the same size and screen position,
   with the same internal zones in the same order:
     1. tool-panel-instructions (title + hint - what the tool does / current
        step)
     2. tool-panel-actions (primary buttons for this tool)
     3. tool-panel-params (tool-specific extra parameters - hidden/empty
        when a tool doesn't have any yet)
   Keeping this consistent means the user always knows where to look,
   regardless of which tool is active. */
/* Full-viewport layer that holds the flag text labels (HTML overlays tracked
   to their 3D anchor points each frame). Non-interactive so it never steals
   clicks/drags from the canvas below. */
.flag-overlay-layer {
  position: absolute;
  inset: 0;
  z-index: 2;
  overflow: hidden;
  pointer-events: none;
}

.flag-label {
  position: absolute;
  transform: translate(-50%, calc(-100% - 10px));
  padding: 2px 8px;
  border-radius: 10px;
  font-size: 12px;
  font-weight: 650;
  line-height: 1.3;
  color: #fff;
  white-space: nowrap;
  border: 1px solid rgba(255, 255, 255, 0.55);
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.45);
}

/* Freehand lasso-cut stroke, drawn as an SVG overlay in screen space while
   the user right-drags. Non-interactive so it never eats the drag. */
.lasso-overlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.lasso-path {
  fill: rgba(255, 210, 63, 0.14);
  stroke: #ffd23f;
  stroke-width: 2;
  stroke-dasharray: 6 4;
}

/* ---- Color legend --------------------------------------------------------
   Compact floating colorbar (viewer bottom-left) explaining the active data
   coloring mode. Non-interactive; sits above the canvas but below tool panels. */
.color-legend {
  position: absolute;
  left: 14px;
  bottom: 14px;
  z-index: 5;
  width: 186px;
  padding: 9px 11px 8px;
  border-radius: 8px;
  color: var(--ink);
  background: color-mix(in srgb, var(--panel) 88%, transparent);
  border: 1px solid rgba(127, 127, 127, 0.32);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
  font-size: 11px;
  pointer-events: none;
  backdrop-filter: blur(3px);
}

.color-legend[hidden] {
  display: none;
}

.color-legend-title {
  font-weight: 650;
  letter-spacing: 0.02em;
  margin-bottom: 6px;
}

.color-legend-bar {
  height: 12px;
  border-radius: 3px;
  border: 1px solid rgba(127, 127, 127, 0.4);
}

.color-legend-labels {
  display: flex;
  justify-content: space-between;
  gap: 6px;
  margin-top: 4px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.color-legend-labels span:nth-child(2) {
  text-align: center;
}

/* ---- Long-running native-op overlay --------------------------------------
   Centered spinner + operation name + live elapsed timer. Non-blocking
   (pointer-events off) so the camera still orbits during a multi-minute op. */
.native-progress {
  position: absolute;
  inset: 0;
  z-index: 9;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.native-progress[hidden] {
  display: none;
}

.native-progress-card {
  display: flex;
  align-items: center;
  gap: 14px;
  max-width: 80%;
  padding: 14px 18px;
  border-radius: 10px;
  color: var(--ink);
  background: color-mix(in srgb, var(--panel) 93%, transparent);
  border: 1px solid rgba(127, 127, 127, 0.3);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(3px);
}

/* Busy indicator: the insole outline drawing itself on a loop (same mark and
   keyframes as the start-up loader) instead of a generic spinning ring. */
.native-progress-insole {
  flex: none;
  width: 26px;
  height: auto;
}

.native-progress-insole path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 12;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1250;
  animation: insole-draw 2.6s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .native-progress-insole path { animation-duration: 6s; }
}

.native-progress-label {
  font-weight: 650;
  font-size: 13px;
}

.native-progress-timer {
  margin-top: 1px;
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.native-progress-hint {
  margin-top: 5px;
  max-width: 280px;
  font-size: 11px;
  line-height: 1.35;
  color: var(--muted);
}

.native-progress-hint[hidden] {
  display: none;
}

/* Determinate bar, only shown for jobs that actually report a percentage
   (the CT service's queued jobs). Hidden by default, so a mesh action still
   reads as "working, elapsed time" rather than a bar stuck at zero. */
.native-progress-bar {
  margin-top: 7px;
  width: 100%;
  min-width: 180px;
  height: 4px;
  border-radius: 2px;
  background: var(--panel-sunken);
  overflow: hidden;
}

.native-progress-bar[hidden] {
  display: none;
}

.native-progress-bar span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 2px;
  background: var(--accent);
  transition: width 180ms ease-out;
}

/* ---- CT (DICOM) import panel ------------------------------------------- */
/* The series cards replace the old app's row of green buttons - same
   information ("[92 MB] SERIES 1-SAG M.T / Dimension / Scale"), laid out to
   fit the sidebar. */

/* Sub-headings inside the CT panel. Slightly tighter than a panel-subtitle
   between panels, because these group controls that all belong to one tool. */
.ct-subtitle {
  margin-top: 12px;
}

.ct-note {
  margin: 2px 0 0;
  font-size: 10.5px;
  line-height: 1.4;
  color: var(--muted);
}

/* Two sliders per axis (from / to). A dual-thumb range is not a native
   control, and two plain ranges are honest about what they do. */
.ct-crop-grid {
  display: grid;
  grid-template-columns: 14px 1fr 1fr;
  align-items: center;
  gap: 4px 8px;
  margin-top: 6px;
}

.ct-crop-grid > span {
  font-size: 11px;
  font-weight: 650;
  color: var(--muted);
}

.ct-crop-grid input {
  min-width: 0;
}

/* The CT card is a <button>, not the <label> the other drop zones use, because
   it opens a dialog on the user's PC rather than a file input. Undo the button
   defaults .drop-zone does not already cover. */
button.drop-zone {
  width: 100%;
  font: inherit;
  text-align: left;
}

/* Reachability of the local agent. Deliberately quiet: nothing is broken, a
   program simply is not started, and this sits on the default stage where most
   users are not thinking about CT at all. One line, muted, no alarm colour -
   the detail is in the tooltip for whoever wants it. */
.ct-agent-status {
  margin-top: 6px;
  padding-left: 2px;
  font-size: 10.5px;
  line-height: 1.4;
  color: var(--muted);
}

.ct-agent-status[hidden] {
  display: none;
}

.ct-agent-status strong {
  color: var(--ink);
  font-weight: 600;
}

.ct-study-info {
  margin-top: 8px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-sunken);
  font-size: 11px;
  line-height: 1.45;
  color: var(--muted);
}

.ct-study-info strong {
  display: block;
  color: var(--ink);
  font-size: 12px;
  font-weight: 650;
}

.ct-study-info[hidden] {
  display: none;
}

.ct-series-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
}

.ct-series-list:empty {
  display: none;
}

.ct-series {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-sunken);
  color: var(--muted);
  font-size: 11px;
  text-align: left;
  cursor: pointer;
}

.ct-series:hover {
  border-color: var(--hover-border);
  background: var(--hover-bg);
}

.ct-series.is-active {
  border-color: var(--accent);
  background: var(--accent-quiet);
  color: var(--accent-ink);
}

.ct-series-name {
  color: var(--ink);
  font-size: 12px;
  font-weight: 650;
}

.ct-series.is-active .ct-series-name {
  color: var(--accent-ink);
}

.ct-series-dims {
  font-variant-numeric: tabular-nums;
}

/* Non-uniform slice spacing, gantry tilt, skipped files - shown rather than
   silently absorbed, because they change how much the resulting model can be
   trusted for measurement. */
.ct-series-warning {
  margin-top: 2px;
  color: var(--danger);
  font-size: 10.5px;
  line-height: 1.35;
}

/* Measurement segment/angle value chip, centered on its anchor. */
.measure-label {
  position: absolute;
  transform: translate(-50%, -50%);
  padding: 1px 6px;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 650;
  line-height: 1.3;
  color: #1a1205;
  white-space: nowrap;
  background: #ffd23f;
  border: 1px solid rgba(0, 0, 0, 0.35);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}

/* Surface (geodesic) labels: bluish to match the blue surface line, so when
   Distance (yellow) and Surface are shown together they're easy to tell apart. */
.measure-label-surface {
  background: #2f80ed;
  color: #fff;
  border-color: rgba(255, 255, 255, 0.55);
}

/* Angle labels: violet. The three measurement kinds can all be on screen at
   once (they are independent checkboxes), and angles used to share Distance's
   yellow - so a chain showed cm and degrees in the same colour. Violet is the
   third hue that stays separable from both yellow and blue under the common
   colour-vision deficiencies. */
.measure-label-angle {
  background: #8b5cf6;
  color: #fff;
  border-color: rgba(255, 255, 255, 0.55);
}

.tool-panel {
  position: absolute;
  right: 12px;
  top: 12px;
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: min(300px, calc(100% - 24px));
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 12px;
  color: var(--ink);
  background: var(--panel);
  box-shadow: 0 18px 44px -18px rgba(15, 30, 50, 0.4);
}

.tool-panel-instructions {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}

.tool-panel-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.tool-panel-title {
  display: block;
  font-size: 14px;
  font-weight: 640;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink);
}

.tool-panel-cancel {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: 26px;
  min-height: 26px;
  padding: 0;
  color: var(--muted);
  border-color: transparent;
  background: none;
}

.tool-panel-cancel:hover {
  color: var(--ink);
  border-color: var(--hover-border);
  background: var(--hover-bg);
}

.tool-panel-cancel svg {
  width: 14px;
  height: 14px;
}

.tool-panel-hint {
  min-height: 68px;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.35;
  white-space: pre-line;
}

/* Visual control legend: a mouse glyph (active button highlighted) + keycaps,
   one row per gesture. Used in the sculpt-brush panel so the controls read at
   a glance instead of as "Alt+Right-Drag" text. */
.ctrl-row {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 4px 0;
}

.ctrl-glyphs {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
  min-width: 66px;
}

.ctrl-act {
  color: var(--muted);
  font-size: 12px;
  white-space: normal;
}

.mouse-ico {
  width: 18px;
  height: auto;
  color: var(--muted);
  flex: 0 0 auto;
}

.keycap {
  display: inline-block;
  min-width: 12px;
  text-align: center;
  padding: 1px 5px;
  font-size: 10px;
  font-weight: 650;
  line-height: 1.4;
  border-radius: 4px;
  border: 1px solid var(--line);
  background: var(--panel-sunken);
  color: var(--ink);
}

.ctrl-plus {
  color: var(--muted);
  font-size: 10px;
}

/* Status line under a control legend (e.g. Surface spray's "N points sprayed").
   A fixed min-height reserves room for up to two lines so the panel doesn't jump
   as the count/instruction text changes length. */
.ctrl-status {
  margin-top: 6px;
  min-height: 34px;
  color: var(--muted);
  font-size: 12px;
}

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

.tool-panel-actions button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 32px;
  min-width: 0;
  flex: 1 1 calc(33% - 6px);
  padding: 0 8px;
  overflow: hidden;
  color: var(--ink);
  border-color: var(--line);
  background: var(--panel);
}

.tool-panel-actions button span {
  overflow-wrap: anywhere;
  text-align: center;
}

.tool-panel-actions button:hover {
  border-color: var(--hover-border);
  background: var(--hover-bg);
}

.tool-panel-params {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}

/* When params sit directly under the instructions block, the instructions'
   bottom border already divides them - drop the params' top border so the two
   don't render as a double line (e.g. the Lasso cut dialog). */
.tool-panel-instructions + .tool-panel-params {
  border-top: none;
}

.tool-panel-params .compact-toggle {
  color: var(--muted);
}

.tool-panel-param {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.tool-panel-param-row {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: var(--muted);
}

.tool-panel-param-row span:last-child {
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.tool-panel-param input[type="range"] {
  width: 100%;
  accent-color: var(--accent);
}

.tool-panel-param-reset {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-top: 2px;
  min-height: 28px;
  padding: 0 8px;
  font-size: 12px;
  color: var(--muted);
  border-color: var(--line);
  background: transparent;
}

.tool-panel-param-reset:hover {
  color: var(--ink);
  border-color: var(--hover-border);
  background: var(--hover-bg);
}

.tool-panel-param-reset svg {
  width: 14px;
  height: 14px;
}

/* Docked top-left (tool panels dock top-right) so it can be open at the same
   time as a tool panel without overlapping it. */
.info-panel {
  right: auto;
  left: 12px;
}

/* The "Restore previous session?" prompt is offered at load and, unlike the
   transient tool dialogs, can linger while the user starts working. Dock it to
   the bottom-center of the viewer so it never overlaps the top-docked tool
   panels or a freshly loaded model, while staying available until answered.
   (ID selector so it wins over the .tool-panel left/right dock rules.) */
#restoreSessionPanel {
  top: auto;
  right: auto;
  bottom: 12px;
  left: 50%;
  transform: translateX(-50%);
}

.info-section + .info-section {
  margin-top: 12px;
}

.info-section h3 {
  margin: 0 0 6px;
  font-size: 12px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--muted);
}

.info-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
  color: var(--muted);
}

.info-table td {
  padding: 3px 0;
  border-top: 1px solid var(--line);
}

.info-table td:first-child {
  color: var(--ink);
  white-space: nowrap;
  padding-right: 12px;
}

.info-table td:last-child {
  text-align: right;
}

.status-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 0 12px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 12px;
  background: var(--panel);
}

@media (max-width: 860px) {
  .app-shell {
    grid-template-columns: 1fr;
    height: auto;
  }

  .sidebar {
    height: auto;
    max-height: 40vh;
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }

  .workspace {
    height: 62vh;
  }
}

/* Key for the mouse-glyph colour coding (Help panel). The lit button in every
   legend is coloured by gesture: hold/drag, click, or scroll. */
.ctrl-row.gesture-key {
  margin-top: 6px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
  font-size: 11.5px;
  color: var(--muted);
}

.ctrl-row.gesture-key .ctrl-glyphs {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-wrap: wrap;
}

.gesture-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  display: inline-block;
  margin-left: 8px;
}

.ctrl-row.gesture-key .gesture-dot:first-child {
  margin-left: 0;
}

/* Collapsed Actors / Background-images list: keep only its bar (grip + the two
   toggles) so the lists stay in the DOM and keep being populated, while the
   sidebar gets its vertical space back. */
.toolrail-footer.collapsed {
  max-height: none;
  overflow: visible;
}

.toolrail-footer.collapsed > *:not(.toolrail-footer-bar) {
  display: none;
}

/* Same alignment as the expanded bar (see the .floating rule above), so
   collapsing changes the panel's height and nothing else. It used to switch
   from space-between to flex-end, which slid the collapse button sideways at
   the exact moment it had just been clicked. */
.toolrail-footer.collapsed .toolrail-footer-bar {
  justify-content: flex-end;
}

/* Save-as field: editable base name plus a fixed, non-editable extension that
   the export format decides. */
.filename-input {
  display: flex;
  align-items: center;
  gap: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel);
  overflow: hidden;
}

.filename-input input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  padding: 6px 8px;
  color: inherit;
  outline: none;
}

.filename-ext {
  flex: none;
  padding: 6px 9px 6px 4px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  border-left: 1px solid var(--line);
  background: var(--panel-sunken);
}

/* Skew/Twist/Stretch guide: the small insole diagram showing where the two
   points go, mirroring the picture the old panel carried. */
.sts-guide {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--muted);
}

.sts-guide-figure {
  flex: none;
  width: 46px;
}

.sts-guide-figure svg {
  width: 100%;
  height: auto;
  display: block;
}



/* Slider + numeric readout inside a Settings row. The shared .field-val grid
   assumes a text label in its first column; these rows have none, so the
   slider would be squeezed into that 72px column and the value would float.
   Fixed-width, right-aligned, tabular-figure readout also stops the text
   shifting as a percentage crosses 99 -> 100. */
.settings-row .field-val {
  grid-template-columns: minmax(120px, 1fr) 52px;
  align-items: center;
  gap: 10px;
  min-width: 220px;
}

.settings-row .field-val output {
  min-width: 52px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Settings tabs. Added once the dialog outgrew one scrolling list. */
.settings-tabs {
  display: flex;
  gap: 4px;
  padding: 0 18px;
  border-bottom: 1px solid var(--line);
}

.settings-tab-btn {
  min-height: 34px;
  padding: 0 12px;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  background: none;
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.settings-tab-btn:hover {
  background: none;
  color: var(--ink);
}

.settings-tab-btn.active {
  color: var(--accent-ink);
  border-bottom-color: var(--accent);
}

/* The first row of each panel shouldn't draw a top divider under the tab bar. */
.settings-tab > .settings-row:first-child {
  border-top: 0;
}

/* Idle viewport mark: the loader's insole outline drawing itself, plus the
   wordmark in two tones - both greyed back so it reads as a placeholder rather
   than as UI you can interact with. */
.empty-state-insole {
  width: 96px;
  height: auto;
  opacity: 0.9;
}

/* Static on purpose: the loader animates this same outline, so animating it
   here too made an idle viewport look like a page still loading. */
.empty-state-insole path {
  fill: none;
  stroke: currentColor;
  stroke-width: 10;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.empty-state-word {
  font-size: 26px;
  font-weight: 680;
  letter-spacing: -0.01em;
  opacity: 1;
}

/* Two tones, both muted - the accent split of the loader/brand, dialled back. */
.empty-state-word .ac {
  color: var(--muted);
  opacity: 0.9;
}

/* Fixed height so switching tabs can never resize the dialog. Scrolls if a
   tab ever outgrows it. */
.settings-body {
  height: 392px;
  overflow-y: auto;
}

/* ---- Reference documents: sidebar list + the resizable reading dock ----
   The dock is the third/fourth column of .app-shell (see .app-shell.doc-open),
   not an overlay: a report is meant to be read WHILE the model is worked on,
   so it takes its own width rather than covering the viewer. ---- */

/* Show/hide the document pane, sitting right on top of the list of documents -
   the shortest possible trip from picking a file to hiding it again, and a long
   way in from the pane's own X on the far edge of the screen. Full width and
   labelled rather than a bare icon: it is a panel row, not a toolbar glyph, and
   the label carries the current state. */
.doc-dock-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  /* Clear air above (on top of the panel's own 10px gap): the drop zone is a
     target you aim at, and with only the panel gap what follows it read as
     part of it. */
  margin-top: 8px;
  padding: 0 10px;
  font-size: 12px;
  font-weight: 600;
}

/* Open vs shut is carried by button.is-active (accent fill) plus the label,
   so the resting state stays neutral and does not compete with the accent-lit
   row of whichever document is currently open. */
.doc-dock-toggle i,
.doc-dock-toggle svg {
  width: 16px;
  height: 16px;
  color: var(--muted);
}

.doc-dock-toggle.is-active i,
.doc-dock-toggle.is-active svg {
  color: var(--accent);
}

.doc-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 320px;
  overflow: auto;
}

.doc-row {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) 30px;
  align-items: center;
  gap: 6px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel-sunken);
}

.doc-row.active {
  border-color: var(--accent);
  background: var(--accent-quiet);
}

.doc-row > i,
.doc-row > svg {
  width: 18px;
  height: 18px;
  justify-self: center;
  color: var(--muted);
}

.doc-row.active > i,
.doc-row.active > svg {
  color: var(--accent-ink);
}

/* The whole middle cell is the open target - a bare button so the name and the
   size stack without a button's own padding/border showing. */
.doc-open-btn {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  text-align: left;
}

.doc-open-btn:hover {
  background: none;
  border: 0;
}

.doc-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  color: var(--ink);
}

.doc-meta {
  font-size: 11px;
  color: var(--muted);
}

.doc-row .delete-button {
  width: 28px;
  min-height: 28px;
}

/* Empty state inside the list, so the panel never looks broken before the
   first drop. */
.doc-empty {
  padding: 10px 8px;
  border: 1px dashed var(--line);
  border-radius: 6px;
  color: var(--muted);
  font-size: 12px;
  text-align: center;
}

/* ---- The dock ---- */

/* Stacked above its neighbours so the handle always takes the pointer, whatever
   a neighbouring column happens to be painting. */
.doc-resizer {
  position: relative;
  z-index: 4;
  cursor: col-resize;
  background: var(--line);
  touch-action: none;
}

.doc-resizer:hover,
.doc-resizer.dragging {
  background: var(--accent);
}

.doc-resizer:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.doc-dock {
  display: grid;
  grid-template-rows: 46px minmax(0, 1fr);
  min-width: 0;
  height: 100vh;
  background: var(--panel);
  border-left: 1px solid var(--line);
}

.doc-dock[hidden] {
  display: none;
}

.doc-dock-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 8px 6px 12px;
  border-bottom: 1px solid var(--line);
  min-width: 0;
}

.doc-dock-title {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}

.doc-dock-title > i,
.doc-dock-title > svg {
  flex: none;
  color: var(--muted);
}

#docDockName {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.doc-dock-tools {
  display: flex;
  align-items: center;
  gap: 2px;
  flex: none;
}

.doc-pager,
.doc-zoom {
  display: flex;
  align-items: center;
  gap: 2px;
}

.doc-pager[hidden],
.doc-zoom[hidden] {
  display: none;
}

.doc-page-label {
  min-width: 46px;
  font-size: 11.5px;
  color: var(--muted);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* Separator between the pager/zoom groups and the download/close pair. */
.doc-dock-tools > .doc-zoom {
  margin-right: 6px;
  padding-right: 6px;
  border-right: 1px solid var(--line);
}

/* Positioned, so a page canvas's offsetTop is measured against the scroller
   itself - that is what the pager scrolls to. */
.doc-viewport {
  position: relative;
  overflow: auto;
  padding: 14px;
  background: var(--panel-sunken);
}

.doc-viewport.is-busy {
  opacity: 0.6;
  cursor: wait;
}

/* PDF pages: paper-on-desk, one canvas per page. No max-width - zooming past
   the pane width has to actually enlarge the page and scroll, not shrink it
   back to fit. White regardless of theme: it is paper.

   The wrapper is what carries the page's box: an interactive form's inputs are
   an absolutely-positioned layer that has to sit exactly over the canvas. */
.doc-page-wrap {
  position: relative;
  margin: 0 auto 14px;
  background: #ffffff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
}

.doc-page-wrap:last-child {
  margin-bottom: 0;
}

.doc-page {
  display: block;
}

/* "12 fields - editable" in the dock header, so an active PDF says so rather
   than looking like any other read-only document. */
.doc-form-badge {
  flex: none;
  margin-right: 6px;
  padding: 2px 7px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: var(--accent-quiet);
  color: var(--accent-ink);
  font-size: 10.5px;
  font-weight: 650;
  white-space: nowrap;
}

.doc-form-badge[hidden] {
  display: none;
}

.doc-page:last-child {
  margin-bottom: 0;
}

/* A reference image. Same paper-on-desk treatment and the same no-max-width
   rule as a PDF page, so zooming past the pane width enlarges and scrolls. */
.doc-image {
  display: block;
  height: auto;
  margin: 0 auto;
  background: #ffffff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
}

/* Converted Word/RTF output. Deliberately a plain, readable page rather than an
   attempt to reproduce Word - this is a reference viewer, not an editor. The
   library's own inline styles still apply inside. */
.doc-html {
  max-width: 46em;
  margin: 0 auto;
  padding: 26px 30px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--ink);
  font-size: 14px;
  line-height: 1.55;
}

.doc-html img {
  max-width: 100%;
  height: auto;
}

.doc-html table {
  border-collapse: collapse;
  max-width: 100%;
}

.doc-html td,
.doc-html th {
  border: 1px solid var(--line);
  padding: 4px 8px;
}

.doc-html a {
  color: var(--accent);
}

.doc-text {
  margin: 0;
  padding: 20px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--ink);
  font-size: 13px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
}

/* "No preview" / failure card. */
.doc-fallback {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  max-width: 340px;
  margin: 40px auto 0;
  padding: 24px 20px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.5;
  text-align: center;
}

.doc-fallback > i,
.doc-fallback > svg {
  width: 30px;
  height: 30px;
  color: var(--muted);
}

.doc-fallback-name {
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
  word-break: break-all;
}

.doc-fallback button {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  width: auto;
  padding: 0 14px;
}

/* Below the shell's breakpoint the columns stack, so the dock gets its own
   band under the viewer instead of squeezing it to nothing. */
@media (max-width: 860px) {
  .app-shell,
  .app-shell.doc-open {
    grid-template-columns: 1fr;
  }

  .doc-resizer {
    display: none;
  }

  .doc-dock {
    height: auto;
    max-height: 60vh;
    border-left: 0;
    border-top: 1px solid var(--line);
  }
}

/* Video and audio in the document dock. The video gets the same paper-on-desk
   framing as a page; audio is just a control bar, so it sits at natural height
   rather than being stretched to fill the pane. */
.doc-video {
  display: block;
  width: 100%;
  max-height: calc(100vh - 140px);
  margin: 0 auto;
  background: #000000;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
}

.doc-audio {
  display: block;
  width: 100%;
  margin: 24px auto 0;
}

/* ---- Image tools in the document dock (port of ImageViewer_pb) ---- */

/* The dock grows a left strip while an image is open. */
.doc-dock {
  grid-template-areas: "head head" "bar view";
  grid-template-columns: auto minmax(0, 1fr);
}

.doc-dock-head { grid-area: head; }
.doc-imagebar { grid-area: bar; }
.doc-viewport { grid-area: view; }

.doc-imagebar {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 6px;
  overflow-y: auto;
  border-right: 1px solid var(--line);
  background: var(--panel);
}

.doc-imagebar[hidden] {
  display: none;
}

.img-tool {
  display: grid;
  place-items: center;
  width: 34px;
  min-height: 30px;
  padding: 0;
}

.img-tool.is-active {
  border-color: var(--accent);
  background: var(--accent-quiet);
  color: var(--accent-ink);
}

.img-tool:disabled {
  opacity: 0.35;
}

.img-tool-sep {
  height: 1px;
  margin: 5px 4px;
  background: var(--line);
}

/* The image and its marks are stacked in register; the wrapper carries the
   displayed size so zooming moves both together. */
.doc-image-wrap {
  position: relative;
  margin: 0 auto;
  background: #ffffff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
}

.doc-image-pixels,
.doc-image-marks {
  display: block;
  width: 100%;
  height: auto;
}

/* The marks canvas is on top and takes the clicks - it is the drawing surface
   for every tool, so the image beneath never needs its own handlers. */
.doc-image-marks {
  position: absolute;
  inset: 0;
  touch-action: none;
}

.doc-imagebar ~ .doc-viewport .doc-image-marks {
  cursor: crosshair;
}

/* ----------------------------------------------------------------------------
   Teaching mode and the lesson recorder

   A lesson is recorded by capturing this tab, so what is on screen IS the
   output. Everything here exists to keep patient-identifying material off that
   screen, and to keep the recorder's own UI from appearing in its recording.
---------------------------------------------------------------------------- */

/* The two stages that carry patient details are removed from the rail, not
   merely disabled: a disabled button is still a thing to try clicking during a
   take, and the rail is one of the first things a viewer's eye lands on. The
   panels themselves are shut out by teachingBlockers() refusing to arm. */
body.teaching .rail-btn[data-stage="reference"],
body.teaching .rail-btn[data-stage="ct"],
body.teaching .tool-group[data-stage="reference"],
body.teaching .tool-group[data-stage="ct"] {
  display: none;
}

.teaching-blockers {
  margin: 6px 0 0;
  padding-left: 18px;
}

.teaching-blockers li {
  margin-bottom: 4px;
}

.link-button {
  align-self: center;
  padding: 7px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-sunken);
  color: var(--ink);
  font-size: 12px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.link-button:hover {
  border-color: var(--hover-border);
  background: var(--hover-bg);
}


/* The recorder lives in the toolbar, so it is styled as a toolbar button and
   not as a panel of its own. Everything it could have shown - elapsed time, a
   step count, a Finish label - is in the tooltip instead: this toolbar is
   inside every recording, and anything that changes here mid-take is burnt
   into the lesson. */
#teachingRecord.is-recording {
  color: var(--danger);
}

#teachingRecord.is-recording svg {
  fill: currentColor;
  animation: teaching-pulse 1.6s ease-in-out infinite;
}

@keyframes teaching-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

@media (prefers-reduced-motion: reduce) {
  #teachingRecord.is-recording svg { animation: none; }
}
