:root {
  --bg: #0e1116;
  --panel: #161b22cc;
  --panel-solid: #161b22;
  --border: #2a313c;
  --text: #d7dde5;
  --muted: #7d8794;
  --accent: #4c8dff;
  --accent-dim: #2b4a86;
  --danger: #ff5d5d;
  --good: #3fb950;

  /* ---- the bottom stack ----------------------------------------------------
     The bottom of the screen is a LAYOUT, not a pile. From the bottom up it is:
     the mobile bar, the status line, whichever stage bar is open (sketch or
     extrude), then the undo/Tips/redo strip. Each band reads the height of the
     bands below it, so nothing has to be stacked on top of anything else.
     Two of those heights wrap (the status line rewraps with its message, the
     stage bars rewrap with the width), so main.js measures them and writes them
     here rather than anyone guessing a constant. */
  --dock-h: 0px;      /* the fixed mobile bottom bar — phones only */
  --status-h: 0px;    /* measured: #statusbar */
  --stage-h: 0px;     /* measured: the open #sketch-bar / #extrude-bar */
  --stage-bottom: calc(var(--dock-h) + var(--status-h) + 10px);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  overflow: hidden;
}

#app { position: relative; width: 100vw; height: 100vh; }

/* touch-action:none stops the browser from scrolling / page-zooming the canvas,
   so one-finger orbit, two-finger pan and pinch-zoom all reach OrbitControls. */
#viewport { position: absolute; inset: 0; display: block; width: 100%; height: 100%; touch-action: none; }

.panel {
  position: absolute;
  top: 12px;
  background: var(--panel);
  backdrop-filter: blur(8px);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px;
  z-index: 10;
}

#toolbar  { left: 12px;  width: var(--toolbar-w, 200px); max-height: calc(100vh - 24px); overflow-y: auto; }
#inspector { right: 12px; width: var(--side-w, 200px);    max-height: 52vh; overflow-y: auto; }

/* Resize handles live in #app (not the panels — a panel's overflow:auto clips
   horizontal overflow). Full-height gutter strips anchored to each panel edge. */
.resize-handle {
  position: absolute; top: 12px; bottom: 12px; width: 11px;
  cursor: ew-resize; z-index: 30; touch-action: none;
}
.resize-handle.toolbar { left:  calc(12px + var(--toolbar-w, 200px) + 1px); }   /* just over the canvas, clear of the scrollbar */
.resize-handle.side    { right: calc(12px + var(--side-w, 200px) + 1px); }
.resize-handle::after {
  content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 3px; height: 30px; border-radius: 3px;
  background: var(--border); opacity: 0; transition: opacity .15s;
}
.resize-handle:hover::after, .resize-handle.dragging::after { opacity: 1; background: var(--accent); }

/* Slim, themed scrollbars — replaces the chunky default OS bar inside panels. */
.panel { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
.panel::-webkit-scrollbar { width: 8px; height: 8px; }
.panel::-webkit-scrollbar-track { background: transparent; }
.panel::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: content-box;
}
.panel::-webkit-scrollbar-thumb:hover { background: var(--accent-dim); background-clip: content-box; }

.brand { font-weight: 600; font-size: 15px; margin-bottom: 12px; letter-spacing: .2px; }
.tag {
  font-size: 10px; font-weight: 600; color: var(--accent);
  border: 1px solid var(--accent-dim); border-radius: 5px;
  padding: 1px 5px; margin-left: 4px; vertical-align: middle;
}

/* Per-panel collapse toggle (top-right). Collapsing hides everything but the
   panel's header line, reclaiming screen for the viewport. */
button.collapse-btn {
  position: absolute; top: 8px; right: 8px;
  width: 22px; height: 22px; margin: 0; padding: 0;
  text-align: center; line-height: 18px; font-size: 15px;
  color: var(--muted);
}
button.collapse-btn { color: var(--text); }
button.collapse-btn:hover { color: var(--accent); }
.panel.collapsed { padding-bottom: 10px; }
.panel.collapsed > *:not(:first-child) { display: none; }

/* Arrange tools — a label plus three compact axis buttons on one row. */
.arrange-row { display: flex; align-items: center; gap: 5px; margin: 3px 0; }
.arrange-lbl { flex: 1; font-size: 12px; color: var(--muted); }
button.mini { width: 30px; margin: 0; padding: 6px 0; text-align: center; flex: 0 0 auto; }

.group { margin-bottom: 14px; }
.group-label { font-size: 11px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); margin-bottom: 6px; }
.hint { text-transform: none; letter-spacing: 0; opacity: .8; }

