/* The review screen, on the design system (DESIGN.md §2-§5).
 *
 * The tokens are NOT here: `/ui/tokens.css` carries them and this file only
 * uses them. Nothing below may write a colour, a radius, a curve or a duration
 * as a literal — if one is missing, it goes into `design/tokens.css`.
 *
 * Structure follows `design/prototype/index.html`, which is the specimen for
 * §5's stage and §4's motion: when this file and that page disagree about a
 * shape or a curve, that page wins. What is different here is only what a live
 * screen needs and a prototype did not — a real SVG whose own viewBox is the
 * zoom, an iframe and a <model-viewer> that must not be transformed, and a
 * panel with six states instead of four.
 *
 * The one rule this file is checked against: **only `transform` and `opacity`
 * animate** (§4). A panel that "changes size" is two layers cross-faded; a bar
 * that fills scales on X; a knob that travels translates.
 */

/* The words bar on the scheme (card #1807) is its own file; an @import must come before any rule. */
@import url('/ui/words.css');
/* The phone's circle, its list and its fan (card #1805) are their own file too. */
@import url('/ui/phone-shell.css');

/* --- Ground ------------------------------------------------------------ */
* { box-sizing: border-box; }
body {
  margin: 0; background: var(--paper); color: var(--ink); overflow: hidden;
  font: 400 15px/1.47 var(--font-ui); font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3 { margin: 0; font-weight: 600; line-height: 1.2; letter-spacing: -.02em; }
a { color: var(--brand-text, var(--sys-text)); text-decoration: none; }
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
button, input, select { font: inherit; }

/* `.btn`, `.ib` and other controls below set their own `display`, which outranks the browser's
 * default rule for `[hidden]` (author origin always beats user-agent origin, whatever the
 * selector) — so an element a client set `.hidden = true` on stayed on screen and kept acting.
 * UI-187: the questions panel's footer save (`#q-save`, `class="btn fill"`) did exactly this,
 * only two of its callers ever noticed (`.topbar .btn[hidden]`, `.question-crop[hidden]`, both
 * removed below in favour of this one rule) and `#q-save` was not one of them. `/web/web.css`
 * (UI-114) already carries the same fix for the entry client that loads this file first; this
 * rule is what makes it hold for every client that loads only this one. `!important` because a
 * rule placed anywhere later in this file, in either client's own stylesheet, or added after
 * this one, must never be able to out-rank it again by source order alone. */
[hidden] { display: none !important; }

/* --- Utilities --------------------------------------------------------- */
.stack { display: flex; flex-direction: column; }
.row { display: flex; align-items: center; }
.row.top { align-items: flex-start; }
.wrap { flex-wrap: wrap; }
.grow { flex: 1; min-width: 0; min-height: 0; }
.spacer { flex: 1; }
.gap-1 { gap: 4px; } .gap-2 { gap: 8px; } .gap-3 { gap: 12px; } .gap-4 { gap: 16px; }
.p-2 { padding: 8px; } .p-3 { padding: 12px; }
.mt-1 { margin-top: 4px; } .mt-2 { margin-top: 8px; } .mt-3 { margin-top: 12px; }
.mb-2 { margin-bottom: 8px; }
.t-12 { font-size: 12px; } .t-13 { font-size: 13px; } .t-14 { font-size: 14px; } .t-15 { font-size: 15px; }
.t-17 { font-size: 17px; font-weight: 600; }
.t-20 { font-size: 20px; font-weight: 600; letter-spacing: -.02em; line-height: 1.2; }
.w-500 { font-weight: 500; } .w-600 { font-weight: 600; }
.mono { font-family: var(--font-mono); }
.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.cap { font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--ink3); }
.dim { color: var(--ink2); } .dim2 { color: var(--ink3); }
.tone-sys { color: var(--sys-text); } .tone-good { color: var(--good-text); } .tone-bad { color: var(--bad-text); }
.lead { line-height: 1.47; }
.nowrap { white-space: nowrap; flex: 0 0 auto; }
.ellipsis { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hair { border-top: 1px solid var(--line); }
.fill { width: 100%; }
.pre { white-space: pre-wrap; font: 12px/1.5 var(--font-mono); color: var(--ink2); }
.reasoning { margin: 12px; }
.reasoning-crop { display: block; width: 100%; height: auto; border-radius: var(--r-control); }
.model-notes-summary { color: var(--sys-text); background: var(--sys-bg); border: 1px solid var(--sys-line); border-radius: var(--r-control); padding: 8px 12px; margin-bottom: 8px; }
.model-notes-details { margin-bottom: 10px; }

/* --- Controls (§4) ------------------------------------------------------
   Hover lifts 1 px with a deeper shadow — on buttons only, the one place §4
   lets a shadow animate. Press scales .98. */
.btn {
  font: 600 14px/1 var(--font-ui); min-height: 40px; padding: 0 16px; border: 0;
  border-radius: var(--r-ctl); background: var(--act, var(--ink)); color: var(--on-act, var(--on-ink));
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  cursor: pointer; white-space: nowrap;
  transition: transform var(--d-micro) var(--ease-out),
              box-shadow var(--d-micro) var(--ease-out),
              background var(--d-state) var(--ease-out),
              color var(--d-state) var(--ease-out);
}
.btn:hover:not(:disabled):not(.is-disabled) { transform: translateY(-1px); box-shadow: var(--sh-lift); }
.btn:active:not(:disabled):not(.is-disabled) { transform: scale(.98); transition-duration: var(--d-press); }
.btn:disabled { opacity: .45; cursor: default; }
/* FE-186: an <a> has no `disabled` attribute of its own (US-16's "an unready action says why" —
   the tooltip still needs somewhere to sit, which only a non-navigating anchor gives for free). */
.btn.is-disabled { opacity: .45; cursor: default; pointer-events: none; }
/* #1411: the unready download still takes a tap, to say why it is unready. */
#download-link.is-disabled { pointer-events: auto; }
.btn.glass { background: var(--glass-tint); color: var(--ink); }
.btn.plain { background: transparent; color: var(--ink2); box-shadow: none; }
.btn.plain:hover:not(:disabled) { background: var(--sunken); box-shadow: none; }
.btn.bad { background: var(--bad-bg); color: var(--bad-text); box-shadow: inset 0 0 0 1px var(--bad-line); }
.btn.dense { min-height: 32px; padding: 0 12px; font-size: 13px; border-radius: 8px; }
.ib {
  width: 40px; height: 40px; border-radius: var(--r-ctl); border: 0; background: transparent;
  color: var(--ink2); display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  transition: background var(--d-state) var(--ease-out), color var(--d-state) var(--ease-out),
              transform var(--d-micro) var(--ease-out);
}
.ib:hover { background: var(--sunken); color: var(--ink); }
.ib:active { transform: scale(.98); transition-duration: var(--d-press); }
.ib.dense { width: 32px; height: 32px; }
.ib[aria-pressed="true"] { color: var(--ink); background: var(--sunken); }

/* A text or number field in the panel's vocabulary. */
.fld {
  font: inherit; font-size: 13px; min-height: 32px; padding: 4px 8px; border: 1px solid var(--line2);
  border-radius: 8px; background: var(--surface); color: var(--ink); min-width: 0;
}
.fld:focus { border-color: var(--focus); outline: none; }
.fld.w-num { width: 96px; }
/* The browser default placeholder colour (~3.7:1 over --surface in the dark theme, UX-25) is
   below WCAG 1.4.3's 4.5:1 floor. --ink2 (the "dim" secondary-text token) clears 4.5:1 in both
   themes and already reads as muted, so a placeholder does not look like entered text. */
.fld::placeholder { color: var(--ink2); }

/* A segmented control: a glass track with one opaque thumb sliding on --spring.
   The cells are equal width deliberately — a thumb that also had to change
   width could only do it by animating a layout property (§4). */
.seg {
  position: relative; display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  padding: 3px; border-radius: 12px; background: var(--glass-tint);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  box-shadow: inset 0 1px 0 var(--glass-highlight), 0 0 0 1px var(--glass-edge);
}
.glass .seg, .glass-strong .seg {
  background: var(--sunken); box-shadow: inset 0 0 0 1px var(--line);
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
.seg-thumb {
  position: absolute; left: 3px; top: 3px; height: 34px; border-radius: 9px;
  background: var(--surface); box-shadow: var(--sh); pointer-events: none;
  transition: transform var(--d-panel) var(--spring);
}
.seg .seg-i {
  appearance: none; border: 0; background: transparent; cursor: pointer; white-space: nowrap;
  font: 500 13px/1 var(--font-ui); color: var(--ink2); height: 34px; padding: 0 14px;
  border-radius: 9px; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  position: relative; z-index: 1; transition: color var(--d-state) var(--ease-out);
}
.seg .seg-i.on { color: var(--ink); font-weight: 600; }
/* An unconfirmed elevation or frame offset (briefs 45/47) keeps its `*`. */
.seg .seg-i .star { color: var(--sys-text); font-weight: 600; }
.seg .seg-i .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink3); }
.seg .seg-i .dot.ok { background: var(--good); }
.seg .seg-i .dot.no { background: var(--bad); }
/* #713 (round 4): the dot and the star never shrink — they are a fixed glyph, not a budget to
   take space from — so every pixel a narrow cell gives up comes from the label beside them. */
.seg .seg-i .dot, .seg .seg-i .star { flex: 0 0 auto; }
/* #713 (round 4): the label is its own flex child (`overlays.ts`'s `mountSeg`), not a bare text
   node the cell's `justify-content: center` centres and clips on both edges with no ellipsis
   mark — the business-analyst's own screenshot read "мна / мна / мна" where "комната 1/2/3"
   belonged. `flex: 1 1 auto` makes it the one thing in the cell that takes the free space (or
   gives it up), so centring the cell as a whole never matters once there IS a deficit: the label
   fills what dot/star leave and truncates at its own trailing edge, reading from its first
   character, like `.ellipsis` does everywhere else in this file. */
.seg .seg-i .seg-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; }

.chip {
  display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 10px;
  border-radius: var(--r-pill); font: 500 12px/1 var(--font-ui); color: var(--ink2);
  background: var(--sunken); box-shadow: inset 0 0 0 1px var(--line); white-space: nowrap;
}
.chip.sys { background: var(--sys-bg); color: var(--sys-text); box-shadow: inset 0 0 0 1px var(--sys-line); }
.chip.good { background: var(--good-bg); color: var(--good-text); box-shadow: inset 0 0 0 1px var(--good-line); }
.chip.bad { background: var(--bad-bg); color: var(--bad-text); box-shadow: inset 0 0 0 1px var(--bad-line); }
.chip.wrap { height: auto; padding: 4px 10px; white-space: normal; line-height: 1.35; }

/* DESIGN.md §5 "Touch sizes and fields": "Nothing is ever under 24 × 24" holds at every width,
   not only under a coarse pointer (unlike the 44 px floor below, which is coarse-only) — so the
   badge is sized off `--touch-target-min` itself rather than a literal that could drift from it
   (UI-162 rework, card #633: this was 22 × 22 under a fine/mouse pointer at the story's own
   1440 × 900 window, US-15.feature "Every control on a question row is at least 24 by 24 pixels"). */
.pin {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--touch-target-min); height: var(--touch-target-min);
  flex: 0 0 auto; border-radius: var(--r-pill); font: 600 12px/1 var(--font-mono);
  font-variant-numeric: tabular-nums; background: var(--ink); color: var(--on-ink);
  transition: transform var(--d-micro) var(--ease-out);
}
/* A filled mark carrying white text fills with --sys-text, never --sys (§1). */
.pin.sys { background: var(--sys-text); color: var(--on-fill); }
.pin.good { background: var(--good); color: var(--on-fill); }

.bar { height: 6px; border-radius: var(--r-pill); background: var(--line); overflow: hidden; }
/* The fill is a full-width layer scaled on X — a width transition would be a
   layout property (§4). */
.bar > i {
  display: block; height: 100%; width: 100%; border-radius: var(--r-pill); background: var(--act, var(--ink));
  transform-origin: left center; transition: transform var(--d-screen) var(--ease-out);
}

/* --- The stage (§5) -----------------------------------------------------
   The graphic surface fills the viewport edge to edge; every piece of chrome
   floats over it and never reflows it. */
.app { position: fixed; inset: 0; overflow: hidden; background: var(--paper); }
.stage { position: absolute; inset: 0; overflow: hidden; background: var(--sunken); touch-action: none; }
.pan {
  position: absolute; left: 0; top: 0; width: 0; height: 0; transform-origin: 0 0;
  transition: transform var(--d-recentre) var(--ease-in-out);
}
/* While a hand is on it the stage tracks the hand with no transition (§4). */
.pan.live { transition: none; }

/* One world box per drawing view, so a view switch cross-fades two surfaces
   that sit in the same place rather than two differently scaled ones. Its size
   is set from the drawing's own aspect when the drawing arrives. */
.surface {
  position: absolute; left: 0; top: 0; opacity: 0; transform: scale(1.02); pointer-events: none;
  transition: opacity var(--d-screen) var(--ease-out), transform var(--d-screen) var(--ease-out);
}
.surface.on { opacity: 1; transform: scale(1); pointer-events: auto; }
/* An <svg> in HTML is 150 px tall by default, not the size of its box: without
   this the drawing renders postage-stamp sized in a corner, and the symptom
   looks exactly like a bad fit calculation. */
.surface > svg, .surface .sheet-stack > svg { position: absolute; inset: 0; width: 100%; height: 100%; }
/* #1426: the drawing is not clipped to its own fitted box. Zoomed or panned, it runs on to the
   window's edge (`.stage` clips there), and the stage itself takes the drag and the wheel
   (`stage.ts`'s `enableZoom`); before, it vanished at the edges of a box in the middle of the
   screen and could be dragged out of it entirely. */
.surface > svg, .surface .sheet-stack > svg { overflow: visible; }
/* The scheme's white sheet (`data-backdrop`, `packages/confirm/src/scheme.ts`) is the size of the
   first frame; on a canvas that runs to the window's edge it would read as a box again, so in
   the workspace it is paper under the whole canvas. CSS geometry, in the SVG's own units. */
.surface > svg > rect[data-backdrop] { x: -1000000px; y: -1000000px; width: 2000000px; height: 2000000px; }
.surface .sheet-stack { position: absolute; inset: 0; }
.surface .sheet-stack canvas { width: 100%; height: 100%; display: block; }
.surface > svg[data-scheme-frame], .surface > svg { cursor: grab; }
.surface > svg.grabbing { cursor: grabbing; }
/* UI-174: the stage owns every touch gesture on a drawing (pinch, pan, double-tap, a two-finger
   tap); `.readstage svg.layers` (the run screen, `web.css`) has no ancestor of its own that
   already sets this, so the class travels with the element itself. */
.zoomable { touch-action: none; }

/* --- The zoom controls and chip (§5 "A column of 44 × 44 controls…") ----
   Floats bottom-right, above the peek/drawer/tab bar, whatever the stage: `position: fixed`
   rather than a child of `.stage`, so neither the workspace's own `.pan` transform nor the run
   screen's own `.readstage` box ever carries it along or clips it. */
