: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;
  /* 960 was set when this was one scrolling list. With six tabs and rows whose
     descriptions are a sentence or two, the extra width is what stops a control
     and its explanation fighting over the same line. */
  width: min(1180px, calc(100% - 32px));
  max-height: calc(100% - 48px);
  overflow-y: auto;
  overflow-x: hidden;
  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;
}

/* A row is a description on the left and its control on the right - and the
   control is the half that must never be the one that gets cut.

   min-width:0 on the label is the load-bearing line. A flex item's default
   floor is its min-content width, so a long description stops shrinking at its
   widest word and pushes the control past the right edge of the card instead;
   the body then scrolls sideways and the control is simply not there any more.
   Zero lets the text wrap instead. wrap is the backstop for a control that
   genuinely cannot fit beside its label - it drops under it rather than off. */
.settings-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px 20px;
  padding: 12px 0;
  border-top: 1px solid var(--line);
}

/* Group heading inside a Settings tab. The first row under it drops its divider -
   the heading already separates it from what came before. */
.settings-group {
  margin: 22px 0 2px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent, var(--muted));
}
.settings-tab > .settings-group:first-child,
.settings-tab > .settings-note + .settings-group {
  margin-top: 4px;
}
.settings-group + .settings-row {
  border-top: 0;
}
.settings-note {
  margin: 0 0 14px;
  font-size: 12px;
  color: var(--muted);
}
/* The long explanation lives in the row's tooltip. */
.settings-row[title] .settings-title {
  cursor: help;
}

/* The control side: never squeezed, never wider than the row it sits in. */
.settings-row > :not(.settings-label) {
  flex: 0 1 auto;
  max-width: 100%;
}

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

.settings-label {
  flex: 1 1 260px;
  min-width: 0;
}

.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;
}

/* The name takes the room between the apps switcher in front and the actions behind. */
.brand-title {
  flex: 1;
  min-width: 0;
}

/* No gap left behind when the switcher has nowhere to go and hides itself. */
#appsMenu:has(> .apps-menu[hidden]),
#appsMenu:empty {
  display: none;
}

.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 - the Guide by default, Import for whoever set
   it that way (Settings -> Where the work starts). It is a mark rather than a
   position because the pin it lands on is a setting and CT/MR sits between the
   two candidates, so "where do I begin" cannot be read off the order.
   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;
  /* Bottom pin: a bottom side, and the solid it makes with its upper. */
  --kind-bottom: #c9772e;
  --kind-solid: #3f9a63;
}

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

/* 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. */
  /* Held in a variable rather than written straight into border-left-color, so
     the one place that has to re-assert it (.actor-row.scene-row.active, below
     .actor-row.active) does not have to know which kind the row is. */
  --kind-stripe: var(--kind-mesh);
  border-left: 6px solid var(--kind-stripe);
}

.actor-row.scene-row-image { --kind-stripe: var(--kind-image); }
.actor-row.scene-row-pressure { --kind-stripe: var(--kind-pressure); }

/* .actor-row.active sets `border-color`, which is a FOUR-side shorthand - it was
   overwriting the kind stripe, so selecting a row turned its stripe accent-blue
   and an image row stopped matching the Image swatch in the legend. Same
   specificity as the rules above and later in the file, so nothing there could
   win; this one is a class heavier than .actor-row.active, so it wins wherever
   the two are declared. */
.actor-row.scene-row.active { border-left-color: var(--kind-stripe); }

.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);
}

/* The foot-side chip in a model row: L / R, or a faint L/R when not set yet. */
.scene-side {
  flex: 0 0 auto;
  min-width: 22px;
  height: 18px;
  padding: 0 4px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: transparent;
  color: var(--muted);
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  cursor: pointer;
}

.scene-side.is-l,
.scene-side.is-r {
  border-color: var(--accent);
  background: var(--accent-quiet);
  color: var(--accent-ink);
}