button {
  display: inline-block;
  width: 100%;
  margin: 3px 0;
  padding: 7px 9px;
  background: #1f262f;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 7px;
  cursor: pointer;
  font: inherit;
  text-align: left;
  transition: background .12s, border-color .12s;
}
button:hover { background: #283039; border-color: #3a434f; }
button:active { transform: translateY(1px); }
button.mode { width: calc(33% - 4px); display: inline-block; text-align: center; padding: 7px 2px; }
button.mode.active { background: var(--accent-dim); border-color: var(--accent); color: #fff; }
button.danger:hover { border-color: var(--danger); color: var(--danger); }

button.tool.active { background: var(--accent-dim); border-color: var(--accent); color: #fff; }

/* Primitive buttons in 2 columns — keeps the toolbar from getting tall/crowded. */
button[data-add] { width: calc(50% - 3px); text-align: center; padding: 7px 4px; }

/* Settings rows — label + dropdown */
.setrow { display: flex; align-items: center; gap: 8px; margin: 5px 0; }
.setrow label { flex: none; width: 58px; font-size: 11px; color: var(--muted); }
.setrow select {
  flex: 1; padding: 5px 6px;
  background: #0c1014; color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  font: inherit; cursor: pointer;
}
.setrow select:focus { outline: none; border-color: var(--accent); }
/* min-width:0 is doing real work. A flex item will not shrink below its
   content's minimum, and three <select>s have a wide minimum, so without it the
   row stayed 203px inside a 198px toolbar and gave the whole panel 83px of
   horizontal scroll at every width above the phone breakpoint. */
.btnmap { flex: 1; min-width: 0; display: flex; gap: 4px; }
.btnmap select {
  flex: 1; min-width: 0; padding: 4px 3px; font-size: 11px;
  background: #0c1014; color: var(--text);
  border: 1px solid var(--border); border-radius: 6px; cursor: pointer;
}
.btnmap select:focus { outline: none; border-color: var(--accent); }

.check { display: flex; align-items: center; gap: 6px; margin-top: 8px; color: var(--muted); font-size: 12px; cursor: pointer; }
.check input { accent-color: var(--accent); }
.snaprow { display: flex; gap: 6px; margin-top: 6px; }
.snaprow label { flex: 1; font-size: 11px; color: var(--muted); display: flex; flex-direction: column; gap: 2px; }
.snaprow input { width: 100%; padding: 3px 4px; background: #0c1014; color: var(--text); border: 1px solid var(--border); border-radius: 5px; font: inherit; }

/* The exact kernel's edge picker. A bare <select> in the toolbar renders as the
   browser's own white control, which is legible but reads as a piece of another
   application dropped into a dark panel. Same treatment as the settings rows,
   restated here because this one lives in a toolbar group rather than a
   .setrow. */
.brep-edges {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  font-size: 11px;
  color: var(--muted);
}
.brep-edges select {
  flex: 1;
  min-width: 0;
  padding: 5px 6px;
  background: #0c1014;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
  cursor: pointer;
}
.brep-edges select:focus { outline: none; border-color: var(--accent); }

.muted { color: var(--muted); font-size: 12px; }

/* Inspector numeric rows */
.field { margin-bottom: 10px; }
.field > label { display: block; font-size: 11px; color: var(--muted); margin-bottom: 4px; text-transform: uppercase; letter-spacing: .4px; }
.vec { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; }
.axis { display: flex; flex-direction: column; }
.axis span { font-size: 10px; color: var(--muted); margin-bottom: 2px; padding-left: 2px; }
/* The axis tints match the gizmo, so they carry meaning and cannot simply be
   dropped. These values are picked for a dark panel; the light skins below
   restate them at a darker value, because #4ade80 on white is 1.28:1 and the Y
   label was effectively missing in Paper and Plush. */
.axis.x span { color: #ff6b6b; }
.axis.y span { color: #4ade80; }
.axis.z span { color: #5b9bff; }
html[data-ui="paper"] .axis.x span { color: #b4231f; }
html[data-ui="paper"] .axis.y span { color: #1c6b30; }
html[data-ui="paper"] .axis.z span { color: #1e4fc4; }
html[data-ui="plush"] .axis.x span { color: #a81f3e; }
html[data-ui="plush"] .axis.y span { color: #1e6636; }
html[data-ui="plush"] .axis.z span { color: #2a49b8; }
input[type="number"], input[type="text"] {
  width: 100%; padding: 5px 4px;
  background: #0c1014; color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  font: inherit;
}
input[type="number"]:focus, input[type="text"]:focus { outline: none; border-color: var(--accent); }
/* Drop the spinner arrows — they steal ~16px and clip values like "-68.0". */
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.swatch-row { display: flex; align-items: center; gap: 8px; }
.swatch-row input[type="color"] { width: 34px; height: 26px; padding: 0; border: 1px solid var(--border); border-radius: 6px; background: none; }
.meta { font-size: 11px; color: var(--muted); margin-bottom: 10px; }
.meta b { color: var(--text); font-weight: 600; }

/* Solid / Hole segmented toggle */
.seg { display: flex; gap: 5px; }
.seg button { width: 50%; text-align: center; padding: 6px 2px; }
/* Not #fff. --accent-dim is a DARK navy in the dark skins and a PALE tint in
   the light ones, so a fixed white foreground is legible in half the skins and
   invisible in the other half: the Add/Cut selector measured 1.36:1 in Plush.
   --text flips with the skin, so it is correct in both directions by
   construction rather than by a per-skin override that the next skin forgets. */
.seg button.on { background: var(--accent-dim); border-color: var(--accent); color: var(--text); }
.seg button[data-role="hole"].on { background: #5c2b2b; border-color: var(--danger); }

/* Outliner (Objects panel) */
#outliner { right: 12px; width: var(--side-w, 200px); bottom: 36px; top: auto; max-height: 38vh; overflow-y: auto; }
.outliner-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.outliner-head .group-label { margin-bottom: 6px; }
.multi-toggle { font-size: 10px; text-transform: uppercase; letter-spacing: .5px; line-height: 1;
  padding: 4px 8px; margin: 0 0 6px; border: 1px solid var(--border); border-radius: 999px;
  background: none; color: var(--muted); cursor: pointer; }
.multi-toggle:hover { color: var(--text); border-color: var(--accent); }
.multi-toggle.active { background: var(--accent); color: #0c1108; border-color: var(--accent); }
/* coarse pointers (touch): bigger tap target for the multi-select toggle */
@media (pointer: coarse) { .multi-toggle { padding: 8px 12px; font-size: 11px; } }
#outliner.multi .orow { cursor: copy; }
.orow { display: flex; align-items: center; gap: 6px; padding: 4px 6px; border-radius: 6px; cursor: pointer; }
.orow:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }
.orow.sel { background: var(--accent-dim); }
.oname { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* --muted is tuned to sit on a PANEL. These two sit on a button fill, which is
   lighter in the dark skins, and the eye measured as low as 2.38:1 there. They
   are controls rather than prose, so they take the full text color. */
.eye { width: auto; margin: 0; padding: 0 4px; background: none; border: none; color: var(--text); font-size: 13px; }
.eye:hover { background: none; color: var(--text); }
.badge { font-size: 10px; padding: 1px 6px; border-radius: 4px; flex: none; }
.b-add { background: #23314f; color: #9cc2ff; }
.b-cut { background: #4a2626; color: #ff9b9b; }
.b-group { background: #274027; color: #9ad29a; }

#statusbar {
  position: absolute; bottom: 0; left: 0; right: 0;
  padding: 5px 14px;
  background: var(--panel-solid);
  border-top: 1px solid var(--border);
  color: var(--muted); font-size: 11.5px;
  z-index: 10;
  display: flex; justify-content: space-between;
}
#statusbar .units { color: var(--text); }

/* Lasso overlay — a freehand selection loop drawn over the viewport. */
/* Fixed + explicit 100vw/100vh so the overlay actually fills the viewport. Without a CSS
   size an inline SVG falls back to its 300x150 intrinsic box and clips the selection box,
   which is why it drew "in the distance". 1 user unit = 1px now maps to the pointer. */
#lasso-svg { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 9; pointer-events: none; }
#lasso-svg path { fill: var(--accent); fill-opacity: 0.10; stroke: var(--accent); stroke-width: 1.5; stroke-dasharray: 5 4; }

/* ============================================================
   UI styles — flip live via Settings ▸ UI style. Each overrides
   the palette (and a few button traits) so the whole app re-skins.
   "midnight" is the base defined in :root above.
   ============================================================ */
/* --muted was #767b84, which read 4.22:1 on the panel. It cleared AA against the
   darker --bg and failed against every panel, which is where muted labels
   actually live. Lightened to 4.78:1 on the panel, still well below --text so
   the hierarchy is unchanged. */
html[data-ui="graphite"] {
  --bg: #0b0d10; --panel: #14171ccc; --panel-solid: #14171c;
  --border: #2c2f36; --text: #d4d7dc; --muted: #7f848d;
  --accent: #9aa0aa; --accent-dim: #3a3f47;
}
html[data-ui="graphite"] button,
html[data-ui="graphite"] .panel,
html[data-ui="graphite"] .setrow select, html[data-ui="graphite"] .brep-edges select { border-radius: 4px; }

html[data-ui="blueprint"] {
  --bg: #0a1622; --panel: #0f2236cc; --panel-solid: #0f2236;
  --border: #1d3a55; --text: #cfe6f5; --muted: #6f96b3;
  --accent: #4fc3f7; --accent-dim: #15466b;
}
html[data-ui="blueprint"] button,
html[data-ui="blueprint"] .setrow select, html[data-ui="blueprint"] .brep-edges select { border-radius: 3px; }
html[data-ui="blueprint"] .group-label { color: #4fc3f7; }

/* The light twin of the graphite problem, found by the same gate. --accent was
   #2f6df6, which scraped 4.53:1 on pure white and then fell to 3.96:1 on the
   gray --bg, so accent-colored labels failed anywhere off a panel. Darkened to
   5.18:1 on --bg. */
html[data-ui="paper"] {
  --bg: #eef0f3; --panel: #ffffffe6; --panel-solid: #ffffff;
  --border: #d3d8e0; --text: #1b2230; --muted: #5d6675;
  --accent: #255ada; --accent-dim: #bcd2ff;
  /* The root --danger is tuned for the dark skins, and on a near white panel it
     falls to 2.7 to 1, well under the 4.5 a warning has to clear. This is the
     same red the Paper axis labels already use, so the palette gains nothing
     new and every danger message becomes readable. */
  --danger: #b4231f;
}
html[data-ui="paper"] button { background: #f3f5f8; }
html[data-ui="paper"] button:hover { background: #e9edf3; }
html[data-ui="paper"] input[type="number"],
html[data-ui="paper"] input[type="text"],
html[data-ui="paper"] .setrow select,
html[data-ui="paper"] .brep-edges select,
html[data-ui="paper"] .snaprow input { background: #fbfcfe; }
html[data-ui="paper"] button.mode.active,
html[data-ui="paper"] button.tool.active { color: #0b2f8a; }

html[data-ui="neon"] {
  --bg: #0d0a18; --panel: #160f2acc; --panel-solid: #160f2a;
  --border: #2e2350; --text: #ece6ff; --muted: #9d8fc7;
  --accent: #b36bff; --accent-dim: #3c2a63; --good: #38e8c0;
}
html[data-ui="neon"] button,
html[data-ui="neon"] .panel,
html[data-ui="neon"] .setrow select, html[data-ui="neon"] .brep-edges select { border-radius: 12px; }
html[data-ui="neon"] button.mode.active,
html[data-ui="neon"] button.tool.active { box-shadow: 0 0 10px -2px var(--accent); }

/* "plush" — soft PINK light skin (Vi's call, 2026-08-06: it used to be lavender).
   Every text pair here was checked against WCAG AA on the surface it actually
   sits on, so the sweetness costs nothing in legibility: body text reads at
   10.2:1, the muted labels at 5.4:1, and the pink accent at 4.8:1. */
html[data-ui="plush"] {
  --bg: #fceaf4; --panel: #fbe3f0e6; --panel-solid: #fbe3f0;
  --border: #f0c4dd; --text: #4a2a3f; --muted: #7d5068;
  --accent: #b32d7d; --accent-dim: #f7d3e8;
  /* Same reason as Paper. On this pink panel the root --danger reads 2.27 to 1,
     which is the worst in the app. A shade darker than Paper's, because the
     pink panel sits lighter than white once the message tint is mixed over it,
     and Paper's red lands a hair under 4.5 against the 12 percent tint. */
  --danger: #a81f1a;
}
html[data-ui="plush"] button { background: #f9dcee; }
html[data-ui="plush"] button:hover { background: #f5cbe4; }
html[data-ui="plush"] input[type="number"],
html[data-ui="plush"] input[type="text"],
html[data-ui="plush"] .setrow select,
html[data-ui="plush"] .brep-edges select,
html[data-ui="plush"] .snaprow input { background: #fdf1f8; }
html[data-ui="plush"] button.mode.active,
html[data-ui="plush"] button.tool.active { color: #8f1f61; box-shadow: 0 0 10px -3px var(--accent); }
html[data-ui="plush"] button,
html[data-ui="plush"] .panel,
html[data-ui="plush"] .setrow select, html[data-ui="plush"] .brep-edges select { border-radius: 14px; }
html[data-ui="plush"] .group-label { color: #b32d7d; }
html[data-ui="plush"] kbd { background: #fbe0f0; }

/* ---------------------------------------------------------------- shortcuts overlay */
#shortcuts-overlay {
  position: absolute; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.45); backdrop-filter: blur(2px);
}
#shortcuts-overlay[hidden] { display: none; }
.shortcuts-card {
  background: var(--panel-solid); border: 1px solid var(--border);
  border-radius: 12px; padding: 18px 20px; max-width: 620px;
  box-shadow: 0 18px 50px -12px rgba(0, 0, 0, 0.6);
}
.shortcuts-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.shortcuts-head span { font-size: 15px; font-weight: 600; }
#shortcuts-close { width: 28px; height: 28px; margin: 0; padding: 0; text-align: center; }
.shortcuts-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 28px; }
.sc-col > div { font-size: 12.5px; color: var(--text); margin: 4px 0; }
.sc-sec { font-size: 11px !important; text-transform: uppercase; letter-spacing: .5px; color: var(--muted) !important; margin-top: 12px !important; }
.sc-col > .sc-sec:first-child { margin-top: 0 !important; }
kbd {
  display: inline-block; min-width: 16px; padding: 1px 5px; margin: 0 1px;
  font: 11px/1.5 inherit; text-align: center;
  background: #1f262f; border: 1px solid var(--border); border-radius: 4px;
}
html[data-ui="paper"] kbd { background: #eef0f3; }

/* ---------------------------------------------------------------- Recipe Timeline */
/* The multiverse history strip: docked along the bottom, between the side panels,
   above the status bar. Time → right, branches → down. */
/* The timeline is a strip across the bottom between the two side panels. The
   slicer is a WIDER right-hand panel, so when it is open the strip has to stop
   further left or the two overlap in the bottom right corner. */
body.slicer-open #timeline { right: calc(12px + min(300px, 34vw) + 14px); }

#timeline {
  position: absolute;
  left: calc(12px + var(--toolbar-w, 200px) + 14px);
  right: calc(12px + var(--side-w, 200px) + 14px);
  /* `top: auto` is doing real work. `.panel` sets `top: 12px` for the side
     panels, and an absolutely positioned box given BOTH a top and a bottom and
     a height is over-constrained, so the browser keeps the top and throws the
     bottom away. The timeline is a bottom strip and was being drawn at the top
     of the viewport, underneath the floating toolbar, which is why its header
     read through a row of icons. */
  top: auto;
  bottom: 28px;
  z-index: 20;
  padding: 8px 10px 10px;
  max-height: 44vh;
}
/* Keep the [hidden] attribute authoritative so the closed panel can't intercept
   clicks; only lay it out when actually toggled on. */
#timeline:not([hidden]) { display: flex; flex-direction: column; }
.tl-head { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.tl-head .group-label { margin: 0; }
.tl-hint { font-size: 11px; color: var(--muted); flex: 1; }
#tl-close {
  flex: none; width: 22px; height: 22px; padding: 0; line-height: 1;
  background: transparent; border: 1px solid var(--border); border-radius: 5px;
  color: var(--muted); cursor: pointer;
}
#tl-close:hover { color: var(--text); border-color: var(--accent); }
/* The offer to bring an abandoned future forward. It sits above the strip
   rather than inside it because it is about the branch you just made, not about
   any one tile, and it has to be readable without hunting for the tile it
   refers to. */
/* `:not([hidden])`, for the same reason `#timeline` above needs it: a flex rule
   out-specifies the browser's own `[hidden] { display: none }`, so the offer
   would sit on screen permanently while the JS politely pretended to toggle it. */
.tl-replay:not([hidden]) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  padding: 8px 10px;
  border: 1px solid var(--accent);
  border-left-width: 3px;
  border-radius: 6px;
  background: var(--panel-solid);
  font-size: 12px;
  color: var(--text);
}
.tl-replay .row { display: flex; gap: 6px; flex: none; }
.tl-replay button { padding: 4px 10px; font-size: 12px; }
.tl-replay #tl-replay-text { flex: 1; min-width: 16ch; }

.tl-scroll { overflow: auto; flex: 1; min-height: 96px; }
.tl-canvas { position: relative; }
.tl-svg { position: absolute; inset: 0; pointer-events: none; overflow: visible; }
.tl-edge { fill: none; stroke: var(--border); stroke-width: 2; }
.tl-edge.active { stroke: var(--accent); stroke-width: 2.5; }

.tl-node {
  position: absolute; width: 72px; padding: 0;
  display: flex; flex-direction: column; align-items: stretch; gap: 3px;
  background: var(--panel-solid); border: 1px solid var(--border);
  border-radius: 7px; cursor: pointer; overflow: hidden;
  transition: border-color .12s, transform .12s, box-shadow .12s;
}
.tl-node:hover { transform: translateY(-2px); border-color: var(--accent-dim); }
.tl-node.on-path { border-color: var(--accent-dim); }
.tl-node.current {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-dim), 0 4px 14px #0008;
}
.tl-thumb { width: 100%; height: 44px; object-fit: cover; display: block; background: var(--bg); }
/* The placeholder hatch is a dark-skin value; on a light skin it reads as a
   black hole where a thumbnail should be. Derived from the tokens instead. */
.tl-thumb.empty {
  background: repeating-linear-gradient(45deg,
    color-mix(in srgb, var(--text) 10%, var(--panel-solid)),
    color-mix(in srgb, var(--text) 10%, var(--panel-solid)) 6px,
    var(--panel-solid) 6px, var(--panel-solid) 12px);
}
.tl-label {
  font-size: 10px; color: var(--muted); text-align: center;
  padding: 0 4px 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The current step's label was #fff on --panel-solid, which is white on white
   in the light skins: it measured 1.09:1 in Paper, so the step you are actually
   standing on was the one step you could not read. */
.tl-node.current .tl-label { color: var(--text); font-weight: 600; }

/* ---------------------------------------------------------------- dimension chips */
/* Editable dimension pills that float on the selected part in the viewport. The
   layer is anchored at the object's top-center (JS sets left/top each frame). */
#dimchips {
  position: absolute; z-index: 8;
  transform: translate(-50%, -100%);
  display: flex; flex-wrap: wrap; gap: 4px; justify-content: center;
  max-width: 280px; pointer-events: none;
}
.dimchip {
  pointer-events: auto;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 7px; font-size: 11px; line-height: 1.6;
  background: var(--panel-solid); border: 1px solid var(--border);
  border-radius: 999px; color: var(--text); cursor: pointer;
  box-shadow: 0 2px 6px #0006; white-space: nowrap;
}
.dimchip:hover { border-color: var(--accent); }
.dimchip .dc-k { color: var(--accent); font-weight: 600; }
.dimchip .dc-v { color: var(--text); }
.dimchip .dc-in {
  width: 52px; padding: 0 2px; font-size: 11px;
  background: var(--bg); border: 1px solid var(--accent); border-radius: 4px; color: #fff;
}

/* ------------------------------------------------- sketch dimensions
   One chip per driving dimension, parked on the curve it drives. Unlike the
   part chips these are absolutely positioned individually, because a dimension
   belongs to a specific edge and a cluster in the corner would not say which. */
/* The experimental gate. One class on <body> hides every opted-out feature at
   once. !important because several of these elements are also shown and hidden
   by their own logic, and the gate has to win over that. */
body.no-experimental [data-experimental] { display: none !important; }

#sk-dims { position: absolute; inset: 0; z-index: 9; pointer-events: none; }
#sk-dims[hidden] { display: none; }
#sk-dims .skdim {
  position: absolute; transform: translate(-50%, -50%);
  pointer-events: auto;
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 6px; font-size: 11px; line-height: 1.55;
  font-variant-numeric: tabular-nums;
  background: var(--panel-solid); border: 1px solid var(--border);
  border-radius: 999px; color: var(--text); cursor: pointer;
  box-shadow: 0 2px 6px #0006; white-space: nowrap;
}
#sk-dims .skdim:hover { border-color: var(--accent); }
/* An auto dimension is one CADchad added for you when you drew the curve. It is
   just as editable, but muted so the ones you set yourself stand out. */
#sk-dims .skdim.auto { opacity: 0.72; }
#sk-dims .skdim.auto:hover { opacity: 1; }
#sk-dims .skdim .sd-k { color: var(--accent); font-weight: 600; }
#sk-dims .skdim .sd-in {
  width: 54px; padding: 0 2px; font-size: 11px; font-variant-numeric: tabular-nums;
  background: var(--bg); border: 1px solid var(--accent); border-radius: 4px; color: var(--text);
}
/* Rejected edits flash rather than silently reverting, so a value the sketch
   cannot reach is visibly refused instead of appearing to do nothing. */
#sk-dims .skdim.bad { border-color: var(--danger); }

/* Group "Parts" editor in the Inspector (parametric re-bake) */
.part { border: 1px solid var(--border); border-radius: 6px; padding: 6px 7px; margin-bottom: 6px; }
.part-nm { font-size: 12px; color: var(--text); margin-bottom: 4px; }

/* ============================================================
   Empty-state prompt + one-time onboarding hint (all screens)
   ============================================================ */
/* Centered "Add a shape to begin" card, shown only when the scene is empty.
   pointer-events stay off the wrapper so it never blocks orbiting; only the
   button is interactive. */
#empty-state {
  position: absolute; inset: 0; z-index: 7;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; pointer-events: none; text-align: center; padding: 0 16px;
}
#empty-state[hidden] { display: none; }
#empty-state .es-title { font-size: 17px; font-weight: 600; color: var(--text); }
#empty-state .es-sub { font-size: 12.5px; color: var(--muted); max-width: 280px; }
#empty-state .es-add {
  pointer-events: auto; width: auto; margin-top: 4px;
  padding: 11px 20px; font-size: 14px; text-align: center;
  background: var(--accent-dim); border-color: var(--accent); color: #fff;
}

/* First-run tip toast (auto-dismisses; remembered in localStorage). */
#onboard-hint {
  position: absolute; left: 50%; top: 14px; transform: translateX(-50%);
  z-index: 90; max-width: min(92vw, 460px);
  display: flex; align-items: flex-start; gap: 10px;
  padding: 11px 13px; font-size: 12.5px; line-height: 1.5;
  background: var(--panel-solid); color: var(--text);
  border: 1px solid var(--accent-dim); border-radius: 10px;
  box-shadow: 0 10px 30px -8px rgba(0,0,0,.6);
}
#onboard-hint[hidden] { display: none; }
#onboard-hint .oh-x {
  width: 24px; height: 24px; flex: none; margin: 0; padding: 0;
  text-align: center; line-height: 1; font-size: 15px; color: var(--muted);
}

/* ============================================================
   Coachmark tour: non-blocking, anchored callouts that point at
   real controls (first run + the Tips button). The page stays
   fully interactive behind them — no modal scrim.
   ============================================================ */
#cm-ring {
  position: fixed; z-index: 95; pointer-events: none; border-radius: 10px;
  border: 2px solid var(--accent); box-shadow: 0 0 0 9999px rgba(8,11,16,.34), 0 0 16px -2px var(--accent);
  transition: left .22s ease, top .22s ease, width .22s ease, height .22s ease;
}
#cm-ring[hidden] { display: none; }
#cm-card {
  position: fixed; z-index: 96; max-width: min(88vw, 320px);
  padding: 13px 14px; background: var(--panel-solid); color: var(--text);
  border: 1px solid var(--accent-dim); border-radius: 12px;
  box-shadow: 0 14px 38px -10px rgba(0,0,0,.7); font-size: 13px; line-height: 1.5;
}
#cm-card .cm-text { margin-bottom: 11px; }
#cm-card .cm-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
#cm-card .cm-count { font-size: 11px; color: var(--muted); }
#cm-card .cm-btns { display: flex; gap: 6px; }
#cm-card .cm-btns button {
  font-size: 12px; padding: 6px 11px; margin: 0; border-radius: 7px; cursor: pointer;
  border: 1px solid var(--border); background: none; color: var(--muted);
}
#cm-card .cm-btns .cm-next { background: var(--accent); color: #0c1108; border-color: var(--accent); font-weight: 600; }
#cm-card .cm-btns button:hover { color: var(--text); }
#cm-card .cm-btns .cm-next:hover { color: #0c1108; filter: brightness(1.08); }
/* Bottom-center, clear of the toolbar (left), inspector (top-right) and outliner
   (bottom-right). Sits just above the status bar. */
/* Undo, Tips, Redo travel together as one centered strip. Undo is a reflex, so
   it belongs in the spot a thumb already rests, not behind a panel. */
/* The strip rides the top of the bottom stack (see the tokens at the top of the
   file) instead of a fixed 38px, so it clears the status line and the mobile
   bar wherever those land. */
#tips-row {
  position: fixed; left: 0; right: 0; margin-inline: auto; width: fit-content;
  bottom: calc(var(--dock-h) + var(--status-h) + 8px); z-index: 80;
  display: flex; align-items: center; gap: 6px;
  max-width: calc(100% - 32px);
}
#tips-btn {
  width: max-content;
  font-size: 11px; padding: 6px 12px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--panel); color: var(--muted); cursor: pointer;
  opacity: .55; transition: opacity .2s ease, color .2s ease;
}
#tips-btn:hover { opacity: 1; color: var(--text); border-color: var(--accent); }
.tips-side {
  display: grid; place-items: center;
  width: 32px; height: 32px; padding: 0; border-radius: 999px;
  font-size: 15px; line-height: 1;
  border: 1px solid var(--border); background: var(--panel); color: var(--text); cursor: pointer;
  opacity: .72; transition: opacity .2s ease, color .2s ease, border-color .2s ease;
}
.tips-side:hover:not(:disabled) { opacity: 1; border-color: var(--accent); }
/* Disabled, not hidden: a control that disappears teaches nothing, while a dim
   one says plainly that there is nothing left to take back. */
.tips-side:disabled { opacity: .25; cursor: default; }
@media (pointer: coarse) {
  #tips-row { gap: 10px; }
  #tips-btn { padding: 9px 14px; font-size: 12px; }
  /* The 44px floor that the rest of the touch pass established. */
  .tips-side { width: var(--touch, 44px); height: var(--touch, 44px); font-size: 19px; opacity: .85; }
}
/* a drawer slides up over the bottom on mobile, so hide the strip while one is open */
body.drawer-active #tips-row { opacity: 0; pointer-events: none; }
/* A sketch or extrude stage owns the bottom band while it is open, so the strip
   steps UP over it rather than being hidden or stacked on top of it. Undo is
   exactly the control you want when an extrude goes wrong, so it has to stay on
   screen and stay 44px — it just moves. */
body.stage-active #tips-row {
  bottom: calc(var(--stage-bottom) + var(--stage-h) + 10px);
}

/* ============================================================
   Mini transparent quick-tools bar, top-center. Icon-only; the
   label shows on hover (desktop) or press (touch). Buttons proxy
   their clicks to the real toolbar buttons (see main.js).
   ============================================================ */
/* Centered by auto margins between left:0 and right:0, NOT by left:50% plus a
   translate. With left:50% the shrink-to-fit width a browser offers is only
   half the viewport, and a row of nowrap buttons simply overflows that box —
   which is how the Render control ended up 17px off the right edge of a 390px
   screen while the no-horizontal-scroll gate stayed green. Between two zero
   offsets the available width is the whole viewport, so max-width and wrap can
   actually do their jobs. */
#topbar {
  position: fixed; top: 10px; left: 0; right: 0; margin-inline: auto;
  width: fit-content; max-width: calc(100vw - 16px); z-index: 80;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 2px; padding: 3px;
  border-radius: 12px; background: transparent;
}
#topbar button {
  position: relative; width: 36px; height: 34px; padding: 0;
  display: grid; place-items: center;
  border: 0; background: transparent; color: var(--muted); cursor: pointer;
  border-radius: 9px; transition: color .15s ease, background .15s ease;
}
#topbar svg {
  width: 21px; height: 21px; fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
  filter: drop-shadow(0 1px 1.5px rgba(0,0,0,.55)); /* legible over a bright scene */
}
#topbar button:hover, #topbar button:focus-visible { color: var(--text); background: rgba(127,127,127,.16); }
#topbar button:active { background: rgba(127,127,127,.26); }
#topbar button.active { color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, transparent); }
/* label tooltip: appears below the icon on hover (desktop) / press (touch) */
#topbar button::after {
  content: attr(data-label); position: absolute; top: calc(100% + 6px); left: 50%;
  transform: translateX(-50%); white-space: nowrap;
  font-size: 11px; padding: 4px 8px; border-radius: 7px;
  background: var(--panel); color: var(--text); border: 1px solid var(--border);
  opacity: 0; pointer-events: none; transition: opacity .12s ease; z-index: 1;
}
#topbar button:hover::after, #topbar button:focus-visible::after, #topbar button:active::after { opacity: 1; }
body.drawer-active #topbar { opacity: 0; pointer-events: none; }
@media (pointer: coarse) { #topbar { top: 8px; gap: 1px; } #topbar button { width: 40px; height: 38px; } }

/* ---- transform mode cycle (Move → Rotate → Scale) ----
   A button that is also the readout: the icon and the word ARE the current
   mode, so there is nothing to keep in step by hand. It sits in the quick bar
   on a desktop and in the phone dock on a phone, never both at once. */
.mode-cycle { font: inherit; cursor: pointer; }
#topbar .mode-cycle {
  width: auto !important; padding: 0 11px !important; gap: 7px;
  display: inline-flex !important; align-items: center;
  color: var(--text); background: rgba(127,127,127,.14);
  border: 1px solid var(--border) !important;
  font-size: 12px; font-weight: 600; letter-spacing: .02em;
}
#topbar .mode-cycle .mc-ico { font-size: 15px; line-height: 1; }
#topbar .mode-cycle .mc-lbl { line-height: 1; }
#topbar .mode-cycle::after { content: none; }   /* the label is already visible */
/* On a phone the same control lives in the bottom dock, where a thumb is. Two
   of them on one screen would be two things to read and one of them redundant. */
@media (max-width: 640px) { #topbar .mode-cycle { display: none !important; } }

/* ============================================================
   Mobile bottom bar + drawer scrim — created by JS, hidden on
   desktop. The drawer layout itself is in the media query below.
   ============================================================ */
#mobile-bar { display: none; }
#mobile-scrim { display: none; }

/* ============================================================
   Phone layout: panels become off-canvas drawers driven by a
   fixed bottom bar. Triggers under 640px wide. Desktop & tablet
   (>=640px, e.g. 768) keep the original floating-panel layout.
   ============================================================ */
@media (max-width: 640px) {
  /* The bottom bar is the floor of the bottom stack on a phone. Everything that
     sits above it reads this, so the safe-area inset is honored once. */
  :root { --dock-h: calc(52px + env(safe-area-inset-bottom, 0px)); }
  /* Resize handles make no sense for full-width drawers. */
  .resize-handle { display: none !important; }
  /* The per-panel collapse chevron is replaced by the bar + scrim. */
  .panel .collapse-btn { display: none; }

  /* Common drawer chrome */
  #toolbar, #inspector, #outliner {
    position: fixed; z-index: 70;
    width: auto; max-height: none;
    backdrop-filter: blur(12px);
    transition: transform .22s ease;
  }

  /* Toolbar slides in from the left, full height, scrollable. */
  #toolbar {
    top: 0; left: 0; bottom: 0; right: auto;
    width: min(86vw, 330px);
    border-radius: 0 14px 14px 0;
    overflow-y: auto;
    transform: translateX(-105%);
  }
  #toolbar.drawer-open { transform: none; }

  /* Inspector & Objects rise as bottom sheets, above the bottom bar. */
  #inspector, #outliner {
    left: 0; right: 0; bottom: 52px; top: auto;
    border-radius: 16px 16px 0 0;
    max-height: 68vh; overflow-y: auto;
    transform: translateY(115%);
  }
  #inspector.drawer-open, #outliner.drawer-open { transform: none; }

  /* Timeline as a full-width bottom sheet (its desktop edges use panel-width
     vars that collapse to negative width on a phone). */
  #timeline {
    left: 8px; right: 8px;
    bottom: 56px; max-height: 50vh;
  }

  /* Dimension chips can overflow a narrow screen; keep them in bounds. */
  #dimchips { max-width: 92vw; }

  /* Roomier tap targets across the touch UI. */
  .panel button { min-height: 44px; }
  button.mode { min-height: 44px; }
  button.mini { width: 38px; min-height: 40px; }
  .setrow select, .btnmap select { min-height: 38px; }
  .snaprow input, input[type="number"], input[type="text"] { min-height: 38px; }
  .orow { padding: 9px 6px; }

  /* Status bar sits just above the bottom bar. */
  #statusbar { bottom: var(--dock-h); }

  /* The bottom action bar: always-visible drawer toggles + Frame. */
  #mobile-bar {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; height: 52px;
    z-index: 80; align-items: stretch;
    background: var(--panel-solid); border-top: 1px solid var(--border);
    padding-bottom: env(safe-area-inset-bottom, 0);
  }
  #mobile-bar button {
    flex: 1; width: auto; margin: 0; border: none; border-radius: 0;
    background: transparent; color: var(--muted);
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px; font-size: 10px; min-height: 52px; text-align: center;
  }
  #mobile-bar button .mb-ico { font-size: 18px; line-height: 1; }
  #mobile-bar button:active { background: #ffffff14; }
  #mobile-bar button.active { color: var(--accent); }
  /* The cycle button is a readout as much as a control, so it is never the
     muted gray the drawer tabs use when they are closed. */
  #mobile-bar .mode-cycle { color: var(--text); }

  /* Scrim behind an open drawer; tap it to dismiss. */
  #mobile-scrim {
    display: block; position: fixed; inset: 0; z-index: 65;
    background: rgba(0,0,0,.45); opacity: 0; pointer-events: none;
    transition: opacity .2s ease;
  }
  #mobile-scrim.show { opacity: 1; pointer-events: auto; }

  /* On a phone the shape grid breathes a little wider. */
  button[data-add] { padding: 9px 4px; }
}