.zoom-controls {
  position: fixed; right: max(12px, env(safe-area-inset-right, 0px)); bottom: max(12px, env(safe-area-inset-bottom, 0px));
  z-index: 40; display: flex; flex-direction: column; align-items: center; gap: 6px;
}
.zoom-controls .ib { width: 44px; height: 44px; box-shadow: 0 0 0 1px var(--glass-edge), var(--sh-float); }
/* #1409: the buttons float over the white plan sheet, so their glyph cannot lean on the page behind it
   (a light glyph on a white sheet was about 2:1 in the dark theme). A solid surface of the theme's own
   gives the glyph the theme's ink against it, whatever lies underneath. */
.zoom-controls .ib { background: var(--surface); color: var(--ink); }
.zoom-controls .ib:hover { background: var(--sunken); }
.zoom-chip {
  background: var(--glass-tint-strong); -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  box-shadow: inset 0 1px 0 var(--glass-highlight), 0 0 0 1px var(--glass-edge), var(--sh-float);
  color: var(--ink); font: inherit; font-size: 12px; font-weight: 600; padding: 3px 9px; border-radius: var(--r-pill);
}
/* `.chip`'s own `display` (line 137 above) beats the UA `[hidden] { display: none }` rule once
   an author stylesheet sets `display` at all — the same reason `.topbar .btn[hidden]` and
   `.question-crop[hidden]` need their own override below. Found live: the empty chip rendered
   as a small blank pill at fit instead of vanishing. */
.zoom-chip[hidden] { display: none; }
/* The drawer's peek/half/full snaps (`band()`, `stage.ts`) sit at the bottom of a phone screen —
   the same three states move the zoom column up so it never sits under the sheet. */
body.drawer-40 .zoom-controls { bottom: calc(40dvh + 12px); }
body.drawer-85 .zoom-controls { bottom: calc(85dvh + 12px); }

/* The 3D viewer and the spec document are not drawings and are never
   transformed by the stage: a WebGL canvas scaled by a parent is a blur, and an
   iframe scaled by one is unreadable. They take the clear band directly,
   positioned (never transitioned) whenever the band changes. */
.fillview {
  position: absolute; z-index: 1; opacity: 0; pointer-events: none; overflow: hidden;
  border-radius: var(--r-panel);
  transition: opacity var(--d-screen) var(--ease-out);
}
.fillview.on { opacity: 1; pointer-events: auto; }
.fillview > model-viewer, .fillview > furnlab-viewer, .fillview > iframe { width: 100%; height: 100%; border: 0; display: block; background: var(--surface); }

/* A retrace dims the drawing and never blanks it (§4, §7). */
.stage-dim {
  position: absolute; inset: 0; z-index: 2; background: var(--sunken); opacity: 0; pointer-events: none;
  transition: opacity var(--d-dim) var(--ease-out);
}
.app.retracing .stage-dim { opacity: .4; }
/* Document views live beside `.stage` so WebGL and text are never transformed. They are still
   the graphic stage and dim to the same 60 % while a decision rebuilds them. */
.app.retracing .fillview.on { opacity: .6; }

/* Card #1942: the note sits in the world the stage scales (a 1000 px wide surface), and at a
   phone's fit that scale is about a third: the owner saw the sentence at 4 px. It undoes the
   stage's scale, so it reads at its own size at every width and every zoom. */
.empty-note {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(calc(1 / var(--stage-k, 1)));
  text-align: center; width: 420px; max-width: 80vw;
}

/* --- The overlays ------------------------------------------------------- */
/* The top bar's own height, and the one number every overlay under it hangs from (the rail,
   the right panel, the pill, the view toolbar, the accept bar, the banner, the header's own
   menus; `stage.ts`'s `band()` reads the bar's real box). 56 px everywhere but a phone, where
   the bar is two rows (UI-172, the `max-width: 767px` block below).
   #1457: this is now only the FLOOR `.topbar` opens on, and the value every breakpoint below
   still writes — the one frame before `topbar-height.ts`'s `ResizeObserver` has measured the
   bar's own real, rendered height and corrected it (an inline style on `:root` then outranks
   every rule here, at every width). A breakpoint's own number was never a safe upper bound: it
   assumes the `order: 0` primary group (the trail, the project and view names, the level and
   room switches, the bell, the avatar) always fits the one row it was given, and once that
   group's own content is long enough (a project's own longer room names; #1457's own
   demo-townhouse `floor-2` at 1200px) `flex-wrap` gives it a row more than the breakpoint ever
   planned for. */
:root { --topbar-h: 56px; }
/* Fixed to the top edge, so it carries the safe-area inset there (§5 "Safe areas"); the
   floating 12 px inset itself stays on a device with no notch, since `env()` is 0 there.
   `min-height`, not `height` (#1457): a FIXED height left the primary group's own extra wrapped
   row overflow the box's bottom edge invisibly — `.topbar`'s own `getBoundingClientRect()`
   still read the breakpoint's guessed height, never the taller box the wrapped row actually
   needed, so nothing (not even a script asking the bar how tall it really is) could see the
   mismatch that produced the overlap. A `min-height` box grows to whatever its own content
   needs, on every width, so the bar visually contains every row it wraps to and its own
   measured height is finally the true one `topbar-height.ts` reads. */
.topbar {
  position: absolute; z-index: 6; min-height: var(--topbar-h);
  left: calc(12px + env(safe-area-inset-left)); right: calc(12px + env(safe-area-inset-right));
  top: calc(12px + env(safe-area-inset-top));
  border-radius: var(--r-panel); padding: 0 8px; display: flex; align-items: center; gap: 12px;
}
/* UI-172: a room switch a long merged room name ("Вітальня + Кухня") can make wider than the
   bar has room for — without this, every cell's own `white-space: nowrap` (§4's segmented
   control) gives the whole `.seg` an automatic minimum equal to its full, un-truncated content,
   so the flex row's shrinking has nowhere to take space from it and takes it all from
   `#view-name` instead (measured live: "Scheme" clipped to "S" at 1440px on a 2-level, 3-room
   project). Each cell keeps its own ellipsis instead, so the level/room switch itself has real
   room to give.
   #713 (round 4, the business-analyst's refusal): "room to give" had no floor of its own, so at
   1440/1200 on `demo-townhouse` the deficit kept landing here anyway — level cells down to
   43/32 px, room cells to 51/37 px, 15/4 and 23/9 px of label after padding, read live as
   "мна / мна / мна" for "комната 1/2/3". `min-width` is the floor the cell never had.
   #713 (round 5): 80px measured (`apps/stand-tests/scratch/probe713.mjs`) in the cached
   Chromium build this sandbox carries (`/opt/pw-browsers/chromium-1194`, no Inter/SF Pro/Segoe
   UI installed, so `--font-ui` falls back to the generic `sans-serif` the container does have —
   the same fallback a Linux CI runner with no desktop fonts gets, not a theoretical case) left
   only 39px for `.seg-label` once the 28px of padding, the 7px dot and the 6px gap are taken —
   room to canvas-measure "ко…" (2 Cyrillic characters, the `.seg-i.on` bold weight included:
   600 13px, `ctx.measureText`) before the fallback font's own "…" glyph (13px wide at this
   weight/size — not a narrow punctuation mark in this stack) ran out of budget, same as round
   4's own table already showed ("ко…"/"Віт…"/"Там…", 2–3 characters) even though round 4's own
   prose read it as meeting the ≥4-characters-or-whole-label bar. `min-width: 100px` (measured
   against the longest of "комната 1"/"кімната 1"/"room 1" at this weight: 4 characters + the
   13px ellipsis needs 50.8px of label, +41px of padding/dot/gap = 91.8px, +≈8px margin) is the
   floor that clears the bar in THIS environment rather than one that assumed a font this
   environment does not carry. The evidence chips (below) carry the deficit past this point
   instead — they still have real room to give before this one does.
   #713 (round 12, the business-analyst's second refusal): a literal `max-width: 420px` on `.seg`
   itself (removed) was dividing that cap by however many cells there were regardless of how much
   of it any one project actually needed — `demo`'s 5 "комната N" cells got 84px each, short of
   each cell's OWN 128px ceiling just below, even though ≈400px sat empty to the room switch's
   right at 1440×900 (the business-analyst's own screenshot). Without a container cap, `.seg`
   sizes to its cells' own content, up to each cell's own ceiling — the thing the business analyst
   actually asked widened. The case this literal existed for (a long merged room name, above) and
   the case round 4 found (no floor at all) are both still covered by the per-cell `min`/`max`
   below; what is no longer covered is a project needing MORE than `.seg` could ever give
   regardless of how much the bar had spare — `overlays.ts`'s `mountPicker` (UI-172 round 12) now
   measures the mounted control a frame after layout and swaps it for the compact "‹current› ▾"
   menu — which lists every item by its whole name in its own popover, never truncated — whenever
   a cell's own `scrollWidth` exceeds its `clientWidth` (the browser truncated it) or the control
   itself no longer shares `#view-name`'s own line (the row wrapped it below the bar, round 11's
   own finding). Removing this cap could, in principle, let a `.seg` grow large enough to force
   exactly that second condition on a project this round never measured; the same fallback catches
   it instead of a new pixel ceiling guessed to fit today's two demo projects (MOB-24). */
.topbar .seg .seg-i { max-width: 128px; min-width: 100px; overflow: hidden; text-overflow: ellipsis; }
/* US-48 (card #1803): ONE 56 px row from 768 px up, in three fixed zones that stand in the same
   place on every view — left: where am I (the product mark, the way back, the project's name with
   its ▾, the view's name); centre: the level switch and the room switch; right: the plan download,
   the bell, the avatar. The left zone's width does not depend on the view or on the project's name
   (it is a fixed basis that only ever shrinks, by the same amount on every view), so the centre
   zone starts at the same x on all four views; the right zone takes what is left and ends at the
   bar's right edge. Under 768 px the zones are not boxes (`display: contents`), so the phone's own
   rows below (a later brief's) still order the controls as before.
   The old rules for this bar — the ≥1025 px second row (`--topbar-h: 104px`), the 768–1024 px
   second row, the evidence chips' container queries and their fold — are gone with the controls
   they served: those controls moved into the two menus (US-48's table "Reachable today"). */
.zone { display: flex; align-items: center; gap: 12px; min-width: 0; }
.zone-l { flex: 0 1 540px; gap: 8px; }
.zone-c { flex: 0 0 auto; }
/* Card #2012: the right zone never shrinks below its own buttons. With `flex: 1 1 0` and `min-width: 0` it
   did, and since its buttons do not shrink and it is packed to the end, they spilled LEFT over the centre
   zone: from 768 to 1024 px the download button covered «комната 1». The left zone (the project's name,
   with its own floor) gives way instead. */
.zone-r { flex: 1 0 auto; justify-content: flex-end; gap: 8px; }
/* The left zone is 540 px from 1280, 560 from 1400, 390 under 1280 and 340 under 1025: wide enough for
   the project's name to keep the story's floor (180 px at 1280, 200 above) beside the longest view
   name («ТЗ на мебель»), and the same width on every view so the switches in the centre stand at the
   same x on every view. */
@media (min-width: 1400px) {
  .zone-l { flex-basis: 560px; }
}
.crumbs { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1 1 auto; }
.crumb { color: var(--ink); gap: 4px; padding: 0 12px 0 8px; flex: 0 0 auto; }
/* Card #1958: the way back is round, as the avatar at the other end of the bar is: a pill with its word from
   768 px up, a circle of the avatar's diameter under it (owner, 2026-10-10, relaying a user: "the back button
   is ugly on a phone; make it round, symmetric with the avatar"). `.btn.crumb` outranks `.btn.dense`'s 8 px. */
.btn.crumb { border-radius: var(--r-pill); }
.crumb > svg { flex: 0 0 auto; }
.crumb-sep { color: var(--ink3); flex: 0 0 auto; margin: 0 -4px; }
/* Card #1805: the title's wrappers (page.ts) are not boxes from 768 px up, so the trail is the flex row it was;
   the floor beside the project's name and the room beside the view's name are the phone title's two lines. */
.trail-title, .trail-sub { display: contents; }
.proj-floor, .view-room { display: none; }
/* UI-163 / US-11: the product's name, a link to the project list on every screen (the entry
   screen's header uses the same class). It borrows `.btn.plain.dense` for its box. */
.brandmark { color: var(--ink); flex: 0 0 auto; }
.brandmark svg { color: var(--brand-text, var(--sys-text)); }
a.brandmark:not([href]) { cursor: default; }
a.brandmark:not([href]):hover { background: transparent; transform: none; }
/* The project's name is the one thing in the bar a person needs (DESIGN.md §5), and the button of the
   project menu (US-48): 260 px at most, then an ellipsis that keeps the ▾. Its floor is the story's
   own (96 px at 768, 140 at 1024, 180 at 1280, 200 above) — the rest of the left zone gives way first. */
#proj-name {
  flex: 0 1 auto; min-width: 96px; max-width: 260px; display: inline-flex; align-items: center; gap: 4px;
  height: 32px; padding: 0 8px; margin: 0 -8px;
  border: 0; border-radius: var(--r-ctl); background: transparent; color: var(--ink); font: inherit; cursor: pointer;
  text-align: left;
}
#proj-name:hover, #proj-name[aria-expanded="true"] { background: var(--sunken); }
@media (pointer: coarse) {
  #proj-name { height: var(--touch-target); }
}
.proj-name-text { min-width: 0; }
.proj-caret { flex: 0 0 auto; color: var(--ink2); }
/* UI-172: the current view's name is a required control at every width (US-13) — unlike the
   `.lbl` labels below, it never hides; it is the third crumb and the only place the view's name is
   written (the toolbar no longer repeats it). */
#view-name { flex: 0 0 auto; min-width: 0; max-width: 160px; color: var(--ink); }
/* UI-172: past eight items — or at ≤1024px, where `mountPicker` (`overlays.ts`) already swaps
   the segmented level/room switch for a "‹current› ▾" menu button — the button itself keeps an
   ellipsis rather than pushing the rest of the bar off screen. */
#levelseg > .btn, #roomseg > .btn { max-width: 160px; overflow: hidden; text-overflow: ellipsis; gap: 4px; }
.topbar > .ib, .topbar > .fld, .topbar > .btn, .zone > .btn, .zone > .notify, .zone > .avatar { flex-shrink: 0; }
/* #1347: the running version and its build time, quiet beside the language switch. */
.buildver { flex: 0 0 auto; white-space: nowrap; font-variant-numeric: tabular-nums; }
/* The survey link is one ordinary button, never the `.fill` utility's full width: with `width:
   100%` it took a row of its own and pushed the build stamp onto a third line the 104 px bar does
   not have, over the toolbar (owner's screenshot, 2026-10-04). Both live on the bar's second row
   with the other "about this page" controls (`order: 2` in the two wrapping layouts below). */
/* A view opened by a person takes the keyboard (NAV-8); the region itself is not a control, so
   it carries no ring of its own. */
.surface[tabindex="-1"]:focus, .fillview[tabindex="-1"]:focus { outline: none; }

/* The rail expands over the stage, so it never moves the drawing. Two glass
   layers cross-faded rather than one whose width animates (§4). */