.scene-side:hover {
  border-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;
  /* A model entry carries the colour its models are drawn in (--legend-color). */
  background: var(--legend-color, 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,
.guide-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;
}

/* ---- The guided path -------------------------------------------------------
   Almost all of this panel is borrowed: the step bodies hold the REAL tool
   controls, moved in from the other stages, so .tool-group, .tf-run and .field
   all arrive already styled and nothing here needs to restate them. What is
   left is the wizard chrome - the dot rail, the heading of the step on screen,
   and the bar that moves between them - plus the states a step can be in. */

/* ---- The dot rail ----------------------------------------------------------
   Seven collapsed headers' worth of overview in one row: how far along the path
   this model is, what is left, and a way into any of it. It replaced the list
   because only one step can ever be open - the borrowing rule sees to that - so
   the other six rows were paying rent on nothing.

   Text and a ::after, never a lucide icon: createIcons() replaces every
   <i data-lucide> with a newly built <svg>, which detaches anything the panel
   captured at build time. A CSS-only swap cannot be caught out by that. */

/* One path's whole set of chrome. The panel holds one per path and shows the
   active one, so this lays out exactly what .guide-panel used to. */
.guide-path-ui {
  display: flex;
  flex-direction: column;
  gap: 14px;
  /* Clear of the sidebar's scrollbar, which otherwise sits on the right edge of
     the rail strip and the progress count. */
  padding-right: 6px;
}

/* Which path and how far along, in words, above the dots. */
.guide-progress {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: -6px;
}

.guide-progress-path {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}

.guide-progress-count {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Sunk into its own strip so it reads as a progress bar rather than a row of
   loose buttons - at 22px with hairline links it was too small to take in at a
   glance. */
.guide-rail {
  display: flex;
  align-items: center;
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel-sunken);
}

/* The connector between two dots - a plain flex spacer, so eight dots space
   themselves across whatever width the sidebar happens to be. It fills in once
   the step before it is behind you, so the strip shows progress as a length. */
.guide-rail-link {
  flex: 1 1 auto;
  min-width: 3px;
  height: 3px;
  border-radius: 2px;
  background: var(--line);
}

.guide-rail-link.is-done {
  background: var(--success-border);
}

.guide-dot {
  position: relative;
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  min-height: 0;
  padding: 0;
  border: 2px solid var(--line);
  border-radius: 50%;
  background: var(--panel);
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.guide-dot.is-done {
  border-color: var(--success-border);
  background: var(--success-bg);
  color: var(--success-ink);
}

.guide-dot.is-done .guide-dot-n { display: none; }

.guide-dot.is-done::after {
  content: "✓";
  font-size: 14px;
  line-height: 1;
}

/* Skipped, not finished. A dash rather than a tick, because a step that was
   waved past and one the model can prove are not the same thing. */
.guide-dot.is-skipped .guide-dot-n { display: none; }

.guide-dot.is-skipped::after {
  content: "–";
  font-size: 15px;
  line-height: 1;
}

/* Last, so it wins over is-done: a step can be both finished and the one on
   screen, and where you ARE is the more useful of the two to show. */
.guide-dot.is-current {
  border-color: var(--accent);
  background: var(--accent-quiet);
  color: var(--accent-ink);
  box-shadow: 0 0 0 3px var(--accent-quiet);
}

/* ---- The step on screen ----------------------------------------------------
   One at a time. Every other step is out of the flow entirely, which is what
   leaves the panel with room to give this one a real heading. */

.guide-step {
  display: none;
  flex-direction: column;
  gap: 12px;
}

.guide-step[data-open="1"] {
  display: flex;
}

.guide-head {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.guide-eyebrow {
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.guide-title {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--ink);
  font-size: 16.5px;
  font-weight: 700;
  line-height: 1.25;
}

/* An icon tile, matching the chooser's, so the step on screen has the same
   anchor as the choice that led to it. */
.guide-title svg {
  flex: none;
  width: 32px;
  height: 32px;
  padding: 7px;
  border-radius: 9px;
  background: var(--accent-quiet);
  color: var(--accent);
}

.guide-step-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.guide-why {
  min-height: 0;
}

.guide-act {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* A step whose controls have been handed back collapses instead of leaving an
   empty box behind - which is what every step that is not the open one looks
   like, since only one step holds borrowed nodes at a time. */
.guide-act-ctl:empty {
  display: none;
}

/* ---- Borrowed toolbar groups, relabelled -----------------------------------
   A .tool-group is a row of icon squares. That works in a toolbar, where they
   are under the pointer all day and a tooltip covers the rest; it fails in a
   guided step, whose whole premise is that the reader does NOT know which glyph
   means "cut with a sphere". Inside the guide the same group becomes a stack of
   full-width labelled rows. The label is an attribute stamped on at borrow time
   (see guideLabelButtons), so the button itself is unchanged and goes home
   exactly as it left. */
.guide-act-ctl .tool-group {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
}

.guide-act-ctl .tool-group button {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  min-height: 32px;
  padding: 0 10px;
  border-radius: 7px;
  text-align: left;
}

.guide-act-ctl .tool-group button::after {
  content: attr(data-guide-label);
  font-size: 12px;
  font-weight: 550;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.guide-act-ctl .tool-group button svg {
  flex: none;
  color: var(--accent);
}

.guide-act-ctl .tool-group button.is-active svg { color: inherit; }

/* ---- Borrowed dialog bodies and tool panels --------------------------------
   The Points pin's methods (and Foam Clean) live in floating dialogs; a step
   borrows the dialog's BODY - description, sliders, Apply - and the method's
   name, which stayed behind in the dialog head, comes across as an attribute
   (see guideLabelButtons). Several can sit in one step, so each is a card. */
.guide-act-ctl .tool-float-body {
  padding: 10px 11px 11px;
  overflow: visible;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--panel-sunken);
}

.guide-act-ctl .tool-float-body::before {
  content: attr(data-guide-title);
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}

/* A tool panel (the foam extractor) floats over the viewer at the top right by
   design; inside a step it is just part of the column. */
.guide-act-ctl .tool-panel {
  position: static;
  z-index: auto;
  width: 100%;
  box-shadow: none;
  background: var(--panel-sunken);
}

.guide-act-ctl .tool-panel-actions,
.guide-act-ctl .tool-panel-params {
  width: 100%;
}

.guide-legend {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.guide-status {
  margin: 0;
}

/* ---- Back and forward ------------------------------------------------------
   The one place the path moves from, sitting under the step rather than inside
   it, so it stays in the same spot as the steps change under it. */

.guide-nav {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding-top: 11px;
  border-top: 1px solid var(--line);
}

/* Why Next is not available yet - the NEXT step's gate, said before it is
   walked into rather than after. */
.guide-nav-note {
  color: var(--muted);
  font-size: 11px;
  line-height: 1.4;
}

.guide-nav-row {
  display: flex;
  gap: 8px;
}

.guide-nav-row .tool-btn {
  min-height: 32px;
  padding: 0 14px;
  font-size: 12px;
}

.guide-back {
  margin-right: auto;
}

/* The forward button carries the path, so it gets the accent - the same
   language the app uses for an armed tool. */
.guide-next {
  border-color: var(--accent);
  background: var(--accent-quiet);
  color: var(--accent-ink);
  box-shadow: inset 0 0 0 1px var(--accent-shadow);
}

.guide-next:hover:not(:disabled) {
  border-color: var(--accent);
  background: var(--accent-quiet);
}

/* Send to CAM while the job is on its way (background send): light coral, no second
   click, back to normal when the notice appears. */
.is-sending {
  background: #fde4dc !important;
  border-color: #f0a58f !important;
  color: #9a3412 !important;
  pointer-events: none;
}

:root[data-theme="dark"] .is-sending {
  background: #4a2a22 !important;
  border-color: #c2705a !important;
  color: #ffd2c4 !important;
}

/* Corner notices (showToast): things that finish out of sight, e.g. a job sent to CAM. */
.toast-host {
  position: fixed;
  right: 16px;
  bottom: 44px;
  z-index: 6000;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: min(420px, calc(100vw - 32px));
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-left: 4px solid #3f9a63;
  border-radius: 8px;
  background: var(--panel, #fff);
  color: var(--ink, inherit);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
  font-size: 13px;
  line-height: 1.4;
  pointer-events: auto;
  animation: toast-in 0.18s ease-out;
}

.toast-warn {
  border-left-color: #d9822b;
}

.toast a {
  flex: 0 0 auto;
  color: var(--accent);
  font-weight: 600;
  white-space: nowrap;
}

.toast-close {
  flex: 0 0 auto;
  margin-left: auto;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}

@keyframes toast-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

/* Quick: the measured size - first and in bold, then how it was found. Quiet like the
   panel around it (its sunken background and line), with only a thin accent edge on the
   left, so it is found at a glance without shouting over the rest. */
.quick-result {
  margin: 8px 0 0;
  padding: 7px 10px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: 6px;
  background: var(--panel-sunken);
  font-size: 12px;
  line-height: 1.45;
  color: var(--muted);
}

/* Three fixed lines - size, side, how - so the box keeps its shape whatever is typed:
   the size never wraps, and the box keeps a minimum height so the panel under it does
   not jump when it is filled or refilled. */
.quick-result {
  min-height: 86px;
  box-sizing: border-box;
}

.quick-result-size {
  display: block;
  white-space: nowrap;
  font-size: 15px;
  font-weight: 700;
  color: var(--ink, inherit);
}

.quick-result-side {
  display: block;
  margin-bottom: 2px;
  font-weight: 600;
}

.quick-result-how {
  display: block;
}

/* Sized from the scan's length only - worth a second look, so it reads as a warning. */
.quick-result.is-rough {
  border-left-color: #d9822b;
}

/* "Continue with the other foot": the next thing to do once a foot is through, so it
   reads as the forward button - full width, accent, with its icon. */
.guide-other-foot-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  margin-top: 8px;
  padding: 9px 12px;
  font-weight: 600;
}

.guide-other-foot-btn svg {
  width: 16px;
  height: 16px;
}

/* Wrong input for this path. Same warm family as the home rail pin, because it
   is the same kind of message: not an error, just "you are not where you think
   you are". */
.guide-warn {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border: 1px solid var(--home-line);
  border-radius: 8px;
  background: var(--home-bg);
  color: var(--home-ink);
  font-size: 12px;
  line-height: 1.45;
}

/* The path standing empty after a finished run. Deliberately not a success
   colour - the green card said "ready to design" and has been acted on; this is
   just the panel saying what it is waiting for. */
.guide-fresh {
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-sunken);
  color: var(--muted);
  font-size: 12px;
  line-height: 1.45;
}

.guide-finish {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border: 1px solid var(--success-border);
  border-radius: 8px;
  background: var(--success-bg);
  color: var(--success-ink);
  font-size: 12px;
  line-height: 1.45;
}

/* ---- All steps -------------------------------------------------------------
   The path in words under the step being worked on: badge, name, "optional",
   and the same done / skipped / current / shut states as the dots. A list
   rather than more dots, because a name can be read and a dot has to be
   hovered. */
.guide-map {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel-sunken);
}

.guide-map-head {
  margin: 0 0 6px 2px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.guide-map-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 34px;
  padding: 3px 8px 3px 4px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: none;
  color: var(--ink);
  text-align: left;
}

.guide-map-row:hover:not(:disabled) {
  border-color: var(--line);
  background: var(--panel);
}

.guide-map-row:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.guide-map-n {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border: 2px solid var(--line);
  border-radius: 50%;
  background: var(--panel);
  font-size: 11.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.guide-map-t {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 13px;
  font-weight: 560;
  line-height: 1.3;
}

.guide-map-tag {
  flex: none;
  font-size: 10.5px;
  color: var(--muted);
}

.guide-map-row.is-done .guide-map-n {
  border-color: var(--success-border);
  background: var(--success-bg);
  color: var(--success-ink);
}

.guide-map-row.is-done .guide-map-n .guide-dot-n,
.guide-map-row.is-skipped .guide-map-n .guide-dot-n { display: none; }

.guide-map-row.is-done .guide-map-n::after { content: "✓"; font-size: 12px; line-height: 1; }
.guide-map-row.is-skipped .guide-map-n::after { content: "–"; font-size: 13px; line-height: 1; }

.guide-map-row.is-skipped .guide-map-t { color: var(--muted); }

/* Last, so it wins over is-done - where you ARE matters most. */
.guide-map-row.is-current {
  border-color: var(--accent);
  background: var(--accent-quiet);
}

.guide-map-row.is-current .guide-map-n {
  border-color: var(--accent);
  background: var(--panel);
  color: var(--accent-ink);
}

.guide-map-row.is-current .guide-map-t {
  color: var(--accent-ink);
  font-weight: 700;
}

/* Asked once at the start of a model and never again, so it sits at the very
   bottom as a line of text rather than a button competing with the step. */
.guide-path-btn {
  min-height: 0;
  padding: 2px 0 0;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 11px;
  text-align: left;
}

.guide-path-btn:hover {
  border: 0;
  background: none;
  color: var(--accent-ink);
  text-decoration: underline;
}

/* ---- "What are you starting from?" -----------------------------------------
   Borrows .settings-modal and .settings-backdrop for the overlay, but not
   .settings-card - that one is 1180px wide for a tabbed settings page, and this
   is a short list of four things. */

.guide-chooser-card {
  position: relative;
  width: min(520px, calc(100% - 32px));
  max-height: calc(100% - 48px);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 22px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel);
  box-shadow: 0 24px 64px rgba(8, 15, 24, 0.34);
}

.guide-chooser-card h2 {
  margin: 0;
  font-size: 17px;
}

.guide-chooser-sub {
  margin: 0 0 4px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
}

.guide-chooser-grid {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Picking one of these is the only thing this dialog asks for, so each is built
   to be pressed rather than read: a full-width slab with a 2px edge, an accent
   icon tile, and an arrow on the right that says "this goes somewhere". They
   used to be flat panels a touch darker than the card, which reads as a list of
   facts, not a row of choices. */
.guide-choice {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 13px 38px 13px 13px;
  border: 2px solid var(--line);
  border-radius: 11px;
  background: var(--panel-sunken);
  text-align: left;
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s, transform 0.12s;
}

.guide-choice:hover:not(:disabled) {
  border-color: var(--accent);
  background: var(--accent-quiet);
  transform: translateY(-1px);
}

.guide-choice:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-quiet);
}

/* The affordance that says "press me". Hidden on the ones that cannot be
   pressed, so a greyed row never promises to go anywhere. */
.guide-choice::after {
  content: "›";
  position: absolute;
  right: 15px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 21px;
  line-height: 1;
  color: var(--muted);
}

.guide-choice:hover:not(:disabled)::after { color: var(--accent); }
.guide-choice.is-soon::after { content: none; }

/* An icon tile rather than a bare glyph - it gives the row a left anchor at the
   same size as the badge column in the guide itself. */
.guide-choice svg {
  width: 34px;
  height: 34px;
  flex: none;
  padding: 8px;
  border-radius: 9px;
  background: var(--accent-quiet);
  color: var(--accent);
}

.guide-choice-txt b {
  display: block;
  font-size: 13px;
  font-weight: 650;
  color: var(--ink);
}

.guide-choice-txt em {
  display: block;
  margin-top: 2px;
  font-style: normal;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--muted);
}

/* Listed, not hidden: somebody starting from a foam box needs to be told it is
   coming and where to go meanwhile, not left wondering if they missed it. */
.guide-choice.is-soon {
  opacity: 0.6;
  cursor: default;
  border-style: dashed;
  background: none;
}

.guide-choice.is-soon svg {
  background: none;
  color: var(--muted);
}

/* The escape hatch, and NOT made to look lesser - most people who use this app
   daily will take it every time. Separated by space rather than by weight. */
.guide-choice-advanced {
  margin-top: 6px;
}

.guide-chooser-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 2px;
  font-size: 11.5px;
  color: var(--muted);
  cursor: pointer;
}