@media (prefers-reduced-motion: reduce) {
  #toolbar, #inspector, #outliner, #mobile-scrim { transition: none !important; }
}

/* ============================ RENDER VIEW ============================ */
/* Toggle lives in the topbar but reads as a mode switch (icon + label pill). */
#rv-toggle {
  width: auto !important; padding: 0 11px !important; gap: 7px;
  display: inline-flex !important; align-items: center;
  color: var(--text); background: rgba(127,127,127,.14);
  border: 1px solid var(--border) !important; font: inherit; font-size: 12px; font-weight: 600;
  letter-spacing: .02em;
}
#rv-toggle .rv-lbl { line-height: 1; }
#rv-toggle.active { color: var(--accent); background: color-mix(in srgb, var(--accent) 18%, transparent); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)) !important; }
#rv-toggle::after { content: none; }  /* it has its own label */
/* On a phone the word does not fit: seven icons plus "⊕ Render" measured 423px
   of a 390px screen and the label ran off the edge. The toggle joins the rest of
   the row as an icon and gets the same press-to-see label every other control in
   the bar already has, rather than the row shrinking under the touch floor. */
@media (max-width: 640px) {
  /* var(--touch) not a smaller number: it is a touch target like the rest of the
     row, and the width the base rule sets is `auto !important`, so the coarse
     44px rule cannot win on its own. */
  #rv-toggle { width: var(--touch, 44px) !important; padding: 0 !important; gap: 0; }
  #rv-toggle .rv-lbl { display: none; }
  #rv-toggle::after { content: attr(data-label); }
}