.rail {
  position: absolute; top: calc(24px + var(--topbar-h)); bottom: 12px; width: 248px; z-index: 5; pointer-events: none;
  left: calc(12px + env(safe-area-inset-left));
}
.rail-layer {
  position: absolute; left: 0; top: 0; bottom: 0; border-radius: var(--r-panel);
  padding: 12px 8px; display: flex; flex-direction: column; gap: 2px; overflow: visible;
  pointer-events: auto;
  transition: opacity var(--d-panel) var(--spring), transform var(--d-panel) var(--spring);
  transform-origin: left center;
}
.rail-layer.icons { width: 56px; align-items: center; padding-left: 6px; padding-right: 6px; }
.rail-layer.full { width: 248px; opacity: 0; transform: translateX(-12px) scale(.98); pointer-events: none; }
.rail.open .rail-layer.icons { opacity: 0; pointer-events: none; }
.rail.open .rail-layer.full { opacity: 1; transform: none; pointer-events: auto; }
.rail:not(.open) .rail-layer { transition-duration: calc(var(--d-panel) * .8); }
.rail-i {
  display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: var(--r-ctl);
  font-size: 14px; font-weight: 500; color: var(--ink2); cursor: pointer; min-height: 40px;
  border: 0; background: transparent; width: 100%; text-align: left;
  transition: background var(--d-state) var(--ease-out), color var(--d-state) var(--ease-out);
}
.rail-i:hover { background: var(--sunken); }
.rail-i.on { background: var(--surface); color: var(--ink); font-weight: 600; box-shadow: var(--sh); }
/* 44 px on every pointer (US-48): with four places and the expander the rail has room for them. */
.rail-i.icon-only { width: 44px; height: 44px; min-height: 44px; padding: 0; justify-content: center; }
.rail-i.foot { min-height: 34px; font-size: 13px; }
.rail-i > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-i .count { margin-left: auto; font: 600 11px/1 var(--font-mono); color: var(--ink3); }
.rail-layer.full .cap { padding: 8px 12px; }

/* Glass over glass does not happen (§2.4): a popover is opaque --surface. It
   scales from .96 at its anchor and fades, 160 ms (§4). */
.pop {
  position: absolute; left: 8px; bottom: 46px; width: 300px; padding: 12px 14px; z-index: 7;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-card);
  box-shadow: var(--sh-lift); transform-origin: left bottom;
  opacity: 0; transform: scale(.96); pointer-events: none;
  transition: opacity var(--d-pop) var(--ease-out), transform var(--d-pop) var(--ease-out);
}
.pop.on { opacity: 1; transform: scale(1); pointer-events: auto; }
.pop:not(.on) { transition-duration: var(--d-micro); }
/* The room menu hangs off the top bar instead — same material, other anchor. */
.pop.under { left: auto; bottom: auto; top: calc(var(--topbar-h) + 4px); transform-origin: top center; max-height: 60dvh; overflow-y: auto; }

/* One right panel. The questions, the walls, the lines, the files and the
   sheet inventory share this slot; only one is open at a time (§5). */
.panel {
  position: absolute; right: 12px; top: calc(24px + var(--topbar-h)); bottom: 12px; width: 380px; z-index: 5;
  border-radius: var(--r-panel); display: flex; flex-direction: column; overflow: hidden;
  transform-origin: right center;
  opacity: 0; transform: translateX(24px) scale(.98); pointer-events: none;
  transition: opacity var(--d-panel) var(--spring), transform var(--d-panel) var(--spring);
}
.panel.open { opacity: 1; transform: none; pointer-events: auto; }
.panel:not(.open) { transition-duration: calc(var(--d-panel) * .8); }
.pstate {
  position: absolute; inset: 0; display: flex; flex-direction: column; overflow: hidden;
  opacity: 0; pointer-events: none; transition: opacity var(--d-state) var(--ease-out);
}
.pstate.on { opacity: 1; pointer-events: auto; }
/* F-964.1: a closed panel is invisible, and its active state's own `pointer-events: auto` (`.pstate.on`)
   still caught clicks meant for whatever sits under it — the furnishing step's right-hand buttons.
   Desktop only: at <= 1024 px the panel is the bottom drawer, visible at its peek without `.open`,
   and this rule made every tap on its grip fall through to the drawing — the owner could not
   collapse it on a phone (2026-10-05). */
@media (min-width: 1025px) {
  .panel:not(.open), .panel:not(.open) .pstate, .panel:not(.open) .pstate * { pointer-events: none; }
}
/* The materials table (§2): a list of files and a table of sheets are simply
   read, so they sit on opaque --surface. The questions, the walls and the lines
   are read *against* the drawing underneath and stay on .glass-strong. */
.pstate[data-state="sheets"] { background: var(--surface); }
.phead { padding: 12px 16px 8px; display: flex; flex-direction: column; gap: 10px; flex: 0 0 auto; }
.pbody { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 0 10px 10px; }
.pfoot { flex: 0 0 auto; padding: 10px 16px 12px; border-top: 1px solid var(--line); font-size: 12px; color: var(--ink2); }
.dhandle { display: none; }

/* The collapsed panel: the edge tab «Вопросы · N» (US-48, card #1804) — 44 px wide, 120 px tall,
   glass, at the right edge and vertically centred, so the questions are one tap away on every view.
   The text runs top to bottom (`writing-mode`) and the dot of a pending question stays at its head.
   It grows with its words (card #2012: a fixed 120 px clipped «Все вопросы · 8» at its foot). */
.pill {
  position: absolute; right: env(safe-area-inset-right); top: 50%; z-index: 5;
  width: 44px; min-height: 120px; border: 0; padding: 12px 0;
  border-radius: var(--r-panel) 0 0 var(--r-panel); display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  font: 500 13px/1 var(--font-ui); color: var(--ink); cursor: pointer; transform-origin: right center;
  opacity: 0; transform: translate(24px, -50%) scale(.98); pointer-events: none;
  transition: opacity var(--d-panel) var(--spring), transform var(--d-panel) var(--spring);
}
.pill > span:not(.dot) { writing-mode: vertical-rl; white-space: nowrap; }
.pill.on { opacity: 1; transform: translateY(-50%); pointer-events: auto; }
.pill .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--sys); flex: 0 0 auto; }

/* The right panel's two tabs «Вопросы N | Ваши правки» (US-48): a row at the head of both sections,
   the close button at its end. 44 px tall on every pointer — they are places to stay in. */
.ptabs-row { display: flex; align-items: center; gap: 8px; }
.ptabs { display: flex; gap: 4px; padding: 3px; border-radius: 12px; background: var(--sunken); box-shadow: inset 0 0 0 1px var(--line); min-width: 0; }
.ptab {
  appearance: none; border: 0; background: transparent; cursor: pointer; white-space: nowrap;
  min-height: 44px; padding: 0 14px; border-radius: 9px; display: inline-flex; align-items: center; gap: 8px;
  font: 500 13px/1 var(--font-ui); color: var(--ink2); min-width: 0;
  transition: background var(--d-state) var(--ease-out), color var(--d-state) var(--ease-out);
}
.ptab:hover { color: var(--ink); }
.ptab.on { background: var(--surface); color: var(--ink); font-weight: 600; box-shadow: var(--sh); }
.ptab-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.ptab-count { font: 600 11px/1 var(--font-mono); color: var(--ink3); }
.ptab.on .ptab-count { color: var(--sys-text); }
.ptabs-row .ib { width: 44px; height: 44px; }

/* Артефакты (US-48): the files of the set, the verdict under the list, and «Скачать план» beside it,
   in the files tab's own view. The built sheets and the specification need no styles of their own:
   they are the drawing and the document they always were. */
.fillview[data-view="files"] { background: var(--surface); overflow: auto; }
.artefact-files { max-width: 720px; margin: 0 auto; padding: 88px 24px 24px; gap: 12px; }
.artefact-files-head { align-items: center; }
.artefact-files-foot { padding-top: 10px; border-top: 1px solid var(--line); }
.artefact-tabs { display: inline-flex; flex: 0 0 auto; }
/* Each tab is as wide as its own name (the thumb is placed by measurement, so equal cells are not
   needed here): with equal cells the longest name, «Развёртка стены B-C», set the width of «План» too,
   and the row overflowed at 1440 with the panel open. What still does not fit scrolls sideways inside
   the toolbar's own row (`.toolbar-row`), with the chosen tab brought into view and a fade on the edge
   that has more behind it (`artefacts.ts`), so a cut tab reads as "there is more", never as a defect. */
.artefact-tabs .seg { grid-auto-columns: max-content; }
/* Card #2012: the 3D view's floor switch («весь дом», «1-й поверх», «2-й поверх»). With equal `1fr`
   cells it shrank to about 62 px a cell at 375 px and read «ве… / 1-й… / 2-…» while half the row
   stood empty; each cell is now as wide as its own name, the thumb placed by measurement as above. */
.model-levels .seg { grid-auto-columns: max-content; }
.artefact-tabs .seg .seg-i { padding: 0 11px; min-width: 0; overflow: visible; }
.toolbar-row.fade-end { -webkit-mask-image: linear-gradient(to right, black calc(100% - 32px), transparent); mask-image: linear-gradient(to right, black calc(100% - 32px), transparent); }
.toolbar-row.fade-start { -webkit-mask-image: linear-gradient(to left, black calc(100% - 32px), transparent); mask-image: linear-gradient(to left, black calc(100% - 32px), transparent); }
.toolbar-row.fade-start.fade-end { -webkit-mask-image: linear-gradient(to right, transparent, black 32px, black calc(100% - 32px), transparent); mask-image: linear-gradient(to right, transparent, black 32px, black calc(100% - 32px), transparent); }

/* The toolbar over the drawing: a glass pill, per view.
   `overflow-x: auto` (not `hidden`): MOB-7 (US-19) measured the sheet switcher holding 471 px
   of content in a 235 px box at 1024 and below, under `overflow: hidden`, with nothing else on
   screen reaching the sheets it cut off — a box a script can move but a finger cannot, exactly
   the trap `docs/drawing-conventions.md`-adjacent DESIGN.md §5 names for the same reason. `auto`
   changes nothing at 1440 (`max-width: calc(100% - 640px)` already fits every toolbar's content
   there, so nothing overflows and no scrollbar appears); it only starts scrolling once a
   toolbar's own content is wider than its box, which today only happens at `<= 1024`
   (`max-width: calc(100% - 140px)` below). The scrollbar itself is hidden (a glass pill, not a
   list) — the content still scrolls under a touch drag or a wheel exactly the same. */
.toolbar {
  position: absolute; z-index: 4; max-width: calc(100% - 640px);
  border-radius: var(--r-pill); padding: 5px 8px; display: flex; flex-direction: column; gap: 2px;
  left: 50%; top: calc(24px + var(--topbar-h)); transform: translateX(-50%);
  opacity: 0; pointer-events: none; transition: opacity var(--d-screen) var(--ease-out);
}
.toolbar.on { opacity: 1; pointer-events: auto; }
/* With the right panel open on a desktop the toolbar centres in the clear band between the rail and the
   panel instead of on the window, so its rightmost buttons are not under the panel (US-48: the scheme's
   toolbar now carries «Оригинал», the trace's acceptance and «Исправить план» together). */
@media (min-width: 1025px) {
  .app:has(.panel.open) .toolbar { left: calc(50% - 162px); max-width: calc(100% - 508px); }
}
/* UI-189 (#729): the buttons' own scrolling row. It used to be `.toolbar` itself — the armed-tool
   hint (`.toolbar-hint`, below) has since moved out to a row of its own, because this row's own
   buttons refuse to shrink (UI-171, just below) and the hint was the row's only child that could,
   so it took 100% of any shortfall: squeezed all the way to `clientWidth: 0` the moment the
   buttons and the rooms chip alone already filled the row (QA's #729 repro, at 1440 px). */