/* The large-model import dialog borrows the chooser's card and choice rows;
   this is only its row of settings above the two choices. */
.import-optimise-params {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px 12px;
  margin-bottom: 4px;
}

.import-optimise-params label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 11.5px;
  color: var(--muted);
}

.import-optimise-params input[type="number"] {
  min-height: 32px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--ink);
  background: var(--panel-sunken);
}

.import-optimise-params .import-optimise-check {
  grid-column: 1 / -1;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

/* 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;
  /* Credit left, version right. Wraps rather than shrinks: if the panel is too
     narrow for both, the version drops to its own line still hard right, which
     is better than ellipsing away half of whoever made the thing. */
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 2px 8px;
  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;
  cursor: default;
}

/* The same stamp on the CAM and Organization pages (version.js): pinned to the bottom
   left, as wide as the design app's sidebar, so it sits where it does there. */
.page-stamp {
  position: fixed;
  left: 12px;
  bottom: 8px;
  width: 360px;
  max-width: calc(100vw - 24px);
  margin: 0;
  padding: 6px 0 0;
  background: var(--bg);
  z-index: 5;
}

.build-version {
  /* Never the thing that wraps or shrinks - it is four characters and it is the
     one people come to this corner to read. */
  flex: none;
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}

.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);
}

/* Left / Right / Both feet, at the left of the bar (the grip, when floating, is
   before it). Same height and line as the two buttons beside it. */