/* In render view, hide the editing chrome; keep the scene + topbar toggle. */
body.render-view #toolbar,
body.render-view #inspector,
body.render-view #outliner,
body.render-view #timeline,
body.render-view #dimchips,
body.render-view #statusbar { display: none !important; }
body.render-view #topbar button[data-proxy] { display: none; }  /* hide edit tools, keep the toggle */
/* The transform cycle goes with them: render view detaches the gizmo entirely,
   so a button offering to switch its mode would be offering nothing. */
body.render-view #topbar [data-modecycle] { display: none !important; }

/* Floating render bar: presets + camera. Mobile-first (bottom, thumb-reachable). */
#rv-bar {
  position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%); z-index: 85;
  display: flex; gap: 10px; align-items: center; max-width: calc(100vw - 24px);
  padding: 7px; border-radius: 14px;
  background: color-mix(in srgb, var(--panel) 88%, transparent);
  border: 1px solid var(--border); box-shadow: 0 10px 30px rgba(0,0,0,.4);
  backdrop-filter: blur(8px);
}
#rv-bar .rv-group { display: flex; gap: 4px; }
#rv-bar .rv-presets { padding: 2px; border-radius: 10px; background: rgba(127,127,127,.12); }
#rv-bar button {
  font: inherit; font-size: 12px; font-weight: 600; color: var(--muted); cursor: pointer;
  border: 0; background: transparent; padding: 8px 12px; border-radius: 8px;
  transition: color .15s ease, background .15s ease; white-space: nowrap;
}
#rv-bar button:hover { color: var(--text); background: rgba(127,127,127,.14); }
#rv-bar .rv-preset.active { color: #fff; background: var(--accent); }
#rv-bar .rv-cam button.active { color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, transparent); }
#rv-bar[hidden] { display: none; }