.toolbar-row {
  min-height: 34px; padding: 3px 6px 3px 12px; display: inline-flex; align-items: center; gap: 10px; flex-wrap: nowrap;
  overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.toolbar-row::-webkit-scrollbar { display: none; }
/* A toolbar that scrolls sideways (UI-171) must not also squeeze its buttons: at 390 px the
   scheme editor's «Исправить план» (UI-178) and «Добавить метку» shrank to their 44 px touch
   floor and painted their labels over each other (the UI wave batch, measured on demo-townhouse). */
.toolbar-row > .btn { flex-shrink: 0; }
.accept-trace .btn-ico { display: none; } /* drawn on a phone only: the phone block below */
.trace-accepted .btn-ico { display: none; } /* the same for the accepted chip (card #1805) */
.toolbar-row input[type="range"] { width: 92px; vertical-align: middle; accent-color: var(--brand-text, var(--sys-text)); }
/* The hint's own row: full width, wrapping rather than fighting the button row's `flex-shrink: 0`
   for space it does not have (#729). `.ellipsis` (shared with the button row's own notes) still
   truncates it to one line when even this whole row is narrower than the text — DESIGN.md §11's
   own bar, `scrollWidth <= clientWidth` at 390 px — but it is never squeezed below that. */
.toolbar-hint { padding: 0 6px 2px 12px; }
/* The retrace's own progress line, in the sheet toolbar where §4 puts it. */
.tb-progress {
  width: 96px; height: 3px; border-radius: 2px; background: var(--sys-text); opacity: 0;
  transform: scaleX(.04);
  transform-origin: left center; flex: 0 0 auto;
  transition: opacity var(--d-dim) var(--ease-out);
}
.app.retracing .tb-progress { opacity: 1; animation: run 1.6s var(--ease-in-out) infinite; }
@keyframes run { from { transform: scaleX(.04); } to { transform: scaleX(1); } }
:root.reduced .app.retracing .tb-progress { animation: none; transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) {
  .app.retracing .tb-progress { animation: none; transform: scaleX(1); }
}

/* The one question the drawing itself raises: a doubtful edge's accept bar,
   floating under the toolbar rather than pushing the drawing down. */
.accept {
  position: absolute; z-index: 5; left: 50%; top: calc(80px + var(--topbar-h)); transform: translateX(-50%);
  width: min(560px, calc(100% - 640px)); border-radius: var(--r-panel); padding: 12px 14px;
  display: flex; flex-direction: column; gap: 8px;
  opacity: 0; pointer-events: none;
  transition: opacity var(--d-state) var(--ease-out), transform var(--d-state) var(--ease-out);
}
.accept.on { opacity: 1; pointer-events: auto; }

/* A banner for a 409 the server answered with a sentence, and a toast for a
   500 (§7). Both arrive from their edge on transform and opacity only. */
.banner {
  position: absolute; z-index: 7; left: 50%; top: calc(80px + var(--topbar-h)); transform: translateX(-50%) translateY(-8px);
  width: min(620px, calc(100% - 200px)); border-radius: var(--r-card); padding: 12px 14px;
  background: var(--bad-bg); color: var(--bad-text); box-shadow: inset 0 0 0 1px var(--bad-line), var(--sh-float);
  display: flex; align-items: flex-start; gap: 10px;
  opacity: 0; pointer-events: none;
  transition: opacity var(--d-state) var(--ease-out), transform var(--d-state) var(--ease-out);
}
.banner.on { opacity: 1; transform: translateX(-50%); pointer-events: auto; }
/* #1411 (failed QA, the rework's own "Done when"): a toast's own sentence is read in ru, uk
   and en from the i18n catalogue, never written to fit one line, so the box has to wrap rather
   than assume one. An explicit `width` (not `max-width`) is this file's own pattern for a
   centred, `left: 50%; transform: translateX(-50%)` overlay that must wrap — `.banner` just
   above uses the same `width: min(…, calc(100% - …))` shape, for the same reason: a
   shrink-to-fit `max-width` here sizes against the box's OWN abspos available width (from
   `left` to the containing block's edge, per CSS2.1 §10.3.7), which `left: 50%` already halves,
   not against the viewport — so the text wrapped far narrower than the window actually allows
   (measured: a 343px-wide window budget still shrank the box to ~155px and five lines). `width`
   sidesteps that by fixing the box's own extent directly. `calc(100% - 32px)` keeps 16px of
   breathing room on each side at any width (phone included); `.toast-msg` (below, in place of
   the nowrap-and-truncate `.ellipsis` the text span used before) lets the sentence actually wrap
   instead of running off both edges while LOOKING like it fit, which is what made QA's own round
   pass on text content alone while this gap existed. */
.toast {
  position: absolute; z-index: 8; left: 50%;
  bottom: calc(24px + env(safe-area-inset-bottom));
  transform: translateX(-50%) translateY(12px);
  width: min(440px, calc(100% - 32px));
  border-radius: var(--r-pill); padding: 10px 16px; display: inline-flex; align-items: center; gap: 10px;
  font-size: 13px; opacity: 0; pointer-events: none;
  transition: opacity var(--d-state) var(--ease-out), transform var(--d-state) var(--ease-out);
}
.toast.on { opacity: 1; transform: translateX(-50%); pointer-events: auto; }
/* The shared `showToast` (overlays.ts) puts every toast's own sentence in a span of this class,
   never `.ellipsis`: a toast's text is a short, whole sentence meant to be read in full, not a
   label to truncate. `min-width: 0` still lets it shrink inside the `.toast` flex row above. */
.toast-msg { min-width: 0; white-space: normal; overflow-wrap: break-word; }

.toast .btn { flex-shrink: 0; }

/* FE-192: the card's/workspace's own "..." action menu, and the one-line delete confirmation
   that follows it — a floating panel in the product's own design system, positioned in script
   next to whatever "..." button opened it. */
.actionmenu, .confirmpanel {
  position: fixed; z-index: 30; border-radius: var(--r-card); box-shadow: var(--sh-lift);
  background: var(--surface); border: 1px solid var(--line); padding: 6px;
}
.actionmenu { min-width: 180px; }
.actionmenu-item {
  display: block; width: 100%; min-height: 44px; text-align: left; appearance: none; border: 0;
  background: transparent; padding: 10px 12px; border-radius: 8px;
  font: 500 14px/1.2 var(--font-ui); color: var(--ink); cursor: pointer;
}
.actionmenu-item:hover, .actionmenu-item:focus-visible { background: var(--sunken); outline: none; }
.actionmenu-item.tone-bad { color: var(--bad-text); }
.confirmpanel { width: min(280px, calc(100vw - 32px)); padding: 14px; }
.confirmpanel .confirmpanel-text { font: 500 14px/1.35 var(--font-ui); color: var(--ink); margin: 0 0 12px; }
.confirmpanel .row { justify-content: flex-end; }
/* Card #1959: the rename panel — one field and two buttons, every target 44 px on any pointer; 16 px
   text so iOS Safari does not zoom the page on focus. */
.renamepanel { width: min(320px, calc(100vw - 32px)); }
.renamepanel .confirmpanel-text { display: block; margin-bottom: 8px; }
.renamepanel-input {
  display: block; width: 100%; box-sizing: border-box; min-height: 44px; padding: 0 12px; margin: 0 0 12px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--surface); color: var(--ink);
  font: 400 16px/1.2 var(--font-ui);
}
.renamepanel-input:focus-visible { outline: 2px solid var(--sys); outline-offset: 1px; }
.renamepanel-why { margin: -6px 0 10px; font: 500 13px/1.3 var(--font-ui); color: var(--bad-text); }
.renamepanel .btn { min-height: 44px; }

/* --- Rows in the panel -------------------------------------------------- */
/* Background only: §4 forbids animating a shadow on anything larger than a
   button, and a row is larger than one — so an answered row's ring appears
   with a cut while its ground fades. */
.qrow {
  padding: 8px 10px; border-radius: var(--r-ctl); position: relative; cursor: default;
  transition: background var(--d-state) var(--ease-out);
}
.qrow + .qrow { box-shadow: inset 0 1px 0 var(--line); }
.qrow.pick { cursor: pointer; }
.qrow.pick:hover, .qrow.hot { background: var(--sys-bg); }
.qrow.on { background: var(--sys-bg); box-shadow: inset 0 0 0 1px var(--sys-line); }
.qrow.done { background: var(--good-bg); box-shadow: inset 0 0 0 1px var(--good-line); }
.qrow.answered { background: var(--good-bg); box-shadow: inset 0 0 0 1px var(--good-line); }
/* FLIP: the row is placed at its old offset and released to 0 (§4). */
.qrow.flip { transition: transform var(--d-flip) var(--ease-in-out), background var(--d-state) var(--ease-out); }
.pop, .panel, .pill, .rail-layer { will-change: transform, opacity; }

.inbox-room { padding-left: 10px; }
/* A heading for a room other than the selected one (UI-177): the same cap look, but a real
   control — reset from the button default, underlined only on interaction so it still reads as
   a section title at rest. */
.inbox-room-link { display: block; width: 100%; border: 0; padding: 0; margin: 0; background: transparent; text-align: left; font: inherit; letter-spacing: inherit; text-transform: inherit; color: inherit; cursor: pointer; }
.inbox-room-link:hover, .inbox-room-link:focus-visible { color: var(--ink); text-decoration: underline; }
.inbox-pin { border: 0; cursor: pointer; padding: 0; }
/* A member of a batch card (card #1563): its own place and field, set in under the shared number. */
.inbox-member { border-left: 2px solid var(--line); padding-left: 10px; }
.inbox-where { border: 0; padding: 0; background: transparent; text-align: left; cursor: pointer; }
.inbox-where:hover { color: var(--ink); }
.inbox-more { align-self: center; margin: 8px auto; }
.question-crop {
  display: block; width: 100%; max-height: 132px; object-fit: cover; border-radius: var(--r-ctl);
  border: 1px solid var(--line); background: var(--surface);
}
.inbox-progress { height: 6px; border-radius: var(--r-pill); background: var(--line); overflow: hidden; }
.inbox-progress > i {
  display: block; width: 100%; height: 100%; transform: scaleX(0); transform-origin: left center;
  background: var(--good); transition: transform var(--d-screen) var(--ease-out);
}

/* Pins arrive inside the server-rendered scheme SVG. Transform-only hover and pulse match the
   prototype; `data-question` is the stable bridge back to the corresponding row. */
.pin-g {
  cursor: pointer; transform-box: fill-box; transform-origin: center;
  transition: transform var(--d-micro) var(--ease-out), opacity var(--d-state) var(--ease-out);
}
.app.retracing .pin-g { opacity: .4; }
.pin-g.lift { transform: scale(1.15); }
.pin-g.pulse { animation: pinpulse var(--d-pulse) var(--ease-in-out); }
.pin-g.closed circle { fill: var(--good); }
.pin-g.closed.leave { opacity: 0; }
@keyframes pinpulse { 0% { transform: scale(1); } 50% { transform: scale(1.3); } 100% { transform: scale(1); } }

/* UI-190 (lane B4, E15-E16): a remark's own room, briefly, when its pin or panel row is tapped. */
/* Card #1811: a space's tint is set by the drawing's own stylesheet (`svg.scheme .space`, 0.38), so the
   highlight names the same element at the same weight, and stays inside the 0.10-0.60 band the story allows. */
.surface .remark-highlight > polygon.space { fill-opacity: .6; transition: fill-opacity var(--d-state) var(--ease-out); }

.group { margin: 2px 0 6px; }
.group > summary { cursor: pointer; font: 600 13px/1 var(--font-ui); padding: 8px 10px; border-radius: var(--r-ctl); list-style: none; }
.group > summary::-webkit-details-marker { display: none; }
.group > summary:hover { background: var(--sunken); }
.group > summary .eye { border: 0; background: none; padding: 0 2px; cursor: pointer; color: var(--ink3); line-height: 1; }
.group > summary .eye[aria-pressed="true"] { color: var(--ink); }
.group > summary .eye svg { display: block; width: 18px; height: 18px; }

/* A length someone typed, not one the drawing gave: marked, because the rest
   of the list is measurement and this one is a decision (§1, --good). */
.len { font-family: var(--font-mono); font-variant-numeric: tabular-nums; cursor: pointer;
       border-bottom: 1px dashed var(--line2); padding: 1px 4px; }
.len:hover { border-bottom-color: var(--ink); }
.len.empty { color: var(--bad-text); }
.len.mine { color: var(--good-text); font-weight: 600; }
.struck .name, .struck .len { text-decoration: line-through; color: var(--ink3); }

/* A line's own stroke, so a person ruling on it is not told everything about
   it except what it was drawn in. */
.stroke { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; font-size: 12px; color: var(--ink2); }
.stroke i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; border: 1px solid var(--line2); }
/* Brief 10: does removing this line alone change any room's polygon? */
.effect { font-size: 12px; font-weight: 600; }
.effect.none { color: var(--good-text); font-weight: 400; }
.effect.moves { color: var(--ink2); }
.effect.opens, .effect.refuses { color: var(--bad-text); }

/* The sheet inventory (brief 22): a table a person reads, on --surface. */
.tbl { border-collapse: collapse; font-size: 13px; width: 100%; }
.tbl th, .tbl td { text-align: left; padding: 4px 10px 4px 0; vertical-align: top; border-bottom: 1px solid var(--line); }
.tbl th { font: 600 11px/1 var(--font-ui); letter-spacing: .06em; text-transform: uppercase; color: var(--ink3); }
.tbl td.evidence { color: var(--ink2); font-size: 12px; }
.tbl tr.wall-source td { font-weight: 600; }

/* --- The drawing -------------------------------------------------------- */
[data-edge-mark], [data-wall-length], [data-seed-id], [data-promote-face] { cursor: pointer; pointer-events: all; }

/* --- Touch and fields (§5 "Touch sizes and fields") ---------------------
   Under a coarse pointer every interactive element gets a hit area of at least
   `--touch-target` (44 px), made from padding or an invisible hit box — never by drawing the
   glyph itself larger, so the desktop's own sizes (checked at 1440 with a fine pointer) do not
   move. Nothing here narrower than `--touch-target-min` (24 px) exists on either pointer type. */
@media (pointer: coarse) {
  .btn, .ib, .seg .seg-i, .rail-i, .inbox-pin, .inbox-where, .inbox-room-link, .len, .group > summary .eye, button.chip {
    min-height: var(--touch-target);
  }
  .ib, .rail-i.icon-only, .inbox-pin, .group > summary .eye {
    min-width: var(--touch-target);
  }
  .btn.dense, .ib.dense { min-height: var(--touch-target); min-width: var(--touch-target); }
  /* `.rail-i.icon-only` and `.rail-i.foot` (specificity 0,2,0) each set their own smaller
     `min-height` above, which beats the bare `.rail-i` rule just above whatever its source
     order — restated here at the same specificity so 44 px wins. */
  .rail-i.icon-only, .rail-i.foot { min-height: var(--touch-target); }
  .seg-thumb { height: var(--touch-target); }
  .group > summary .eye { display: inline-flex; align-items: center; justify-content: center; }
  /* `.len` and `.inbox-where` are inline/inline-text by default (min-height has no effect on an
     inline box, and `.inbox-where`'s text can wrap to two lines and still fall short of 44 px);
     both grow on the block axis and centre their text rather than becoming a taller row. */
  .len { display: inline-flex; align-items: center; }
  .inbox-where { display: flex; align-items: center; }
  /* A native checkbox/radio cannot be padded without inflating the box it draws, so the hit
     area comes from the label that already wraps it (`inspector.ts`'s "only rejected" row) —
     the checkbox itself stays the platform's own size, excluded below from the generic `input`
     rule for exactly that reason. */
  label:has(> input[type="checkbox"]), label:has(> input[type="radio"]) {
    min-height: var(--touch-target);
  }

  /* Every text field and select is at least 16 px, so focus never zooms the page (WCAG 1.4.4);
     density stays in the padding, not the text. A checkbox/radio is excluded — see the label
     rule above — since giving the input itself a min-height would inflate its own drawn box. */
  .fld, select, textarea, input:not([type="checkbox"]):not([type="radio"]) {
    font-size: var(--field-font-touch);
    min-height: var(--touch-target);
  }
  .fld.w-num { min-height: var(--touch-target); }
}

/* --- Breakpoints (§5) -----------------------------------------------------
   The three widths below are `design/tokens.css`'s `--bp-wide` (1280), `--bp-drawer` (1024)
   and `--bp-tablet` (768) — a `@media` rule cannot read a custom property, so the pixel value
   is still written here, but it is always one of these three (MOB-24: never a per-screen
   literal). Below `--bp-tablet` is the phone layout (DESIGN.md §5 "Phones (< 768 px)"), built
   by the phone stories on top of this file rather than in it. */
@media (max-width: 1279px) {
  .panel { width: 340px; }
  .toolbar { max-width: calc(100% - 560px); }
  /* Under 1280 the secondary actions keep their icons and drop their labels:
     at 1024 they were eating the project's own name, and the name is the one
     thing in the bar a person needs. `#view-name` is excluded above (US-13:
     the current view is a required control at every width, not only ≥1280). */
  .topbar .lbl { display: none; }
  /* The left zone is 390 px under 1280 (540 above). */
  .zone-l { flex-basis: 390px; }
  #proj-name { min-width: 180px; }
}
/* One row from 768 px up (US-48, card #1803). UI-172 round 9 had split the bar in two at ≥1025 px
   because eleven controls did not fit a line; theme, language, fit, the evidence chips, the build
   stamp, the run link and «Этажи» are in the menus now, so what is left (the zones above) fits one
   line at every width from 768 — and `topbar-height.ts` still measures the bar's real height, so a
   label that did wrap would still not put anything under the bar. */
@media (min-width: 768px) {
  :root { --topbar-h: 56px; }
  .topbar { flex-wrap: nowrap; }
}
/* The plan download is a glass button with its words from 1024 px up, an icon with its name below. */
.zone-r #download-link { gap: 6px; }
@media (max-width: 1023px) {
  .zone-r #download-link .dl-lbl { display: none; }
  .zone-r #download-link { padding: 0; width: var(--touch-target); min-width: var(--touch-target); justify-content: center; }
}
/* A toolbar with no control in it is not drawn (US-48): the view's name is the header's, so a view
   with nothing of its own (3D of one level, the spec, the furnishing) has an empty pill otherwise.
   The retrace's own progress line keeps it while it runs. */