.footer-side-filter {
  margin-right: auto;
  height: 30px;
  padding: 0 6px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--panel);
  color: var(--ink, inherit);
  font-size: 12px;
  cursor: pointer;
}

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

.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%;
}

/* Twin view: two halves over the viewer - a label in each, a divider between them, and
   the half being worked on outlined. Clicks go straight through to the viewer. */
.twin-overlay {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: grid;
  grid-template-columns: 1fr 1fr;
  pointer-events: none;
}

.twin-overlay[hidden] {
  display: none;
}

.twin-half {
  position: relative;
  border: 2px solid transparent;
}

.twin-half + .twin-half {
  border-left: 1px solid var(--line);
}

.twin-half.is-active {
  border-color: var(--accent);
}

.twin-label {
  position: absolute;
  top: 10px;
  left: 12px;
  padding: 2px 8px;
  border-radius: 10px;
  background: var(--panel);
  color: var(--muted);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}

.twin-half.is-active .twin-label {
  background: var(--accent);
  color: #fff;
}

.twin-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}

.twin-empty[hidden] {
  display: none;
}

.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-agent-install {
  margin-top: 4px;
}

.ct-agent-install a {
  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);
}

/* ---- The measuring grid ----------------------------------------------------
   Ruler numbers and the position readout. Both are HTML over the canvas rather
   than 3D text: they have to stay upright and legible at every camera angle,
   which is the one thing text drawn into the scene cannot do. */