@media (pointer: coarse), (max-width: 640px) {
  /* Above the phone's bottom bar rather than on it (--dock-h is 0 everywhere
     that bar does not exist, so this is still bottom:12px on a tablet). */
  #rv-bar { bottom: calc(var(--dock-h) + 12px); gap: 6px; flex-wrap: wrap; justify-content: center; padding: 6px; }
  #rv-bar button { padding: 10px 12px; }
}
/* hide onboarding/tour + tips in render view (belt-and-suspenders to the JS guard) */
body.render-view #cm-card,
body.render-view #cm-ring,
body.render-view #tips-row { display: none !important; }

/* ---- parametric sketch ---------------------------------------------------
   The inspector block for a constrained sketch, plus the on-canvas sketcher
   toolbar. The state pill is the important bit: a sketch that is not fully
   constrained is not wrong, it just still has freedom, and the color says
   which of the two you are looking at. */

.sketch-field { border: 1px solid var(--border); border-radius: 8px; padding: 8px; }
.sk-state {
  float: right; font-size: 10px; letter-spacing: .3px; text-transform: none;
  padding: 1px 7px; border-radius: 999px; border: 1px solid var(--border);
}
.sk-state.ok   { color: var(--good);   border-color: var(--good); }
.sk-state.warn { color: var(--accent); border-color: var(--accent-dim); }
.sk-state.bad  { color: var(--danger); border-color: var(--danger); }
.sk-warn {
  font-size: 11px; color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, transparent);
  border-radius: 6px; padding: 5px 7px; margin-bottom: 6px; line-height: 1.35;
}
.sk-sub {
  font-size: 10px; text-transform: uppercase; letter-spacing: .4px;
  color: var(--muted); margin: 8px 0 4px;
}
.sk-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.sk-chip {
  font: inherit; font-size: 11px; cursor: pointer;
  background: var(--panel-solid); color: var(--text);
  border: 1px solid var(--border); border-radius: 999px; padding: 2px 8px;
}
.sk-chip:hover { border-color: var(--danger); color: var(--danger); }
.sk-chip span { opacity: .6; margin-left: 2px; }
.sk-note { margin-top: 8px; line-height: 1.4; }