.app:not(.retracing) .toolbar:not(:has(.toolbar-row :is(button, input, a, .seg))):not(:has(.toolbar-hint:not([hidden]))) { display: none; }
/* At 1024 and under the right panel is a bottom drawer: a 56 px peek, drag or
   tap to open, snap points at 40 % and 85 % (§5, "a tap on the grip cycles
   peek -> 40 % -> 85 %"). The snap is the drawer's own HEIGHT, not a transform
   over a box fixed at 85 %: MOB-6 (US-19) measured that a fixed-85dvh box
   translated down to show only its top 40 % leaves the list's OWN scroll area
   sized for the full 85 %, so content that fits within 85dvh never overflows
   that box and the part below the visible 40dvh is simply off-screen and
   unreachable — no swipe brings it back, because nothing there is actually
   scrolled. Sizing `.panel` itself to the visible fraction makes `.pbody`
   (`flex: 1; overflow-y: auto`, above) compute its real available room from
   the SAME number the drawer is showing on screen, so it overflows and
   scrolls exactly when the drawer is too short to show everything — which is
   every open position on a phone with 3 or more questions. */
@media (max-width: 1024px) {
  /* The drawer is a bottom surface (§5 "Safe areas"): its content keeps clear of the home
     indicator and, in landscape, of the side notch. */
  .panel {
    top: auto; width: auto;
    left: calc(12px + env(safe-area-inset-left)); right: calc(12px + env(safe-area-inset-right));
    bottom: calc(12px + env(safe-area-inset-bottom));
    border-radius: var(--r-sheet); transform-origin: center bottom;
    opacity: 1; pointer-events: auto; transform: none; height: var(--panel-peek, 56px);
    transition: height var(--d-panel) var(--spring);
  }
  .panel.open { height: var(--panel-peek, 56px); }
  .panel.snap40 { height: 40dvh; }
  .panel.snap85 { height: 85dvh; }
  /* The open drawer never reaches under the top bar: its grip and header are how a person closes
     it. At 85dvh on a phone it ran under the three-row bar (#1400, 152 px) and could no longer be
     dragged or tapped down (owner's screenshot, 2026-10-05, after «Применить ко всем»). The cap
     is the bar's own height plus both insets, so it follows any later change to the bar. A drag
     sets an inline height; this cap holds over it too. */
  .panel {
    max-height: calc(100dvh - var(--topbar-h) - 12px - env(safe-area-inset-top) - 8px - 12px - env(safe-area-inset-bottom));
  }
  .panel.drag { transition: none; }
  .dhandle {
    display: flex; flex-direction: column; align-items: stretch; gap: 8px;
    padding: 8px 16px 4px; flex: 0 0 auto; cursor: grab; touch-action: none;
  }
  .dhandle .grip { width: 40px; height: 5px; border-radius: var(--r-pill); background: var(--line2); align-self: center; }
  .dhandle .peek { min-height: 34px; display: flex; align-items: center; gap: 10px; }
  .pstate { top: var(--panel-peek, 56px); }
  /* The drawer's own peek row already carries the title and the count; the
     panel's head would say both again. Its second line stays. Scoped to `.panel` (the
     workspace's own drawer) alone: the run screen's transcript panel (`.readpanel`, web.css)
     loads this same stylesheet and the same `.phead` markup but is not a drawer and has no
     peek — this rule hid its heading row and its turn-switch control too until MOB-4. */
  .panel .phead > .row:first-child { display: none; }
  .pill { display: none; }
  .toolbar { max-width: calc(100% - 140px); }
  .accept { width: calc(100% - 140px); }

  /* UI-172, US-13: the header itself has to fit in the same ≤1024px band the drawer already
     narrows for. The product mark drops its own text (round 2) and, since round 3, gives way
     to the trail's "‹ Projects" crumb. US-48: the level and room pickers are the compact menu button
     (`mountPicker`) and the bar is still one row. */
  .topbar { gap: 8px; }
  .zone-l { gap: 8px; flex-basis: 340px; }
  .crumb { padding: 0 10px 0 6px; }
  .crumb-sep { margin: 0 -5px; }
  #home-link { display: none; }
  #proj-name { min-width: 96px; }
  #levelseg, #roomseg { flex: 0 1 auto; min-width: var(--touch-target-min); }
  #levelseg, #roomseg { max-width: 160px; }
  #levelseg > .btn, #roomseg > .btn { max-width: 100%; }
}
/* 1024 to 1279: both pickers are menu buttons (about 210 px for the two), so the left zone can be 480 px
   and keep the project's name beside the longest view name («ТЗ на мебель») without covering the level
   menu; the product mark's words are already gone (`.lbl`) and the plan download keeps its words. */
@media (min-width: 1024px) and (max-width: 1279px) {
  .zone-l { flex-basis: 480px; }
  #proj-name { min-width: 140px; }
}

/* UI-178: the scheme editor. The action sheet floats over the stage's foot, a finger's reach. */
.editor-tool { display: inline-flex; align-items: center; gap: 6px; }
.editor-tool svg { flex: none; }
.editor-tool[disabled] { opacity: 0.45; cursor: default; }
.editor-sheet { position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%); z-index: 40; padding: 8px; border-radius: 14px; flex-wrap: wrap; max-width: calc(100% - 24px); }
svg.editing [data-editor-wall] { cursor: pointer; }
svg.editing [data-area] { cursor: pointer; }
svg.editing .editor-handle { cursor: move; }
.editor-sheet .editor-number { width: 84px; }
/* B3 (E10, E11): a room's own name/number (short text) and class (a select the width of its
 * longest option). */
.editor-sheet .editor-text { width: 108px; }
.editor-sheet .editor-select { max-width: 140px; }
@media (pointer: coarse) {
  .editor-tool { min-width: 44px; min-height: 44px; }
  .editor-sheet .editor-number,
  .editor-sheet .editor-text,
  .editor-sheet .editor-select { min-height: 44px; }
}

/* UI-188 (lane B5): the «Этажи» overlay — two floors' outer rings, confirmed or dragged into
   place. Sits over everything, including the scheme editor's own action sheet. */
.levels-backdrop {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
}
.levels-backdrop::before {
  content: ""; position: absolute; inset: 0; background: var(--ink); opacity: .45;
}
.levels-backdrop[hidden] { display: none; }
.levels-panel {
  position: relative; background: var(--surface); color: var(--ink); border-radius: var(--r-ctl); box-shadow: var(--sh-float);
  width: min(920px, calc(100vw - 32px)); max-height: calc(100dvh - 32px); display: flex; flex-direction: column;
  padding: 16px;
}
.levels-panel-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.levels-panel-head .t-15 { flex: 1; }
.levels-stage { position: relative; background: var(--sunken); border-radius: 10px; overflow: hidden; }
.levels-stage svg { display: block; width: 100%; height: 420px; }
.levels-ref { fill: var(--ink2); fill-opacity: 0.28; stroke: var(--ink); stroke-width: 2; }
.levels-tgt { fill: var(--sys); stroke: var(--sys-text); stroke-width: 2; cursor: move; }
.levels-tgt-handle { fill: var(--surface); stroke: var(--sys-text); stroke-width: 2; cursor: grab; }
.levels-controls { display: flex; align-items: center; gap: 12px; margin-top: 10px; flex-wrap: wrap; }
.levels-controls input[type="range"] { flex: 1; min-width: 120px; }
/* US-30 gap 4 (card #706): move/turn the upper floor with a single click, no drag. */
.levels-nudge { display: flex; align-items: center; gap: 4px; }
.levels-note { font-size: 12px; color: var(--ink2); margin-top: 6px; }
#levels-verdict { font-size: 12px; color: var(--ink2); margin-top: 2px; min-height: 1.4em; }
#levels-hint { font-size: 12px; color: var(--ink2); margin-top: 2px; }
@media (pointer: coarse) {
  .levels-tgt-handle { r: 14; }
}

@media (max-width: 1024px) and (pointer: coarse) {
  #levelseg, #roomseg { min-width: var(--touch-target); }
}
/* Card #1805 (US-48, the phone): under 768 px the header is ONE 52 px row, «‹ | title | search | bell | avatar» (search: #1874),
   where the title is two lines: the project's name with its floor and ▾ (the button of the project menu,
   which also holds the floor and the room switches and «Скачать план» on a phone), and under it the view's
   name and the current room. It replaces the three-row header of #1399 (a trail, then the view with the two
   switches, then the download and the evidence): everything that was in those rows is in the project menu
   now (`project-menu.ts`) or the account menu, so a phone shows the drawing, not three rows of controls
   (owner, 2026-10-09: "maximum use of space; icons, not button text").
   The grid is the bar itself: the zones and the trail are not boxes here (`display: contents`), so the back
   control, the title, the bell and the avatar are its four cells. The title's box is the one 52 px target of
   the project menu; its second line is text laid over the lower half of that same box (`pointer-events:
   none`), so there is one control, not two stacked 26 px ones. The level and room switches, the product mark
   and the plan download are not drawn in the bar (the project menu has them), and the left rail is gone with
   them: the circle at the bottom left is the places' switch (`phone-shell.ts`, `phone-shell.css`). */