/* The numbers down the three edges of the grid box. Deliberately quiet - warm
   grey, no chip behind them - because there are up to fifty of them on screen
   and a scale is meant to be glanced at, not read. */
.grid-label {
  position: absolute;
  transform: translate(-50%, -50%);
  color: #d9a441;
  font-size: 11px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85), 0 0 2px rgba(0, 0, 0, 0.9);
}

/* The readout under the pointer. These DO get a chip: there are four of them,
   they change as you move, and they are the answer to the question the tool was
   opened for - so they have to win against whatever is behind them. */
.grid-probe-label {
  position: absolute;
  transform: translate(-50%, -50%);
  padding: 1px 6px;
  border-radius: 6px;
  border: 1px solid rgba(255, 255, 255, 0.45);
  background: rgba(14, 20, 28, 0.92);
  color: #f2f7fc;
  font-size: 11px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  line-height: 1.35;
  white-space: nowrap;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}

/* Each axis readout carries its own axis colour, the same three the move/rotate
   rings use - so "which number is which" needs no legend. */
.grid-probe-label-x { color: #ff8a8a; }
.grid-probe-label-y { color: #7ee6a0; }
.grid-probe-label-z { color: #8ec6ff; }

/* The height, at the top of the needle - bigger, because it is the one the eye
   should land on first. */
.grid-probe-height {
  padding: 2px 8px;
  font-size: 13px;
}

/* Where each cross-section is cutting, in the section's own red so the number
   and the line it belongs to are one thing. */
.grid-section-label {
  color: #ff9a9a;
}

/* The rotate dial's live reading. Dark rather than the measurement yellow: this
   one is a readout of a drag in progress rather than a measurement that stays on
   the model, and it has to stay legible sitting on top of the white dial. */
.manipulate-angle-label {
  background: rgba(14, 20, 28, 0.92);
  border-color: rgba(255, 255, 255, 0.45);
  color: #f2f7fc;
  font-variant-numeric: tabular-nums;
}

/* 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;
}

/* A field whose whole content is one control - it has no label of its own,
   because the settings row already carries the title and the explanation. The
   two-column .field grid would put that control in the 72px LABEL column and
   leave the wide one empty, which is how a dropdown ends up too narrow to read
   its own longest option. */
.settings-row .field-plain {
  display: block;
  min-width: 190px;
}

.settings-row .field-plain > select,
.settings-row .field-plain > input {
  width: 100%;
}

.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, and scrolls when
   a tab outgrows it. Was a flat 392px, which is a laptop's worth of dialog on
   every monitor - now it takes what the window can spare, up to 640px, and the
   subtraction is the header, the tab strip and the card's own margins.

   overflow-x is hidden rather than left to follow overflow-y into `auto`: a row
   that did not fit used to make the whole body scroll sideways, which puts every
   control off the right edge at once - and the control is exactly what a settings
   row is FOR. The rows now wrap instead (see .settings-row). */
.settings-body {
  height: min(640px, calc(100vh - 210px));
  overflow-y: auto;
  overflow-x: hidden;
}

/* ---- 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; }
}

/* The Export dialog: format buttons with an icon over a label, four to a row, and Send to
   CAM as one wide row under them. .tool-group buttons are 38 px icon squares elsewhere
   (the toolbar); here they carry a word, which wrapped "Send to CAM" over three lines. */
.tool-group.export-actions {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}
.tool-group.export-actions button {
  width: auto;
  min-height: 58px;
  padding: 8px 4px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  font-size: 13px;
  white-space: nowrap;
}
.tool-group.export-actions button svg {
  width: 20px;
  height: 20px;
  flex: none;
}
.tool-group.export-actions.export-cam {
  grid-template-columns: 1fr;
  margin-top: 8px;
}
.tool-group.export-actions.export-cam button {
  min-height: 40px;
  flex-direction: row;
  gap: 8px;
  font-weight: 600;
}