/* On-canvas sketcher */
/* Centered between two zero offsets rather than by left:50% + translate: the
   half-viewport shrink-to-fit box that left:50% produces squeezed this bar into
   a 195px column four rows tall on a phone. Its `bottom` comes from the bottom
   stack so it sits above the status line and the mobile bar instead of under
   them. */
#sketch-bar {
  position: absolute; left: 0; right: 0; margin-inline: auto; width: fit-content;
  bottom: var(--stage-bottom);
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: center;
  max-width: min(96vw, 720px);
  background: var(--panel); backdrop-filter: blur(10px);
  border: 1px solid var(--border); border-radius: 12px; padding: 7px 9px; z-index: 40;
}
#sketch-bar[hidden] { display: none; }
/* width:auto and flex:none are load-bearing: the app's panel buttons are
   full-width block buttons, and without these the sketch bar inherits that and
   stacks into a tall column that covers the canvas. */
#sketch-bar button {
  font: inherit; font-size: 12px; cursor: pointer; white-space: nowrap;
  background: transparent; color: var(--text);
  border: 1px solid var(--border); border-radius: 8px; padding: 5px 9px;
  width: auto; flex: 0 0 auto; margin: 0;
}
#sketch-bar input {
  font: inherit; font-size: 12px; background: var(--panel-solid); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px; padding: 4px 6px; margin: 0;
}
#sk-dim-entry { display: flex; gap: 4px; align-items: center; }
#sketch-bar button:hover { border-color: var(--accent); }
#sketch-bar button.on { background: var(--accent-dim); border-color: var(--accent); }
#sketch-bar .sk-sep { width: 1px; align-self: stretch; background: var(--border); margin: 0 2px; }
#sketch-bar .sk-read {
  font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums;
  min-width: 96px; text-align: center;
}
#sketch-bar .sk-read b { color: var(--text); font-weight: 600; }
/* The snap name, so what the next click will land on is readable at a glance. */
#sketch-bar .sk-read .sk-snap {
  font-style: normal; font-weight: 600; color: var(--accent);
  padding: 1px 5px; margin-right: 3px; border-radius: 5px;
  background: var(--accent-dim);
}
#sketch-bar .sk-read { min-width: 168px; }

/* ------------------------------------------------- live dimension input (HUD)
   Rides the cursor while a curve is in progress. Two jobs: show what you are
   drawing as a number, and let you overwrite that number to get an exact one.
   Deliberately small and low-contrast until a field is focused, so it informs
   without fighting the geometry underneath it. */
/* Opaque, and text-colored rather than muted. Both are contrast decisions: the
   translucent panel puts whatever is behind it under the text, and --muted over
   --panel drops below 4.5:1 in the graphite skin. Hierarchy comes from size and
   weight here instead of from color. */
#sk-hud {
  position: absolute; z-index: 45; pointer-events: none;
  display: flex; align-items: center; gap: 4px;
  background: var(--panel-solid);
  border: 1px solid var(--border); border-radius: 9px; padding: 4px 5px;
  font-size: 11px; font-variant-numeric: tabular-nums; color: var(--text);
  white-space: nowrap;
}
#sk-hud[hidden] { display: none; }
/* Fields accept input only once the user starts typing, so the HUD never steals
   a click meant for the canvas. */
#sk-hud.typing { pointer-events: auto; }
#sk-hud .hud-f { display: flex; align-items: center; gap: 3px; }
#sk-hud .hud-k { font-weight: 600; color: var(--text); }
#sk-hud .hud-v {
  font: inherit; font-variant-numeric: tabular-nums;
  background: transparent; color: var(--text); font-weight: 600;
  border: 1px solid transparent; border-radius: 5px;
  padding: 1px 3px; margin: 0; width: 52px; text-align: right;
  -moz-appearance: textfield;
}
#sk-hud .hud-v::-webkit-outer-spin-button,
#sk-hud .hud-v::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#sk-hud .hud-v:focus { outline: none; background: var(--panel-solid); border-color: var(--accent); color: var(--text); }
/* A locked field is one the user typed into: it holds its value while the mouse
   keeps moving, which is what makes "type the length, aim the angle" work. */
#sk-hud .hud-f.locked .hud-v { border-color: var(--accent); color: var(--accent); }
#sk-hud .hud-u { color: var(--text); }
/* Supplementary, but still held to the same contrast floor: it is set apart by
   being lighter in weight, not lighter in color. */
#sk-hud .hud-tip { color: var(--text); font-weight: 400; margin-left: 4px; }

/* ---------------------------------------------------------------- view cube
   A real cube rather than a row of buttons, because the point is to show which
   way you are already facing, not only to offer somewhere to go. The faces are
   buttons, so it is keyboard and screen reader reachable too. */
/* Top left of the canvas, just clear of the toolbar. The bottom of the screen
   belongs to the sketch and extrude bars, and both side gutters are panels, so
   this is the one corner that stays free at every width. Tracks the toolbar's
   resize variable so dragging the panel wider pushes the cube along. */
/* top:40 not 12: a cube rotated to a corner-on view pokes roughly 28px past the
   edges of its own 76px box, and at top:12 that overflow was clipped off. */
#viewcube-wrap {
  position: absolute; top: 40px; left: calc(var(--toolbar-w, 200px) + 40px); z-index: 39;
  /* The gap clears the cube's 3D overflow, so Iso is never tucked under a face. */
  display: flex; flex-direction: column; align-items: center; gap: 26px;
  /* Never swallow an orbit drag aimed at the scene behind it. */
  pointer-events: none;
}
/* Only the faces and the Iso button are clickable. Every wrapper above them
   stays transparent to the pointer, so neither the perspective box nor the cube
   can intercept a click meant for a face, and a drag over the corner of the
   widget still orbits the scene behind it. */
#viewcube {
  width: 76px; height: 76px; perspective: 340px; pointer-events: none;
}
.vc-cube {
  position: relative; width: 100%; height: 100%;
  transform-style: preserve-3d;
  /* JS writes the matrix each frame from the camera. */
  /* The container is a flat 76px square sitting in front of its own rotated
     faces, so leaving it clickable means it eats every click before a face
     sees one. Only the faces take pointer events. */
  pointer-events: none;
}
.vc-face { pointer-events: auto; }
.vc-face {
  position: absolute; inset: 0; margin: 0; padding: 0; width: 100%; height: 100%;
  display: grid; place-items: center;
  font: inherit; font-size: 9px; font-weight: 700; letter-spacing: .6px;
  color: var(--text); cursor: pointer;
  /* Solid rather than translucent: at .82 the face labels washed out against a
     light skin, and this is a label you read at a glance or not at all. */
  background: var(--panel-solid); opacity: .95;
  border: 1px solid var(--border); border-radius: 5px;
  backface-visibility: hidden;
}
.vc-face:hover { background: var(--accent-dim); border-color: var(--accent); opacity: 1; }
.vc-face:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; opacity: 1; }
/* The cube is draggable, so the browser must not claim the gesture as a scroll
   or a pinch first. Without this a swipe on a phone scrolls the page instead of
   turning the model, which is most of why the widget felt stuck. */