@media (max-width: 767px) {
  :root { --topbar-h: 52px; }
  .zone, .crumbs { display: contents; }
  .topbar {
    display: grid; grid-template-columns: var(--touch-target) minmax(0, 1fr) var(--touch-target) var(--touch-target) var(--touch-target) var(--touch-target);
    align-items: center; column-gap: 2px; padding: 0 4px;
  }
  #home-link, #crumb-sep-1, #levelseg, #roomseg, #download-link { display: none; }
  /* The way back: the chevron alone, 44 x 44; its word is its accessible name («Все объекты»). */
  .crumb { padding: 0; min-width: var(--touch-target); width: var(--touch-target); height: var(--touch-target); justify-content: center; }
  .crumb > .ellipsis {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  /* The title: the project's button over the whole 52 px, its text on the first line; the view line sits on the second. */
  .trail-title { display: block; position: relative; min-width: 0; height: 52px; }
  #proj-name {
    position: absolute; inset: 0; width: 100%; height: 100%; min-width: 0; max-width: none; margin: 0;
    align-items: flex-start; padding: 6px 8px 0; gap: 4px; font-size: 14px; line-height: 20px;
  }
  /* The floor keeps its whole word («1-й поверх»); the project's name, which is longer, gives way first and the ▾ stays. */
  .proj-floor { display: inline; flex: 0 0 auto; white-space: nowrap; }
  .proj-floor[hidden] { display: none; }
  .proj-name-text { flex: 0 1 auto; min-width: 5ch; }
  .trail-sub {
    position: absolute; left: 8px; right: 8px; bottom: 6px; display: flex; align-items: baseline; gap: 4px; min-width: 0;
    font-size: 12px; line-height: 16px; color: var(--ink2); pointer-events: none;
  }
  #crumb-sep-2 { display: none; }
  #view-name { font-size: 12px; line-height: 16px; max-width: none; color: var(--ink2); flex: 0 0 auto; }
  .view-room:not([hidden]) { display: inline; flex: 0 1 auto; min-width: 0; }
  /* The bell and the avatar: 44 x 44 on every pointer, since there is no other control to give way to. */
  .notify-btn { width: var(--touch-target); height: var(--touch-target); }
  .avatar-btn { width: var(--touch-target); height: var(--touch-target); }
  /* UI-172: the level/room pickers' own menu (`mountPicker`, `overlays.ts`) must stay on the screen. */
  .pop { width: calc(100vw - 40px); max-width: 300px; }
  .pop.under { right: 8px; }
  /* F-QA-UI-172.2: `grid-auto-columns: 1fr` alone sizes each track's automatic minimum from its item's own
     max-content width, so a nowrap `.seg-i` whose label is the widest of a row (the home screen's own
     upload-mode picker, web/client/home.ts) forced the whole track past its container at 375 px: 152 px of
     sideways scroll on the project list. Scoped to this phone width, not global: at 1440/1200/1024 the label
     already fits. `min-width: 0` lets the track shrink to the space actually available. */
  .seg { min-width: 0; }
  .seg .seg-i { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  /* The rail is the circle now. Its markup stays (the views are still its `a` items, hidden), so a script that
     looks for them finds the same set; a person reaches them through the circle. */
  .rail { display: none; }
}

/* --- The furnishing step (UI-964): rooms, needs, variants, configurator, chat ---------------
   Tokens only (`design/tokens.css`); the origin marks keep the review screen's own meaning:
   amber dashed = proposed by the system, green = yours, plain = from the drawing. */
.fillview[data-view="furnish"] { background: var(--sunken); overflow: auto; }
.furnish { display: flex; flex-direction: column; gap: 14px; padding: 20px 28px 32px; max-width: 1280px; margin: 0 auto; }
.furnish h1, .furnish h2 { margin: 0; }
.furnish-head { align-items: center; }
.furnish-head .spacer { flex: 1; }
.furnish-note { padding: 8px 12px; border-radius: var(--r-ctl); background: var(--sys-bg); color: var(--sys-text); box-shadow: inset 0 0 0 1px var(--sys-line); }
.furnish-note.bad { background: var(--bad-bg); color: var(--bad-text); box-shadow: inset 0 0 0 1px var(--bad-line); }
.furnish-understood { padding: 8px 12px; border-radius: var(--r-ctl); background: var(--good-bg); color: var(--good-text); box-shadow: inset 0 0 0 1px var(--good-line); }
.furnish-rooms .furnish-room { padding: 12px 16px; background: var(--surface); border-radius: var(--r-card); box-shadow: inset 0 0 0 1px var(--line); }
.furnish-needs { background: var(--surface); border-radius: var(--r-card); padding: 14px 16px; box-shadow: inset 0 0 0 1px var(--line); }
.furnish-ticks { display: flex; flex-wrap: wrap; gap: 6px 16px; }
.furnish-tick { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.furnish-tick input { width: 16px; height: 16px; accent-color: var(--sys); }
.furnish-words, .furnish-chat-input, .furnish-width, .furnish-select, .furnish-model {
  font: inherit; color: var(--ink); background: var(--surface); border: 0; border-radius: var(--r-ctl);
  box-shadow: inset 0 0 0 1px var(--line2); padding: 8px 10px; min-width: 0;
}
.furnish-words { width: 100%; resize: vertical; box-sizing: border-box; }
.furnish-words:focus, .furnish-chat-input:focus, .furnish-width:focus, .furnish-select:focus, .furnish-model:focus { outline: 2px solid var(--sys); outline-offset: 1px; }
.furnish-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; }
.furnish-card { background: var(--surface); border-radius: var(--r-card); padding: 14px; box-shadow: inset 0 0 0 1px var(--line); }
.furnish-card.chosen { box-shadow: inset 0 0 0 2px var(--sys); }
.furnish-letter { display: inline-grid; place-items: center; width: 24px; height: 24px; border-radius: 8px; background: var(--ink); color: var(--surface); font-size: 13px; font-weight: 600; }
.furnish-plan { width: 100%; height: 200px; background: var(--sunken); border-radius: var(--r-ctl); }
.furnish-plan-w { pointer-events: none; font-variant-numeric: tabular-nums; }
.furnish-check.good { color: var(--good-text); }
.furnish-check.bad { color: var(--bad-text); }
/* F-962.4: a need the layout left out — said plainly, not coloured as a defect. */
.furnish-check.unmet { color: var(--ink2); font-style: italic; }
.furnish-glyph { flex: 0 0 auto; display: inline-flex; width: 14px; justify-content: center; }
.furnish-config { background: var(--surface); border-radius: var(--r-card); padding: 14px 16px; box-shadow: inset 0 0 0 1px var(--line); }
.furnish-cells { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 6px; }
.furnish-cell { flex: 0 0 148px; padding: 8px; border-radius: var(--r-ctl); background: var(--sunken); cursor: grab; }
.furnish-cell.filler { cursor: default; opacity: .8; }
.furnish-cell .furnish-width { width: 76px; }
.furnish-cell .furnish-select { width: 100%; padding: 5px 6px; }
.furnish-cell .furnish-model { width: 100%; padding: 5px 6px; }
.furnish-origin { display: inline-flex; align-items: center; gap: 5px; }
.furnish-origin.sys { color: var(--sys-text); }
.furnish-origin.good { color: var(--good-text); }
.furnish-icon { width: 30px; height: 30px; display: inline-grid; place-items: center; border: 0; border-radius: 8px; background: var(--surface); color: var(--ink2); cursor: pointer; font-size: 15px; }
.furnish-icon:hover:not(:disabled) { background: var(--sys-bg); color: var(--sys-text); }
.furnish-icon:disabled { opacity: .35; cursor: default; }
.furnish-unmeasured { background: var(--sys-bg); color: var(--sys-text); box-shadow: inset 0 0 0 1px var(--sys-line); }
.furnish-composer { position: sticky; bottom: 0; padding: 10px 0 2px; background: linear-gradient(to top, var(--sunken) 70%, transparent); }
.furnish-composer .furnish-chat-input { flex: 1; }
@media (max-width: 640px) {
  .furnish { padding: 14px 16px 24px; }
  .furnish-cards { grid-template-columns: 1fr; }
}
/* #1357: «Мебель» before «План верен» — what is open and the way to it. */
.furnish-gate { background: var(--surface); border-radius: var(--r-card); padding: 18px 20px; box-shadow: inset 0 0 0 1px var(--line); max-width: 720px; }
.furnish-gate p { margin: 0; }
/* #1357: the furniture editor — the scheme (or a wall's elevation) on the left, the panels beside it.
   Module tones keep the screen's own meaning: amber dashed = proposed, green = yours, slate = from the drawing. */
.furnish { max-width: 1480px; }
.furnish-editor-grid { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 18px; align-items: start; }
.furnish-main, .furnish-side { min-width: 0; }
.furnish-side .furnish-cards { grid-template-columns: 1fr; }
.furnish-side .furnish-plan { height: 150px; }
.furnish-card.on-stage { box-shadow: inset 0 0 0 2px var(--ink3); }
.furnish-card.chosen.on-stage { box-shadow: inset 0 0 0 2px var(--sys); }
.furnish-letter.sheet { background: var(--plan-stroke); }
/* UI-977 (US-51): the layouts as thumbnails (a tap chooses), the wishes as tap chips, the details folded. */
.furnish-thumbs { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(150px, 200px); gap: 10px; overflow-x: auto; padding: 2px 2px 6px; scroll-snap-type: x proximity; }
.furnish-thumb {
  display: flex; flex-direction: column; gap: 6px; align-items: stretch; text-align: left; font: inherit; color: var(--ink);
  background: var(--surface); border: 0; border-radius: var(--r-card); padding: 10px; cursor: pointer; scroll-snap-align: start;
  box-shadow: inset 0 0 0 1px var(--line); min-height: 44px;
}
.furnish-thumb .furnish-plan { height: 110px; pointer-events: none; }
.furnish-thumb.on-stage { box-shadow: inset 0 0 0 2px var(--ink3); }
.furnish-thumb.chosen { box-shadow: inset 0 0 0 2px var(--sys); background: var(--sys-bg); }
.furnish-thumb:focus-visible { outline: 2px solid var(--sys); outline-offset: 2px; }
.furnish-thumb-name { font-weight: 600; }
.furnish-thumb-fit { display: inline-flex; gap: 5px; align-items: flex-start; }
.furnish-thumb-fit.good { color: var(--good-text); }
.furnish-thumb-fit.bad { color: var(--bad-text); }
.furnish-thumb-fit.unmet, .furnish-thumb-fit.dim { color: var(--ink2); }
.furnish-wishes { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.furnish-wish {
  display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 14px 0 10px; font: inherit; font-size: 14px;
  color: var(--ink); background: var(--surface); border: 0; border-radius: 999px; box-shadow: inset 0 0 0 1px var(--line2); cursor: pointer;
}
.furnish-wish.on { background: var(--sys-bg); color: var(--sys-text); box-shadow: inset 0 0 0 1px var(--sys-line); font-weight: 600; }
.furnish-wish-ico { display: inline-flex; }
.furnish-why summary { cursor: pointer; min-height: 44px; display: flex; align-items: center; color: var(--ink2); }
.furnish-why summary::after { content: '▾'; margin-left: 8px; font-size: 12px; }
.furnish-why[open] summary::after { content: '▴'; }
.furnish-why-list { list-style: none; margin: 0; padding: 0 0 8px; }
.furnish-why-quote { margin: 0; padding: 4px 10px; border-left: 3px solid var(--line); font-style: italic; overflow-wrap: anywhere; }
.furnish-why li { overflow-wrap: anywhere; }
.furnish-details summary { cursor: pointer; min-height: 32px; display: flex; align-items: center; color: var(--ink2); }
/* UI-978 (US-51): a selected module's toolbar — big buttons, the width between «−» and «+». */
.furnish-toolbar { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 6px; border-radius: var(--r-card); background: var(--surface); box-shadow: inset 0 0 0 1px var(--line); width: fit-content; max-width: 100%; }
.furnish-tool { width: 44px; height: 44px; display: inline-grid; place-items: center; border: 0; border-radius: 12px; background: var(--sunken); color: var(--ink); font: inherit; font-size: 20px; cursor: pointer; }
.furnish-tool:hover:not(:disabled) { background: var(--sys-bg); color: var(--sys-text); }
.furnish-tool:disabled { opacity: .35; cursor: default; }
.furnish-tool[data-tool="remove"] { color: var(--bad-text); }
.furnish-tool-width { min-width: 56px; text-align: center; font-weight: 600; }
/* «Внутри» says its word beside its picture (owner, 2026-10-19: the icon alone was not found). */
.furnish-tool[data-tool="inside"] { width: auto; padding: 0 12px; display: inline-flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 600; }
/* How the 3D view is moved, top right over it, always there (owner, 2026-10-19); folded it is a «?». */
.furnish-3d-help { position: absolute; top: 8px; right: 8px; z-index: 2; max-width: min(300px, calc(100% - 16px)); padding: 6px 4px 8px 12px; border-radius: 12px; color: var(--ink); background: color-mix(in srgb, var(--surface) 90%, transparent); box-shadow: inset 0 0 0 1px var(--line), var(--sh-float); }
.furnish-3d-help.folded { padding: 0; background: none; box-shadow: none; }
.furnish-3d-help-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.furnish-3d-help-title { color: var(--ink2); font-weight: 600; }
.furnish-3d-help-fold { width: var(--touch-target); height: var(--touch-target); margin: -6px -6px -6px 0; border: 0; border-radius: 8px; background: none; color: var(--ink2); font: inherit; font-size: 18px; cursor: pointer; }
.furnish-3d-help-fold:hover { background: var(--sunken); color: var(--ink); }
.furnish-3d-help-list { margin: 0; padding: 0 8px 0 0; list-style: none; display: grid; gap: 3px; line-height: 1.3; }
.furnish-3d-help-list b { font-weight: 600; }
.furnish-3d-help-open { min-width: var(--touch-target); min-height: var(--touch-target); padding: 0; font-size: 18px; font-weight: 700; }
/* On a phone the view is short: «Прогулка» is its own button there, so its line goes. */
@media (max-width: 767px) { .furnish-3d-help { max-width: min(220px, calc(100% - 16px)); } .furnish-3d-help-list [data-help="walk"] { display: none; } }
.furnish-tool-sep { width: 1px; height: 28px; background: var(--line2); margin: 0 4px; }
.furnish-stage-bar { align-items: center; }
.furnish-stage-bar .spacer { flex: 1; }
/* Card #2060: «Скачать» in a Quick start piece's stage bar and its list. */
.furnish-dl-btn, .furnish-dl-file, .furnish-dl-zip { min-height: var(--touch-target, 44px); min-width: var(--touch-target, 44px); }
.furnish-dl { padding: 10px 12px; border-radius: var(--r-ctl); background: var(--surface); box-shadow: inset 0 0 0 1px var(--line); }
/* «Файлы проекта» closes by its own «×» too; the lead line keeps clear of it. Room headings split one room's set from the next. */
.furnish-dl { position: relative; }
.furnish-dl-x { position: absolute; top: 2px; right: 2px; min-width: var(--touch-target); min-height: var(--touch-target); padding: 0; font-size: 22px; line-height: 1; }
.furnish-dl > p:first-of-type { padding-right: 40px; }
.furnish-dl-room { margin: 6px 0 0; color: var(--ink2); }
.furnish-dl-file { display: flex; align-items: center; gap: 10px; padding: 0 12px; border-radius: 10px; background: var(--sunken); text-decoration: none; color: inherit; overflow-wrap: anywhere; }
.furnish-dl-file .furnish-dl-ext { margin-left: auto; flex: none; }
.furnish-dl-zip { justify-content: center; }
.furnish-stage { position: relative; height: min(62dvh, 620px); min-height: 320px; background: var(--paper); border-radius: var(--r-card); box-shadow: inset 0 0 0 1px var(--line); overflow: hidden; }
.furnish-stage > svg { display: block; width: 100%; height: 100%; }
.furnish-stage.elevation { background: var(--surface); }
.furnish-stage-note { position: absolute; left: 12px; bottom: 10px; right: 12px; }
.furnish-stage-empty { padding: 40px 20px; background: var(--surface); border-radius: var(--r-card); box-shadow: inset 0 0 0 1px var(--line); }
.furnish-overlay .furnish-mod { cursor: pointer; fill-opacity: .72; }
.furnish-overlay .furnish-mod.proposed { fill: var(--sys-bg); stroke: var(--sys); stroke-dasharray: 60 36; }
.furnish-overlay .furnish-mod.user { fill: var(--good-bg); stroke: var(--good); }
.furnish-overlay .furnish-mod.source { fill: var(--sunken); stroke: var(--plan-stroke); }
.furnish-overlay .furnish-mod.filler { fill: var(--sunken); stroke: var(--ink3); cursor: default; }
.furnish-overlay .furnish-mod:hover { fill-opacity: .95; }
.furnish-overlay .furnish-mod.selected { fill-opacity: 1; stroke: var(--ink); stroke-dasharray: none; stroke-width: 2.5%; }
/* Owner, 2026-10-19: a module that cannot open (a drawer against the next run in a corner) is red until fixed. */
.furnish-overlay .furnish-mod.blocked { fill: var(--bad-bg); stroke: var(--bad); stroke-dasharray: none; }
.furnish-elev .furnish-overlay .furnish-mod.blocked { fill: var(--bad); fill-opacity: .14; stroke: var(--bad); stroke-width: 22; }
.furnish-blocked { align-items: center; }
.furnish-blocked-text { flex: 1; text-align: left; background: none; border: 0; padding: 0; color: inherit; font: inherit; cursor: pointer; }
.furnish-fix { min-height: 44px; flex: none; }
/* The elevation is the run's own print sheet (white in both themes, its own tints and captions):
   the overlay only outlines what can be pressed, so the sheet stays readable under it. */
.furnish-elev .furnish-overlay .furnish-mod { fill: var(--sys); fill-opacity: 0; stroke: none; }
.furnish-elev .furnish-overlay .furnish-mod:hover { fill-opacity: .12; }
.furnish-elev .furnish-overlay .furnish-mod.selected { fill-opacity: .16; stroke: var(--sys); stroke-width: 22; stroke-dasharray: none; }
.furnish-overlay .furnish-plan-w { fill: var(--ink); pointer-events: none; font-variant-numeric: tabular-nums; }
.furnish-wall-hit { stroke: var(--sys); stroke-opacity: 0; stroke-linecap: round; cursor: pointer; }
.furnish-wall-hit:hover { stroke-opacity: .35; }
.furnish-mod-own { cursor: pointer; }
.furnish-mod-own.selected { stroke: var(--ink); stroke-dasharray: none; }
.furnish-mod-own.blocked { fill: var(--bad-bg); stroke: var(--bad); stroke-dasharray: none; }
/* Card #2036 step 2: a module dragged by hand — the ghost where it will land, the line it sticks to. */
[data-draggable] { cursor: grab; touch-action: none; }
[data-zone-handle] { cursor: move; touch-action: none; }
.furnish-dragging [data-draggable] { cursor: grabbing; }
[data-draggable].drag-source { opacity: .3; }
.furnish-ghost { fill: var(--sys-bg); fill-opacity: .8; stroke: var(--sys); stroke-dasharray: none; }
.furnish-ghost.bad { fill: var(--bad-bg); stroke: var(--bad); }
.furnish-ghost.shift { fill: var(--sunken); fill-opacity: .6; stroke: var(--ink3); }
.furnish-snap-guide { stroke: var(--sys); stroke-linecap: round; }
/* Card #2036 step 3: the same ghost and guide drawn over the 3D model, in the viewer's pixels. */
.furnish-stage > svg.furnish-3d-overlay { position: absolute; inset: 0; pointer-events: none; }
.furnish-3d-overlay .furnish-ghost { fill-opacity: .28; stroke-linejoin: round; }
.furnish-3d-overlay .furnish-ghost.shift { fill-opacity: .22; }
.furnish-dragging .furnish-3d { cursor: grabbing; }
.furnish-drag-note { position: absolute; top: 8px; left: 8px; right: 8px; padding: 6px 10px; border-radius: 9px; background: var(--surface); box-shadow: inset 0 0 0 1px var(--line), var(--sh-float); pointer-events: none; }
.furnish-drag-note.bad { background: var(--bad-bg); color: var(--bad-text); box-shadow: inset 0 0 0 1px var(--bad-line); }
/* «Развёртка» (owner, 2026-10-19): the selected or dragged module's millimetres, the services on the wall, a module put in another's place. */
.furnish-elev-dims line { stroke: var(--sys); stroke-width: 6; stroke-linecap: round; }
.furnish-elev-dims line.main { stroke-width: 9; }
.furnish-elev-dim-text { fill: var(--sys); font-weight: 600; paint-order: stroke; stroke: var(--surface); stroke-width: 26; stroke-linejoin: round; font-variant-numeric: tabular-nums; }
.furnish-dragging .furnish-overlay > [data-layer="dims"] { display: none; }
.furnish-placing .furnish-mod.replaceable:hover { stroke: var(--sys); fill: var(--sys-bg); }
.furnish-placing { cursor: copy; }
.furnish-svc-hit { fill: transparent; stroke: transparent; stroke-width: 14; cursor: pointer; }
.furnish-svc-hit:hover, .furnish-svc-hit.selected { stroke: var(--sys); fill: var(--sys-bg); fill-opacity: .5; }
.furnish-svc-hit.draft { stroke: var(--sys); stroke-dasharray: 30 18; fill: var(--sys-bg); fill-opacity: .5; pointer-events: none; }
.furnish-svc-float { position: absolute; right: 8px; bottom: 8px; z-index: 4; max-width: calc(100% - 16px); display: flex; flex-direction: column; gap: 8px; padding: 8px 12px 12px; border-radius: 16px; background: color-mix(in srgb, var(--surface) 96%, transparent); box-shadow: inset 0 0 0 1px var(--line), var(--sh-float); }
.furnish-svc-float.top { top: 8px; bottom: auto; }
.furnish-svc-float .spacer { flex: 1; }
.furnish-svc-color { min-width: var(--touch-target, 44px); padding: 0; justify-content: center; }
.furnish-svc-color.on { box-shadow: inset 0 0 0 2px var(--brand-text, var(--sys-text)); }
.furnish-svc-swatch { width: 22px; height: 22px; border-radius: 6px; border: 1px solid var(--ink2); box-sizing: border-box; }
.furnish-svc-fields { align-items: flex-end; }
.furnish-svc-field input { width: 104px; min-height: var(--touch-target, 44px); }
.furnish-svc-float .btn { min-height: var(--touch-target, 44px); }
.furnish-svc-float .furnish-look-close { width: var(--touch-target, 44px); height: var(--touch-target, 44px); margin: -6px -8px -6px 0; border: 0; border-radius: 10px; background: none; color: var(--ink2); display: inline-flex; align-items: center; justify-content: center; cursor: pointer; }
@media (max-width: 599px) {
  .furnish-svc-float { left: 8px; gap: 4px; padding: 6px 10px 10px; }
  .furnish-svc-fields { gap: 6px; flex-wrap: nowrap; overflow-x: auto; }
  .furnish-svc-field input { width: 66px; }
}
.furnish-inspector { background: var(--surface); border-radius: var(--r-card); padding: 12px 14px; box-shadow: inset 0 0 0 1px var(--line); }
.furnish-inspector .spacer { flex: 1; }
.furnish-cell.wide { flex: none; display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px; align-items: center; cursor: default; }
.furnish-cell.selected { box-shadow: inset 0 0 0 2px var(--ink); }
.furnish-config > summary, .furnish-needs-fold > summary { cursor: pointer; }
.furnish-config[open] > summary { margin-bottom: 10px; }
.furnish-needs-fold { background: var(--surface); border-radius: var(--r-card); padding: 12px 14px; box-shadow: inset 0 0 0 1px var(--line); }
.furnish-needs-fold[open] > summary { margin-bottom: 10px; }
.furnish-needs-fold .furnish-needs { padding: 0; box-shadow: none; }
@media (max-width: 1024px) {
  .furnish-editor-grid { grid-template-columns: 1fr; }
  .furnish-stage { height: 52dvh; }
}

/* #1651: the room screen as the v2 boards draw it (`design/mockups-v2`, `VARIANTS` and `SPEC`):
   the variants side by side as cards with a well for the plan and one full-width action, the
   runs as rows of cells with the sum line on the right, one composer pill floating at the bottom.
   The plan's zoom column belongs to the drawing views, not to this page. */
body:has(.fillview[data-view="furnish"].on) .zoom-controls { display: none; }
.furnish { padding-bottom: 0; }
.furnish-head h1 { font-weight: 600; }
.furnish-need-chips { align-items: center; }
.furnish-cards { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; align-items: stretch; }
.furnish-card { padding: 16px; }
.furnish-card.recommended { box-shadow: inset 0 0 0 1.5px var(--ink); }
.furnish-card.on-stage { box-shadow: inset 0 0 0 2px var(--ink3); }
.furnish-card.recommended.on-stage { box-shadow: inset 0 0 0 2px var(--ink); }
.furnish-card.chosen, .furnish-card.chosen.on-stage { box-shadow: inset 0 0 0 2px var(--sys); }
.furnish-well { display: block; width: 100%; padding: 8px; border: 0; border-radius: var(--r-ctl); background: var(--sunken); cursor: pointer; font: inherit; color: inherit; }
.furnish-well[aria-pressed="true"] { cursor: default; box-shadow: inset 0 0 0 1px var(--line2); }
.furnish-well:focus-visible { outline: 2px solid var(--focus, var(--sys)); outline-offset: 2px; }
.furnish-well .furnish-plan { height: 190px; background: transparent; display: block; }
.furnish-choose { justify-content: center; width: 100%; }
.furnish-work { background: var(--surface); border-radius: var(--r-card); padding: 16px; box-shadow: inset 0 0 0 1px var(--line); }
.furnish-work h2, .furnish-config h2 { font-weight: 600; }
.furnish-work .furnish-editor-grid { grid-template-columns: minmax(0, 1fr) 360px; }
.furnish-work .furnish-inspector { background: var(--sunken); box-shadow: none; }
.furnish-work .furnish-inspector .furnish-cell { background: var(--surface); }
.furnish-config { padding: 16px 20px; }
.furnish-config .spacer { flex: 1; }
.furnish-spec-cells { display: grid; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 8px; }
.furnish-spec-cell {
  position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 2px; min-height: 86px;
  padding: 8px 10px; border: 0; border-radius: var(--r-ctl); background: var(--sunken); color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--line); font: inherit; text-align: left; cursor: pointer;
}
.furnish-spec-cell:hover:not(:disabled) { box-shadow: inset 0 0 0 1px var(--line2); }
.furnish-spec-cell.selected { box-shadow: inset 0 0 0 2px var(--ink); }
.furnish-spec-cell.filler { opacity: .75; cursor: default; background: transparent; box-shadow: inset 0 0 0 1px var(--line); border: 0; }
.furnish-spec-cell:disabled { color: var(--ink); }
.furnish-spec-cell:focus-visible { outline: 2px solid var(--focus, var(--sys)); outline-offset: 2px; }
.furnish-spec-mark { position: absolute; top: 8px; right: 8px; display: inline-flex; }
.furnish-composer {
  position: sticky; bottom: 0; z-index: 2; align-items: center; padding: 14px 0 16px;
  background: linear-gradient(to top, var(--sunken) 55%, transparent);
}
.furnish-composer > * { width: min(680px, 100%); }
.furnish-pill {
  align-items: center; padding: 6px 6px 6px 14px; border-radius: var(--r-pill); background: var(--surface);
  box-shadow: 0 0 0 1px var(--line2), var(--sh-float);
}
.furnish-pill .furnish-chat-input { flex: 1; box-shadow: none; background: transparent; padding: 8px 4px; }
.furnish-pill .furnish-chat-input:focus { outline: none; }
.furnish-pill:focus-within { box-shadow: 0 0 0 2px var(--sys), var(--sh-float); }
.furnish-pill .btn { border-radius: var(--r-pill); }
.furnish-spark { display: inline-flex; color: var(--sys-text); }
.furnish-said { padding: 8px 12px; border-radius: var(--r-ctl); background: var(--surface); box-shadow: inset 0 0 0 1px var(--line); }
@media (max-width: 1024px) {
  .furnish-work .furnish-editor-grid { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
  .furnish-head h1 { font-size: 20px; flex-basis: 100%; }
  .furnish-work, .furnish-config { padding: 12px; }
}

/* --- The avatar menu (card #1366) -----------------------------------------
   A round button at the end of the header's first row — the Google picture, or the initial — and
   a small menu under it. 32 px under a fine pointer, the 44 px touch floor under a coarse one
   (§5); in the workspace's bar it carries no `order`, so it sits on row one and never on row two. */
.avatar { position: relative; flex: 0 0 auto; order: 0; margin-left: auto; }
.topbar .avatar { margin-left: 0; }
.avatar[hidden] { display: none; }
.avatar-btn {
  position: relative; display: inline-grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 0;
  border-radius: var(--r-pill); background: var(--act, var(--ink)); color: var(--on-act, var(--on-ink)); cursor: pointer;
  font: 600 14px/1 var(--font-ui); overflow: hidden;
  transition: box-shadow var(--d-micro) var(--ease-out);
}
.avatar-btn:hover { box-shadow: 0 0 0 3px var(--glass-edge); }
.avatar-initial, .avatar-img { grid-area: 1 / 1; }
.avatar-img { width: 100%; height: 100%; object-fit: cover; border-radius: var(--r-pill); }
@media (pointer: coarse) {
  .avatar-btn { width: var(--touch-target); height: var(--touch-target); }
}
/* US-48: the menu hangs from <body> and is placed under the avatar by `menu-pos.ts`, so it is true
   glass (`.glass-strong`) over the drawing, 280 px wide, radius 18, as the mockups draw it. */
.avatar-menu {
  position: fixed; z-index: 45; width: min(280px, calc(100vw - 16px)); padding: 8px; border-radius: 18px;
  display: flex; flex-direction: column; gap: 4px; overflow-y: auto;
}
.avatar-menu[hidden] { display: none; }
.avatar-who { padding: 6px 10px 8px; min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
.avatar-name { font: 600 14px/1.3 var(--font-ui); color: var(--ink); overflow-wrap: anywhere; }
.avatar-mail { font: 400 12px/1.3 var(--font-ui); color: var(--ink2); overflow-wrap: anywhere; }
.avatar-tier { font: 400 13px/1.3 var(--font-ui); color: var(--ink2); overflow-wrap: anywhere; }
.avatar-item {
  display: flex; align-items: center; min-height: var(--touch-target); padding: 0 10px; border-radius: var(--r-ctl);
  color: var(--ink); text-decoration: none; font: 500 14px/1.2 var(--font-ui);
  width: 100%; border: 0; background: transparent; text-align: left; cursor: pointer;
}
.avatar-item:hover, .avatar-item:focus-visible { background: var(--sunken); }
/* Card #1752: an icon before each item's words, and the light / dark / system switch as one track. */
.avatar-item { gap: 10px; }
.avatar-item > svg { flex: none; color: var(--ink2); }
.avatar-theme { display: flex; gap: 2px; padding: 3px; margin: 0; background: var(--sunken); border-radius: 12px; }
.avatar-theme-opt {
  flex: 1 1 0; display: flex; align-items: center; justify-content: center; min-height: var(--touch-target); border: 0; cursor: pointer;
  border-radius: 9px; background: transparent; color: var(--ink2); font: 500 14px/1 var(--font-ui);
}
.avatar-theme-opt:hover { color: var(--ink); }
.avatar-theme-opt[aria-checked="true"] { background: var(--surface); color: var(--ink); box-shadow: var(--sh); }
.avatar-lang-opt[aria-checked="true"] { font-weight: 600; }
/* The build stamp, moved here from the bar by avatar.ts: a quiet last line, at every width (the
   bar's own `display: none` at <= 1024 px is for the bar, not this menu). */
.avatar-sep { height: 1px; background: var(--line); margin: 4px 0; }
/* Card #1752: what the person has left, under the who-line, and the search item with its key. */
.avatar-limits { padding: 0 10px 8px; margin-bottom: 4px; border-bottom: 1px solid var(--line); display: flex; flex-direction: column; gap: 2px; }
.avatar-limits[hidden] { display: none; }
.avatar-limit { font: 400 12px/1.4 var(--font-ui); color: var(--ink2); }
button.avatar-item { border: 0; background: transparent; cursor: pointer; text-align: left; width: 100%; }
/* «FurnLab на компьютер» (owner, 2026-10-20): the accent marks it out; two lines, the second says what it is. */
.avatar-install > svg { color: var(--sys-text); }
.avatar-install { padding-top: 6px; padding-bottom: 6px; background: var(--sys-bg); }
.avatar-install:hover, .avatar-install:focus-visible { background: color-mix(in srgb, var(--sys) 18%, var(--sys-bg)); }
.avatar-install-words { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.avatar-install-words .avatar-label { color: var(--sys-text); font-weight: 600; }
.avatar-install-sub { font: 400 12px/1.3 var(--font-ui); color: var(--ink2); }
.avatar-install-hint { padding: 4px 10px 8px 38px; font: 400 13px/1.4 var(--font-ui); color: var(--ink2); }
.avatar-install-hint[hidden] { display: none; }
/* Card #2146: the count of friend requests waiting, at the end of «Друзья». */
.avatar-count { margin-left: auto; min-width: 22px; justify-content: center; }
.avatar-kbd { margin-left: auto; font: 500 11px/1 var(--font-ui); color: var(--ink3); padding: 3px 5px; border-radius: 4px; background: var(--sunken); }
@media (pointer: coarse) { .avatar-kbd { display: none; } }

/* Card #1752: the quick search (⌘K) and the feedback / help sheet — one veil over the page, a panel near its top. */
.search-veil { position: fixed; inset: 0; z-index: 60; display: flex; justify-content: center; align-items: flex-start; padding: 12dvh 16px 16px; background: color-mix(in srgb, var(--ink) 18%, transparent); }
.search-veil[hidden] { display: none; }
.search-panel { width: min(560px, 100%); border-radius: var(--r-card); background: var(--surface); box-shadow: 0 0 0 1px var(--line2), var(--sh-lift); overflow: hidden; }
.search-input { display: block; width: 100%; box-sizing: border-box; min-height: 48px; padding: 0 16px; border: 0; border-bottom: 1px solid var(--line); background: transparent; color: var(--ink); font: 400 16px/1.2 var(--font-ui); outline: none; }
.search-list { max-height: min(420px, 60dvh); overflow-y: auto; padding: 6px; }
.search-group { padding: 8px 10px 4px; font: 600 12px/1.2 var(--font-ui); color: var(--ink3); }
.search-row { display: flex; align-items: center; min-height: 40px; padding: 0 10px; border-radius: var(--r-ctl); color: var(--ink); text-decoration: none; font: 500 14px/1.2 var(--font-ui); overflow-wrap: anywhere; }
.search-row[aria-selected="true"] { background: var(--sunken); }
.search-empty { padding: 14px 10px; color: var(--ink3); font: 400 14px/1.3 var(--font-ui); }
@media (pointer: coarse) { .search-row { min-height: var(--touch-target, 44px); } }
/* Card #1155: «Помощник», the assistant. Docked right and not modal from 700 px (the plan stays
   usable beside it); a bottom sheet on a phone. Every control 44 px. */
.as-panel {
  position: fixed; z-index: 55; right: 12px; top: calc(var(--topbar-h, 56px) + 8px); bottom: 12px;
  width: clamp(320px, 34vw, 420px); display: flex; flex-direction: column; border-radius: var(--r-card);
  background: var(--surface); box-shadow: 0 0 0 1px var(--line2), var(--sh-lift); overflow: hidden;
}
.as-panel[hidden] { display: none; }
.as-head { display: flex; align-items: center; gap: 4px; padding: 4px 4px 4px 14px; border-bottom: 1px solid var(--line); }
.as-title { margin: 0 auto 0 0; font: 600 16px/1.3 var(--font-ui); color: var(--ink); }
.as-tools { display: flex; gap: 2px; }
.as-icon { min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; padding: 0; }
.as-log { flex: 1 1 auto; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 10px; overscroll-behavior: contain; }
.as-msg { max-width: 92%; display: flex; flex-direction: column; gap: 6px; font: 400 15px/1.45 var(--font-ui); color: var(--ink); overflow-wrap: anywhere; }
.as-words { white-space: pre-wrap; }
.as-user { align-self: flex-end; padding: 8px 12px; border-radius: var(--r-card); background: var(--sunken); }
.as-assistant { align-self: flex-start; }
.as-note { align-self: center; color: var(--ink2); font-size: 13px; text-align: center; }
.as-thumb { max-width: 100%; max-height: 160px; border-radius: var(--r-ctl); box-shadow: 0 0 0 1px var(--line2); object-fit: contain; align-self: flex-end; }
.as-input { border-top: 1px solid var(--line); padding: 8px; display: flex; flex-direction: column; gap: 6px; }
.as-shots { display: flex; gap: 8px; flex-wrap: wrap; }
.as-shots[hidden] { display: none; }
.as-shot { position: relative; width: 64px; height: 64px; }
.as-shot img { width: 100%; height: 100%; object-fit: cover; border-radius: var(--r-ctl); box-shadow: 0 0 0 1px var(--line2); }
.as-shot-x { position: absolute; top: -10px; right: -10px; width: 44px; height: 44px; display: grid; place-items: center; padding: 0; border: 0; background: transparent; cursor: pointer; color: var(--ink); }
.as-shot-x svg { padding: 3px; border-radius: 50%; background: var(--surface); box-shadow: 0 0 0 1px var(--line2); }
.as-row { display: flex; align-items: flex-end; gap: 4px; }
.as-text { flex: 1 1 auto; min-width: 0; min-height: 44px; max-height: 160px; box-sizing: border-box; resize: none; padding: 11px 12px; border-radius: var(--r-ctl); border: 1px solid var(--line2); background: var(--surface); color: var(--ink); font: 400 16px/1.35 var(--font-ui); }
.as-status { margin: 0; font: 400 13px/1.3 var(--font-ui); color: var(--ink2); }
.as-status:empty { display: none; }
/* «Отправить разработчикам»: always under the input, small and labelled; accented when it is the next step. */
.as-report { align-self: flex-start; min-height: 44px; display: inline-flex; align-items: center; gap: 6px; padding: 0 8px; font: 500 14px/1.2 var(--font-ui); color: var(--ink2); }
.as-report svg { width: 16px; height: 16px; flex: 0 0 auto; }
.as-report.as-suggest { color: var(--sys-text); background: var(--sys-bg); }
@media (max-width: 699px) {
  .as-panel { left: 0; right: 0; top: auto; bottom: 0; width: auto; height: min(78dvh, 640px); border-radius: var(--r-sheet) var(--r-sheet) 0 0; padding-bottom: env(safe-area-inset-bottom); }
}
/* The assistant's icon is in the bar at every width, before the magnifier (owner, 2026-10-10: «the assistant
   must not hide in the user menu, it must always be in the header»). */
.assistant-btn { flex: 0 0 auto; margin-left: auto; }
.topbar .assistant-btn, .assistant-btn + .search-btn { margin-left: 0; }
.avatar-menu > #build-version { display: block; order: 0; padding: 6px 10px 4px; color: var(--ink3); }
/* US-48: the workspace's own last line, «Версия … · собрана … в …». */
.avatar-version { padding: 4px 8px; font: 400 11.5px/1.3 var(--font-ui); color: var(--ink2); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
/* «Клавиши»: the row opens the key list under it. A keyboard's help, so not on a touch screen. */
.avatar-keys { display: flex; flex-direction: column; gap: 6px; padding: 4px 10px 8px 38px; }
.avatar-keys[hidden] { display: none; }
.avatar-key { display: flex; align-items: center; gap: 8px; font: 400 12px/1.3 var(--font-ui); }
@media (pointer: coarse) {
  .avatar-keys-row, .avatar-keys { display: none; }
}

/* --- The project menu (US-48, card #1803; project-menu.ts) ---------------------------------------
   Under the project's name, 280 px wide (360 once «Об этом чтении» is open), glass like the account
   menu's. Every row is a 44 px hit target. */
.project-menu {
  position: fixed; z-index: 45; width: min(280px, calc(100vw - 16px)); padding: 8px; border-radius: 18px;
  display: flex; flex-direction: column; gap: 4px; overflow-y: auto;
}
.project-menu[hidden], .pm-main[hidden], .pm-sheet[hidden], .pm-item[hidden] { display: none; }
.project-menu.wide { width: min(360px, calc(100vw - 16px)); }
.pm-main, .pm-sheet { display: flex; flex-direction: column; gap: 2px; }
.pm-who { padding: 6px 10px 6px; display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.pm-name { font: 600 15px/1.3 var(--font-ui); color: var(--ink); overflow-wrap: anywhere; }
.pm-levels { font: 400 13px/1.3 var(--font-ui); color: var(--ink2); }
.pm-sep { height: 1px; background: var(--line); margin: 2px 6px; }
.pm-item, .pm-back {
  display: flex; align-items: center; gap: 10px; min-height: var(--touch-target); padding: 0 10px; width: 100%;
  border: 0; border-radius: 10px; background: transparent; color: var(--ink); text-align: left; text-decoration: none;
  font: 500 15px/1.2 var(--font-ui); cursor: pointer;
}
.pm-item:hover, .pm-item:focus-visible, .pm-back:hover, .pm-back:focus-visible { background: var(--sunken); }
.pm-item > svg { flex: none; color: var(--ink2); }
/* #1899: «Удалить проект», the menu's one destructive row. */
.pm-item.tone-bad, .pm-item.tone-bad > svg { color: var(--bad-text); }
.pm-label { flex: 1 1 auto; min-width: 0; }
.pm-chevron { margin-left: auto; color: var(--ink2); font-size: 16px; }
.pm-back { font-weight: 600; }
.pm-back > svg { flex: none; color: var(--ink2); }
/* «Об этом чтении»: the three lines of the reading's evidence, each its own block with its detail
   written under it, then the way to the reading screen, the stale-trace mark and the project's path. */
.pm-evidence { display: flex; flex-direction: column; gap: 10px; padding: 4px 10px; }
.pm-evidence > div[hidden] { display: none; }
.ev-head { font: 600 13px/1.35 var(--font-ui); color: var(--ink); overflow-wrap: anywhere; }
.ev-body { margin-top: 2px; }
.pm-more { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; padding: 8px 10px 4px; border-top: 1px solid var(--line); margin-top: 4px; }
.pm-more > .row { width: 100%; }
/* Card #1805, the phone's own rows: the floor and room switches and «Скачать план». From 768 px up they are the
   header's centre and right zones, so the menu does not draw them; `.dl-lbl` here is the download anchor's own
   label (artefacts.ts draws it), which takes the row like `.pm-label`. */
.pm-pick, #pm-download { display: none; }
.pm-pick[hidden], #pm-download[hidden] { display: none; }
#pm-download .dl-lbl { flex: 1 1 auto; min-width: 0; }
#pm-download > svg { flex: none; color: var(--ink2); }
#pm-download.is-disabled { opacity: .6; }
.pm-pick-cap { padding: 6px 10px 2px; font: 400 12px/1.3 var(--font-ui); color: var(--ink2); }
.pm-chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 4px 4px; max-height: min(176px, 28dvh); overflow-y: auto; }
.pm-chip {
  flex: 0 1 auto; min-width: var(--touch-target); min-height: var(--touch-target); max-width: 100%; padding: 0 14px;
  border: 0; border-radius: 12px; background: var(--sunken); color: var(--ink); font: 500 14px/1.2 var(--font-ui); cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pm-chip.on { background: var(--surface); box-shadow: 0 0 0 2px var(--sys-text); font-weight: 600; }
.pm-chip:focus-visible { outline: 2px solid var(--focus, var(--sys)); outline-offset: 2px; }
@media (max-width: 767px) {
  .pm-pick:not([hidden]) { display: block; }
  #pm-download:not([hidden]) { display: flex; }
  /* The menu is a sheet under the header on a phone: the window's width less its margins. */
  .project-menu, .project-menu.wide { width: calc(100vw - 16px); }
}


/* --- The notification bell (card #1365) ------------------------------------
   Directly left of the avatar, same slot, same size: 32 px, 44 px on a coarse pointer. A dot on
   it while something is unread; the popover opens under it, anchored to the screen's right edge. */
.notify { position: relative; flex: 0 0 auto; order: 0; margin-left: auto; }
.topbar .notify { margin-left: 0; }
/* Card #1874 (US-49): the search's magnifier sits right before the bell on every bar, the bell's own button. */
.search-btn { flex: 0 0 auto; margin-left: auto; }
.topbar .search-btn, .search-btn + .notify { margin-left: 0; }
.notify + .avatar { margin-left: 0; }
.notify[hidden] { display: none; }
.notify-btn {
  position: relative; display: inline-grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 0;
  border-radius: var(--r-pill); background: transparent; color: var(--ink); cursor: pointer;
}
.notify-btn:hover, .notify-btn[aria-expanded="true"] { background: var(--sunken); }
.notify-dot { position: absolute; top: 5px; right: 5px; width: 9px; height: 9px; border-radius: 50%; background: var(--act, var(--ink)); box-shadow: 0 0 0 2px var(--surface); }
.notify-dot[hidden] { display: none; }
.notify-pop {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 30; min-width: 240px; max-width: min(320px, calc(100vw - 16px));
  padding: 6px; border-radius: var(--r-card); display: flex; flex-direction: column; gap: 2px;
  background: var(--surface); box-shadow: 0 0 0 1px var(--line2), var(--sh-lift);
}
.notify-pop[hidden] { display: none; }
.notify-item {
  display: flex; align-items: center; min-height: 36px; padding: 6px 10px; border-radius: var(--r-ctl);
  color: var(--ink); text-decoration: none; font: 500 14px/1.3 var(--font-ui); overflow-wrap: anywhere;
}
.notify-item:hover, .notify-item:focus-visible { background: var(--sunken); }
.notify-empty { padding: 8px 10px; color: var(--ink2); font: 400 13px/1.3 var(--font-ui); }
@media (pointer: coarse) {
  .notify-btn { width: var(--touch-target); height: var(--touch-target); }
  .notify-item { min-height: var(--touch-target); }
}
@media (pointer: coarse) {
  .avatar-item, .avatar-theme-opt { min-height: var(--touch-target); }
}

/* --- Nothing covers anything (#1399) ------------------------------------------------------------
   The owner, 2026-10-04, on a phone: "you can see how objects overlap in the interface — that must
   not be; find a solution so that nothing gets in the way of anything else or covers it". Every
   floating piece of chrome gets a slot of its own, so the rule holds by construction rather than
   by each piece's luck at one width; `scripts/reviewOverlapCheck.ts` measures it at 320–1440.

   - The zoom column stands ABOVE the drawer's peek wherever there is a drawer (≤ 1024), and beside
     the right panel, never over it, wherever the panel is docked (≥ 1025). It already followed the
     drawer to 40 %; at 85 % the drawer reaches the top bar and the person is reading the list, so
     the column steps aside (hidden, not under the bar).
   - The rail ends above the drawer's peek too, and is only as tall as its icons: on a short screen
     its own strip scrolls instead of running under the drawer.
   - On a phone the view toolbar starts beside the rail rather than centred over it, and scrolls
     sideways inside itself (UI-171); the accept bar hangs under it in the same column. */
@media (max-width: 1024px) {
  .zoom-controls { bottom: calc(24px + var(--panel-peek, 56px) + env(safe-area-inset-bottom, 0px)); }
  body.drawer-40 .zoom-controls { bottom: calc(40dvh + 24px + env(safe-area-inset-bottom, 0px)); }
  body.drawer-85 .zoom-controls { opacity: 0; pointer-events: none; }
  .rail { bottom: calc(24px + var(--panel-peek, 56px) + env(safe-area-inset-bottom, 0px)); }
  .rail-layer.icons { bottom: auto; max-height: 100%; overflow-y: auto; overflow-x: hidden; scrollbar-width: none; }
  .rail-layer.icons::-webkit-scrollbar { display: none; }
}
@media (min-width: 1025px) {
  .app:has(> .panel.open) .zoom-controls { right: calc(24px + 380px + env(safe-area-inset-right, 0px)); }
}
@media (min-width: 1025px) and (max-width: 1279px) {
  .app:has(> .panel.open) .zoom-controls { right: calc(24px + 340px + env(safe-area-inset-right, 0px)); }
}
/* Card #1805: the rail is the circle at the bottom left now, so the view toolbar and the accept bar start at the
   window's own edge, and the furnishing screen's composer leaves the circle its corner (phone-shell.css). */
@media (max-width: 767px) {
  .toolbar {
    left: calc(12px + env(safe-area-inset-left)); transform: none;
    max-width: calc(100% - 24px - env(safe-area-inset-left) - env(safe-area-inset-right));
  }
  /* The scheme's toolbar row has to fit 375 px: «Оригинал» keeps its name as the slider's own (no word drawn),
     and «Подтвердить обводку по оригиналу» is a 44 px check-mark button whose words are its name and tooltip. */
  .toolbar-label { display: none; }
  .toolbar-row input[type="range"] { width: 72px; }
  .accept-trace { width: 44px; min-width: 44px; padding: 0; justify-content: center; }
  .accept-trace .btn-ico { display: inline-flex; }
  .trace-accepted .btn-ico { display: inline-flex; }
  .accept-trace .btn-words, .trace-accepted .btn-words {
    position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .accept {
    left: calc(12px + env(safe-area-inset-left)); transform: none;
    width: calc(100% - 24px - env(safe-area-inset-left) - env(safe-area-inset-right));
    top: calc(24px + var(--topbar-h) + 64px);
  }
}

/* The drum a phone sets a size with (owner, 2026-10-16 23:53 Kyiv: «как на айфон … прокруткой»). */
.wheel-shade { position: fixed; inset: 0; z-index: 90; background: color-mix(in srgb, var(--ink) 35%, transparent); }
.wheel-sheet { position: fixed; left: 0; right: 0; bottom: 0; z-index: 91; padding: 10px 16px calc(14px + env(safe-area-inset-bottom)); border-radius: 18px 18px 0 0; background: var(--surface); box-shadow: var(--sh-float); }
.wheel-head { align-items: center; margin-bottom: 6px; }
.wheel-title { text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wheel-body { position: relative; }
.wheel-drum { overflow-y: auto; scroll-snap-type: y mandatory; overscroll-behavior: contain; scrollbar-width: none; box-sizing: border-box; outline: none;
  -webkit-mask-image: linear-gradient(transparent, black 35%, black 65%, transparent); mask-image: linear-gradient(transparent, black 35%, black 65%, transparent); }
.wheel-drum::-webkit-scrollbar { display: none; }
.wheel-row { height: 44px; display: flex; align-items: center; justify-content: center; scroll-snap-align: center; font-size: 22px; color: var(--ink2); }
.wheel-row.on { color: var(--ink); font-weight: 600; }
.wheel-band { position: absolute; left: 0; right: 0; top: 50%; height: 44px; transform: translateY(-50%); border-radius: 12px; background: color-mix(in srgb, var(--ink) 8%, transparent); pointer-events: none; }

/* A wall dimension remains a legible touch action when the stage bar wraps. */
.furnish-wall-length { min-height: 44px; max-width: 100%; white-space: normal; text-align: left; }