.vc-face { touch-action: none; }
#viewcube-wrap.vc-dragging .vc-face { cursor: grabbing; background: var(--accent-dim); }
#viewcube-wrap.vc-dragging { cursor: grabbing; }
/* 38px is half the cube, so each face sits on its own side. */
.vc-top    { transform: rotateX(90deg)  translateZ(38px); }
.vc-bottom { transform: rotateX(-90deg) translateZ(38px); }
.vc-front  { transform: translateZ(38px); }
.vc-back   { transform: rotateY(180deg) translateZ(38px); }
.vc-right  { transform: rotateY(90deg)  translateZ(38px); }
.vc-left   { transform: rotateY(-90deg) translateZ(38px); }
#vc-home {
  pointer-events: auto; width: auto; margin: 0; flex: 0 0 auto;
  font: inherit; font-size: 11px; cursor: pointer;
  background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px; padding: 3px 10px;
}
#vc-home:hover { border-color: var(--accent); }
/* On a phone the sketch bar owns the bottom of the screen, so the cube moves up
   out of its way and shrinks. */
@media (max-width: 640px) {
  /* The cube used to shrink here to make room, which is backwards: a face of a
     56px cube is under 30px of thumb target, and the faces sit edge to edge so
     a near miss picks the WRONG view rather than none. 72px puts every face
     back over 36px without taking meaningfully more of the screen. */
  /* left:32 not 10, for the same reason the desktop rule uses top:40 not 12: a
     cube turned corner-on paints up to ~28px outside its own box, and at left:10
     the TOP and RIGHT faces were sliced off by the edge of the screen. The
     vertical case was fixed here already; the horizontal one was not. */
  /* top follows the measured quick-tools bar (it wraps to two rows at 360px),
     plus the same ~12px the cube paints above its own box. */
  #viewcube-wrap { top: calc(var(--topbar-h, 56px) + 26px); left: 32px; }
  #viewcube { width: 72px; height: 72px; perspective: 300px; }
  .vc-face { font-size: 8px; }
  .vc-top    { transform: rotateX(90deg)  translateZ(36px); }
  .vc-bottom { transform: rotateX(-90deg) translateZ(36px); }
  .vc-front  { transform: translateZ(36px); }
  .vc-back   { transform: rotateY(180deg) translateZ(36px); }
  .vc-right  { transform: rotateY(90deg)  translateZ(36px); }
  .vc-left   { transform: rotateY(-90deg) translateZ(36px); }
}
/* The render view is for looking, so the chrome steps out. */
body.render-view #viewcube-wrap { display: none; }

/* ---------------------------------------------------------------- extrude stage
   Sits where the sketch bar sits and borrows its chrome, so finishing a sketch
   reads as the same task continuing rather than a new place to learn. */
/* nowrap on desktop: shrink-to-fit sizing lands a few pixels under what the row
   actually needs, so the last button wrapped to a second line and sat half off
   the bottom of the screen. The row is ~590px wide and only wraps once the
   viewport is genuinely too narrow for it. */
#extrude-bar {
  position: absolute; left: 0; right: 0; margin-inline: auto; width: fit-content;
  bottom: var(--stage-bottom);
  display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; justify-content: center;
  max-width: min(96vw, 800px);
  background: var(--panel); backdrop-filter: blur(10px);
  border: 1px solid var(--accent); border-radius: 12px; padding: 7px 9px; z-index: 41;
}
#extrude-bar[hidden] { display: none; }
#extrude-bar button {
  font: inherit; font-size: 12px; cursor: pointer; white-space: nowrap;
  background: transparent; color: var(--text);
  border: 1px solid var(--border); border-radius: 8px; padding: 5px 9px;
  width: auto; flex: 0 0 auto; margin: 0;
}
#extrude-bar button:hover { border-color: var(--accent); }
#extrude-bar button[aria-pressed="true"] { background: var(--accent-dim); border-color: var(--accent); }
#extrude-bar button.primary { background: var(--accent-dim); border-color: var(--accent); font-weight: 600; }
#extrude-bar .ex-title { font-size: 12px; font-weight: 600; color: var(--text); }
#extrude-bar .ex-field { display: flex; align-items: center; gap: 4px; font-size: 12px; color: var(--muted); }
#extrude-bar .ex-field input {
  font: inherit; font-size: 12px; width: 76px; text-align: right;
  background: var(--panel-solid); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px; padding: 4px 6px; margin: 0;
  font-variant-numeric: tabular-nums;
}
#extrude-bar .ex-field input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
#extrude-bar .ex-unit { color: var(--muted); }
#extrude-bar .ex-hint { font-size: 11px; color: var(--muted); }
/* Narrow enough that the row genuinely will not fit, so let it stack again and
   drop the hint, which is the first thing worth losing. */
@media (max-width: 700px) {
  #extrude-bar { flex-wrap: wrap; }
  #extrude-bar .ex-hint, #extrude-bar .sk-sep { display: none; }
}

@media (max-width: 640px) {
  /* The bars keep the full width of the screen minus a gutter. Anything narrower
     makes them wrap into a tall tower over the model. */
  #sketch-bar, #extrude-bar { max-width: calc(100vw - 16px); }
  #sketch-bar { gap: 4px; padding: 6px; }
  #sketch-bar button { padding: 6px 8px; font-size: 12px; }
  #sketch-bar .sk-read { width: 100%; order: 99; }
}

/* Three-way segmented control (extrude end types). Wraps to a column on very
   narrow phones so the labels never truncate to nonsense. */
.seg.seg-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
.seg.seg-3 button { width: auto; margin: 0; padding: 7px 4px; font-size: 12px; }
/* The scene-aware pair (through all, up to face) sits under the three distance
   types as its own row, because it is a different kind of choice: those are
   numbers you type, these are answers the model works out. */
.seg.seg-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px; margin-top: 4px; }
.seg.seg-2 button { width: auto; margin: 0; padding: 7px 4px; font-size: 12px; }
@media (max-width: 420px) {
  .seg.seg-3, .seg.seg-2 { grid-template-columns: 1fr; }
}
.sk-note.repaired {
  color: var(--text); background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-radius: 6px; padding: 6px 8px; margin-top: 6px; font-size: 11px; line-height: 1.4;
}

/* ---- the slicer ----------------------------------------------------------
   A wide right-hand panel, because slicing is a settings-heavy task and
   squeezing it into the Inspector's width would mean two columns of truncated
   labels. On a phone it becomes a bottom sheet like the other panels, and it
   never scrolls sideways: every row is either full width or a two-up grid that
   collapses to one at 380px. */

/* `display:flex` below out-specifies the browser's `[hidden] { display:none }`,
   so without this the panel is permanently on screen and the toggle only looks
   like it works. Any panel given an explicit display has to re-state it. */
#slicer[hidden] { display: none; }

#slicer {
  right: 12px;
  top: 12px;
  bottom: 36px;
  width: min(300px, 34vw);
  display: flex;
  flex-direction: column;
  padding: 12px 12px 8px;
  z-index: 20;
}
#slicer .sl-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 8px; flex: 0 0 auto;
}
#slicer .sl-head button {
  width: auto; padding: 2px 8px; margin: 0; background: none;
  border: none; color: var(--muted); font-size: 14px; cursor: pointer;
}
#slicer .sl-head button:hover { color: var(--text); }
#slicer .sl-scroll { overflow-y: auto; overflow-x: hidden; flex: 1 1 auto; padding-right: 2px; }
#slicer .sl-head-actions { display: flex; align-items: center; gap: 2px; }

/* ---- the layer dock ------------------------------------------------------
   Outside the scrolling body so the controls you use while WATCHING the model
   never scroll away, and so there is something left to drive when the panel is
   collapsed. */
#slicer #sl-dock[hidden] { display: none; }
#slicer #sl-dock {
  flex: 0 0 auto; margin-top: 8px; padding-top: 8px;
  border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 6px;
}
#slicer #sl-dock .sl-dock-read { margin: 0; }

/* Collapsed: head + dock only. The point is the viewport behind it, which on a
   phone the expanded sheet covers almost entirely — and a layer preview you
   cannot see is not a preview. */
#slicer.sl-collapsed .sl-scroll { display: none; }
#slicer.sl-collapsed { max-height: none; height: auto; bottom: auto; }
#slicer.sl-collapsed #sl-dock { margin-top: 0; border-top: none; }

/* Supports get a full-width row: it is a sentence, not a number. */
.sl-stats .sl-stat-wide { grid-column: 1 / -1; }

.sl-sec {
  font-size: 10px; text-transform: uppercase; letter-spacing: .5px;
  color: var(--muted); margin: 12px 0 6px;
  border-top: 1px solid var(--border); padding-top: 8px;
}
.sl-scroll > .sl-sec:first-child { margin-top: 0; border-top: none; padding-top: 0; }

.sl-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.sl-grid label {
  display: flex; flex-direction: column; gap: 3px;
  font-size: 11px; color: var(--muted);
}
.sl-grid input {
  width: 100%; padding: 4px 6px; background: #0c1014; color: var(--text);
  border: 1px solid var(--border); border-radius: 5px; font: inherit;
}
@media (max-width: 380px) { .sl-grid { grid-template-columns: 1fr; } }

.sl-range { display: block; font-size: 11px; color: var(--muted); margin: 8px 0 4px; }
.sl-range b { color: var(--text); font-weight: 600; }
.sl-range input[type="range"] { width: 100%; margin-top: 4px; accent-color: var(--accent); }

.sl-run { margin-top: 12px; }
/* The panel's normal ink on the dimmed accent, which is what the extrude bar's
   Done button has always used and measures 6.3:1. White here is 3.2:1 and fails
   AA at this size, and so does the normal ink on the FULL accent at 2.3:1, so
   the hover lifts the fill only part of the way rather than all of it. */
button.primary {
  background: var(--accent-dim); border-color: var(--accent); color: var(--text); font-weight: 600;
}
/* Mixed from the tokens, not a fixed color. A hardcoded navy hover was legible
   in the four dark skins and 1.79:1 in Plush, and hover states are easy to miss
   because a screenshot never has the pointer in it. Blending accent-dim toward
   accent lifts the fill by the same amount in every skin and keeps --text
   readable on it, measured 4.6:1 at worst. */
button.primary:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent-dim) 80%, var(--accent));
}
button:disabled { opacity: .5; cursor: default; }

.sl-progress { margin: 8px 0; }
.sl-progress .sl-stage { font-size: 10px; color: var(--muted); }
.sl-bar-track, .sl-progress {
  --h: 4px;
}
.sl-progress .sl-bar, .sl-bar-track .sl-bar {
  height: 4px; background: var(--accent); border-radius: 3px;
  transition: width .15s linear; width: 0;
}
.sl-bar-track { background: var(--border); border-radius: 3px; overflow: hidden; margin: 4px 0; }

.sl-msg {
  font-size: 11px; line-height: 1.4; color: var(--muted);
  border-left: 2px solid var(--border); padding: 4px 0 4px 7px; margin: 5px 0;
}
.sl-msg.bad {
  color: var(--danger); border-left-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  border-radius: 0 5px 5px 0;
}

.sl-stats { display: grid; gap: 3px; }
.sl-stats div { display: flex; justify-content: space-between; gap: 8px; font-size: 11px; }
.sl-stats span { color: var(--muted); }
.sl-stats b { color: var(--text); font-weight: 600; text-align: right; }

.sl-seg { display: flex; gap: 0; margin: 6px 0; }
.sl-seg button { flex: 1; margin: 0; border-radius: 0; }
.sl-seg button:first-child { border-radius: 6px 0 0 6px; }
.sl-seg button:last-child { border-radius: 0 6px 6px 0; border-left: none; }

.sl-legend {
  display: flex; flex-wrap: wrap; gap: 4px 10px;
  font-size: 10px; color: var(--muted); margin: 8px 0 4px;
}
.sl-legend span { display: inline-flex; align-items: center; gap: 4px; }
.sl-legend i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; flex: 0 0 auto; }

.sl-printer { margin-top: 8px; }
.sl-print { border: 1px solid var(--border); border-radius: 8px; padding: 8px; }
.sl-print.printing { border-color: var(--accent); }
.sl-print.stopped { border-color: var(--danger); }
.sl-print-head { display: flex; justify-content: space-between; font-size: 11px; }
.sl-print-head b { text-transform: capitalize; }
.sl-print-temps { font-size: 10px; color: var(--muted); }

/* Phone: a bottom sheet, matching the Inspector and Objects panels. */
@media (max-width: 640px) {
  #slicer {
    position: fixed; z-index: 75;
    /* Above the bottom bar, not over it. Sitting at bottom:0 hid the Tools,
       Objects and Inspect tabs entirely, so opening the slicer on a phone took
       the rest of the app away with it. */
    left: 0; right: 0; bottom: 52px; top: auto;
    width: auto; max-height: 72vh;
    border-radius: 16px 16px 0 0;
    backdrop-filter: blur(12px);
  }
  /* Collapsed still hugs the bottom bar. The desktop rule zeroes `bottom` so the
     panel shrinks upward from its top anchor; here the anchor is the bottom, so
     zeroing it would tear the sheet off the bar it is supposed to sit above. */
  #slicer.sl-collapsed { bottom: 52px; top: auto; }
  /* The strip has nowhere to go on a phone once the sheet is up. */
  body.slicer-open #timeline { display: none; }
  /* Same call as a drawer: the sheet fills the lower screen, so the undo strip
     would float on top of the layer dock — the one part of the slicer you drive
     while you are watching the model. Nothing in the slicer is an undo step. */
  body.slicer-open #tips-row { opacity: 0; pointer-events: none; }
}

/* A stale result: the numbers stay readable but visibly demoted, because the
   worst outcome is someone reading a print time for a model they have since
   changed. The Slice button is what they want next, so it says so. */
#sl-results.stale .sl-stats,
#sl-results.stale .sl-legend,
#sl-results.stale .sl-range { opacity: .45; }
.sl-msg.warn {
  color: var(--text);
  border-left-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-radius: 0 5px 5px 0;
}
button.wants-attention {
  animation: sl-nudge 2.4s ease-in-out infinite;
}
@keyframes sl-nudge {
  0%, 92%, 100% { box-shadow: 0 0 0 0 transparent; }
  96% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 45%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  button.wants-attention { animation: none; border-color: var(--accent); }
}

/* The experimental feature list, rendered from the same table the gate reads. */
.exp-list { list-style: none; padding: 0; margin: 6px 0 0; }
.exp-list li { margin: 0 0 5px; line-height: 1.4; }
.exp-list b { color: var(--text); font-weight: 600; }
.exp-list .exp-caution { margin-top: 8px; padding-top: 6px; border-top: 1px solid var(--border); }

/* ---- touch sizing --------------------------------------------------------
   Everything below applies only to a coarse pointer, so the desktop layout is
   untouched. The number is 44 CSS pixels, which is what both platform
   guidelines settle on as the smallest thing a fingertip hits reliably, and the
   audit found roughly sixty controls under it: a 13px snap checkbox, 24px
   dimension chips, 16px sliders, and every form field in the app at 38px.

   Height is raised rather than font size, so nothing reflows and the panels do
   not suddenly need more horizontal room. */
@media (pointer: coarse) {
  :root { --touch: 44px; }

  #toolbar button, #inspector button, #outliner button, #slicer button,
  #sketch-bar button, #extrude-bar button {
    min-height: var(--touch);
  }
  .setrow select, .btnmap select, .brep-edges select, .sl-grid input, .snaprow input,
  input[type="number"], input[type="text"], #slicer select {
    min-height: var(--touch);
  }
  /* A range track you cannot land on is a range you cannot set. The track stays
     visually thin; the reachable box around it grows. */
  input[type="range"] { height: var(--touch); }
  input[type="range"]::-webkit-slider-thumb { width: 26px; height: 26px; }
  input[type="range"]::-moz-range-thumb { width: 26px; height: 26px; }

  /* A 13px checkbox is a dare. Scaling keeps the tick crisp where a width/height
     override would just stretch the browser's own glyph. */
  .check input[type="checkbox"] { width: 22px; height: 22px; }
  .check { min-height: var(--touch); }

  /* Everything the touch sweep found that the rules above did not already
     reach: the Iso button under the view cube, the onboarding card's two
     buttons, and the colour swatch in the inspector.

     The view cube's own FACES are deliberately not here. They are 3D rotated,
     so what a finger meets is the projected parallelogram, about 32px tall at
     the default angle, and the only way to make that 44 is to grow the cube to
     roughly 110px, which on a 390px screen covers the model it exists to
     orient you around. Recorded as an exception in site-audit.mjs rather than
     silently left. */
  #cm-card .cm-btns button { min-height: var(--touch); }
  input[type="color"] { min-height: var(--touch); }

  /* The visibility eye was 19px wide inside a 44px row. */
  .eye { min-width: var(--touch); min-height: var(--touch); }
  .orow { min-height: var(--touch); }

  /* Dimension chips are editable values, so they are targets, not labels. */
  .dimchip { padding: 8px 12px; font-size: 13px; }

  /* The orientation cube and its Iso button. 40px was an arbitrary near-miss,
     four pixels under the floor every other control here clears. */
  #vc-home { min-height: var(--touch); min-width: 52px; padding: 8px 14px; }
  #slicer .sl-head button { min-width: var(--touch); }
  .dimchip { min-height: 44px; }
  #tips-btn { min-height: var(--touch); }
  .multi-toggle { min-height: 38px; }

  /* The quick-tools bar is the most-used control on a phone. */
  #topbar button { width: var(--touch); height: var(--touch); }
}

/* Layer stepper: a slider for the sweep, buttons for the exact layer. */
.sl-step { display: flex; align-items: center; gap: 6px; }
.sl-step input[type="range"] { flex: 1; min-width: 0; margin: 0; }
.sl-step button {
  flex: 0 0 auto; width: 30px; min-width: 30px; margin: 0; padding: 0;
  font-size: 15px; line-height: 1;
}
@media (pointer: coarse) {
  .sl-step button { width: var(--touch); min-width: var(--touch); }
}
