/* UI/UX Pro Max audit fixes */
:root {
  /* Transparent-panel mode's glass tint. The Interface Scale panel's "Color panel" swatch
     writes --dora-panel-color on :root (InterfaceStyle.apply) and the SOLID-panel path
     already reads it directly -- but this token, used for every glass surface when
     Transparent panel is ON, was two hardcoded neutral rgba(255,255,255,...) stops with no
     reference to it at all, so the swatch had no visible effect in glass mode. color-mix()
     blends the chosen hue INTO each stop rather than replacing it, so the existing depth
     cue survives -- matches the standard glassmorphism technique (tint the translucent fill
     itself, keep borders neutral for edge legibility regardless of hue). Panel color's own
     CSS default (#d0d4d8, a near-neutral gray) mixes to something close to the original
     stops, so this is a no-op until the user actually picks a color.
     Alpha is applied OUTSIDE color-mix(), via the rgba(from ... / alpha) relative-color
     syntax, not as one of color-mix()'s own inputs. Feeding the alpha into an rgba() INSIDE
     color-mix() (the first version of this) meant the Panel-opacity slider was only ever
     multiplying the NEUTRAL side's alpha -- color-mix still blended in --dora-panel-color at
     a fixed percentage as a fully OPAQUE color, so the mix's own alpha math floored the
     result well above 0 (measured ~0.30 minimum) no matter how far the slider was dragged:
     the tint could visibly fade but never actually disappear. Extracting r/g/b from an
     OPAQUE color-mix() and attaching the slider's alpha afterward decouples "what hue" from
     "how visible" -- at panelAlpha 0 this is rgba(..., 0), genuinely gone. */
  --toolbar-shell-bg:
    linear-gradient(145deg,
      rgba(from color-mix(in srgb, var(--dora-panel-color) 24%, rgb(255, 255, 255)) r g b / calc(0.78 * var(--dora-panel-alpha))),
      rgba(from color-mix(in srgb, var(--dora-panel-color) 24%, rgb(246, 248, 250)) r g b / calc(0.64 * var(--dora-panel-alpha))));
  --toolbar-shell-border: rgba(255, 255, 255, 0.74);
  --toolbar-shell-shadow:
    0 14px 38px rgba(44, 51, 63, 0.15),
    inset 0 1px 0 rgba(255, 255, 255, 0.92);
  --toolbar-shell-blur: blur(var(--dora-blur-px)) saturate(145%);
  --toolbar-shell-radius: 15px;
  --toolbar-button-radius: 10px;
  --toolbar-icon: rgb(204, 204, 204);
  --toolbar-icon-hover: rgb(168, 172, 177);
  --toolbar-hover-bg: rgba(255, 255, 255, 0.52);
  --toolbar-hover-border: rgba(17, 18, 20, 0.08);
  --toolbar-hover-shadow: 0 4px 12px rgba(44, 51, 63, 0.08);
  /* Pale tint of --dora-highlight-color, not the raw hue -- a full-strength lime bg reads too
     loud against light-mode chrome. color-mix() toward white for the fill/glow, toward black
     for the border (a believable "darker shade of the same hue" without hand-fitting each
     channel -- the ORIGINAL hardcoded rgbas here don't reduce to a clean 2-stop formula
     either, they were hand-picked, so this reproduces the shape/spirit, not exact channels). */
  --toolbar-active-bg:
    linear-gradient(180deg,
      rgba(from color-mix(in srgb, var(--dora-highlight-color) 35%, white) r g b / 0.96),
      rgba(from color-mix(in srgb, var(--dora-highlight-color) 28%, white) r g b / 0.94));
  --toolbar-active-border: rgba(from color-mix(in srgb, var(--dora-highlight-color) 70%, black) r g b / 0.9);
  --toolbar-active-icon: rgb(133, 144, 92);
  --toolbar-active-shadow:
    0 0 0 3px rgba(from color-mix(in srgb, var(--dora-highlight-color) 40%, white) r g b / 0.22),
    0 8px 18px rgba(from color-mix(in srgb, var(--dora-highlight-color) 55%, black) r g b / 0.14),
    inset 0 1px 0 rgba(255, 255, 255, 0.76);

  /* Interface scale -- user-tunable via the top-bar Interface panel
     (GuiDoraPanel._toggleInterfaceTools / dora-ux.css .dora-interface-flyout).
     Every rule below that reads one of these used to hardcode the literal
     default value on its right; the panel-scale-unification pass this
     project went through is what fixed those defaults, so a slider here is
     just that same pass made live instead of hand-edited. Desktop chrome
     only -- the @media(max-width:760px) mobile layout keeps its own fixed
     sizing, untouched. */
  --dora-ui-scale: 1;
  --dora-panel-width: 72px;
  --dora-font-scale: 1;
  --dora-gap: 6px;
  --dora-gap-left: 1;
  --dora-blur-px: 16px;
  /* Multiplies --toolbar-shell-bg's baked per-stop alphas below (Interface Scale's own
     "Panel opacity" slider, InterfaceScale.apply -> generic numeric passthrough). 1 = ship
     default, unchanged. */
  --dora-panel-alpha: 1;
  --dora-panel-color: #d0d4d8;
  --dora-text-color: #3f464d;
  /* Base hue for every "this is active/selected" lime accent app-wide (GuiDoraPanel's
     Interface > Workspace > Highlight color). --accent (dora.css), --toolbar-active-bg/
     border/shadow below and the nav-cube-face active rule all derive from this ONE token via
     color-mix() instead of hardcoding their own lime -- see InterfaceStyle.DEFAULTS's comment
     for why. Ink/text on top of the accent is left fixed near-black at each use site, not
     derived here. */
  --dora-highlight-color: #d2fb14;
  /* The four chrome containers (both rails + both bars) are pinned to --dora-panel-width for
     their thickness, but every button/icon inside them is sized independently off
     --dora-ui-scale (calc(56px * ui-scale), all over this file). Panel width and Overall scale
     are two different sliders in the Interface panel -- nothing kept them in sync, so a small
     Panel width + a large Overall scale (e.g. 56 / 1.24) scaled the buttons past their own
     container and they clipped/overflowed the rail instead of fitting inside it. This is the
     single source of truth every container-thickness rule below reads instead of the raw
     --dora-panel-width, so a container can never end up thinner than what its own (scaled)
     buttons need, regardless of what the two sliders are set to independently. */
  --dora-panel-fit: max(var(--dora-panel-width), calc(56px * var(--dora-ui-scale)));
}

/* Solid-panel mode (Transparent panel unchecked): replace glass bg with flat color, no blur.
   .dora-workspace-tools is deliberately NOT in this list any more -- it is now a transparent
   OUTER container holding two independently-painted .dora-workspace-group cards (see the
   group rule further below); painting it here too was wrapping both cards in an extra
   larger box of the same colour, so the rail read as one merged 2-lump panel instead of
   two separate cards. .dora-workspace-group carries the solid fill instead. */
:root.dora-solid-panels .gui-sidebar.dora-rightbar,
:root.dora-solid-panels .dora-topbar,
:root.dora-solid-panels .dora-bottombar,
:root.dora-solid-panels .dora-topright-bar,
:root.dora-solid-panels .dora-workspace-group,
/* .dora-mask-menu (the floating bar shown while the Masking tool is active) and
   .dora-conn-menu (the connector-placement bar) both read var(--toolbar-shell-bg) directly,
   same as the bars above did before this rule existed -- found via grepping every remaining
   "background: var(--toolbar-shell-bg)" site after the Interface flyout report, rather than
   waiting for a report on each one individually. */
:root.dora-solid-panels .dora-mask-menu,
:root.dora-solid-panels .dora-conn-menu {
  background: var(--dora-panel-color) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}

/* .dora-split-menu (the left-rail Split floating bar) missed the sweep above -- same
   "background: var(--toolbar-shell-bg)" site, same reason it needs the solid fill: with
   Solid panels on, every OTHER chrome box (.dora-workspace-group's rail cards, the mask/
   connector bars above) already switches to the flat --dora-panel-color; this bar alone kept
   the neutral glass gradient, reading as a different, un-themed panel next to them. New rule,
   not an edit to the selector list above, per 03_CONSTRAINTS.md §2's additive-only rule. */
:root.dora-solid-panels .dora-split-menu {
  background: var(--dora-panel-color) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}

/* .dora-trim-menu (the left-rail Trim floating bar): same reasoning, new rule. */
:root.dora-solid-panels .dora-trim-menu {
  background: var(--dora-panel-color) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}

/* .dora-mirror-menu (the left-rail Mirror floating bar): same reasoning, new rule. */
:root.dora-solid-panels .dora-mirror-menu {
  background: var(--dora-panel-color) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}

/* Same idea for the dropdown/flyout panel family (Shapes, Pieces, Detail, Interface, Cut
   actions, Shading, Paint, Render image, Mask tools, Assets, Alpha map) -- Color panel only
   ever reached the chrome bars above, not these. Confusing in practice from the Interface
   flyout specifically: it's the panel the user is actually looking at while dragging the
   swatch, so "the picker doesn't do anything" reads as broken even though the bars were
   changing correctly the whole time just off-screen behind it. Every one of these classes
   independently hardcodes the same rgba(250, 251, 252, 0.9) background (and its own dark-mode
   override) rather than sharing one rule -- 03_CONSTRAINTS.md §2 forbids editing an existing
   rule's selector list, so this adds a new rule instead of touching each of those, same
   reasoning as .dora-workspace-group being split out above. !important is required to beat
   each one's own dark-mode variant too when both dark mode and solid panels are on at once. */
:root.dora-solid-panels .dora-shapes-flyout,
:root.dora-solid-panels .dora-parts-flyout,
:root.dora-solid-panels .dora-detail-flyout,
:root.dora-solid-panels .dora-interface-flyout,
:root.dora-solid-panels .dora-cut-actions,
:root.dora-solid-panels .dora-shading-flyout,
:root.dora-solid-panels .dora-paint-flyout,
:root.dora-solid-panels .dora-render-flyout,
:root.dora-solid-panels .dora-maskfull-flyout,
:root.dora-solid-panels .dora-asset-flyout,
:root.dora-solid-panels .dora-alpha-flyout {
  background: var(--dora-panel-color) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}

.dora-mode .gui-sidebar.dora-rightbar {
  background:
    radial-gradient(circle at 12% 0%, rgba(255, 255, 255, 0.72), transparent 30%),
    linear-gradient(145deg, rgba(255, 255, 255, 0.76), rgba(245, 247, 250, 0.64)) !important;
  -webkit-backdrop-filter: blur(var(--dora-blur-px)) saturate(145%);
  backdrop-filter: blur(var(--dora-blur-px)) saturate(145%);
}

.dora-mode .gui-sidebar.dora-rightbar::before {
  opacity: 0.08;
}

.dora-panel button,
.dora-empty button {
  min-height: 44px;
  touch-action: manipulation;
}

.dora-model-row {
  grid-template-areas:
    "load generate generate"
    "undo redo reset";
}

.dora-generate {
  white-space: nowrap;
}

.dora-generate {
  border-color: var(--glass-border) !important;
  background: rgba(255, 255, 255, 0.5) !important;
  color: var(--text-primary) !important;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.72);
}

.dora-generate:hover:not(:disabled) {
  border-color: rgba(17, 18, 20, 0.12) !important;
  background: rgba(255, 255, 255, 0.86) !important;
}

.dora-panel:not(.dora-has-model) .dora-model-row [data-action="load"],
.dora-panel:not(.dora-has-model) .dora-model-row [data-needs-model] {
  display: none;
}

.dora-panel:not(.dora-has-model) .dora-model-row {
  grid-template-areas: "generate generate generate";
}

.dora-panel:not(.dora-has-model) .dora-model-only {
  display: none;
}

.dora-panel:not(.dora-has-model) .dora-footer [data-needs-model] {
  display: none;
}

.dora-panel:not(.dora-has-model) .dora-footer {
  display: none;
}

.dora-panel:not(.dora-has-model) .dora-footer-grid {
  grid-template-columns: 1fr;
}

.dora-panel:not(.dora-has-model) .dora-footer {
  background: transparent;
}

.dora-mode .gui-sidebar.dora-rightbar:has(.dora-panel:not(.dora-has-model)) {
  bottom: auto !important;
  height: auto !important;
  max-height: calc(100dvh - 32px) !important;
}

.dora-panel:not(.dora-has-model) {
  height: auto;
  max-height: calc(100dvh - 32px);
}

.dora-panel:not(.dora-has-model) .dora-scroll {
  flex: 0 1 auto;
}

.dora-panel:not(.dora-has-model) .dora-footer {
  margin-top: 0;
}

.dora-generate-run.dora-loading::before {
  content: "";
  width: 14px;
  height: 14px;
  border: 2px solid rgba(26, 28, 30, 0.25);
  border-top-color: var(--accent-ink);
  border-radius: 50%;
}

.dora-generate-run.dora-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.dora-panel:not(.dora-split-ready) .dora-split-section,
.dora-panel:not(.dora-split-ready) .dora-parts-section,
.dora-panel:not(.dora-split-ready) .dora-footer {
  display: none;
}

.dora-panel.dora-split-ready .dora-prep-section {
  display: none;
}

.dora-fix {
  border-color: rgba(17, 18, 20, 0.08) !important;
  background:
    linear-gradient(145deg, rgba(255, 255, 255, 0.76), rgba(248, 249, 251, 0.58)) !important;
  color: #3f454b !important;
}

.dora-fix:hover:not(:disabled) {
  border-color: rgba(159, 190, 14, 0.46) !important;
  background: rgba(210, 251, 20, 0.18) !important;
  color: #4d5f00 !important;
}

.dora-mode .gui-sidebar.dora-rightbar .nm-rightbar-inner {
  display: none !important;
}

.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active.dora-fix-active {
  /* Selector repeats .dora-fix-active (valid CSS, same specificity-boost trick
     already used for the mobile .dora-fix-active rule further down) to beat
     the empty-state ".dora-rightbar:has(.dora-panel:not(.dora-has-model))"
     rule below (line ~96), which sets its own top/max-height !important and
     otherwise wins a mixed split (it beat this rule's plain 4-class form on
     max-height while THIS rule still won on top/bottom, producing a broken
     hybrid box). That :has() rule targets the classic non-pinned sidebar's
     empty state; it isn't scoped away from .dora-fix-active, so it leaks in
     here too whenever no model is loaded -- confirmed live (bottom/height.js
     divide by 100dvh over-reporting the same way top-down calc()s do below).
     5 classes here now TIES its 5 (:has() specificity = its argument's
     specificity, ".dora-panel" + ":not(.dora-has-model)" = 2, plus this
     selector's own base 3 = 5) -- and this rule sits later in the file
     (~line 175 vs ~96), so the tie resolves in its favour: it now fully
     wins across every property instead of splitting with it. */
  /* top: 88px = .dora-topright-bar's bottom edge (18 + 56) + a 14px gap. Was 169px, which
     cleared the NAV-CUBE's bottom (155) -- but the cube does not actually sit in this rail's
     column: measured live at 1280 wide, cube spans x 1125-1190 and the lock badge 1092-1114,
     while this rail spans 1208-1264, so neither can ever collide with it vertically no matter
     how high it goes. Only .dora-topright-bar genuinely overlaps this column (it is an
     auto-width pill, x 1113-1262), so it alone sets the ceiling. Anchored to fixed pixels
     matching that bar's own fixed top/height rather than a --dora-panel-fit formula, so it
     cannot silently drift out of sync with a sibling again. This still has strictly higher
     specificity than dora.css's rule (4 classes vs. 3), so overriding just the "top"
     component of that shorthand "inset" is a clean, unambiguous win -- no cascade-anomaly
     risk here.

     bottom:82px (was max-height: calc(100dvh - 116px), a top-down budget
     aimed only at the original 16px screen-edge clearance -- it never
     accounted for .dora-bottombar, which reserves its own ~56px-tall strip
     14px off the real viewport bottom. That target was ALSO computed from
     `dvh`, which in some rendering contexts (confirmed in this app's own
     preview) resolves taller than `window.innerHeight`/`vh` by tens of
     pixels -- so the rail's used height came out bigger than intended on
     top of the missing bottombar clearance, and it visibly overlapped the
     bottombar once enough cells (56px each, post size-increase) pushed
     content that far down. Pinning top AND bottom instead of computing a
     height from `dvh` removes both failure modes at once: the browser
     resolves the box height directly from these two edges against the real
     containing block, with no viewport-unit math to drift. 98px clears the
     bottombar's 14px + ~72px (bottombar buttons also went 44->56px +
     padding 5->7px in the same panel-scale-unification pass, growing the
     bar itself from 56 to 72px tall -- re-measured live, 82px was 4px short
     of the old 12px gap once that landed) with a 12px gap. */
  top: 88px !important;
  bottom: 98px !important;
  width: var(--dora-panel-fit) !important;
  min-width: var(--dora-panel-fit) !important;
  height: auto !important;
  max-height: none !important;
  overflow: visible !important;
  border: 1px solid var(--toolbar-shell-border) !important;
  border-radius: var(--toolbar-shell-radius) !important;
  background: var(--toolbar-shell-bg) !important;
  backdrop-filter: var(--toolbar-shell-blur) !important;
  -webkit-backdrop-filter: var(--toolbar-shell-blur) !important;
  box-shadow: var(--toolbar-shell-shadow) !important;
}

/* Interface > Color panel (solid mode) must also win here -- this rule doubles
   .dora-fix-active on purpose (see the comment above) to out-specificity another rule, which
   incidentally put its background ABOVE :root.dora-solid-panels's plain-4-class override too.
   .dora-fix-active is the "no model loaded" empty-state layout (this is what the screenshot
   that found this bug was actually showing), so solid-panel mode silently never reached the
   right rail in that state even after the earlier dark-mode fix -- that fix only addressed the
   loaded-model rule further down, not this one. --toolbar-shell-bg is already a token that
   flips with the theme, so ONE override (not a separate light/dark pair) covers both. */
:root.dora-solid-panels .dora-mode .gui-sidebar.dora-rightbar.dora-fix-active.dora-fix-active {
  background: var(--dora-panel-color) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}

.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active::before {
  opacity: 0 !important;
}

.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active .dora-panel {
  display: none !important;
}

.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active .nm-rightbar-inner {
  display: flex !important;
  flex-direction: column;
  align-items: center;
  /* !important on gap/padding too: theme-dark.css's un-scoped base rule
     (".nm-rightbar-inner { padding: 4px 0 !important; gap: 0 !important; }",
     meant for classic mode's rail) shares this exact class name and was
     silently winning over these two properties despite this selector's much
     higher specificity -- !important beats specificity regardless. Confirmed
     live: computed gap read 0px, padding read "4px 0px", not the 6px/7px
     below. It read fine anyway (each cell's OWN padding already gave enough
     internal breathing room that zero flex-gap wasn't visually obvious), but
     the intended values were never actually applied. */
  gap: var(--dora-gap) !important;
  width: 100%;
  /* 100%, not an independent viewport calc() -- the outer box now pins BOTH
     top and bottom (see its rule above), which gives it a definite resolved
     height instead of an auto/content-driven one, so a plain percentage
     here reliably fills exactly that space. Two independently maintained
     "available height" formulas (one on each element) is how this drifted
     out of sync and overlapped the bottombar in the first place -- letting
     this one just inherit the outer's real box removes the second formula
     entirely instead of re-tuning it to match by coincidence. */
  max-height: 100%;
  overflow-x: visible;
  overflow-y: auto;
  padding: calc(var(--dora-gap) + 1px) !important;
  box-sizing: border-box;
  scrollbar-width: none;
}

.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active .nm-rightbar-inner::-webkit-scrollbar {
  display: none;
}

/* :where(...) shares this whole block between the right rail and
   .dora-workspace-tools -- Invert/Radius/Intensity got physically
   reparented into the left rail (see GuiDoraPanel.js init()), and without
   this they'd fall back to theme-dark.css's dark Nomad-chrome styling
   (black icon/slider backgrounds) against the light Dora panel, the exact
   bug already fixed once for the right rail itself. :where() carries zero
   specificity so these rules cascade identically to before (still beaten/
   beating the same things); only the ancestor match widens. */
:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-cell {
  position: relative !important;
  flex: 0 0 44px !important;
  width: 44px !important;
  height: 44px !important;
  min-height: 44px !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  padding: 0 !important;
  border: 1px solid transparent !important;
  border-radius: var(--toolbar-button-radius) !important;
  background: transparent !important;
  color: var(--toolbar-icon) !important;
  box-shadow: none !important;
  transform: none !important;
}

:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-cell:hover,
:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-cell:focus-visible {
  border-color: var(--toolbar-hover-border) !important;
  background: var(--toolbar-hover-bg) !important;
  color: var(--toolbar-icon-hover) !important;
  box-shadow: var(--toolbar-hover-shadow) !important;
  outline: none !important;
}

:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-cell.nm-on {
  border-color: var(--toolbar-active-border) !important;
  background: var(--toolbar-active-bg) !important;
  color: var(--toolbar-active-icon) !important;
  box-shadow: var(--toolbar-active-shadow) !important;
}

/* .nm-cell.nm-disabled (nomad.css) doesn't cover .nm-tool-cell -- Invert is
   the first tool-cell-style action that needs a disabled state (current
   tool has no add/subtract direction, e.g. Move/Twist/Paint/Transform). */
:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-cell.nm-disabled {
  opacity: 0.4 !important;
  pointer-events: none !important;
}

:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-icon {
  width: 18px !important;
  height: 18px !important;
  margin: 0 !important;
  color: inherit !important;
  opacity: 1 !important;
}

/* Default (non-active) icon glyph: theme-dark.css's base .nm-tool-icon rule gives
   it a dark Nomad-chrome button background (#1e1e1e) that reads as a solid black
   square in this rail's light Dora context -- override to a plain grey glyph, no
   fill. :not(.nm-on) keeps the lime "active tool" background (set by theme-dark.css's
   .nm-tool-cell.nm-on .nm-tool-icon rule) intact. */
:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-cell:not(.nm-on) .nm-tool-icon {
  background: transparent !important;
  color: var(--toolbar-icon) !important;
}

/* theme-dark.css's own bare ".nm-tool-icon { color: #888 !important; }" measured lower
   specificity than the rule above and should already lose to it -- it didn't (same
   unresolved cascade anomaly as the dora-ux-important-cascade-anomaly note elsewhere in
   this file: a rule that should win on paper sometimes doesn't). "body." + a doubled class
   is this file's own established fix for exactly that, so repeat it here rather than trust
   the specificity math a second time. */
body.dora-mode .gui-sidebar.dora-rightbar .nm-tool-cell:not(.nm-on) .nm-tool-icon.nm-tool-icon,
body.dora-mode .dora-workspace-tools .nm-tool-cell:not(.nm-on) .nm-tool-icon.nm-tool-icon {
  color: var(--toolbar-icon) !important;
}

/* Radius/Intensity slider track: theme-dark.css's --slider-track (#252525) is
   likewise a dark-chrome value that looks black on this light rail -- use a
   neutral grey track instead. Fill/thumb stay lime (already themed correctly
   for Dora mode via the --accent override). */
:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-slider {
  background: rgba(17, 18, 20, 0.16) !important;
}

:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-icon svg {
  width: 18px !important;
  height: 18px !important;
  color: inherit !important;
  stroke: currentColor !important;
  stroke-width: 1.8 !important;
  opacity: 0.82 !important;
}

:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-icon svg * {
  stroke: currentColor !important;
}

:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-icon svg [fill="currentColor"] {
  fill: currentColor !important;
}

/* The Masking cell is this grid's one flat-coloured exception (TOOL_DEFS in
   GuiSidebarRight.js -- same logo as the top-bar/left-rail Mask buttons). The blanket
   `stroke:currentColor!important` two rules up exists for the OTHER cells' plain line
   glyphs and would otherwise paint a grey outline over this icon's solid rect/shield
   fills, which are meant to have no stroke at all. */
:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-icon svg .nm-logo-fill {
  stroke: none !important;
}

/* The Smooth cell's gradient wave lines (TOOL_DEFS in GuiSidebarRight.js) are the
   stroke-side equivalent of nm-logo-fill above: these paths ARE their stroke (no fill),
   so suppressing stroke like nm-logo-fill does would erase them entirely. Re-forces
   stroke back to the icon's own gradient instead. */
:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-icon svg .nm-logo-stroke {
  stroke: url(#dora-smooth-grad) !important;
}

:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-cell:hover .nm-tool-icon svg,
:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-cell:focus-visible .nm-tool-icon svg,
:where(.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active, .dora-mode .dora-workspace-tools) .nm-tool-cell.nm-on .nm-tool-icon svg {
  opacity: 1 !important;
}

/* nomad.css's .nm-slidercell/.nm-slider/.nm-slider-fill/.nm-slider-thumb
   rules (track dimensions, fill/thumb positioning, the "hide the text
   label" rule) are scoped to ".nm-leftbar .X, .nm-rightbar .X" ancestors
   only -- the earlier :where(...) block above only re-scoped dora-ux.css's
   OWN color overrides (.nm-slider background), not nomad.css's base
   layout rules. Neither .nm-leftbar nor .nm-rightbar is an ancestor of
   .dora-workspace-tools, so after reparenting the sliders there they had
   no track/fill/thumb sizing at all and their labels rendered as visible
   text ("RADIUS"/"INTENSITY") instead of being hidden -- confirmed via a
   user screenshot showing exactly that. Re-declare the same rules here,
   scoped to .dora-workspace-tools; nomad.css has no !important on any of
   these so normal specificity (3 classes here vs nomad.css's 2) is enough,
   no !important needed on this side either. */
.dora-mode .dora-workspace-tools .nm-slidercell {
  /* Explicit width, not implicit -- matches the 56px column every sibling .nm-tool-cell
     button already uses (see the shared rule below), so the wider track this redesign
     adds has a predictable column to center in instead of however wide flex/grid would
     otherwise size an unconstrained cell (the exact "Transform label widens the whole
     rail" bug class documented on the shared .nm-tool-cell rule below -- fixing that one
     took a fixed width for the same reason). */
  width: calc(56px * var(--dora-ui-scale));
  /* Was a flat 142px -- every sibling in this rail (.nm-tool-cell/.nm-tool-icon above)
     scales with --dora-ui-scale, but these two got missed, so the Radius/Intensity
     sliders stayed pinned at their original length while the rest of the rail scaled
     around them -- confirmed live: at a non-1 ui-scale the sliders visibly dominate the
     rail instead of matching its proportions. calc() ties them into the same system. */
  height: calc(142px * var(--dora-ui-scale));
  /* Scaled too, not just the height above -- the 142/138 gap (4px) is exactly this
     padding's top+bottom (2px+2px), sized so the slider fits with zero flex-shrink at
     scale 1. Left flat it stays 4px regardless of scale, so a scaled cell has slightly
     LESS spare room than the (also scaled) slider wants and flexbox shrinks the slider
     by the difference -- harmless (~1-2px, self-correcting) but not exact. Scaling the
     padding by the same factor keeps the gap proportional at every scale, so it never
     needs to shrink. */
  padding: calc(2px * var(--dora-ui-scale)) calc(4px * var(--dora-ui-scale));
  overflow: visible;
  cursor: default;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

.dora-mode .dora-workspace-tools .nm-slidercell .nm-label {
  display: none;
}

/* Redesigned per user reference: a bold, wide, fully-filled bar (the Nomad Sculpt mobile
   brush-settings look this codebase is skinned from) instead of the thin 6px track + small
   floating thumb every other web-style slider here uses. Kept the app's own signature
   accent for the fill rather than the reference's red -- red is this app's established
   danger/cancel color (FlexiCut's Cancel button, .dora-tool-danger), so reusing it here on
   a routine value slider would read as a warning. Radius scales with --dora-ui-scale like
   its cell above; --toolbar-button-radius reuses the same corner rounding every other
   control in this app already uses instead of inventing a new radius. */
.dora-mode .dora-workspace-tools .nm-slider {
  position: relative;
  width: calc(32px * var(--dora-ui-scale));
  /* Track length scales with --dora-ui-scale now too -- see the .nm-slidercell
     comment just above (4px shorter than its cell, same margin as before). */
  height: calc(138px * var(--dora-ui-scale));
  border-radius: var(--toolbar-button-radius);
  cursor: ns-resize;
  overflow: visible;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

.dora-mode .dora-workspace-tools .nm-slider-fill {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 0;
  border-radius: 0 0 var(--toolbar-button-radius) var(--toolbar-button-radius);
  background: var(--nm-accent);
  pointer-events: none;
  transition: filter 120ms ease-out;
}

/* Old design's separate floating circular handle is gone -- the reference reads the fill's
   own top edge as the handle, no distinct dot on top of it. A thin cap line at that edge
   keeps the current-value boundary easy to spot without reintroducing a handle shape the
   reference doesn't have. */
.dora-mode .dora-workspace-tools .nm-slider-thumb {
  position: absolute;
  left: 2px;
  right: 2px;
  bottom: 0;
  height: 2px;
  margin-bottom: -1px;
  border-radius: 1px;
  background: rgba(255, 255, 255, 0.85);
  pointer-events: none;
}

.dora-mode .dora-workspace-tools .nm-slider:hover .nm-slider-fill,
.dora-mode .dora-workspace-tools .nm-slider.nm-dragging .nm-slider-fill {
  filter: brightness(1.08);
}

.dora-mode .dora-workspace-tools .nm-slider:hover .nm-slider-thumb,
.dora-mode .dora-workspace-tools .nm-slider.nm-dragging .nm-slider-thumb {
  background: #ffffff;
  box-shadow: 0 0 0 3px var(--toolbar-active-border);
}

/* Both rails: bigger cell + permanent caption under each tool icon (was a 44px
   icon-only cell with a hover-only tooltip pill). Originally right-rail-only
   (left rail stayed at the 44px minimum touch target, on the reasoning that it
   hosts denser controls and had no room to grow) -- extended to
   .dora-workspace-tools too so both rails share one scale, per explicit
   request. Covers Invert (the one plain .nm-tool-cell left in that rail;
   Radius/Intensity are .nm-slidercell, a different class, untouched here).
   Specificity: 5 repeated classes (right) / 3 classes (left) both clear the
   shared :where(...) block's .nm-tool-cell (:where() contributes zero
   specificity, so that rule is really just 1 class) even though every side
   uses !important. */
.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active .nm-tool-cell,
.dora-mode .dora-workspace-tools .nm-tool-cell {
  flex-direction: column !important;
  justify-content: center !important;
  gap: 3px !important;
  width: calc(56px * var(--dora-ui-scale)) !important;
  /* flex-basis:auto, not a fixed 56px: this cell is itself a flex ITEM of a
     COLUMN-direction parent (.nm-rightbar-inner / .dora-workspace-tools), so
     flex-basis sets its HEIGHT, not its width -- a fixed basis + the default
     flex-grow:0 rigidly locked every cell at exactly 56px regardless of
     content, silently overriding the height:auto/min-height:56px below (flex
     sizing consults flex-basis before the height property, and only falls
     back to height when flex-basis is auto). Two-word labels ("Local scale")
     that wrap to 2 lines at the current 10px font then had nowhere to go and
     got flex-shrunk to fit -- confirmed live, computed label height 16px vs
     its own content needing 24px, clipped by .nm-label's overflow:hidden
     (an un-scoped nomad.css base rule, nothing here ever contested that
     property). auto restores min-height as the real floor and lets the cell
     grow taller on demand instead of clamping at it. */
  flex-basis: auto !important;
  height: auto !important;
  min-height: calc(56px * var(--dora-ui-scale)) !important;
  padding: 5px 3px !important;
}

.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active .nm-tool-icon,
.dora-mode .dora-workspace-tools .nm-tool-icon {
  width: calc(22px * var(--dora-ui-scale)) !important;
  height: calc(22px * var(--dora-ui-scale)) !important;
}

.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active .nm-tool-icon svg,
.dora-mode .dora-workspace-tools .nm-tool-icon svg {
  width: calc(22px * var(--dora-ui-scale)) !important;
  height: calc(22px * var(--dora-ui-scale)) !important;
}

.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active .nm-tool-cell .nm-label,
.dora-mode .dora-workspace-tools .nm-tool-cell .nm-label {
  display: block !important;
  position: static !important;
  max-width: 100% !important;
  padding: 0 !important;
  border: 0 !important;
  border-radius: 0 !important;
  background: transparent !important;
  color: var(--toolbar-icon) !important;
  box-shadow: none !important;
  font-weight: 700 !important;
  line-height: 1.2 !important;
  letter-spacing: 0 !important;
  text-transform: none !important;
  text-align: center !important;
  opacity: 1 !important;
  pointer-events: none !important;
  transform: none !important;
  /* Single line, never wrapped -- a label spilling a few px past the icon column reads fine
     on a vertical rail with open space either side; a broken word ("Flatte" / "n") does not. */
  white-space: nowrap !important;
  font-size: calc(10px * var(--dora-font-scale)) !important;
  /* nomad.css's base .nm-label ships overflow:hidden + text-overflow:ellipsis (a single-line-
     truncation design, for the original icon-only tooltip use); visible+clip here so a label
     wider than the column spills instead of being cut off mid-word. */
  overflow: visible !important;
  text-overflow: clip !important;
  /* Reserve a CONSTANT 1-line box (10px font * 1.2 line-height) on every label so all cells'
     content sum (icon + gap + label + padding) is identical and they land at the same height
     down the rail, regardless of how many characters any one label has. */
  min-height: calc(12px * var(--dora-font-scale)) !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
}

/* Interface > Text color. Deliberately does NOT touch the .nm-on rule just below -- that one
   is the "this tool is currently selected" accent tint, a state signal, not styling, and
   should keep reading as accent regardless of what the user picks here. */
.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active .nm-tool-cell .nm-label,
.dora-mode .dora-workspace-tools .nm-tool-cell .nm-label {
  color: var(--dora-text-color) !important;
}

.dora-mode .gui-sidebar.dora-rightbar.dora-fix-active .nm-tool-cell.nm-on .nm-label,
.dora-mode .dora-workspace-tools .nm-tool-cell.nm-on .nm-label {
  color: var(--toolbar-active-icon) !important;
}

.dora-empty span,
.dora-help,
.dora-shape-status,
.dora-model-status {
  color: #454b52;
}

.dora-panel button,
.dora-panel button small,
.dora-footer-grid button {
  color: #31363c;
}

.dora-panel button:disabled,
.dora-panel button:disabled small {
  color: #737981;
  opacity: 0.55;
}

.dora-generate-status {
  padding: 8px 10px;
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.48);
  color: #454b52;
  line-height: 1.5;
}

.dora-section-intro {
  margin: -4px 0 12px;
  color: #525860;
  font-size: 12px;
  line-height: 1.45;
}

.dora-section h2 {
  color: #4e565e;
  letter-spacing: 0.08em;
}

.dora-section h2 + .dora-section-intro,
.dora-section h2 + .dora-parts,
.dora-section h2 + .dora-model-row,
.dora-section h2 + .dora-wide {
  position: relative;
}

.dora-shape-flow {
  padding-top: 10px;
  border-top: 1px solid var(--hairline);
}

.dora-shape-flow button {
  min-height: 52px;
  padding: 8px 10px;
  font-size: 12px;
  line-height: 1.25;
}

.dora-export {
  margin-top: 0;
  border-color: #a5c50c !important;
  box-shadow:
    0 10px 26px rgba(126, 151, 8, 0.14),
    inset 0 1px 0 rgba(255, 255, 255, 0.56);
}

/* Solid-panel mode must also survive an active canvas drag, which is when this rule (and its
   dark-mode counterpart below) fire -- same specificity gap as the plain-state rightbar rules
   near the dark-mode block, just here for the light-mode / actively-dragging case. Declared
   BEFORE the rule it overrides on a genuine tie would lose on source order, so this one adds
   the extra .dora-mode ancestor step to win outright instead of relying on placement. */
:root.dora-solid-panels .dora-mode:has(#canvas:active) .gui-sidebar.dora-rightbar {
  background: var(--dora-panel-color) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}

.dora-mode:has(#canvas:active) .gui-sidebar.dora-rightbar {
  background: rgba(248, 249, 251, 0.94) !important;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

@media (prefers-reduced-motion: no-preference) {
  .dora-generate-run.dora-loading::before {
    animation: dora-spin 800ms linear infinite;
  }
}

@keyframes dora-spin {
  to { transform: rotate(360deg); }
}

/* Mask split tools: compact hierarchy with one lime action color. */
.dora-mask-toggle {
  border-color: var(--glass-border) !important;
  background: rgba(255, 255, 255, 0.54) !important;
  color: var(--text-primary) !important;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8);
}

.dora-mask-toggle[aria-expanded="true"] {
  border-color: rgba(172, 207, 9, 0.56) !important;
  background: rgba(210, 251, 20, 0.12) !important;
}

.dora-mask-toggle[aria-expanded="true"] svg,
.dora-mask-toggle[aria-expanded="true"] b {
  color: #668000;
}

.dora-mask-tools {
  padding: 0 2px 2px;
  border: 0;
  border-radius: 0;
  background: transparent;
}

.dora-mask-tools-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 2px 10px;
}

.dora-mask-tools-head strong {
  color: var(--text-primary);
  font-size: 12px;
  font-weight: 600;
}

.dora-mask-tools-head small,
.dora-mask-final small {
  color: #596068;
  font-size: 10px;
  line-height: 1.4;
}

.dora-mask-group {
  padding: 11px 0;
  border-top: 1px solid var(--hairline);
}

.dora-mask-group-label {
  display: block;
  margin-bottom: 8px;
  color: #596068;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.03em;
}

.dora-mask-actions {
  grid-template-columns: 0.8fr 1.2fr;
  gap: 7px;
}

.dora-mask-actions button {
  min-height: 42px;
  border-color: rgba(26, 28, 30, 0.09) !important;
  background: rgba(255, 255, 255, 0.58) !important;
  color: #30353a !important;
  font-weight: 500 !important;
}

.dora-mask-actions [data-action="extrude-split"] {
  border-color: rgba(172, 207, 9, 0.58) !important;
  background: rgba(210, 251, 20, 0.13) !important;
  color: #3b4800 !important;
}

.dora-mask-actions [data-action="extrude-cancel"] {
  min-height: 34px;
  background: transparent !important;
  color: #626970 !important;
}

.dora-mask-actions button:hover:not(:disabled) {
  border-color: rgba(148, 180, 4, 0.7) !important;
  background: rgba(210, 251, 20, 0.2) !important;
  box-shadow: 0 5px 16px rgba(126, 151, 8, 0.12);
}

.dora-mask-actions .dora-active {
  border-color: #9fbe0e !important;
  background: var(--accent) !important;
  color: var(--accent-ink) !important;
  box-shadow: 0 0 0 3px rgba(210, 251, 20, 0.18), 0 7px 18px rgba(126, 151, 8, 0.16);
}

.dora-mask-check {
  grid-template-columns: 20px 1fr auto;
  min-height: 44px;
  margin: 0;
  padding: 0 10px;
  border: 1px solid rgba(26, 28, 30, 0.09);
  border-radius: var(--radius-control);
  background: rgba(255, 255, 255, 0.48);
  cursor: pointer;
}

.dora-mask-check b {
  color: #6d747b;
  font-size: 9px;
  font-weight: 500;
}

.dora-mask-check:has(input:checked) {
  border-color: rgba(159, 190, 14, 0.62);
  background: rgba(210, 251, 20, 0.12);
  box-shadow: inset 3px 0 0 #a9c90f;
}

.dora-mask-check input {
  accent-color: #a9c90f;
}

.dora-connector-group:not(:has([data-mask-setting="connectors"]:checked)) .dora-connector-settings {
  display: none;
}

.dora-connector-settings {
  display: grid;
  gap: 11px;
  padding: 10px 4px 2px;
}

.dora-mask-field {
  margin: 0;
  color: #555c63;
  font-size: 11px;
}

.dora-mask-field output {
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-weight: 500;
}

.dora-mask-field select {
  min-height: 40px;
  border-color: rgba(26, 28, 30, 0.1);
  background: rgba(255, 255, 255, 0.68);
  color: var(--text-primary);
}

.dora-mask-field select:focus-visible,
.dora-mask-field input:focus-visible,
.dora-mask-check input:focus-visible {
  outline: 2px solid #92af08;
  outline-offset: 2px;
}

.dora-mask-field input[type="range"] {
  accent-color: #a9c90f;
  /* pan-y, NOT none: these are NATIVE horizontal ranges with no custom drag JS,
     so `none` disabled the browser's own thumb-drag on touch/pen (a tap still
     set the value -- the reported "click works, drag doesn't"). pan-y hands the
     horizontal thumb-drag back to the range while still letting a vertical
     touch scroll the flyout (it is overflow-y:auto). Shared by the connector,
     Cut and Remesh sliders. The .nm-slider rail sliders keep `none` -- their JS
     owns a vertical gesture. */
  touch-action: pan-y;
}

.dora-mask-final {
  padding-top: 12px;
  border-top: 1px solid var(--hairline);
}

.dora-mask-split {
  min-height: 46px;
  margin: 0 0 7px;
  border-color: #a5c50c !important;
  background: var(--accent) !important;
  color: var(--accent-ink) !important;
  font-size: 12px;
  font-weight: 600 !important;
  box-shadow: 0 8px 20px rgba(126, 151, 8, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.52);
}

.dora-mask-split:hover:not(:disabled) {
  border-color: #91ad0c !important;
  background: #c4ec12 !important;
  box-shadow: 0 10px 24px rgba(126, 151, 8, 0.22), 0 0 0 3px rgba(210, 251, 20, 0.16);
}

.dora-mask-split:active:not(:disabled),
.dora-mask-actions button:active:not(:disabled) {
  transform: scale(0.98);
}

.dora-wide.dora-pink {
  box-shadow:
    0 10px 26px rgba(141, 166, 10, 0.16),
    inset 0 1px 0 rgba(255, 255, 255, 0.5);
}

.dora-wide.dora-cyan {
  border-color: rgba(26, 28, 30, 0.08) !important;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.82);
}

.dora-on-tap,
.dora-split-shape {
  border-width: 1px !important;
  border-style: solid !important;
  border-color: rgba(26, 28, 30, 0.08) !important;
  background: rgba(255, 255, 255, 0.5) !important;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.74);
}

.dora-on-tap {
  border-color: rgba(159, 190, 14, 0.35) !important;
  background: rgba(210, 251, 20, 0.09) !important;
}

.dora-parts-section {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0));
}

.dora-part {
  border-color: rgba(26, 28, 30, 0.08) !important;
  background: rgba(255, 255, 255, 0.46) !important;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.72);
}

/* Bold full-row fill for the active piece, not a tint -- was a pale 12%-alpha lime
   background plus a 3px left inset-shadow "stripe" (functionally a side-stripe accent,
   this skill's own absolute ban, just built from box-shadow instead of border-left).
   Solid --accent/--accent-ink is the SAME "this one's selected" pairing already used
   everywhere else in this app (nav-cube active face, Choose-file, scene-toolbar active
   button) -- reusing it here instead of the reference screenshot's literal red, which
   already means something else in this app (Boolean cutter role). color cascades to
   every currentColor-stroked icon in the row (checkbox label, eye, "..." menu) for free;
   .dora-part-tris needs its own override below since it hardcodes a muted grey that
   would wash out on a solid accent fill. */
.dora-part.dora-selected {
  border-color: #a5c50c !important;
  background: var(--accent) !important;
  color: var(--accent-ink);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
}

.dora-part.dora-selected .dora-part-tris {
  color: var(--accent-ink);
}

@media (prefers-reduced-motion: no-preference) {
  .dora-export,
  .dora-wide.dora-pink,
  .dora-wide.dora-cyan,
  .dora-on-tap,
  .dora-split-shape,
  .dora-mask-toggle,
  .dora-mask-actions button,
  .dora-mask-check,
  .dora-mask-split {
    transition: background-color 180ms ease, border-color 180ms ease, color 180ms ease,
      box-shadow 180ms ease, transform 140ms ease;
  }
}

/* Workspace actions live beside the model, leaving the right panel task-focused. */
.dora-model-row.dora-generate-only {
  display: block;
}

.dora-model-row.dora-generate-only .dora-generate {
  width: 100%;
}

/* "body." prefix boosts specificity above dora.css's ".dora-mode #viewport
   > div:has(.nav-cube-face) { right: calc(var(--dora-panel-w) + 32px) }"
   (line ~1039) -- same selector otherwise, so without it this and that
   rule would tie on the now-shared "right" property, and per this file's
   documented cascade-anomaly note a same-specificity tie can't be trusted
   to resolve on source order alone. */
body.dora-mode #viewport > div:has(.nav-cube-face) {
  /* Sits right under .dora-topright-bar (top:18-74px) with a small gap, then the sculpt
     rail continues right under both (top:169px) -- one snug top-right cluster instead of
     the cube floating with a big gap above everything else. right:90px clears
     .dora-topright-bar horizontally (it's an auto-width pill ~179px wide, not a narrow
     strip, so it reaches further left than its own right:18px edge alone would suggest). */
  top: 90px !important;
  right: 90px !important;
  left: auto !important;
  /* 160, not 26 -- SplitTool's and the primitive gizmo's own draw overlays
     (.nm-split-overlay / .nm-prim-gizmo-overlay) are FIXED full-canvas <canvas> elements at
     z-index 150 while armed, and this container establishes its own stacking context, so only
     ITS z-index competes with the overlay's sibling-level 150 (raising a descendant's, e.g.
     .dora-navcube-ball's, cannot -- same reasoning already documented where topbar/workspace-
     tools/rightbar/the flyouts were bumped to 160 for this exact overlay, near
     ".dora-workspace-tools, .dora-topbar, ..." below). The nav cube was missed in that pass --
     confirmed live: elementFromPoint at the cube's own centre returned the primitive gizmo's
     overlay canvas, not the cube, whenever a primitive was pending. 160 clears 150 while
     staying below the Trim/Split popup's own 200.
     !important, unlike the rest of this pass's siblings: GuiNavCube.js sets z-index (and
     top/right/width/height/perspective) as an INLINE style on this exact element -- confirmed
     via el.style.cssText live -- and a plain stylesheet value can never beat that, only an
     !important one can (inline beats normal-weight stylesheet rules regardless of specificity).
     That inline z-index is '10', which is what was actually active here, not the 26 this rule
     used to read for a human skimming it. */
  z-index: 160 !important;
  /* No card chrome -- the ball itself (.dora-navcube-ball) is the only shape that should
     read as solid. This container used to be a rounded-square glass card behind the
     cube, which matched the cube's own square footprint; now that the cube renders
     through a circular porthole, that same square card showed as a mismatched box
     peeking out around the ball. Purely a positioning/stacking wrapper now. */
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: none;
  /* Interface tab > Nav cube > Nav cube scale. Uniform scale on the whole group (ball +
     lock + home all live inside this one container) -- GuiNavCube.js's own per-frame
     matrix3d transform lives on the CUBE element two levels deeper, so this is a
     completely separate transform layer and can't conflict with that math.
     Origin is the visual group's own TOP-RIGHT corner. The group spans -34..57x, -8..42y
     (ball -8..42y/7..57x, lock -8..14y/-34..-12x, home 20..42y/-34..-12x) -- lock/home sit
     OUTSIDE this 65x65 box, up and to the left of it, so the group's real corner is (57, -8),
     not the container's own.
     Scaling about that point pins the top and right edges and lets the group grow DOWN and
     LEFT, into open canvas. Every other origin lets it grow towards chrome: the container's
     own `top right` moved lock/home (furthest from it) the most, and the group's CENTRE
     (11.5, 17) -- what this was before -- expanded evenly in all directions, which means
     upward and rightward too. Measured with the centre origin: clearance under
     .dora-topright-bar was 33px - 25px*scale, i.e. 8px at scale 1 but -7px (overlapping the
     bar) by scale 1.6, and the right edge crossed into the sculpt rail at the same setting.
     A corner origin makes both invariant with no compensating top/right arithmetic to keep
     in sync, and is identical to the old behaviour at scale 1. */
  transform: scale(var(--dora-nav-cube-scale, 1));
  transform-origin: 57px -8px;
}

/* Ball, not cube: GuiNavCube.js's real 3D transform/drag/snap engine is untouched --
   only the skin changed. Two rounds of per-face border-radius (46%, then 58x58/20%
   trying to overlap-hide the gap) BOTH still showed a seam: two independently rounded
   flat squares meeting at 90 degrees pull away from the true cube edge on BOTH sides at
   any angle off dead-on, and no amount of face-level rounding tuning closes that
   reliably. Replaced that whole approach: .dora-navcube-ball (GuiNavCube.js's new
   wrapper, between the perspective container and the cube) is a plain circular
   overflow:hidden porthole -- THAT is what makes the silhouette one disc from every
   angle, not the faces. Faces go back to a small flat radius (nomad.css's original
   50x50/top:0/left:0 sizing, also restored -- no more oversize hack) so they tile
   edge-to-edge with zero gap, same as a real cube always has; where two colours meet
   is now a clean abutting seam (like a two-tone beach ball), not a hole. */
/* Interface tab > Nav cube > Square nav cube. Sphere (default) is the circular-porthole
   redesign above; square drops the clip back to a soft-cornered square, closer to the
   original flat-cube look, no other change (faces/colours/scale/lock/home untouched). */
:root.dora-navcube-square .dora-navcube-ball {
  border-radius: 14%;
}

.dora-navcube-ball {
  border-radius: 50%;
  overflow: hidden;
  /* Safety-net fill: at exact/near-45deg angles a sub-pixel seam between two adjacent
     faces can show a sliver of whatever's behind them. Backing the ball itself in a
     neutral tone (not black, not any one axis colour) means that sliver reads as soft
     shading instead of a stray dark line or a wrong-axis colour peeking through. */
  background: #7c8a6a;
  box-shadow: inset 0 -6px 12px rgba(0, 0, 0, 0.18), inset 0 4px 8px rgba(255, 255, 255, 0.35);
}

.dora-mode .nav-cube-face {
  /* theme-dark.css's legacy (pre-ball-redesign) .nav-cube-face still hardcodes
     width/height: 60px !important -- 10px bigger than the 50px scheme GuiNavCube.js's
     translateZ(25px) (half of 50) and the .dora-navcube-ball container actually use.
     Under the 200px perspective this renders each face ~69px (200/(200-25) * 60), pushing
     it visibly off-center and oversized past the circular porthole -- read as "text isn't
     centered" (the text IS centered within its own oversized/offset face) and, before that,
     as the cube looking distorted/telescope-like. !important + the extra .dora-mode class
     is what it takes to beat theme-dark.css's own !important. */
  width: 50px !important;
  height: 50px !important;
  border: 1px solid rgba(255, 255, 255, 0.55) !important;
  border-radius: 8%;
  color: #2b2f1f !important;
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
  /* Flexbox (align-items/justify-content:center, nomad.css's base .nav-cube-face) centres
     the LINE BOX, not the glyphs -- an all-caps label like FRONT has no descenders, so the
     default ~1.2 line-height reserves unused space below the text that the glyphs never
     fill, reading as "sitting too high" in a circle even though the box itself is
     centred correctly. line-height:1 removes that reserved slack. */
  letter-spacing: 0.06em;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.6),
    inset 0 -6px 10px rgba(0, 0, 0, 0.08),
    0 8px 16px rgba(30, 40, 10, 0.14);
  transition: background 0.2s ease-out, border-color 0.2s ease-out, box-shadow 0.2s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .dora-mode .nav-cube-face { transition: none; }
}

.dora-mode .nav-cube-face.navcube-axis-x {
  background:
    radial-gradient(circle at 32% 28%, rgba(255, 205, 200, 0.98), rgba(224, 122, 110, 0.92) 75%) !important;
}

.dora-mode .nav-cube-face.navcube-axis-y {
  background:
    radial-gradient(circle at 32% 28%, rgba(213, 245, 195, 0.98), rgba(130, 189, 96, 0.92) 75%) !important;
}

.dora-mode .nav-cube-face.navcube-axis-z {
  background:
    radial-gradient(circle at 32% 28%, rgba(196, 220, 250, 0.98), rgba(101, 145, 214, 0.92) 75%) !important;
}

.dora-mode .nav-cube-face:hover,
.dora-mode .nav-cube-face.navcube-active {
  /* Third independent lime recipe (see --dora-highlight-color's :root comment) -- same
     color-mix() indirection, not gated light/dark like --toolbar-active-* (this rule never
     had a dark-mode override to begin with, so neither does this). */
  border-color: rgba(from color-mix(in srgb, var(--dora-highlight-color) 75%, black) r g b / 0.9) !important;
  background:
    radial-gradient(circle at 32% 28%,
      rgba(from color-mix(in srgb, var(--dora-highlight-color) 30%, white) r g b / 0.98),
      rgba(from color-mix(in srgb, var(--dora-highlight-color) 92%, white) r g b / 0.96) 75%) !important;
  color: #1d2500 !important;
  text-shadow: none;
  box-shadow:
    0 0 0 3px rgba(from var(--dora-highlight-color) r g b / 0.16),
    0 10px 22px rgba(from color-mix(in srgb, var(--dora-highlight-color) 60%, black) r g b / 0.18),
    inset 0 1px 0 rgba(255, 255, 255, 0.72);
}

/* Small lock badge riding inside the nav-cube's own container div (not a
   sibling in #viewport) so it moves with the cube for free wherever the
   cube's own position rules relocate it -- no separate anchor to keep in
   sync. Sits just outside the container's padding box (container itself
   is 65px content + 8px padding + 1px border ~= 83px rendered). */
.dora-mode .dora-navcube-lock {
  position: absolute;
  /* Upper-left of the cube with real separation, not centered tight against its edge --
     matches the reference's own lock placement (a standalone icon above-left of the ball,
     not a badge glued to it). */
  top: -8px;
  left: -34px;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
  cursor: pointer;
  border: 1px solid rgba(177, 193, 88, 0.46);
  border-radius: 50%;
  background:
    linear-gradient(180deg, rgba(253, 255, 247, 0.96), rgba(238, 243, 224, 0.82));
  color: #5c690d;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.92),
    0 8px 18px rgba(134, 152, 24, 0.08);
}

.dora-mode .dora-navcube-lock svg {
  width: 13px;
  height: 13px;
}

.dora-mode .dora-navcube-lock:hover {
  border-color: rgba(177, 201, 12, 0.9);
}

.dora-mode .dora-navcube-lock:focus-visible {
  outline: 2px solid rgba(177, 201, 12, 0.9);
  outline-offset: 2px;
}

.dora-mode .dora-navcube-lock.dora-navcube-lock-active {
  border-color: rgba(177, 201, 12, 0.9);
  background:
    linear-gradient(180deg, rgba(226, 255, 83, 0.98), rgba(206, 247, 22, 0.96));
  color: #1d2500;
  box-shadow:
    0 0 0 3px rgba(210, 251, 20, 0.16),
    0 10px 22px rgba(141, 166, 10, 0.18),
    inset 0 1px 0 rgba(255, 255, 255, 0.72);
}

/* Home: reset-to-Front shortcut, same badge language as the lock above -- stacked
   directly beneath it (same left offset, top = lock's own top + height + a small gap),
   not centred under the ball. */
.dora-mode .dora-navcube-home {
  position: absolute;
  top: 20px;
  left: -34px;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
  cursor: pointer;
  border: 1px solid rgba(177, 193, 88, 0.46);
  border-radius: 50%;
  background:
    linear-gradient(180deg, rgba(253, 255, 247, 0.96), rgba(238, 243, 224, 0.82));
  color: #5c690d;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.92),
    0 8px 18px rgba(134, 152, 24, 0.08);
  transition: border-color 0.2s ease-out, color 0.2s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .dora-mode .dora-navcube-home { transition: none; }
}

.dora-mode .dora-navcube-home svg {
  width: 13px;
  height: 13px;
}

.dora-mode .dora-navcube-home:hover,
.dora-mode .dora-navcube-home:focus-visible {
  border-color: rgba(177, 201, 12, 0.9);
  color: #1d2500;
}

.dora-mode .dora-navcube-home:focus-visible {
  outline: 2px solid rgba(177, 201, 12, 0.9);
  outline-offset: 2px;
}

/* Locked: kill face interactivity outright (belt-and-suspenders next to the
   JS `_locked` guards in GuiNavCube.js) so hover/drag/click never engage.
   .navcube-active (current view snap) is untouched -- it's not pointer-driven,
   and should still show which face you're looking at while locked. */
.navcube-locked .nav-cube-face {
  pointer-events: none !important;
  opacity: 0.55;
}

.dora-workspace-tools {
  position: absolute;
  z-index: 24;
  /* Anchored below .dora-topbar, not the nav-cube -- .dora-topbar now lives in this
     same left column (14px, see its rule below) instead of centered, so this rail must
     clear ITS bottom edge instead of a fixed pixel guess. Derived from --dora-panel-fit
     (14px topbar anchor + topbar height + 14px clearance) so it stays correct if the
     Interface "Panel width"/"Overall scale" sliders grow the topbar taller -- a plain
     `top: 100px` only happened to work at the default 72px --dora-panel-fit and would
     have let the two collide once the bars moved into the same column. Evaluates to
     100px by default (28 + 72), matching the old hardcoded value exactly. */
  top: calc(28px + var(--dora-panel-fit));
  left: 14px;
  /* Plain vertical stack of the two group panels below, not the single bordered/blurred/
     padded shell this used to be directly -- that look moved onto .dora-workspace-group so
     each group reads as its own separate card, per the reference (a small Symmetry/brush
     cluster above a larger tool-list card, with a visible gap between them). */
  display: flex;
  flex-direction: column;
  /* Wider than the item-level gap the groups use internally, so the split reads clearly as
     "two cards" rather than just an extra-wide item gap -- floored at 14px since the scaled
     term alone is ~5px at the default Interface-scale settings, too tight to read as a real
     gap between cards (still grows past the floor once the user scales the UI up). */
  gap: max(14px, calc(var(--dora-gap) * var(--dora-gap-left) * 2.2));
  /* Width == the right rail's width and both bars' thickness: all four chrome containers
     read the same --dora-panel-fit so the Interface "Panel width" slider moves them together
     (--dora-panel-fit floors at whatever the scaled buttons need -- see its definition in
     :root -- so raising "Overall scale" past what "Panel width" allows widens the rail to
     match instead of the buttons overflowing it). Was content-sized (~54px) while the right
     rail sat at 56px -- the small but visible left/right asymmetry. */
  width: var(--dora-panel-fit);
  box-sizing: border-box;
  /* 198px, not 116px: the old budget only targeted a generic 16px screen-edge
     clearance and never accounted for .dora-bottombar, which reserves its own
     strip 14px off the real bottom -- so once enough rows (Theme/Symmetry/
     sliders/Invert/Alpha/Detail/Material/Mask/Smooth/Gizmo) stacked up, this
     rail's bottom edge landed inside that strip. Same fix, same target
     clearance as the right rail's .dora-rightbar.dora-fix-active rule above:
     bottom edge 98px off the real viewport bottom (14px anchor + ~72px bar
     height, bar height grown from ~56 to ~72px in this same pass since its
     own buttons/padding scaled up too -- 82px undershot by 4px once that
     landed, re-measured live and corrected here). Still the right budget now
     that the rail is two cards instead of one shell -- it bounds the whole
     stack's scroll area, not either card individually. */
  max-height: calc(100dvh - 198px);
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: none;
}

.dora-workspace-tools::-webkit-scrollbar {
  display: none;
}

/* One card per group (see .dora-workspace-tools above for why this moved off the outer
   rail). Identical shell tokens to what the outer rail used to carry directly, so the two
   cards read as the same chrome family as the topbar/bottombar/right-rail -- just split into
   two instead of the old single strip. */
.dora-workspace-group {
  display: grid;
  justify-items: center;
  width: 100%;
  box-sizing: border-box;
  /* Left rail's own spacing multiplier (--dora-gap-left) layered on the shared --dora-gap,
     same as the outer rail used to read directly -- this is the rail the Interface panel's
     "Vertical spacing (Left)" slider targets independently of the global "Vertical spacing". */
  gap: calc(var(--dora-gap) * var(--dora-gap-left));
  padding: calc(var(--dora-gap) * var(--dora-gap-left) + 1px);
  border: 1px solid var(--toolbar-shell-border);
  border-radius: var(--toolbar-shell-radius);
  background: var(--toolbar-shell-bg);
  -webkit-backdrop-filter: var(--toolbar-shell-blur);
  backdrop-filter: var(--toolbar-shell-blur);
  /* Inset top-edge highlight ONLY -- deliberately NOT --toolbar-shell-shadow like the other
     chrome containers. That token's projected drop shadow (0 14px 38px) is tuned for a bar
     floating alone over the canvas; here two cards sit 14px apart, so the upper card's
     shadow lands squarely in that gap and across the lower card's top edge, painting the
     grey smear that made the split read as one merged panel rather than two cards. The
     cards already separate from the canvas via their border + fill, so the projected shadow
     buys nothing here and only costs the gap. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.92);
}

/* Dark's own inset value (0.06, not light's 0.92 -- see the dark --toolbar-shell-shadow
   redeclaration further down). Needed as its own rule because the base above hardcodes the
   highlight instead of reading the theme-flipping token, for the gap reason explained there. */
body.dora-mode.dora-dark .dora-workspace-group {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

/* Third rail card, empty for now (GuiDoraPanel.js init() -- this._workspaceExtraPanel).
   Everything but min-height comes free from .dora-workspace-group above; the min-height is
   only here so the card reads as a real placeholder while empty instead of collapsing to a
   sliver of padding. Content (buttons/sliders TBD) will very likely make this explicit
   height unnecessary -- drop it once real children give the grid its own size. */
.dora-workspace-extra-panel {
  min-height: 180px;
}

/* Floating top/bottom bars -- empty shells (see GuiDoraPanel.js init());
   buttons get added on request. Left-anchored (not centered) to stack in the
   same corner column as .dora-workspace-tools below -- matches the reference
   layout (icon rows pinned to the left corners) and reads as one coherent
   left-side dock instead of three unrelated floating pills. Same left offset
   (14px) as .dora-workspace-tools so all three align into a single edge; the
   nav-cube lives top-RIGHT (right:18px, dora.css) so this corner was free.
   Scrolls internally rather than growing past the viewport edge once they
   have content, same defensive pattern as .dora-workspace-tools. Shared
   shell/button styling; only the anchor edge (top vs bottom) differs. */
.dora-topbar,
.dora-bottombar {
  position: absolute;
  z-index: 24;
  left: 14px;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--dora-gap);
  /* Vertical padding is 0 -- the bar's THICKNESS is pinned to --dora-panel-width below
     (the same var the right rail's width uses), so the buttons are centred in that fixed
     height by align-items instead of being spaced out by padding. Horizontal padding stays:
     it is the rounded-pill end-cap before the first / after the last button. */
  padding: 0 calc(var(--dora-gap) + 1px);
  border: 1px solid var(--toolbar-shell-border);
  border-radius: var(--toolbar-shell-radius);
  background: var(--toolbar-shell-bg);
  -webkit-backdrop-filter: var(--toolbar-shell-blur);
  backdrop-filter: var(--toolbar-shell-blur);
  box-shadow: var(--toolbar-shell-shadow);
  box-sizing: border-box;
  /* Thickness == both rails' width. All four chrome containers now read the SAME
     --dora-panel-fit (right rail already did; left rail set to match below), so the
     Interface panel's "Panel width" slider drives every one of them in lockstep and they can
     never drift apart again -- and --dora-panel-fit's own floor (see :root) keeps a bar from
     going thinner than "Overall scale" needs, so its buttons never overflow it. Was
     `min-height: 44px` + auto height, which resolved to ~57px from button+padding while the
     rails sat at 54-56px -- the visible mismatch. */
  height: var(--dora-panel-fit);
  min-width: 44px;
  max-width: calc(100vw - 28px);
  overflow-x: auto;
  scrollbar-width: none;
}

.dora-topbar {
  top: 14px;
}

.dora-bottombar {
  bottom: 14px;
}

/* Top-right floating bar -- started as an empty shell (see the matching GuiDoraPanel.js
   init() comment), now holds FaceGroup/Mask tools/Plane cut, moved out of .dora-topbar so
   they read as a distinct secondary group. Same visual language as .dora-topbar/
   .dora-bottombar (duplicated rather than added to their shared selector, since this one
   anchors from the right/top corner instead of the left) -- min-width keeps it visible as
   a small pill even when empty. Sits below the nav-cube badge (top:18px/right:18px in
   dora.css) rather than beside it, so it doesn't need to track the cube's own rendered
   width to avoid overlapping it. */
.dora-topright-bar {
  position: absolute;
  /* Was 91px, clearing the nav-cube's old corner position (top:18px) -- the cube has since
     moved down to top:155px (floats independently now, see its own rule), so this is free to
     sit close to the actual corner instead, matching the left side's topbar/bottombar 14-18px
     convention. */
  top: 18px;
  right: 18px;
  z-index: 24;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--dora-gap);
  padding: 0 calc(var(--dora-gap) + 1px);
  border: 1px solid var(--toolbar-shell-border);
  border-radius: var(--toolbar-shell-radius);
  background: var(--toolbar-shell-bg);
  -webkit-backdrop-filter: var(--toolbar-shell-blur);
  backdrop-filter: var(--toolbar-shell-blur);
  box-shadow: var(--toolbar-shell-shadow);
  box-sizing: border-box;
  height: var(--dora-panel-fit);
  min-width: 44px;
  max-width: calc(100vw - 28px);
  overflow-x: auto;
  scrollbar-width: none;
}

.dora-topright-bar::-webkit-scrollbar {
  display: none;
}

.dora-topbar::-webkit-scrollbar,
.dora-bottombar::-webkit-scrollbar {
  display: none;
}

.dora-topbar > button,
.dora-bottombar > button {
  width: calc(56px * var(--dora-ui-scale));
  min-width: calc(56px * var(--dora-ui-scale));
  height: calc(56px * var(--dora-ui-scale));
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}

.dora-topbar > button svg,
.dora-bottombar > button svg {
  width: calc(22px * var(--dora-ui-scale));
  height: calc(22px * var(--dora-ui-scale));
}

.dora-topbar > button:hover:not(:disabled),
.dora-bottombar > button:hover:not(:disabled) {
  border-color: var(--toolbar-hover-border);
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon-hover);
  box-shadow: var(--toolbar-hover-shadow);
}

.dora-topbar button.dora-active,
.dora-bottombar button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}

.dora-topbar button:active:not(:disabled),
.dora-bottombar button:active:not(:disabled) {
  transform: scale(0.95);
}

.dora-topbar button:focus-visible,
.dora-bottombar button:focus-visible {
  outline: 2px solid #92af08;
  outline-offset: 2px;
}

.dora-topbar button:disabled,
.dora-bottombar button:disabled {
  color: #9da3a9;
  cursor: not-allowed;
  opacity: 0.42;
}

/* Icon + short caption per user request ("add label text for these tools"). New rule,
   scoped to just .dora-bottombar > button -- doesn't touch the shared .dora-topbar rule
   above, so that bar's much longer row stays icon-only (never asked for labels, far less
   room per button). Bar height stays pinned to --dora-panel-fit (see that var's own
   comment on why every chrome container has to match); the icon shrinks a little instead,
   freeing the vertical room a caption needs inside the SAME fixed-height box. */
.dora-bottombar > button {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
}

.dora-bottombar > button svg {
  /* Bumped from 18px per user request -- was already a deliberate shrink off the app's
     normal 22px plain-glyph size (see the caption-label comment above for why), this moves
     it back toward that baseline instead of picking an arbitrary new number. */
  width: calc(22px * var(--dora-ui-scale));
  height: calc(22px * var(--dora-ui-scale));
  flex: none;
}

/* Overrides the bare .dora-mask-tool-label rule's nowrap: that default assumes an
   auto-width button (FaceGroup/Mask triggers). This bar's buttons are pinned to 56px (see
   the box rule above), so the caption has to be the thing that wraps -- same fix
   .dora-workspace-tools already needed for the same reason, see its own comment.
   font-size/font-weight now match .dora-workspace-tools .dora-mask-tool-label exactly (10px
   * --dora-font-scale, weight 600) per explicit user request ("same as the right side bar")
   -- was deliberately kept on --dora-ui-scale instead to guard against outgrowing this bar's
   floor-clamped height at min overall-scale + max font-scale (still a real risk the rail
   doesn't share, since its cells grow taller to fit), but matching the rail exactly is what
   was asked for, so that guard is gone; verify for clipping if font-scale is pushed high. */
.dora-bottombar .dora-mask-tool-label {
  font-size: calc(10px * var(--dora-font-scale));
  font-weight: 600;
  line-height: 1.1;
  white-space: normal;
  /* NOT overflow-wrap/word-break:break-word -- a single unbreakable word ("Material") came
     out ~1px wider than the box at this size, and break-word's job is to break ANYWHERE
     necessary to avoid that, mid-word if there's no space to use instead ("Materia"/"l").
     Plain white-space:normal still wraps multi-word captions at their natural space
     ("FG Color" -> two clean lines); a lone long word just overflows the box edge by that
     ~1px instead of getting mangled -- imperceptible, and never mangled beats rarely mangled. */
  text-align: center;
  min-width: 0;
  max-width: 100%;
}

.dora-workspace-tools .dora-workspace-group > button,
.dora-shapes-tool > button,
.dora-parts-tool > button,
.dora-cut-tool > button,
.dora-mirror-tool > button,
.dora-remesh-tool > button,
.dora-decal-tool > button,
.dora-interface-tool > button,
.dora-detail-tool > button {
  width: calc(56px * var(--dora-ui-scale));
  min-width: calc(56px * var(--dora-ui-scale));
  height: calc(56px * var(--dora-ui-scale));
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}

/* Mirror is a top-right-bar button but a line glyph (like Cut), not a flat brand icon (like
   FaceGroup/Mask) -- so it rides the 22px line-glyph sizing here, NOT the 36px brand-icon
   rule those two use, and shares Cut's box/hover/active recipe below. */
.dora-workspace-tools .dora-workspace-group > button svg,
.dora-shapes-tool > button svg,
.dora-parts-tool > button svg,
.dora-cut-tool > button svg,
.dora-mirror-tool > button svg,
.dora-remesh-tool > button svg,
.dora-decal-tool > button svg,
.dora-interface-tool > button svg,
.dora-detail-tool > button svg {
  width: calc(22px * var(--dora-ui-scale));
  height: calc(22px * var(--dora-ui-scale));
}

/* Every left-rail tool button carries a visible caption under its icon, matching the
   right rail (.nm-tool-cell .nm-label). Mask/Transform get the bigger 24px treatment
   since they're flat-coloured brand marks (ICONS.mask / ICONS.transform in
   GuiDoraPanel.js) rather than currentColor line glyphs -- the rest keep their existing
   17px icon (.dora-workspace-tools .dora-workspace-group > button svg above) and just gain the same
   column-layout box.
   FIXED width, not auto: .dora-workspace-tools is `display:grid` with one implicit
   auto-sized column, so an auto-width item here doesn't just size itself, it widens
   THAT COLUMN for the whole rail -- "Transform" on one line once dragged the entire
   panel (border, background and all, since the panel styling lives on
   .dora-workspace-tools itself) 23px wider than every other row, poking past its own
   rounded background. Pinning width stops the item contributing its content's intrinsic
   size to the track; the label wraps to a second line instead of pushing outward. */
.dora-workspace-tools .dora-workspace-group > button[data-action="mask-tool"],
.dora-workspace-tools .dora-workspace-group > button[data-action="transform-tool"],
.dora-workspace-tools .dora-workspace-group > button[data-action="symmetry"],
.dora-workspace-tools .dora-workspace-group > button[data-action="alpha-toggle"],
.dora-workspace-tools .dora-workspace-group > button[data-action="material-toggle"],
.dora-workspace-tools .dora-workspace-group > button[data-action="smooth-tool"],
.dora-workspace-tools .dora-detail-tool > button[data-action="detail-toggle"] {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  width: calc(56px * var(--dora-ui-scale));
  min-width: calc(56px * var(--dora-ui-scale));
  max-width: calc(56px * var(--dora-ui-scale));
  height: auto;
  min-height: calc(56px * var(--dora-ui-scale));
  padding: 4px 2px 5px;
  box-sizing: border-box;
}

.dora-workspace-tools .dora-workspace-group > button[data-action="mask-tool"] svg,
.dora-workspace-tools .dora-workspace-group > button[data-action="transform-tool"] svg {
  width: calc(30px * var(--dora-ui-scale));
  height: calc(30px * var(--dora-ui-scale));
  flex: none;
}

/* Scoped to .dora-workspace-tools (2 classes) so this wins over the topbar's bare
   .dora-mask-tool-label rule further down (FaceGroup/Mask triggers, auto-width button +
   nowrap, deliberately different -- see that rule's comment) instead of losing to it on
   file order. Both rules target the same class name because the two spots share the
   caption-under-logo idea, not the same layout: the topbar button grows to fit its
   caption, this rail's button is pinned to 44px (see the box rule above), so the caption
   itself has to be the thing that wraps here. */
.dora-workspace-tools .dora-mask-tool-label {
  font-size: calc(10px * var(--dora-font-scale));
  font-weight: 600;
  line-height: 1.15;
  white-space: normal;
  overflow-wrap: break-word;
  word-break: break-word;
  text-align: center;
  color: var(--dora-text-color);
  /* Flex children default to min-width:auto (never shrink below content size), which
     silently overrides white-space/overflow-wrap and lets a single unbreakable word push
     past its column parent instead of wrapping -- exactly how "Transform" kept spilling
     past its 44px button even after the button itself stopped widening the grid track. */
  min-width: 0;
  max-width: 100%;
}

/* Smooth specifically: user wants this ONE label to never break onto a 2nd line (was
   wrapping "Smoot"/"h" at smaller Overall-scale/Font-scale settings), unlike the rest of
   this list which stay on the shared wrap behavior above. Scoped to just this button
   (higher specificity than the shared rule, no changes to Transform/Mask/etc): nowrap +
   a touch smaller font so it fits the fixed-width column at small scales instead of
   spilling past it. The button's width stays pinned by the rule above either way, so
   this can't reopen the old grid-track-growing bug regardless of overflow. */
.dora-workspace-tools button[data-action="smooth-tool"] .dora-mask-tool-label {
  white-space: nowrap !important;
  overflow: visible !important;
  font-size: calc(9px * var(--dora-font-scale)) !important;
}

.dora-workspace-tools .dora-workspace-group > button:hover:not(:disabled),
.dora-shapes-tool > button:hover:not(:disabled),
.dora-parts-tool > button:hover:not(:disabled),
.dora-cut-tool > button:hover:not(:disabled),
.dora-mirror-tool > button:hover:not(:disabled),
.dora-remesh-tool > button:hover:not(:disabled),
.dora-decal-tool > button:hover:not(:disabled),
.dora-interface-tool > button:hover:not(:disabled),
.dora-detail-tool > button:hover:not(:disabled) {
  border-color: var(--toolbar-hover-border);
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon-hover);
  box-shadow: var(--toolbar-hover-shadow);
}

.dora-workspace-tools button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}

/* Symmetry gets the app's actual signature lime (--accent, #d2fb14 -- the
   same color as the nav-cube's active face, the "Choose file" CTA, and
   .dora-mask-actions' .dora-active state) instead of the shared pale
   olive/khaki --toolbar-active-* tokens, per user request: the first,
   more muted green attempt didn't match the app's branding. Recipe
   (solid fill + dark ink + lime-tinted glow) copied from that same
   .dora-mask-actions .dora-active precedent for consistency. Scoped with
   [data-action="symmetry"] so only this button is affected; the extra
   attribute selector already makes this strictly more specific than the
   shared rule above, so no source-order or specificity-tie risk. */
.dora-workspace-tools button[data-action="symmetry"].dora-active {
  border-color: #9fbe0e;
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow:
    0 0 0 3px rgba(210, 251, 20, 0.18),
    0 7px 18px rgba(126, 151, 8, 0.16);
}

.dora-workspace-tools button:active:not(:disabled) {
  transform: scale(0.95);
}

.dora-workspace-tools button:focus-visible {
  outline: 2px solid #92af08;
  outline-offset: 2px;
}

.dora-workspace-tools button:disabled {
  color: #9da3a9;
  cursor: not-allowed;
  opacity: 0.42;
}

.dora-workspace-tools .dora-tool-danger:hover:not(:disabled) {
  border-color: rgba(190, 48, 58, 0.38);
  background: rgba(226, 67, 78, 0.1);
  color: #a62c35;
  box-shadow: 0 5px 14px rgba(166, 44, 53, 0.1);
}

.dora-tool-divider {
  width: 24px;
  height: 1px;
  margin: 3px auto;
  background: rgba(26, 28, 30, 0.1);
}

/* .dora-topbar/.dora-bottombar are horizontal rows, so their dividers need
   to run vertically (a horizontal line would be nearly invisible
   sandwiched between two 56px-tall buttons). */
.dora-topbar .dora-tool-divider,
.dora-bottombar .dora-tool-divider {
  width: 1px;
  height: 30px;
  margin: auto 3px;
}

.dora-shapes-tool,
.dora-parts-tool,
.dora-cut-tool,
.dora-remesh-tool,
.dora-decal-tool,
.dora-interface-tool,
.dora-detail-tool {
  position: relative;
}

.dora-shapes-flyout,
.dora-parts-flyout,
.dora-detail-flyout,
.dora-interface-flyout {
  /* position: fixed, not absolute -- these used to be positioned relative to
     their (position: relative) toggle-button wrapper inside .dora-workspace-tools,
     but that rail now scrolls (overflow-y: auto) and clips (overflow-x: hidden)
     to stay clear of the nav-cube (see the workspace-toolbar positioning fix),
     which was silently clipping every flyout off-screen since they render
     outside the 58px-wide rail. Fixed + JS-computed top/left (_positionFlyout)
     escapes that clipping entirely -- same pattern GuiSidebarLeft/GuiSidebarRight
     already use for their popups (mask/gizmo/trim/split).

     z-index: reparenting to document.body escapes CONTAINMENT (clipping,
     stacking-context confinement) but does NOT automatically win the
     z-index competition -- .dora-topbar and .dora-workspace-tools both sit
     at z-index: 24, and a flyout opening beside a .dora-topbar button
     overlaps the OTHER topbar buttons still in that row (unlike the left
     rail, where flyouts opened into empty canvas space with nothing else
     competing there). Without an explicit z-index the flyout is auto/0 and
     loses to those 24-index siblings in the overlapping region -- confirmed
     live via elementFromPoint hitting a topbar button instead of the
     flyout underneath it. 40 clears every toolbar's 24 with margin. */
  position: fixed;
  z-index: 40;
  width: 188px;
  padding: 8px;
  border-radius: 14px;
  /* Same --toolbar-shell-* tokens the topbar/rails/mask-bar/split-bar all read, not this
     family's own hand-copied rgba(250,251,252,0.9) -- that hardcoded fill never referenced
     --dora-panel-color or --dora-panel-alpha, so this exact panel (the one hosting the Color
     panel swatch and Panel opacity slider) was the one glass surface in the app that never
     demonstrated its own settings, and read as visibly mismatched to every panel that did.
     One shared token means "look exactly the same" holds by construction, not by two values
     someone kept in sync by hand. */
  border: 1px solid var(--toolbar-shell-border);
  background: var(--toolbar-shell-bg);
  -webkit-backdrop-filter: var(--toolbar-shell-blur);
  backdrop-filter: var(--toolbar-shell-blur);
  box-shadow: var(--toolbar-shell-shadow);
}

/* Widened from 220px: the multi-select checkbox added a 5th column to each
   row (see body.dora-mode .dora-part below) and 220px left almost nothing
   for the name once it, the checkbox and three 34px icon buttons all had to
   fit -- measured names truncating to 2-3 characters at that width. */
/* max-height/overflow-y moved here from .dora-remesh-flyout's own now-deleted sizing rule --
   Voxel Remesh & Boolean's content lives nested inside this flyout too (Pieces + Add shape +
   Voxel Remesh, combined), so THIS box is what needs to cap its height and scroll, not the
   nested content div (which no longer paints or positions anything of its own -- see the
   .dora-remesh-flyout comment below).
   Widened 248->260px: user found the Pieces rows too narrow for the name text, matching a
   reference screenshot's denser proportions -- paired with the .dora-part column trims
   below, this is the "minimal" widen they asked for (not the wider ~280-320px options). */
.dora-parts-flyout {
  width: 260px;
  max-height: calc(100dvh - 40px);
  overflow-y: auto;
}

/* Voxel Remesh & Boolean's content div. Deliberately UNSTYLED now (no width/position/
   background/max-height/z-index of its own) -- it used to be an independently-positioned,
   independently-painted top-level flyout with all of those, but now lives nested inside
   .dora-parts-flyout (the Pieces menu) as plain flowed content, and that parent owns the box
   entirely. The class name stays only because GuiDoraPanel.js's onRemeshSettingInput/
   _syncRemeshUI still query it by that name (querySelector finds it regardless of nesting
   depth) -- descendant-scoped rules further down (.dora-remesh-flyout .mask-group,
   .dora-remesh-flyout button, the dark-mode button colours, etc.) are UNCHANGED and still
   correctly style its actual contents. */
.dora-remesh-flyout {
}

.dora-interface-flyout {
  width: 250px;
  max-height: calc(100dvh - 40px);
  overflow-y: auto;
}

.dora-remesh-flyout .dora-mask-group:first-of-type {
  padding-top: 0;
  border-top: 0;
}

.dora-remesh-hint,
.dora-repair-hint {
  display: block;
  padding: 8px 6px 3px;
  border-top: 1px solid rgba(26, 28, 30, 0.08);
  color: #60676e;
  font-size: 9px;
  line-height: 1.4;
}

/* Collapsible sections inside the remesh popover (Voxel Remesh / Repair / Boolean) -- each
   is a plain disclosure row + a body hidden by default (GuiDoraPanel._toggleRemeshSection).
   The section itself now owns the hairline divider .dora-mask-group used to draw on its own
   (neutralised on the nested body below), so the popover reads as three short header rows
   instead of a wall of sliders on open. */
.dora-remesh-section {
  border-top: 1px solid var(--hairline);
}

.dora-remesh-section:first-of-type {
  border-top: 0;
}

.dora-remesh-section-head {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 11px 4px;
  /* !important: overrides the shared ".dora-remesh-flyout button" border/background reset
     above -- this is a header row, not a button chip, same cascade-win technique already
     used elsewhere in this file for same-specificity conflicts. */
  border: 0 !important;
  background: transparent !important;
  color: var(--text-primary);
  font-size: 11px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.dora-remesh-section-head:hover {
  color: #4d5f00;
  background: rgba(210, 251, 20, 0.1) !important;
}

.dora-remesh-section-head:focus-visible {
  outline: 2px solid #92af08;
  outline-offset: -2px;
}

.dora-remesh-section-head svg {
  width: 16px;
  height: 16px;
  flex: none;
  color: #8a9098;
  transition: transform 180ms ease;
}

.dora-remesh-section-head[aria-expanded="true"] {
  color: #4d5f00;
}

.dora-remesh-section-head[aria-expanded="true"] svg {
  color: #a5c50c;
  transform: rotate(180deg);
}

.dora-remesh-section-body.dora-mask-group {
  padding-top: 2px;
  padding-bottom: 12px;
  border-top: 0;
}

.dora-remesh-section-body[hidden] {
  display: none;
}

/* Primary action inside each section -- Remesh / Close holes + make manifold / Cut. Same
   accent-filled recipe as .dora-iface-commit (this app's one established "this button
   actually changes the mesh" language), scaled up so it reads as THE action of its section
   rather than another row in the settings list. Every declaration is !important so it stays
   correct in dark mode too WITHOUT a parallel dark-mode block -- .dora-iface-commit skipped
   !important and had to be patched separately for dark mode (see that rule's dark override);
   this button avoids needing the same patch. */
.dora-remesh-primary-btn {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  margin-top: 4px;
  border: 1px solid #a5c50c !important;
  border-radius: var(--radius-control);
  background: var(--accent) !important;
  color: var(--accent-ink) !important;
  font-size: 13px;
  font-weight: 700;
  box-shadow: 0 4px 14px rgba(126, 151, 8, 0.22);
}

.dora-remesh-primary-btn:hover:not(:disabled) {
  background: var(--accent) !important;
  filter: brightness(0.97);
  color: var(--accent-ink) !important;
  box-shadow: 0 6px 18px rgba(126, 151, 8, 0.3);
}

.dora-remesh-primary-btn:disabled {
  border-color: transparent !important;
  background: rgba(120, 128, 136, 0.14) !important;
  color: #8a9098 !important;
  font-weight: 600;
  box-shadow: none;
  cursor: default;
  opacity: 1 !important;
}

.dora-detail-flyout {
  width: 200px;
}

.dora-detail-info {
  padding: 2px 6px 10px;
  color: #596068;
  font-size: 11px;
  font-family: var(--font-mono);
}

/* Was ".dora-cut-tool > .dora-cut-actions" -- but init() reparents
   .dora-cut-actions to document.body (see the floatingSelectors loop, same
   as every other flyout), so that child-combinator selector never matched
   at runtime and this whole rule -- position:fixed included -- was dead.
   _positionFlyout() only ever sets style.top/left, which do nothing without
   position:fixed from CSS, so the cut flyout has been rendering unpositioned
   in normal document flow instead of anchored to the cut button. Confirmed
   live: getComputedStyle(...).position was "static". Dropped the dead
   ".dora-cut-tool >" prefix to match how the sibling flyouts (.dora-shapes-flyout
   etc.) are already selected by bare class name post-reparent. */
.dora-cut-actions {
  position: fixed;
  z-index: 40;
  width: 226px;
  max-height: calc(100dvh - 40px);
  overflow-y: auto;
  margin-top: 0;
  padding: 8px;
  border: 1px solid var(--glass-border);
  border-radius: 14px;
  background: rgba(250, 251, 252, 0.9);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  box-shadow: 0 16px 42px rgba(44, 51, 63, 0.18), inset 0 1px 0 #fff;
}

/* dora.css ships a legacy ".dora-cut-actions:not([hidden]) { display: grid;
   grid-template-columns: 1fr 1fr }" rule that crams the Connector group and
   the Apply/Cancel group into two side-by-side ~50%-width columns instead
   of stacking them -- confirmed live via getBoundingClientRect (both at the
   same top, split left/right). Same selector text/specificity as dora.css's
   rule, so per the dora-ux.css cascade-anomaly note an equal-specificity
   override isn't safe to rely on here even though this file loads later;
   the class is repeated to force a higher-specificity, unambiguous win. */
.dora-cut-actions.dora-cut-actions:not([hidden]) {
  display: flex;
  flex-direction: column;
}

.dora-cut-actions .dora-mask-group:first-of-type {
  padding-top: 0;
  border-top: 0;
}

.dora-cut-connector-group:not(:has([data-cut-setting="connectors"]:checked)) .dora-connector-settings {
  display: none;
}

.dora-workspace-tools [data-action="on-tap"].dora-active {
  border-color: #d07194;
  background: rgba(184, 91, 125, 0.3);
  color: #f0a9c2;
}

.dora-workspace-tools [data-action="split-shape"]:not(:disabled) {
  border-color: rgba(74, 174, 224, 0.52);
  color: #8dd8f5;
}

.dora-shapes-flyout[hidden],
.dora-parts-flyout[hidden],
.dora-remesh-flyout[hidden],
.dora-detail-flyout[hidden],
.dora-interface-flyout[hidden] {
  display: none;
}

.dora-shapes-flyout strong,
.dora-parts-flyout strong,
.dora-remesh-flyout strong,
.dora-detail-flyout strong,
.dora-interface-flyout strong {
  display: block;
  padding: 4px 6px 8px;
  color: var(--text-primary);
  font-size: 12px;
  font-weight: 600;
}

/* Interface > Text color -- scoped to ONLY this flyout (not the shared rule above, which also
   covers .dora-detail-flyout) so the picker never reaches outside the Interface panel. */
.dora-interface-flyout strong {
  color: var(--dora-text-color) !important;
}

.dora-shapes-flyout button,
.dora-cut-actions button,
.dora-remesh-flyout button,
.dora-detail-flyout button,
/* Scene toolbar -- Add/Delete/Clone/Join, one icon-only square button each, in a row above
   the "Scene" header (reference-style). Reuses the same 8px-radius/hairline-border language
   as the rest of this panel's controls rather than inventing a new button language. */
.dora-scene-toolbar {
  display: flex;
  gap: 6px;
  margin-bottom: 4px;
}

/* .dora-shapes-flyout/.dora-cut-actions/.dora-remesh-flyout/.dora-detail-flyout button's
   resting state never got a background/border reset (only :hover/:active/:disabled were
   ever authored -- see those rules below), so it fell back to the browser's native button
   face. Invisible on GLASS mode's own light frosted backdrop; a stark white box once a
   user picks a solid, saturated custom panel color (--dora-panel-color). Same hairline-
   border/transparent language as .dora-scene-toolbar > button and
   .dora-scene-add-popover > button just below, which never had this gap.
   Layout (width/display/gap/padding/radius) had the exact same gap -- never authored here
   either, only reset elsewhere for the OTHER icon+label list this flyout looks like
   (.dora-scene-add-popover > button, further down). Left at native <button> box-model, an
   icon+label pair had no flex row to lay out in, so a long label (e.g. Vietnamese's "Xuất
   các phần STL (ZIP)") wrapped with the icon reading mis-aligned against the wrapped text,
   and .dora-active's solid fill rendered as a sharp-cornered "square box" instead of a
   pill, since border-radius was never set at all -- reported as both bugs by the same
   user, same root cause. Same recipe as .dora-scene-add-popover > button below, so this
   flyout's rows finally match every other icon+label list in the app. */
.dora-shapes-flyout button,
.dora-cut-actions button,
.dora-remesh-flyout button,
.dora-detail-flyout button {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 10px;
  min-height: 42px;
  border-radius: 9px;
  background: transparent;
  border: 1px solid rgba(26, 28, 30, 0.12);
  text-align: left;
  /* Bumped from the inherited ~13px per user request ("too small compare to other tab") --
     explicit here rather than chasing whatever ancestor rule supplied 13px, so this stays
     correct regardless of what that turns out to be. */
  font-size: 14px;
}

/* File menu > language buttons -- same accent-filled "this one's active" look as
   .dora-scene-toolbar > button.dora-active (the other place a plain action-button list
   also needs a persistent selected state, not just a hover/press flash). */
.dora-shapes-flyout button.dora-active {
  /* Same tone as the fill (was a darker #a5c50c) -- that contrast read as a visible frame
     outlining the lime pill, plus the bottom edge sitting right under the label looked like
     a stray underline. Border stays PRESENT (not none) so the row keeps the exact box size
     as every non-active sibling (still 1px solid, just invisible against its own fill). */
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
}

.dora-scene-toolbar > button {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 36px;
  padding: 0;
  border: 1px solid rgba(26, 28, 30, 0.12);
  border-radius: 8px;
  background: transparent;
  color: #3f464d;
  cursor: pointer;
}

.dora-scene-toolbar > button svg {
  width: 18px;
  height: 18px;
}

.dora-scene-toolbar > button:hover:not(:disabled) {
  border-color: rgba(159, 190, 14, 0.4);
  background: rgba(210, 251, 20, 0.14);
  color: #4d5f00;
}

.dora-scene-toolbar > button.dora-active {
  border-color: #a5c50c;
  background: var(--accent);
  color: var(--accent-ink);
}

.dora-scene-toolbar > button:disabled {
  color: #b7bcc1;
  cursor: not-allowed;
  opacity: 0.5;
}

.dora-scene-add-popover {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-bottom: 6px;
  padding: 6px;
  border: 1px solid rgba(26, 28, 30, 0.08);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.03);
}

.dora-scene-add-popover[hidden] { display: none; }

/* Voxel Remesh & Boolean's popover -- same box treatment as the Add popover above, but its
   content (.dora-remesh-flyout, deliberately unstyled -- see the dora-parts-flyout comment)
   supplies its own padding/spacing internally, so this wrapper only needs the outer card. */
.dora-scene-remesh-popover {
  margin-bottom: 6px;
  padding: 6px 6px 2px;
  border: 1px solid rgba(26, 28, 30, 0.08);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.03);
}

.dora-scene-remesh-popover[hidden] { display: none; }

/* Add-shape's 5 buttons -- children of .dora-scene-add-popover, the popover the toolbar's
   Add icon opens (used to be direct children of .dora-parts-flyout before that toolbar
   existed). Still a scoped selector, not a bare descendant one: the piece-row buttons
   (part-select/rename/visible/delete) live nested two levels deeper inside
   .dora-parts .dora-part, with their own bespoke multi-column layout, and a plain
   ".dora-parts-flyout button" would incorrectly restyle those (and the toolbar) too. */
.dora-scene-add-popover > button {
  width: 100%;
  min-height: 42px;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 0 10px;
  border: 1px solid transparent;
  border-radius: 9px;
  background: transparent;
  color: #3f464d;
  cursor: pointer;
}

.dora-scene-add-popover > button svg {
  width: 16px;
  height: 16px;
}

/* The button's own font-size:14px (rule above) never reaches this span: yagui.css's `* {
   font: 400 13px "Open Sans" }` targets the span DIRECTLY, and a direct match always beats
   an inherited value regardless of specificity -- inheritance only fills in when NOTHING
   matches the element itself. Same reason the icon needed its own dedicated rule instead
   of relying on the button-level change to cascade down. */
.dora-shapes-flyout button span,
.dora-cut-actions button span,
.dora-remesh-flyout button span,
.dora-detail-flyout button span {
  font-size: 14px;
}

/* Split out from the shared rule above (used to bundle these three with
   .dora-scene-add-popover, all at 16px) so File/Cut/Remesh/Detail's rows can scale up
   independently per user request, without also growing the unrelated Add-shape popover
   past its own untouched 13px label -- see the button-layout rule's own comment for why
   these three specifically needed attention. */
.dora-shapes-flyout button svg,
.dora-remesh-flyout button svg,
.dora-detail-flyout button svg {
  width: 20px;
  height: 20px;
  /* Without this, a long wrapped label (see the button rule above) shrinks the icon to
     squeeze in beside it -- flex-shrink defaults to 1 for every flex child, icons included. */
  flex-shrink: 0;
}

.dora-shapes-flyout button:hover:not(:disabled),
.dora-cut-actions button:hover:not(:disabled),
.dora-remesh-flyout button:hover:not(:disabled),
.dora-detail-flyout button:hover:not(:disabled),
.dora-scene-add-popover > button:hover:not(:disabled) {
  border-color: rgba(159, 190, 14, 0.4);
  background: rgba(210, 251, 20, 0.14);
  color: #4d5f00;
}

.dora-remesh-flyout button:disabled,
.dora-detail-flyout button:disabled {
  color: #b7bcc1;
  cursor: not-allowed;
  opacity: 0.5;
}

/* Interface scale flyout. Its own block, not joined onto the
   .dora-shapes-flyout/.dora-remesh-flyout/.dora-detail-flyout "button" comma-group above --
   this flyout has TWO kinds of button (5 tiny per-row reset icons, one full-width "Reset
   all") and joining would force the icon buttons to that group's width:100%/42px-tall row
   styling, which reads fine for "Reset all" but wrong for a 14px icon square. */
.dora-interface-flyout .dora-iface-field {
  margin: 0 0 10px;
}

.dora-interface-flyout .dora-iface-field:last-of-type {
  margin-bottom: 4px;
}

.dora-iface-section {
  display: block;
  padding: 10px 6px 6px;
  color: var(--dora-text-color);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Collapsible section headers (all 5, including what used to be the flyout's own bigger
   "Interface scale" <strong> title -- unified to one peer treatment now that every
   section is an equal collapsible "tab", not one main title + 4 sub-sections). Overrides
   the generic .dora-interface-flyout > button pill (width/height/centered) below with a
   left-label/right-chevron header row instead. Collapsed by default (JS sets
   .dora-iface-section-body[hidden] on init) -- this flyout grew to 5 sections and always
   dumping the whole list read as clutter; open one at a time instead. */
.dora-interface-flyout > button.dora-iface-section-toggle {
  justify-content: space-between;
  min-height: 30px;
  padding: 6px;
  margin-top: 4px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--dora-text-color);
}

.dora-interface-flyout > button.dora-iface-section-toggle:first-child {
  margin-top: 0;
}

.dora-interface-flyout > button.dora-iface-section-toggle svg {
  width: 12px;
  height: 12px;
  flex: none;
  transition: transform 0.2s ease-out;
}

.dora-interface-flyout > button.dora-iface-section-toggle.dora-iface-section-open svg {
  transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
  .dora-interface-flyout > button.dora-iface-section-toggle svg { transition: none; }
}

.dora-iface-section-body {
  margin: 2px 0 6px;
}

.dora-iface-section-body[hidden] {
  display: none;
}

/* Breathing room above Commit regardless of which section happens to be open right
   above it -- was sitting flush against whatever content preceded it. */
.dora-interface-flyout > button.dora-iface-commit {
  margin-top: 8px;
}

/* Commit + Reset all pinned to the bottom of the scrolling flyout.
   This panel caps its own height and scrolls internally (see the max-height rule above and
   _positionFlyout's room-below clamp), so on a short viewport these two -- the only controls
   that persist anything, and deliberately outside the collapsible sections so they always
   apply -- ended up below the fold, pushed further down every time a section was expanded.
   Sticky keeps them on screen at any scroll position instead.
   `bottom: 42px` on Commit is Reset-all's own height, so the two stack rather than overlap.
   `background: inherit` takes the flyout's own background, whatever the active theme and
   panel-style set it to (glass fill or a solid custom colour) -- no per-theme constant to keep
   in sync -- so rows scrolling underneath do not read through them. */
.dora-interface-flyout > button.dora-iface-commit,
.dora-interface-flyout > button[data-action="interface-reset-all"] {
  position: sticky;
  z-index: 1;
}
.dora-interface-flyout > button.dora-iface-commit { bottom: 42px; }
.dora-interface-flyout > button[data-action="interface-reset-all"] {
  bottom: 0;
  background: inherit;
}
/* The disabled Commit is a translucent grey; composite it OVER the inherited panel background
   (as a gradient layer, which paints above background-color) rather than hardcoding a per-theme
   opaque grey, so it stays opaque without a second dark-mode rule. */
.dora-interface-flyout > button.dora-iface-commit:disabled {
  background-color: inherit;
  background-image: linear-gradient(rgba(120, 128, 136, 0.14), rgba(120, 128, 136, 0.14));
}

/* Checkbox row: label left, checkbox right */
.dora-iface-check-field {
  display: flex;
  align-items: center;
  justify-content: space-between;
  cursor: pointer;
}

.dora-iface-check-field input[type="checkbox"] {
  width: 16px;
  height: 16px;
  cursor: pointer;
  accent-color: #9fbe0e;
}

/* Color row: label+reset left, swatch right */
.dora-iface-color-field {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.dora-iface-color-swatch {
  width: 44px;
  height: 22px;
  padding: 1px 2px;
  border: 1px solid rgba(26, 28, 30, 0.18);
  border-radius: 6px;
  background: none;
  cursor: pointer;
}

.dora-iface-name {
  color: var(--dora-text-color);
}

.dora-iface-out-group {
  display: flex;
  align-items: center;
  gap: 6px;
}

.dora-iface-reset {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: #8b9299;
  cursor: pointer;
}

.dora-iface-reset svg {
  width: 12px;
  height: 12px;
}

.dora-iface-reset:hover {
  border-color: rgba(159, 190, 14, 0.4);
  background: rgba(210, 251, 20, 0.14);
  color: #4d5f00;
}

.dora-interface-flyout > button {
  width: 100%;
  min-height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 0 10px;
  border: 1px solid transparent;
  border-radius: 9px;
  background: transparent;
  color: #3f464d;
  cursor: pointer;
}

.dora-interface-flyout > button:hover {
  border-color: rgba(159, 190, 14, 0.4);
  background: rgba(210, 251, 20, 0.14);
  color: #4d5f00;
}

/* Commit: the one action that persists (everything else here only live-previews), so it reads
   as the primary action -- accent-filled like every other Commit button in this app (decal,
   connector, flexicut). Disabled (nothing uncommitted) drops back to a flat, inert look. */
.dora-interface-flyout > button.dora-iface-commit {
  border-color: #a5c50c;
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}
.dora-interface-flyout > button.dora-iface-commit:hover:not(:disabled) {
  background: var(--accent);
  filter: brightness(0.97);
  color: var(--accent-ink);
}
.dora-interface-flyout > button.dora-iface-commit:disabled {
  border-color: transparent;
  background: rgba(120, 128, 136, 0.14);
  color: #8a9098;
  font-weight: 500;
  cursor: default;
  opacity: 1;
}

/* Shading + Paint topbar flyouts (Session 1 shells,
   docs/features/topbar-lighting-paint/04_PLAN.md). Own position:fixed block
   per flyout, own button styling, own trigger-button sizing -- deliberately
   NOT joined onto the existing .dora-shapes-flyout/.dora-detail-flyout/
   .dora-cut-actions comma-groups above, or onto .dora-workspace-tools >
   button, .dora-shapes-tool > button, ... -- 03_CONSTRAINTS.md §2 requires
   new selectors only, never editing an existing rule's selector list, so
   every property below is duplicated from those existing recipes rather
   than shared with them. */
.dora-shading-flyout,
.dora-paint-flyout,
.dora-render-flyout {
  position: fixed;
  z-index: 40;
  max-height: calc(100dvh - 40px);
  overflow-y: auto;
  padding: 8px;
  border: 1px solid var(--glass-border);
  border-radius: 14px;
  background: rgba(250, 251, 252, 0.9);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  box-shadow: 0 16px 42px rgba(44, 51, 63, 0.18), inset 0 1px 0 #fff;
}

/* The Render-image flyout (PNG export, Ambient occlusion, Bloom and Shadow --
   all moved out of Shading into their own tool). */
.dora-render-flyout {
  width: 208px;
}

/* Same tighter cap as .dora-shading-flyout/.dora-paint-flyout below, and for
   the same reason (see those comments): with Ambient occlusion + Bloom +
   Shadow all showing their fields at once, this flyout is tall enough to hit
   the shared "calc(100dvh - 40px)" cap, which would push it up over the
   topbar via _positionFlyout's bottom-edge clamp. Reserving ~100px up front
   avoids re-discovering that bug a third time. Class repeated for
   specificity over the shared group rule, per this file's cascade-anomaly
   note. */
.dora-render-flyout.dora-render-flyout {
  max-height: calc(100dvh - 100px);
}

.dora-shading-flyout {
  width: 240px;
}

/* Tighter height cap than the shared "calc(100dvh - 40px)" above. With 4
   light rows the Shading flyout is the only one that gets tall enough to
   hit the cap, and at the shared value _positionFlyout's bottom-edge clamp
   (top = min(button.bottom + 10, innerHeight - height - 8)) pushed it UP
   past the topbar -- covering the very Shading button used to close it
   again (measured: 760px tall, clamped to y=32, over a topbar spanning
   14-70). Reserving ~100px keeps the clamp from ever winning, so the
   flyout stays anchored below the bar and scrolls internally instead.
   Class repeated for specificity over the shared group rule, per this
   file's cascade-anomaly note. */
.dora-shading-flyout.dora-shading-flyout {
  max-height: calc(100dvh - 100px);
}

.dora-paint-flyout {
  width: 208px;
}

/* Same tighter cap as .dora-shading-flyout, and for the same reason: once
   the channel, value and fill sections were all present this flyout got
   tall enough that _positionFlyout's bottom-edge clamp pushed it up over
   the topbar (measured 560px tall clamped to y=32 against a topbar
   spanning 14-70). Reserving ~100px keeps the clamp from winning so it
   stays anchored below the bar and scrolls internally. Class repeated for
   specificity over the shared group rule, per this file's cascade-anomaly
   note. */
.dora-paint-flyout.dora-paint-flyout {
  max-height: calc(100dvh - 100px);
}

.dora-shading-flyout[hidden],
.dora-paint-flyout[hidden] {
  display: none;
}

.dora-shading-flyout strong,
.dora-paint-flyout strong {
  display: block;
  padding: 4px 6px 8px;
  color: var(--text-primary);
  font-size: 12px;
  font-weight: 600;
}

/* MODE segmented control -- two buttons side by side (01_UI_UX.md §4), not
   the single-column full-width action list every other flyout's buttons
   use, so it gets its own local button styling instead of the shared
   full-width recipe. Active state uses --toolbar-active-* (NOT --accent) --
   01_UI_UX.md §2 reserves the lime accent for nav-cube/Choose-file/Symmetry. */
.dora-shading-mode {
  display: flex;
  gap: 4px;
  padding: 2px;
  border-radius: 10px;
  background: rgba(26, 28, 30, 0.05);
}

.dora-shading-mode button {
  flex: 1;
  min-height: 36px;
  padding: 0 8px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: #3f464d;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
}

.dora-shading-mode button:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.6);
}

.dora-shading-mode button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}

.dora-shading-mode button:disabled {
  color: #b7bcc1;
  cursor: not-allowed;
  opacity: 0.5;
}

/* Paint color swatch -- native <input type="color">, restyled to read as a
   swatch card instead of the browser-default rectangle. */
.dora-paint-color-swatch {
  width: 100%;
  height: 42px;
  padding: 0;
  border: 1px solid rgba(26, 28, 30, 0.1);
  border-radius: 9px;
  background: none;
  cursor: pointer;
}

.dora-paint-color-swatch::-webkit-color-swatch-wrapper {
  padding: 4px;
}

.dora-paint-color-swatch::-webkit-color-swatch {
  border: none;
  border-radius: 6px;
}

.dora-paint-color-swatch:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

/* Trigger-button sizing for the two new topbar tool groups -- duplicated
   from ".dora-workspace-tools .dora-workspace-group > button, .dora-shapes-tool > button, ..."
   (dora-ux.css, above) rather than joining that comma-list. Shading/Paint's
   triggers are one DOM level under .dora-topbar (wrapped in their own
   .dora-X-tool div, same shape as .dora-shapes-tool/.dora-cut-tool/etc.),
   so the existing ".dora-topbar > button" direct-child rule (which only
   ever matched Load/Export/Undo/Redo/X-ray/Hide/Isolate -- the topbar/
   bottombar children with no wrapper div) does not reach them either. */
.dora-shading-tool > button,
.dora-paint-tool > button {
  width: calc(56px * var(--dora-ui-scale));
  min-width: calc(56px * var(--dora-ui-scale));
  height: calc(56px * var(--dora-ui-scale));
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}

.dora-shading-tool > button svg,
.dora-paint-tool > button svg {
  width: calc(22px * var(--dora-ui-scale));
  height: calc(22px * var(--dora-ui-scale));
}

.dora-shading-tool > button:hover:not(:disabled),
.dora-paint-tool > button:hover:not(:disabled) {
  border-color: var(--toolbar-hover-border);
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon-hover);
  box-shadow: var(--toolbar-hover-shadow);
}

.dora-shading-tool > button.dora-active,
.dora-paint-tool > button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}

.dora-shading-tool > button:disabled,
.dora-paint-tool > button:disabled {
  color: #9da3a9;
  cursor: not-allowed;
  opacity: 0.42;
}

@media (prefers-reduced-motion: no-preference) {
  .dora-workspace-tools button {
    transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease,
      box-shadow 160ms ease, transform 120ms ease;
  }
}

@media (max-width: 760px) {
  /* "body." prefix, same reason as the desktop rule above -- dora.css has
     its own mobile ".dora-mode #viewport > div:has(.nav-cube-face) { right:
     15px }" (line ~1269) at this exact breakpoint/selector, and this rule
     now also sets "right" (previously only "left", so no property overlap
     existed). Ties aren't safe to rely on in this file; boost wins outright. */
  body.dora-mode #viewport > div:has(.nav-cube-face) {
    top: 14px !important;
    right: 14px !important;
  }

  /* .dora-topbar's default centering (left: 50% + translateX(-50%)) put its
     right edge under the nav-cube once enough items joined (6 items well
     past 281px wide, centered on a 375px viewport runs past the cube's left
     edge). Was anchored past the cube on its OTHER side (left:88px) back
     when the cube was left-positioned; now that the cube moved to the right
     (see the #viewport > div:has(.nav-cube-face) rule above), anchor left
     instead so the bar's box stays clear of the cube's now-right-side
     84x84 footprint. Content this wide was always going to need the bar's
     existing overflow-x:auto on narrow phones regardless of exact position. */
  .dora-topbar {
    left: 8px;
    transform: none;
    max-width: calc(100vw - 96px);
  }

  .dora-workspace-tools {
    top: 92px;
    left: 8px;
    max-height: calc(100dvh - 108px);
  }

  /* Radius/Intensity (moved into this rail, see GuiDoraPanel.js init())
     are full 142px vertical faders sized for desktop -- at that height,
     two of them plus the rest of the rail's buttons push total content
     past the rail's own max-height cap above, overlapping .dora-bottombar
     and the mobile sculpt-tool rail underneath (confirmed live: rail
     bottom edge at 796px collided with both; an initial 84px/80px
     compaction attempt still collided with .dora-bottombar at 726px vs
     its top at ~664px). .dora-workspace-tools stays VERTICAL on mobile
     (unlike the right rail, which flips to a horizontal bar), so this
     doesn't need that rail's full 44px-tall compaction, just enough to
     clear the ~564px budget between the rail's top:92 and
     .dora-bottombar's top (~664) once the other 8 buttons/dividers
     (~426px) are accounted for. */
  .dora-mode .dora-workspace-tools .nm-slidercell {
    height: 48px;
  }

  .dora-mode .dora-workspace-tools .nm-slider {
    height: 44px;
  }

  /* .dora-fix-active (the sculpt-tool rail) moves to a bottom-anchored
     horizontal bar on mobile per user request, instead of the right-anchored
     vertical column it uses on desktop. width: auto + the inset's left/right
     margins let it span the available width (overriding the base rule's
     fixed width: 58px); height is left to size to content (~56px: 44px
     cells + 6px top/bottom padding) via the base rule's height: auto.

     Selector repeats .dora-fix-active (valid CSS -- same specificity boost
     as e.g. .btn.btn) to reliably outrank the base (non-media) rule at
     line ~158, which shares this rule's nominal 4-class specificity and
     sits earlier in the file. Empirically, a same-specificity !important
     override placed later in the cascade (including a fresh <style> tag
     appended at runtime) was NOT winning against that base rule for
     top/bottom/max-height in this app's build -- verified live: only
     boosting specificity (not source order) fixed it. Root cause not fully
     isolated (no @layer usage in any stylesheet, media query genuinely
     matches, no inline styles involved); this is the confirmed-working fix. */
  .dora-mode .gui-sidebar.dora-rightbar.dora-fix-active.dora-fix-active {
    inset: auto 8px 12px 8px !important;
    width: auto !important;
    min-width: 0 !important;
    max-height: 72px !important;
  }

  /* Flip the tool rack from a vertical column to a horizontal, swipeable
     strip -- 14 icon cells + 2 sliders don't come close to fitting a phone
     width, so this scrolls sideways instead of vertically (same hidden-
     scrollbar treatment as .dora-workspace-tools/.dora-topbar). Cells keep
     their fixed 44x44 size regardless of axis (both dimensions are set
     explicitly on .nm-tool-cell), so no per-cell changes are needed. */
  .dora-mode .gui-sidebar.dora-rightbar.dora-fix-active.dora-fix-active .nm-rightbar-inner {
    flex-direction: row !important;
    align-items: center !important;
    max-height: none !important;
    max-width: 100% !important;
    overflow-x: auto !important;
    overflow-y: hidden !important;
    -webkit-overflow-scrolling: touch;
  }

  .dora-shapes-flyout {
    width: 174px;
  }

  .dora-detail-flyout {
    width: 180px;
  }

  /* .dora-bottombar would otherwise sit at the same bottom:14px as
     desktop, directly under the sculpt-tool rail -- which on mobile is
     ALSO bottom-anchored (bottom: 12px, ~72px tall, see .dora-fix-active
     above). Sit above it instead: 12 + 72 + 8 clearance = 92px.
     left/transform: the desktop rule below this breakpoint left-anchors
     both bars (14px, left-corner dock matching the reference). Mobile
     wasn't part of that reference and .dora-topbar already has its own
     cube-driven left-anchor at this breakpoint (left:8px above) that would
     make a left-anchored .dora-bottombar sit under it too -- untested and
     out of scope here, so pin the prior centered position explicitly
     instead of silently inheriting the new desktop default. */
  .dora-bottombar {
    bottom: 92px;
    left: 50%;
    transform: translateX(-50%);
  }
}

/* FaceGroup editor: branded light-glass workspace */
#nm-mesh-editor-panel {
  inset: 16px 16px 16px auto;
  width: min(320px, calc(100vw - 32px));
  padding: 18px;
  border: 1px solid rgba(255, 255, 255, 0.76);
  border-radius: 22px;
  background:
    radial-gradient(circle at 14% 0%, rgba(255, 255, 255, 0.84), transparent 32%),
    rgba(248, 249, 251, 0.76);
  -webkit-backdrop-filter: blur(16px) saturate(145%);
  backdrop-filter: blur(16px) saturate(145%);
  box-shadow:
    0 16px 46px rgba(44, 51, 63, 0.16),
    inset 0 1px 0 rgba(255, 255, 255, 0.92);
  color: #1a1c1e;
  font-family: var(--font-sans);
}

.nmep-header {
  margin-bottom: 14px;
}

.nmep-header-title {
  color: #1a1c1e;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.01em;
  text-transform: none;
}

.nmep-header-badge {
  padding: 4px 7px;
  border-radius: 999px;
  background: rgba(17, 18, 20, 0.06);
  color: #5b6067;
  font-size: 11px;
}

.nmep-complete-actions {
  display: grid;
  grid-template-columns: 88px minmax(0, 1fr);
  gap: 8px;
  margin-bottom: 20px;
}

.nmep-back-btn,
.nmep-done-btn {
  width: 100%;
  min-height: 44px;
  margin: 0;
  border-radius: 12px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
}

.nmep-back-btn {
  border: 1px solid rgba(17, 18, 20, 0.08);
  background: rgba(255, 255, 255, 0.5);
  color: #31363c;
}

.nmep-done-btn {
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
}

.nmep-back-btn:hover,
.nmep-done-btn:hover {
  filter: brightness(0.97);
}

.nmep-back-btn:focus-visible,
.nmep-done-btn:focus-visible,
.nmep-action-btn:focus-visible,
.nmep-slider:focus-visible {
  outline: 2px solid #1a1c1e;
  outline-offset: 2px;
}

.nmep-section-label {
  margin-bottom: 14px;
  color: #5b6067;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
}

.nmep-divider {
  margin: 18px 0;
  background: rgba(17, 18, 20, 0.07);
}

.nmep-slider-row {
  margin-bottom: 14px;
  padding: 10px 11px 8px;
  border: 1px solid rgba(17, 18, 20, 0.05);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.34);
}

.nmep-slider-header {
  margin-bottom: 5px;
}

.nmep-icon {
  color: #5b6067;
}

.nmep-slider-label {
  color: #31363c;
  font-size: 13px;
}

.nmep-slider-value {
  color: #5b6067;
  font-family: var(--font-mono);
  font-size: 12px;
}

.nmep-slider {
  height: 28px;
}

.nmep-slider::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 999px;
  background: linear-gradient(
    to right,
    var(--accent) var(--pct, 0%),
    rgba(17, 18, 20, 0.1) var(--pct, 0%)
  );
}

.nmep-slider::-webkit-slider-thumb {
  width: 18px;
  height: 18px;
  margin-top: -7px;
  border: 2px solid #fff;
  background: var(--accent);
  box-shadow: 0 2px 8px rgba(44, 51, 63, 0.2);
}

.nmep-slider::-moz-range-track {
  height: 4px;
  background: rgba(17, 18, 20, 0.1);
}

.nmep-slider::-moz-range-progress {
  height: 4px;
  background: var(--accent);
}

.nmep-slider::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border: 2px solid #fff;
  background: var(--accent);
  box-shadow: 0 2px 8px rgba(44, 51, 63, 0.2);
}

.nmep-brush-param {
  display: none;
}

#nm-mesh-editor-panel.nmep-brush-mode .nmep-auto-param {
  display: none;
}

#nm-mesh-editor-panel.nmep-brush-mode .nmep-brush-param {
  display: block;
}

.nmep-btn-grid {
  gap: 8px;
}

.nmep-action-btn {
  min-height: 58px;
  padding: 10px;
  border: 1px solid rgba(17, 18, 20, 0.07);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.4);
  color: #5b6067;
  font-size: 12px;
}

.nmep-action-btn span,
.nmep-color-sublabel {
  color: #5b6067;
}

.nmep-action-btn:hover {
  border-color: rgba(17, 18, 20, 0.14);
  background: rgba(255, 255, 255, 0.72);
}

.nmep-action-btn.nm-on {
  border-color: rgba(210, 251, 20, 0.9);
  background: rgba(210, 251, 20, 0.26);
  color: #1a1c1e;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.64);
}

.nmep-action-btn.nm-on span {
  color: #1a1c1e;
}

.nmep-color-btn {
  justify-content: center;
}

.nmep-color-name {
  color: #31363c;
}

@media (max-width: 760px) {
  #nm-mesh-editor-panel {
    inset: auto 10px 10px 10px;
    width: auto;
    max-height: min(54dvh, 520px);
  }
}

/* Per-piece color swatch, added to the Pieces list (.dora-parts row).
   ".dora-part" 's grid-template-columns is defined in dora.css (FROZEN --
   3 columns: name, eye, delete) via a single-class ".dora-part" selector.
   "body.dora-mode .dora-part" here is 1 type + 2 classes, unambiguously
   higher specificity than that single class, not a same-specificity tie,
   so this is a clean override rather than something that depends on
   source order (per this file's cascade-anomaly note).

   History: 4 columns (name, color, eye, delete) -> 5 (checkbox added first) -> 6 (tris added
   after name). Checkbox/color/eye/delete were tightened twice (34->30->28) trying to win the
   name column more room, plus the flyout itself was widened 248->260px -- still "can't even
   see the name" per the user. The actual fix wasn't more shrinking: it was FEWER columns.
   Matching a reference screenshot exactly now: checkbox moved from first to just-before-eye,
   and the color swatch + delete button (previously 2 always-visible columns) folded into one
   new "..." overflow button (_togglePartMenu) that opens a small popover anchored to the row
   (.dora-part-menu-popover, position:absolute -- see ".dora-part" below). 6 columns -> 5,
   and the name track picks up everything that used to belong to the color column. */
body.dora-mode .dora-part {
  grid-template-columns: minmax(0, 1fr) auto 22px 28px 28px;
  position: relative; /* anchors .dora-part-menu-popover */
}

/* Every row is position:relative with no z-index, so plain DOM order decides which row
   paints on top -- a row further down the list would otherwise cover an OPEN popover
   belonging to a row above it. Lifts the whole open row (popover included) above every
   sibling row while its menu is showing. */
.dora-part.dora-part-menu-open {
  z-index: 6;
}

/* "..." button -- 5th grid column. dora.css's own ".dora-part > .dora-part-eye,
   .dora-part > .dora-part-delete { justify-content:center; padding:0; color:var(--dora-muted); }"
   only names those two classes, so a plain new class here needs the same treatment repeated
   rather than trying to piggyback on that selector. */
.dora-part > .dora-part-menu-btn {
  justify-content: center;
  padding: 0;
  color: var(--dora-muted);
}

.dora-part > .dora-part-menu-btn:hover {
  color: var(--text-primary, #23272b);
}

/* The copy+color+delete popover this button opens. position:fixed, left/top set by JS
   (_togglePartMenu) from the "..." button's own rect -- floats beside the Scene panel
   instead of dropping below the row inside it (see _togglePartMenu's comment for why).
   background/text use the SAME Interface-tab tokens every other Dora panel does (was
   hardcoded #fff/--text-primary -- this popover just never got wired up). */
.dora-part-menu-popover {
  position: fixed;
  /* Was 5 -- position:fixed escapes .dora-parts-flyout's box geometrically (see
     _togglePartMenu's own comment: left = button rect.right+4, top = button rect.top), but
     z-index is compared globally regardless of DOM nesting. .dora-parts-flyout's OWN rule
     just below (~line 1921) says 40, but that's stale: a LATER !important rule (the
     Trim/Split-overlay tier comment, ~line 4353) bumps every persistent Dora chrome
     container -- .dora-parts-flyout included -- to 160. 5 (and a first attempt at 45) both
     lost to that, painting the flyout OVER this popover wherever they visually overlapped --
     reads as "the popover renders inside the panel" even though its coordinates are already
     correct. 200 matches this file's own next tier up (the Trim/Split/Mirror floating bars,
     same "sits above the persistent 160 chrome" need), clearing 160 with the same margin
     that tier already established. */
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 128px;
  padding: 6px;
  border: 1px solid rgba(26, 28, 30, 0.08);
  border-radius: 10px;
  background: var(--dora-panel-color);
  box-shadow: 0 10px 24px rgba(44, 51, 63, 0.18);
}

.dora-part-menu-popover[hidden] { display: none; }

.dora-part-menu-copy,
.dora-part-menu-color,
.dora-part-menu-delete {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 30px;
  padding: 0 6px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--dora-text-color);
  font-size: 12px;
  cursor: pointer;
}

.dora-part-menu-copy:hover,
.dora-part-menu-color:hover,
.dora-part-menu-delete:hover {
  background: rgba(26, 28, 30, 0.06);
}

.dora-part-menu-delete:hover {
  color: #a62c35; /* this app's danger color -- Delete is the one destructive action here */
}

.dora-part-menu-copy svg,
.dora-part-menu-delete svg {
  width: 15px;
  height: 15px;
  flex: none;
}

/* Swatch shrinks from its 28px grid-cell size (still used, just relocated into this
   popover) to sit comfortably next to its own text label instead of dominating the row. */
.dora-part-menu-color .dora-part-color.dora-part-color {
  width: 20px;
  height: 20px;
  flex: none;
}

/* Flex child of .dora-part-name-cell > button now (see comment above), not its own
   grid column -- flex: none below so it can't be squeezed by the name text's ellipsis. */
.dora-part-tag {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 5px;
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
}

.dora-part-tag-a {
  background: rgba(210, 251, 20, 0.22);
  color: #6a8000;
  box-shadow: inset 0 0 0 1px rgba(159, 190, 14, 0.55);
}

.dora-part-tag-b {
  background: rgba(230, 53, 59, 0.16);
  color: #c62828;
  box-shadow: inset 0 0 0 1px rgba(230, 53, 59, 0.55);
}

.dora-part-tris {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 0 4px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: #8a9098;
  white-space: nowrap;
}

/* Resizes the existing .dora-paint-color-swatch look (border/radius/
   webkit-color-swatch styling, added in the Shading/Paint session) down
   to the 28x28 icon-button footprint every other Pieces-row control uses
   (tightened from 34x34, then 30x30 -- see the .dora-part grid comment above), instead
   of its full-width 42px-tall flyout treatment. Both classes repeated so
   this reliably outranks the single-class ".dora-paint-color-swatch" rule
   on the overlapping width/height properties, per the same cascade-anomaly
   precaution. */
.dora-paint-color-swatch.dora-part-color {
  width: 28px;
  height: 28px;
}

/* Multi-select checkbox (touch/no-keyboard alternative to shift-click).
   The whole 28px grid cell is the tap target, not just the browser's native
   checkbox render -- the entire point of this control is being reliably
   tappable without a keyboard modifier, so the label fills the cell instead
   of just wrapping a small native box in a sliver of it. */
.dora-part-multi {
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.dora-part-multi input {
  width: 16px;
  height: 16px;
  margin: 0;
  cursor: pointer;
  accent-color: var(--toolbar-active-bg, #d2fb14);
}

/* Name-select + Rename now share the name column as two flex siblings
   (frozen dora.css's ".dora-part > button" styling -- height/padding/flex
   layout -- still applies to both, since both are still direct <button>
   children of .dora-part; this wrapper only arranges them side by side
   without being a grid cell of its own). */
.dora-part-name-cell {
  display: flex;
  align-items: center;
  min-width: 0;
  overflow: hidden;
}

/* dora.css's ".dora-part > button" (height/display:flex/align-items/gap/padding, plus a
   "span:last-child" ellipsis-truncation rule right below) was written when the name-select
   button was a DIRECT child of .dora-part. It no longer is -- .dora-part-name-cell was added
   later to hold it alongside the rename button -- so that whole frozen rule silently stopped
   matching. The button fell back to native <button> layout (no gap between the cube icon and
   the name, no ellipsis truncation), so a name that didn't fit WRAPPED to 2 lines instead of
   truncating, and the taller wrapped cell spilled into the tag/tris columns beside it. Same
   values as the dead dora.css rule, just re-scoped to where the button actually lives now. */
.dora-part-name-cell > [data-action="part-select"] {
  flex: 1 1 auto;
  min-width: 0;
  height: 34px;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 0 6px;
  border: 0;
  background: transparent;
  text-align: left;
}

/* The Boolean role tag (.dora-part-tag, flex:none above) can follow the name span
   as a 3rd child now, so this targets the name span by class, not :last-child --
   :last-child would style the TAG's text instead of the name's on an A/B row. */
.dora-part-name-cell > [data-action="part-select"] .dora-part-name-text {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* .dora-part-cube is 12x12 in frozen dora.css, sized for the old 4-column layout.
   This narrow panel's name column has far less room to spare now that 3 more
   columns (checkbox/tag/tris) sit ahead of it -- shrink the icon a touch so it
   doesn't visually outweigh an 11px name at this width ("logo too big"). Shrunk
   again (9px -> 7px) plus the name button's own gap/padding trimmed alongside
   (6px->4px, 7px->6px) -- user: "too little space for text" -- together they
   hand back a few more px to the name before its ellipsis kicks in. */
.dora-part-name-cell > [data-action="part-select"] .dora-part-cube {
  width: 7px;
  height: 7px;
}

/* Hover-reveal, not always-on: at 42px the name button had only ~13px left for text
   after the cube icon + this button's own fixed 26px, on a name/tag row even less.
   Collapsed to 0 by default hands that 26px back to the name; :focus-visible keeps
   it keyboard-reachable without needing a hover. */
.dora-part-rename {
  flex: none;
  width: 0;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 !important;
  overflow: hidden;
  opacity: 0;
  color: var(--dora-muted, #7a8087);
}

.dora-part:hover .dora-part-rename,
.dora-part-rename:focus-visible {
  width: 26px;
  opacity: 1;
}

.dora-part-rename:hover {
  color: var(--text-primary, #23272b);
}

/* Hidden-piece feedback. Previously the eye button rendered the same open-eye icon
   whether the piece was shown or hidden -- the only way to tell was to remember which
   way you last clicked it, or hover the tooltip. Dim the row so a hidden piece visibly
   recedes in the list (matches showing/hidden state at a glance, like a Figma/Blender
   layer list), but the eye-off icon and Delete stay at full opacity -- the indicator
   and the destructive action are exactly what shouldn't fade with the rest of the row. */
.dora-part.dora-part-hidden {
  opacity: 0.55;
}

.dora-part.dora-part-hidden .dora-part-eye,
.dora-part.dora-part-hidden .dora-part-delete {
  opacity: 1;
}

/* Red is this app's established danger/cancel color (.dora-tool-danger, FlexiCut's
   Cancel button) -- reused here rather than a new hue so "hidden" reads through the
   same vocabulary as every other warning state in this app.
   ".dora-part-eye-off" alone (1 class) LOSES to dora.css's ".dora-part > .dora-part-eye"
   (2 classes, color: var(--dora-muted)) on real specificity, not a same-specificity tie --
   confirmed live, the icon stayed grey. Qualified with the row + button classes for an
   unambiguous 3-class win instead. */
.dora-part .dora-part-eye.dora-part-eye-off {
  color: #a62c35;
}

.dora-part .dora-part-eye.dora-part-eye-off:hover {
  color: #8a232b;
}

.dora-part-rename svg {
  width: 14px;
  height: 14px;
}

/* Fills the name cell while a rename is in progress (see _renamePart) --
   sized to match the row rather than the browser's default text-input
   chrome, which would look like a foreign element dropped into the list. */
.dora-part-rename-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 24px;
  margin: 0 9px;
  padding: 0 6px;
  border: 1px solid var(--toolbar-active-border, #9fbe0e);
  border-radius: 6px;
  background: #fff;
  color: var(--text-primary, #23272b);
  font: inherit;
  font-size: 12px;
}

/* Paint channel toggles (Session 3 / MP-2) -- three equal-width buttons,
   same segmented-control recipe as .dora-shading-mode above, duplicated
   rather than sharing its selector list because 03_CONSTRAINTS.md §2
   allows only NEW selectors, never editing an existing rule's selector
   list. Active state uses --toolbar-active-* (not --accent), per
   01_UI_UX.md §2. */
.dora-paint-channels {
  display: flex;
  gap: 4px;
  padding: 2px;
  border-radius: 10px;
  background: rgba(26, 28, 30, 0.05);
}

.dora-paint-channels button {
  flex: 1;
  min-height: 34px;
  padding: 0 4px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: #3f464d;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
}

.dora-paint-channels button:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.6);
}

.dora-paint-channels button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}

.dora-paint-channels button:disabled,
.dora-paint-all-channels:disabled {
  color: #b7bcc1;
  cursor: not-allowed;
  opacity: 0.5;
}

.dora-paint-all-channels {
  width: 100%;
  min-height: 34px;
  margin-top: 6px;
  padding: 0 10px;
  border: 1px solid rgba(26, 28, 30, 0.1);
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.55);
  color: #3f464d;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
}

.dora-paint-fill,
.dora-paint-eyedropper {
  width: 100%;
  min-height: 34px;
  margin-top: 6px;
  padding: 0 10px;
  border: 1px solid rgba(26, 28, 30, 0.1);
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.55);
  color: #3f464d;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
}

.dora-paint-fill:hover:not(:disabled),
.dora-paint-eyedropper:hover:not(:disabled) {
  border-color: rgba(159, 190, 14, 0.4);
  background: rgba(210, 251, 20, 0.14);
  color: #4d5f00;
}

.dora-paint-eyedropper.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}

.dora-paint-fill:disabled,
.dora-paint-eyedropper:disabled {
  color: #b7bcc1;
  cursor: not-allowed;
  opacity: 0.5;
}

.dora-paint-fill-note {
  display: block;
  padding: 8px 6px 3px;
  color: #60676e;
  font-size: 9px;
  line-height: 1.4;
}

.dora-paint-all-channels:hover:not(:disabled) {
  border-color: rgba(159, 190, 14, 0.4);
  background: rgba(210, 251, 20, 0.14);
  color: #4d5f00;
}

/* A channel's value slider is hidden while that channel is off. This
   NEEDS to be explicit: dora.css (frozen) sets ".dora-mask-check,
   .dora-mask-field { display: grid }", and an author `display` declaration
   beats the browser's built-in `[hidden] { display: none }` UA rule -- so
   without this the hidden fields would still render. Doubled class for
   specificity over that frozen single-class rule, per this file's
   cascade-anomaly note. */
.dora-mask-field.dora-mask-field[hidden],
.dora-mask-group.dora-mask-group[hidden] {
  display: none;
}

/* Light list (Session 4 / Milestone 2). Row = eye toggle | name | intensity
   readout, mirroring the Pieces list's row grammar so the two read as the
   same kind of control. Selected row uses --toolbar-active-* (NOT --accent,
   which 01_UI_UX.md §2 reserves for nav-cube/Choose-file/Symmetry). */
.dora-light-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.dora-light-head-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}

.dora-light-gizmo-toggle {
  min-height: 26px;
  padding: 0 8px;
  border: 1px solid rgba(26, 28, 30, 0.12);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.6);
  color: #3f464d;
  font-size: 10px;
  font-weight: 500;
  cursor: pointer;
}

.dora-light-gizmo-toggle.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
}

.dora-light-gizmo-toggle:disabled {
  color: #b7bcc1;
  cursor: not-allowed;
  opacity: 0.5;
}

.dora-light-add {
  width: 26px;
  height: 26px;
  flex: none;
  padding: 0;
  border: 1px solid rgba(26, 28, 30, 0.12);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.6);
  color: #3f464d;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}

.dora-light-add:hover:not(:disabled) {
  border-color: rgba(159, 190, 14, 0.5);
  background: rgba(210, 251, 20, 0.16);
  color: #4d5f00;
}

.dora-light-add:disabled,
.dora-light-actions button:disabled {
  color: #b7bcc1;
  cursor: not-allowed;
  opacity: 0.5;
}

.dora-light-list {
  display: grid;
  gap: 4px;
  margin: 8px 0 10px;
}

.dora-light-row {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) auto;
  align-items: center;
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  background: rgba(255, 255, 255, 0.42);
}

.dora-light-row.dora-selected {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  box-shadow: var(--toolbar-active-shadow);
}

.dora-light-row > button {
  min-height: 30px;
  display: flex;
  align-items: center;
  padding: 0;
  border: 0;
  background: transparent;
  color: #3f464d;
  font-size: 11px;
  cursor: pointer;
}

.dora-light-name-btn {
  overflow: hidden;
  padding: 0 4px !important;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dora-light-eye {
  justify-content: center;
  color: #a8adb3;
}

/* Eye lit = light contributing. Reads as on/off at a glance without
   needing a second icon. */
.dora-light-eye.dora-active {
  color: #6f7a2b;
}

.dora-light-eye svg {
  width: 14px;
  height: 14px;
}

.dora-light-intensity {
  padding: 0 8px 0 4px;
  color: #6d747b;
  font-family: var(--font-mono);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
}

.dora-light-name {
  width: 100%;
  min-height: 30px;
  padding: 0 8px;
  border: 1px solid rgba(26, 28, 30, 0.1);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.68);
  color: var(--text-primary);
  font: inherit;
  font-size: 11px;
}

/* World/Camera attachment toggle -- same segmented recipe as
   .dora-shading-mode / .dora-paint-channels, duplicated rather than
   joined onto their selector lists because 03_CONSTRAINTS.md §2 allows
   only NEW selectors. */
.dora-light-attach {
  display: flex;
  gap: 4px;
  padding: 2px;
  border-radius: 10px;
  background: rgba(26, 28, 30, 0.05);
}

.dora-light-attach button {
  flex: 1;
  min-height: 30px;
  padding: 0 6px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: #3f464d;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
}

.dora-light-attach button:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.6);
}

.dora-light-attach button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}

.dora-light-attach button:disabled {
  color: #b7bcc1;
  cursor: not-allowed;
  opacity: 0.5;
}

.dora-light-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
  margin-top: 10px;
}

.dora-light-actions button {
  min-height: 32px;
  border: 1px solid rgba(26, 28, 30, 0.1);
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.55);
  color: #3f464d;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
}

.dora-light-actions button:hover:not(:disabled) {
  border-color: rgba(159, 190, 14, 0.4);
  background: rgba(210, 251, 20, 0.14);
  color: #4d5f00;
}

.dora-light-actions [data-action="light-delete"]:hover:not(:disabled) {
  border-color: rgba(226, 67, 78, 0.4);
  background: rgba(226, 67, 78, 0.1);
  color: #a4262f;
}

/* "MatCap ignores roughness & metalness" cross-feature hint
   (01_UI_UX.md §5). Matches .dora-remesh-hint's existing treatment. */
.dora-paint-hint {
  display: block;
  padding: 8px 6px 3px;
  border-top: 1px solid rgba(26, 28, 30, 0.08);
  color: #60676e;
  font-size: 9px;
  line-height: 1.4;
}

.dora-paint-hint[hidden] {
  display: none;
}

/* Material presets (Session 11 / MP-5).
   TWO columns, swatch BESIDE the label -- arrived at by measurement, not
   taste. Four columns was tried first and fails: inside this flyout's
   capped width (01_UI_UX.md §6) a 4-across cell is 44px, but "Rubber" and
   "Copper" render ~46px at the 10-11px the other flyout buttons use, so
   the labels bled into the gutters. Dropping the type to 9px made them fit
   and made them unreadable -- these are interactive button labels, not
   footnotes, so shrinking below the sibling channel buttons' 11px is the
   wrong trade. Two columns give ~90px cells, which fit 11px comfortably.
   The dielectric/metal split still reads: MaterialPresets.LIST is ordered
   so the first two rows are the non-metals and the last two the metals.
   Selected cell uses --toolbar-active-*, NOT --accent -- that variable is
   reserved for the nav-cube / Choose-file / Symmetry per 01_UI_UX.md §2. */
.dora-paint-presets {
  display: grid;
  /* minmax(0, 1fr), not a bare 1fr: 1fr floors each track at its
     min-content width, which made the columns unequal. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 3px;
  padding: 2px;
  border-radius: 10px;
  background: rgba(26, 28, 30, 0.05);
}

.dora-paint-preset {
  display: flex;
  align-items: center;
  gap: 7px;
  /* 44px min touch target (both axes clear it: ~90px wide here). */
  min-height: 44px;
  padding: 4px 8px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: #3f464d;
  /* Matches .dora-paint-channels button -- these sit in the same flyout and
     do the same kind of job, so they should not be a different size. */
  font-size: 11px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
}

.dora-paint-preset-swatch {
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  /* Inner ring, not an outer border: Silver and Glossy are near-white and
     would otherwise vanish into the flyout's own light background. */
  box-shadow: inset 0 0 0 1px rgba(26, 28, 30, 0.22);
}

/* font-size MUST be restated here, it does not inherit from the button.
   The frozen yagui.css carries a universal `* { font-size: 13px }`, and a
   rule that matches an element DIRECTLY always beats a value inherited from
   its parent, whatever the specificity -- so this span ignored the 11px on
   .dora-paint-preset and rendered at 13px. The sibling .dora-paint-channels
   buttons dodge this only because their text is a direct child with no span
   of its own. Any future nested element in this UI needs the same treatment. */
.dora-paint-preset-label {
  font-size: 11px;
  line-height: 1;
}

.dora-paint-preset:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.6);
}

.dora-paint-preset.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}

.dora-paint-preset:disabled {
  color: #b7bcc1;
  cursor: not-allowed;
  opacity: 0.5;
}

.dora-paint-preset-note {
  display: block;
  padding: 8px 6px 3px;
  color: #60676e;
  font-size: 9px;
  line-height: 1.4;
}

/* Lighting presets (Session 12 / M10). Text-only pills in a 2-up grid --
   no swatch, unlike the material presets: a lighting setup has no single
   representative colour, and a fake one would imply the preset tints the
   render. The names carry it, and each has a title with what it is for. */
.dora-lighting-presets {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 3px;
  padding: 2px;
  border-radius: 10px;
  background: rgba(26, 28, 30, 0.05);
}

.dora-lighting-preset {
  min-height: 34px;
  padding: 0 8px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: #3f464d;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
}

.dora-lighting-preset:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.6);
}

.dora-lighting-preset:disabled {
  color: #b7bcc1;
  cursor: not-allowed;
  opacity: 0.5;
}

/* No .dora-active state on purpose. A lighting preset is a one-shot that
   hands over ordinary, editable lights -- once one nudge is made the list
   is no longer "the preset", and a highlight that lingered would claim
   otherwise. The material presets DO highlight because there the brush
   either matches the preset's values exactly or it does not. */

.dora-shading-preset-note,
.dora-shading-export-note {
  display: block;
  padding: 8px 6px 3px;
  color: #60676e;
  font-size: 9px;
  line-height: 1.4;
}

.dora-shading-export-note:empty {
  display: none;
}

.dora-shading-export {
  width: 100%;
  min-height: 36px;
  margin-top: 6px;
  padding: 0 10px;
  border: 1px solid rgba(26, 28, 30, 0.1);
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.75);
  color: #23272b;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}

.dora-shading-export:hover:not(:disabled) {
  border-color: rgba(159, 190, 14, 0.4);
  background: rgba(210, 251, 20, 0.14);
  color: #4d5f00;
}

.dora-shading-export:disabled {
  color: #b7bcc1;
  cursor: not-allowed;
  opacity: 0.5;
}

/* Shadow section (Session 9 / M7). Note element only -- the fields reuse the
   existing .dora-mask-field / .dora-mask-check grammar so this section reads
   as part of the same panel rather than a bolted-on extra. */
.dora-shading-shadow-note {
  display: block;
  padding: 8px 6px 3px;
  color: #60676e;
  font-size: 9px;
  line-height: 1.4;
}

.dora-shading-shadow-note[hidden] {
  display: none;
}

/* Bloom section (M11). Note element only -- the fields reuse the existing
   .dora-mask-field grammar, same as the shadow section. */
.dora-shading-bloom-note {
  display: block;
  padding: 8px 6px 3px;
  color: #60676e;
  font-size: 9px;
  line-height: 1.4;
}

.dora-shading-bloom-note[hidden] {
  display: none;
}

/* AO section (M11 SSAO). Note element only -- fields reuse .dora-mask-field. */
.dora-shading-ao-note {
  display: block;
  padding: 8px 6px 3px;
  color: #60676e;
  font-size: 9px;
  line-height: 1.4;
}

.dora-shading-ao-note[hidden] {
  display: none;
}

/* Reset lighting (P1 persistence escape hatch). Quiet, full-width, and
   distinct from the primary Save PNG button above it -- it is a rare,
   destructive-ish action, so it reads as secondary rather than inviting. */
.dora-shading-reset {
  width: 100%;
  min-height: 32px;
  margin-top: 2px;
  padding: 0 10px;
  border: 1px solid rgba(26, 28, 30, 0.12);
  border-radius: 9px;
  background: transparent;
  color: #60676e;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
}

.dora-shading-reset:hover {
  border-color: rgba(26, 28, 30, 0.22);
  background: rgba(26, 28, 30, 0.04);
  color: #23272b;
}

.dora-shading-reset-note {
  display: block;
  padding: 8px 6px 3px;
  color: #7a8087;
  font-size: 9px;
  line-height: 1.4;
}

/* Custom HDRI import (S14). The file input is hidden; its <label> is the
   button. Styled like the secondary Reset button, not the primary Save PNG. */
.dora-shading-hdri-import {
  display: block;
  width: 100%;
  min-height: 32px;
  margin-top: 6px;
  padding: 7px 10px;
  border: 1px solid rgba(26, 28, 30, 0.12);
  border-radius: 9px;
  background: transparent;
  color: #3f464d;
  font-size: 11px;
  font-weight: 500;
  text-align: center;
  cursor: pointer;
  box-sizing: border-box;
}

.dora-shading-hdri-import:hover {
  border-color: rgba(159, 190, 14, 0.4);
  background: rgba(210, 251, 20, 0.14);
  color: #4d5f00;
}

.dora-shading-hdri-note {
  display: block;
  padding: 6px 6px 2px;
  color: #60676e;
  font-size: 9px;
  line-height: 1.4;
}

.dora-shading-hdri-note:empty {
  display: none;
}

/* MatCap material picker + mode-based section visibility (UX refinement).
   In MatCap mode the Lit/PBR sections are inert, so hide every group except
   the MatCap picker, the render-image export, and the reset control. Using
   :not() rather than tagging each Lit group means any future Lit section
   auto-hides in MatCap mode without a new class. Two classes deep so it beats
   frozen dora.css's `.dora-mask-group { display: grid }`. */
.dora-shading-flyout.dora-mode-matcap .dora-mask-group:not(.dora-shading-matcap-group):not(.dora-shading-export-group):not(.dora-shading-reset-group) {
  display: none;
}
/* The MatCap picker is Lit's opposite: hidden unless MatCap mode is active. */
.dora-shading-flyout:not(.dora-mode-matcap) .dora-shading-matcap-only {
  display: none;
}

.dora-matcap-picker {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
  padding: 2px;
}

.dora-matcap-swatch {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1 / 1;
  padding: 3px;
  border: 2px solid transparent;
  border-radius: 12px;
  background: rgba(26, 28, 30, 0.04);
  cursor: pointer;
  overflow: hidden;
}

.dora-matcap-swatch img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 9px;
  pointer-events: none;
}

.dora-matcap-swatch:hover {
  border-color: rgba(26, 28, 30, 0.18);
}

.dora-matcap-swatch.dora-active {
  border-color: var(--toolbar-active-border);
  box-shadow: var(--toolbar-active-shadow);
}

/* Render-image top-bar tool button + flyout header. Wrapped in .dora-render-tool
   like .dora-shading-tool/.dora-paint-tool, so it needs the same button styling
   (the generic .dora-topbar > button rule only hits DIRECT children). */
.dora-render-tool > button {
  width: calc(56px * var(--dora-ui-scale));
  min-width: calc(56px * var(--dora-ui-scale));
  height: calc(56px * var(--dora-ui-scale));
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}

.dora-render-tool > button svg {
  width: calc(22px * var(--dora-ui-scale));
  height: calc(22px * var(--dora-ui-scale));
}

.dora-render-tool > button:hover:not(:disabled) {
  border-color: var(--toolbar-hover-border);
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon-hover);
  box-shadow: var(--toolbar-hover-shadow);
}

.dora-render-tool > button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}

.dora-render-tool > button:disabled {
  color: #9da3a9;
  cursor: not-allowed;
  opacity: 0.42;
}

.dora-render-flyout strong {
  display: block;
  padding: 4px 6px 8px;
  color: var(--text-primary);
  font-size: 12px;
  font-weight: 600;
}

/* Trim + Split popups (GuiSidebarLeft._initTrimPopup / GuiSidebarRight.
   _initSplitPopup) reuse the pre-Dora .nm-mask-popup skin (nomad.css +
   theme-dark.css's #1a1a1a dark panel), which breaks two ways once Dora
   mode is active: the panel is a dark box floating next to every other
   flyout's light glass chrome, and dora.css's unscoped :root --text-primary
   override (#1a1c1e, meant for light chrome) collides with theme-dark.css's
   dark --bg-btn (#1e1e1e) button background -- every non-selected button's
   label renders near-black-on-near-black, effectively invisible. The
   .nm-trimsplit-popup class (added only to these two popups, not the
   Masking tool's own nm-mask-popup) gets its own explicit skin instead of
   depending on tokens shared across both theme layers. !important matches
   theme-dark.css's own specificity; this file loads after it so the later
   rule wins. Active-state tokens (--toolbar-active-*) match every other
   Dora control's "on" styling (.dora-shading-mode button.dora-active etc.)
   instead of the legacy --nm-accent/--accent-border pairing. */
.dora-mode .nm-trimsplit-popup {
  background: var(--dora-panel-color) !important;
  border: 1px solid var(--glass-border) !important;
  border-radius: 14px !important;
  box-shadow: 0 16px 42px rgba(44, 51, 63, 0.18), inset 0 1px 0 #fff !important;
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
}

.dora-mode .nm-trimsplit-popup button {
  background: rgba(255, 255, 255, 0.5) !important;
  border: 1px solid var(--glass-border) !important;
  color: var(--dora-text-color) !important;
}

.dora-mode .nm-trimsplit-popup button:hover {
  background: rgba(255, 255, 255, 0.86) !important;
  border-color: rgba(17, 18, 20, 0.12) !important;
}

.dora-mode .nm-trimsplit-popup button.nm-on {
  background: var(--toolbar-active-bg) !important;
  border: 1px solid var(--toolbar-active-border) !important;
  color: var(--toolbar-active-icon) !important;
  box-shadow: var(--toolbar-active-shadow) !important;
}

/* Cut/Split buttons disable themselves for the ~1-3s manifold boolean (see
   GuiSidebarLeft/GuiSidebarRight's Cut/Split click handlers) -- without a
   visible dimmed state that busy period looks identical to an idle button. */
.dora-mode .nm-trimsplit-popup button:disabled {
  opacity: 0.5 !important;
  cursor: not-allowed !important;
}

.dora-mode .nm-trimsplit-popup .nm-mask-depth-label {
  color: #5b6067 !important;
}

/* Connector stepper (Split popup): a −/+ pair under a count label. Reuses the
   .nm-mask-depth wrapper for spacing, so only the row itself needs styling. */
.dora-mode .nm-trimsplit-popup .nm-split-conn-row {
  display: flex;
  gap: 4px;
  margin-top: 2px;
}

.dora-mode .nm-trimsplit-popup button.nm-split-conn-step {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  line-height: 1;
}

.dora-mode .nm-trimsplit-popup .nm-split-connectors.nm-disabled .nm-mask-depth-label {
  opacity: 0.55;
}

/* "Place manually" tick box -- opts into the interactive connector session. */
.dora-mode .nm-trimsplit-popup .nm-split-manual {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  font-size: 12px;
  line-height: 1.2;
  cursor: pointer;
  user-select: none;
}

.dora-mode .nm-trimsplit-popup .nm-split-manual input {
  flex: none;
  width: 13px;
  height: 13px;
  margin: 0;
  cursor: pointer;
  accent-color: #b6f04a;
}

.dora-mode .nm-trimsplit-popup .nm-split-manual.nm-disabled {
  opacity: 0.55;
  cursor: default;
}

.dora-mode .nm-trimsplit-popup .nm-split-manual.nm-disabled input {
  cursor: default;
}

.dora-mode .nm-trimsplit-popup .nm-mask-depth-range {
  background: rgba(17, 18, 20, 0.12) !important;
}

.dora-mode .nm-trimsplit-popup .nm-mask-depth-range::-webkit-slider-thumb {
  background: #d2fb14 !important;
  border-color: #ffffff !important;
}

.dora-mode .nm-trimsplit-popup .nm-mask-depth-range::-moz-range-thumb {
  background: #d2fb14 !important;
  border-color: #ffffff !important;
}

/* Trim/Split's draw-capture overlay (editing/TrimTool.js + SplitTool.js,
   .nm-trim-overlay / .nm-split-overlay) is a fixed, full-viewport <canvas>
   at z-index:150 -- sized to #canvas's own bounding rect so it can capture
   pointerdown/move for drawing the cut line/shape. In Dora mode #canvas
   fills the whole viewport behind the floating chrome, so that overlay
   covers the chrome too, and every persistent Dora container sits well
   below 150 (.dora-workspace-tools/.dora-topbar/.dora-bottombar: 24,
   .gui-sidebar.dora-rightbar: 40, every reparented flyout: 40) -- meaning
   the instant Trim or Split arms, the overlay wins every hit-test over the
   whole screen and NOTHING else is clickable (confirmed live via
   elementFromPoint landing on the overlay at both a right-rail cell's
   coordinates and a topbar button's). Raising a descendant's z-index can't
   fix this -- each of these containers establishes its own stacking
   context, so only the container's OWN z-index competes with the overlay's
   sibling-level 150 (same reasoning as the .dora-topbar/.dora-workspace-
   tools z-index note in GuiDoraPanel.js's floatingSelectors block). 160
   clears the overlay (150) while staying below the Trim/Split popup itself
   (200, so the popup still layers correctly above the chrome when both are
   visible) and far below the toast/tooltip tier (100000+). */
.dora-workspace-tools,
.dora-topbar,
.dora-bottombar,
.dora-shapes-flyout,
.dora-parts-flyout,
.dora-detail-flyout,
.dora-shading-flyout,
.dora-paint-flyout,
.dora-render-flyout,
.dora-interface-flyout,
.dora-cut-actions {
  z-index: 160 !important;
}

.dora-mode .gui-sidebar.dora-rightbar {
  z-index: 160 !important;
}

/* ============================================================
   LOGO BUTTON + DARK WORKSPACE THEME
   The brand mark at the top of the left rail toggles body.dora-dark.
   ============================================================ */

.dora-mode .dora-workspace-tools .dora-workspace-group > button.dora-logo-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--icon-container, 34px);
  height: var(--icon-container, 34px);
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--accent);
  cursor: pointer;
}

.dora-mode .dora-workspace-tools .dora-workspace-group > button.dora-logo-toggle svg {
  width: 20px;
  height: 20px;
  stroke: var(--accent);
  stroke-width: 2.4;
}

.dora-mode .dora-workspace-tools .dora-workspace-group > button.dora-logo-toggle:hover {
  border-color: var(--toolbar-hover-border);
  background: var(--toolbar-hover-bg);
  box-shadow: var(--toolbar-hover-shadow);
}

/* Design tokens, re-declared for dark. dora.css and this file both define the
   light palette on :root; a body-level redeclaration beats :root on
   specificity, so every surface that reads a token flips with no per-element
   rule. The brand lime is deliberately UNCHANGED -- it is the identity colour
   and already reads well on both backdrops. */
body.dora-mode.dora-dark {
  /* Workspace backdrop -- the area behind the model. Mirrors light mode's
     structure (lighter at the top, darker at the bottom) so the viewport reads
     the same way in both themes. Kept clearly BELOW the panels: --glass-fill
     composites to ~#353a41 over this, so the floating chrome still reads as
     lifted rather than sunken. Not pure black, which swallows dark meshes.
     Fine-tune per taste via Shading > Workspace > Background colour -- that
     picker writes these two tokens inline on <body>, and an inline style beats
     this rule, so an explicit pick stays in charge until "Reset to theme
     default" clears it.
     NB: a dark viewport is now the DESIGN. It is no longer evidence of the old
     canvas-sizing bug (buffer stuck at 300x150 and stretched full-screen as
     opaque black) -- that is guarded in Scene.applyRender(). Check
     canvas.width/height against the viewport before blaming the theme. */
  --canvas-top: #212429;
  --canvas-bottom: #17191d;
  /* The area behind the model is NOT painted by the gradient above -- the WebGL
     background quad covers the viewport opaquely, so #viewport's CSS never
     shows there. Background.js reads this token to colour that quad. Declared
     for dark ONLY: with no token the drawable keeps its built-in grey, so the
     light workspace is untouched. Renders a little deeper than the nominal hex
     (ShaderMerge's filmic curve is steep near black) -- see Background.js. */
  --canvas-backdrop: #212429;
  --glass-fill: rgba(62, 67, 75, 0.66);
  --glass-border: rgba(255, 255, 255, 0.10);
  --glass-highlight: rgba(255, 255, 255, 0.06);
  --text-primary: #e8eaed;
  --text-secondary: #a8adb4;
  --text-muted: #767c84;
  --shadow: rgba(0, 0, 0, 0.5);
  --hairline: rgba(255, 255, 255, 0.10);
  --dora-panel-2: rgba(255, 255, 255, 0.06);
  --dora-cyan: rgba(255, 255, 255, 0.12);
  --dora-cyan-hover: rgba(255, 255, 255, 0.20);

  /* Dark theme's own glass tint -- same color-mix()-then-rgba(from...) reasoning as the
     :root declaration above (alpha applied AFTER the mix, not fed into it, or Panel opacity
     can never reach a genuinely empty fill); slightly higher mix percentage since these
     stops start heavier/less translucent, so an equal percentage read visibly weaker
     against near-black surroundings than in light mode. */
  --toolbar-shell-bg:
    linear-gradient(145deg,
      rgba(from color-mix(in srgb, var(--dora-panel-color) 30%, rgb(70, 76, 84)) r g b / calc(0.92 * var(--dora-panel-alpha))),
      rgba(from color-mix(in srgb, var(--dora-panel-color) 30%, rgb(58, 63, 70)) r g b / calc(0.88 * var(--dora-panel-alpha))));
  --toolbar-shell-border: rgba(255, 255, 255, 0.10);
  --toolbar-shell-shadow:
    0 14px 38px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);
  --toolbar-icon: rgb(154, 160, 168);
  --toolbar-icon-hover: rgb(226, 231, 237);
  --toolbar-hover-bg: rgba(255, 255, 255, 0.08);
  --toolbar-hover-border: rgba(255, 255, 255, 0.12);
  --toolbar-hover-shadow: 0 4px 12px rgba(0, 0, 0, 0.45);
  /* Same --dora-highlight-color indirection as the :root block above, dark theme's own
     recipe: near-full-strength, mixed toward black rather than white (dark chrome can carry
     the vivid hue directly, see that block's comment for why exact channels won't match the
     old hardcoded values). Border/shadow-ring reuse the highlight colour directly (its own
     old values WERE the raw hue at reduced alpha) via the rgba(from ...) relative-color
     syntax rather than a redundant color-mix. */
  --toolbar-active-bg:
    linear-gradient(180deg,
      rgba(from color-mix(in srgb, var(--dora-highlight-color) 90%, black) r g b / 0.95),
      rgba(from color-mix(in srgb, var(--dora-highlight-color) 78%, black) r g b / 0.92));
  --toolbar-active-border: rgba(from var(--dora-highlight-color) r g b / 0.55);
  --toolbar-active-icon: #1b2200;
  --toolbar-active-shadow:
    0 0 0 3px rgba(from var(--dora-highlight-color) r g b / 0.14),
    0 8px 18px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

/* --- Dark-theme surface overrides (generated) -------------------------
   Rules in this file that hardcode light-theme literals instead of reading a
   token do not flip with the token block above, so each gets an explicit dark
   counterpart here. Covers: light glass fills, dark-on-light tints (which
   would otherwise go invisible on a dark surface), dark ink, focus rings, and
   the pale-lime hover washes whose olive text would be unreadable on dark.
   Light slider thumbs are deliberately left alone -- they read correctly on a
   dark track. --- */
body.dora-mode.dora-dark .gui-sidebar.dora-rightbar {
  background: rgba(62, 67, 75, 0.96) !important;
}

/* Solid-panel mode (Interface > Transparent panel unticked) must win over the rule just
   above. :root.dora-solid-panels's own override (near the top of this file) loses here: it
   is html.dora-solid-panels + .gui-sidebar.dora-rightbar (4 classes), while the rule above is
   body.dora-mode.dora-dark + .gui-sidebar.dora-rightbar (4 classes + the `body` ELEMENT
   selector) -- one more specificity point, so in dark mode it silently kept winning and the
   right rail never went solid (every other container's background is token-driven, so the
   same override reached them fine; only this rail hardcodes its dark colour outside the
   token). dora-solid-panels lives on <html>, dora-dark on <body> -- different elements -- so
   both ancestors have to be named for the count to actually beat it. */
:root.dora-solid-panels body.dora-mode.dora-dark .gui-sidebar.dora-rightbar {
  background: var(--dora-panel-color) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}

body.dora-mode.dora-dark .dora-generate {
  background: rgba(255, 255, 255, 0.07) !important;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-generate:hover:not(:disabled) {
  border-color: rgba(255, 255, 255, 0.14) !important;
  background: rgba(255, 255, 255, 0.07) !important;
}

body.dora-mode.dora-dark .dora-generate-run.dora-loading::before {
  border: 1px solid rgba(255, 255, 255, 0.14);
}

body.dora-mode.dora-dark .dora-fix {
  border-color: rgba(255, 255, 255, 0.14) !important;
  background: rgba(62, 67, 75, 0.96) !important;
  color: #c3c8ce !important;
}

body.dora-mode.dora-dark .dora-fix:hover:not(:disabled) {
  color: #d2fb14 !important;
}

body.dora-mode.dora-dark .dora-workspace-tools .nm-slider {
  background: rgba(255, 255, 255, 0.26) !important;
}

body.dora-mode.dora-dark .gui-sidebar.dora-rightbar.dora-fix-active .nm-tool-cell .nm-label {
  border: 0 !important;
  background: transparent !important;
  color: #c3c8ce !important;
}

body.dora-mode.dora-dark .dora-empty span,
body.dora-mode.dora-dark .dora-help,
body.dora-mode.dora-dark .dora-shape-status,
body.dora-mode.dora-darkl-status {
  color: #a8adb4;
}

body.dora-mode.dora-dark .dora-panel button,
body.dora-mode.dora-dark .dora-panel button small,
body.dora-mode.dora-dark .dora-footer-grid button {
  color: #d4d8dd;
}

body.dora-mode.dora-dark .dora-panel button:disabled,
body.dora-mode.dora-dark .dora-panel button:disabled small {
  color: #7e848c;
}

body.dora-mode.dora-dark .dora-generate-status {
  background: rgba(255, 255, 255, 0.07);
  color: #a8adb4;
}

body.dora-mode.dora-dark .dora-section-intro {
  color: #a8adb4;
}

body.dora-mode.dora-dark .dora-section h2 {
  color: #b4b9c0;
}

body.dora-mode.dora-dark .dora-export {
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark:has(#canvas:active) .gui-sidebar.dora-rightbar {
  background: rgba(62, 67, 75, 0.96) !important;
}

:root.dora-solid-panels body.dora-mode.dora-dark:has(#canvas:active) .gui-sidebar.dora-rightbar {
  background: var(--dora-panel-color) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}

body.dora-mode.dora-dark .dora-mask-toggle {
  background: rgba(255, 255, 255, 0.07) !important;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-mask-toggle[aria-expanded="true"] svg,
body.dora-mode.dora-dark .dora-mask-toggle[aria-expanded="true"] b {
  color: #c3ee2a;
}

body.dora-mode.dora-dark .dora-mask-tools-head small,
body.dora-mode.dora-dark .dora-mask-final small {
  color: #a8adb4;
}

body.dora-mode.dora-dark .dora-mask-group-label {
  color: #a8adb4;
}

body.dora-mode.dora-dark .dora-mask-actions button {
  border-color: rgba(255, 255, 255, 0.14) !important;
  background: rgba(255, 255, 255, 0.07) !important;
  color: #d4d8dd !important;
}

body.dora-mode.dora-dark .dora-mask-actions [data-action="extrude-split"] {
  color: #d2fb14 !important;
}

body.dora-mode.dora-dark .dora-mask-actions [data-action="extrude-cancel"] {
  color: #9aa0a6 !important;
}

body.dora-mode.dora-dark .dora-mask-check {
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
}

body.dora-mode.dora-dark .dora-mask-check b {
  color: #9aa0a6;
}

body.dora-mode.dora-dark .dora-mask-field {
  color: #a8adb4;
}

body.dora-mode.dora-dark .dora-mask-field select {
  border-color: rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
}

body.dora-mode.dora-dark .dora-mask-field select:focus-visible,
body.dora-mode.dora-dark .dora-mask-field input:focus-visible,
body.dora-mode.dora-dark .dora-mask-check input:focus-visible {
  outline: 2px solid #d2fb14;
}

body.dora-mode.dora-dark .dora-mask-split {
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-wide.dora-pink {
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-wide.dora-cyan {
  border-color: rgba(255, 255, 255, 0.14) !important;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-on-tap,
body.dora-mode.dora-dark .dora-split-shape {
  border-color: rgba(255, 255, 255, 0.14) !important;
  background: rgba(255, 255, 255, 0.07) !important;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-part {
  border-color: rgba(255, 255, 255, 0.14) !important;
  background: rgba(255, 255, 255, 0.07) !important;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

/* Without this, the generic dark .dora-part rule above (same !important, one class less
   specific) still wins the background property for a selected row in dark mode -- the
   light-mode .dora-part.dora-selected rule's fix never actually showed up once dark mode
   was on, which is every screenshot this feature was reported/verified from. Lime is
   unchanged in dark mode everywhere else in this app (nav-cube, lock/home active states),
   same here. */
body.dora-mode.dora-dark .dora-part.dora-selected {
  border-color: #a5c50c !important;
  background: var(--accent) !important;
  color: var(--accent-ink);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
}

body.dora-mode.dora-dark .dora-part.dora-selected .dora-part-tris {
  color: var(--accent-ink);
}

body.dora-mode.dora-dark .dora-part-tris {
  color: #767c84;
}

:root.dora-solid-panels body.dora-mode.dora-dark .dora-part-menu-popover {
  border-color: rgba(255, 255, 255, 0.14);
  background: var(--dora-panel-color);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-part-menu-copy,
body.dora-mode.dora-dark .dora-part-menu-color,
body.dora-mode.dora-dark .dora-part-menu-delete {
  color: var(--dora-text-color);
}

body.dora-mode.dora-dark .dora-part-menu-copy:hover,
body.dora-mode.dora-dark .dora-part-menu-color:hover,
body.dora-mode.dora-dark .dora-part-menu-delete:hover {
  background: rgba(255, 255, 255, 0.08);
}

body.dora-mode.dora-dark .dora-part-menu-delete:hover {
  color: #f2606a;
}

body.dora-mode.dora-dark .dora-part .dora-part-eye.dora-part-eye-off {
  color: #f2606a;
}

body.dora-mode.dora-dark .dora-part .dora-part-eye.dora-part-eye-off:hover {
  color: #ff8a92;
}

body.dora-mode.dora-dark .dora-part-tag-a {
  background: rgba(210, 251, 20, 0.16);
  color: #d2fb14;
}

body.dora-mode.dora-dark .dora-part-tag-b {
  background: rgba(230, 53, 59, 0.22);
  color: #ff6b6b;
}

body.dora-mode.dora-dark #viewport > div:has(.nav-cube-face) {
  background: rgba(62, 67, 75, 0.96);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .nav-cube-face {
  color: #d6ef62 !important;
  text-shadow: none;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .nav-cube-face:hover,
body.dora-mode.dora-dark .nav-cube-face.navcube-active {
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-topbar button:focus-visible,
body.dora-mode.dora-dark .dora-bottombar button:focus-visible {
  outline: 2px solid #d2fb14;
}

body.dora-mode.dora-dark .dora-topbar button:disabled,
body.dora-mode.dora-dark .dora-bottombar button:disabled {
  color: #6b7178;
}

body.dora-mode.dora-dark .dora-workspace-tools button:focus-visible {
  outline: 2px solid #d2fb14;
}

body.dora-mode.dora-dark .dora-workspace-tools button:disabled {
  color: #6b7178;
}

body.dora-mode.dora-dark .dora-workspace-tools .dora-tool-danger:hover:not(:disabled) {
  color: #f2606a;
}

body.dora-mode.dora-dark .dora-tool-divider {
  background: rgba(255, 255, 255, 0.16);
}

/* Dark + glass (Transparent panel ticked): these 11 flyouts had no dark-mode glass background
   of their own -- only the light-mode default (rgba(250, 251, 252, 0.9), see ~line 1761) and
   the solid-panels override below existed, so dark + non-solid fell through to that bright
   near-white light glass, which the dark-mode text-color rules elsewhere (e.g.
   .dora-interface-flyout > button: #c3c8ce) turn near-invisible against. --glass-fill/
   --glass-border/--glass-highlight/--shadow (declared in the dark :root block) are exactly
   the tokens .gui-sidebar.dora-rightbar's own dark-glass rule already uses for this same
   problem -- reused here instead of inventing new values. Lower specificity than the
   solid-panels override just below (3 classes vs 4), so solid mode still wins when active. */
/* .dora-interface-flyout deliberately NOT in this list any more -- see the base rule above.
   var(--glass-fill) here is the SAME kind of second, hand-copied neutral token
   --toolbar-shell-bg's own fix (above) exists to stop relying on; the rest of this family
   (Shapes/Pieces/Detail/Cut/Shading/Paint/Render/Mask-full/Assets/Alpha) hasn't been
   reported as mismatched, so left on their existing token rather than widening this diff. */
body.dora-mode.dora-dark .dora-shapes-flyout,
body.dora-mode.dora-dark .dora-parts-flyout,
body.dora-mode.dora-dark .dora-detail-flyout,
body.dora-mode.dora-dark .dora-cut-actions,
body.dora-mode.dora-dark .dora-shading-flyout,
body.dora-mode.dora-dark .dora-paint-flyout,
body.dora-mode.dora-dark .dora-render-flyout,
body.dora-mode.dora-dark .dora-maskfull-flyout,
body.dora-mode.dora-dark .dora-asset-flyout,
body.dora-mode.dora-dark .dora-alpha-flyout {
  background: var(--glass-fill);
  border-color: var(--glass-border);
  box-shadow: 0 16px 42px var(--shadow), inset 0 1px 0 var(--glass-highlight);
}

:root.dora-solid-panels body.dora-mode.dora-dark .dora-shapes-flyout,
:root.dora-solid-panels body.dora-mode.dora-dark .dora-parts-flyout,
:root.dora-solid-panels body.dora-mode.dora-dark .dora-detail-flyout,
:root.dora-solid-panels body.dora-mode.dora-dark .dora-interface-flyout {
  background: var(--dora-panel-color);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-interface-flyout > button,
body.dora-mode.dora-dark .dora-iface-reset {
  color: #c3c8ce;
}

/* Explicit dark override for the section-toggle headers specifically: the light-mode rule
   above (.dora-interface-flyout > button.dora-iface-section-toggle) has HIGHER specificity
   than the generic dark rule just above (one more class), so without this it would win in
   dark mode too and paint dark-on-dark text -- var(--dora-text-color) defaults dark
   (light-mode ink), which is illegible on this dark panel. */
body.dora-mode.dora-dark .dora-interface-flyout > button.dora-iface-section-toggle {
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-interface-flyout > button.dora-iface-section-toggle:hover {
  color: #d2fb14;
}

body.dora-mode.dora-dark .dora-interface-flyout > button:hover,
body.dora-mode.dora-dark .dora-iface-reset:hover {
  color: #d2fb14;
}

/* Commit stays accent-filled in dark mode too -- the two rules above would otherwise flatten
   it to the same plain grey text every other flyout button gets. */
body.dora-mode.dora-dark .dora-interface-flyout > button.dora-iface-commit,
body.dora-mode.dora-dark .dora-interface-flyout > button.dora-iface-commit:hover:not(:disabled) {
  color: var(--accent-ink);
}
body.dora-mode.dora-dark .dora-interface-flyout > button.dora-iface-commit:disabled {
  background: rgba(255, 255, 255, 0.08);
  color: #7d838a;
}

body.dora-mode.dora-dark .dora-iface-name {
  color: var(--dora-text-color);
}

body.dora-mode.dora-dark .dora-remesh-hint,
body.dora-mode.dora-dark .dora-repair-hint {
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  color: #9aa0a6;
}

body.dora-mode.dora-dark .dora-detail-info {
  color: #a8adb4;
}

:root.dora-solid-panels body.dora-mode.dora-dark .dora-cut-actions {
  background: var(--dora-panel-color);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-scene-toolbar > button {
  border-color: rgba(255, 255, 255, 0.14);
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-scene-toolbar > button:hover:not(:disabled) {
  border-color: rgba(210, 251, 20, 0.35);
  background: rgba(210, 251, 20, 0.16);
  color: #d2fb14;
}

body.dora-mode.dora-dark .dora-scene-toolbar > button.dora-active {
  border-color: #a5c50c;
  background: var(--accent);
  color: var(--accent-ink);
}

body.dora-mode.dora-dark .dora-scene-add-popover {
  border-color: rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.03);
}

body.dora-mode.dora-dark .dora-scene-remesh-popover {
  border-color: rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.03);
}

/* .dora-remesh-section-head's plain :hover already falls through to the dark hover rule
   below (higher specificity, so no override needed there) and lands on the correct bright
   #d2fb14. The PERSISTENT expanded state has no light-mode counterpart to inherit from, so
   without this it would sit at the light-mode #4d5f00 (dark olive) -- close to invisible on
   a dark panel when the pointer isn't over it. */
body.dora-mode.dora-dark .dora-remesh-section-head[aria-expanded="true"] {
  color: #d2fb14;
}

body.dora-mode.dora-dark .dora-shapes-flyout button,
body.dora-mode.dora-dark .dora-cut-actions button,
body.dora-mode.dora-dark .dora-remesh-flyout button,
body.dora-mode.dora-dark .dora-detail-flyout button,
body.dora-mode.dora-dark .dora-scene-add-popover > button {
  color: #c3c8ce;
  border-color: rgba(255, 255, 255, 0.14);
}

/* This dark-mode block outranks the plain .dora-shapes-flyout button.dora-active rule
   (extra .dora-dark class = higher specificity), so it was winning border-color back to
   the translucent-white idle tone -- a visible frame around the lime fill again, in dark
   mode specifically. Same fix, same reason: match the border to the fill. */
body.dora-mode.dora-dark .dora-shapes-flyout button.dora-active {
  border-color: var(--accent);
}

body.dora-mode.dora-dark .dora-shapes-flyout button:hover:not(:disabled),
body.dora-mode.dora-dark .dora-cut-actions button:hover:not(:disabled),
body.dora-mode.dora-dark .dora-remesh-flyout button:hover:not(:disabled),
body.dora-mode.dora-dark .dora-detail-flyout button:hover:not(:disabled),
body.dora-mode.dora-dark .dora-scene-add-popover > button:hover:not(:disabled) {
  color: #d2fb14;
}

body.dora-mode.dora-dark .dora-remesh-flyout button:disabled,
body.dora-mode.dora-dark .dora-detail-flyout button:disabled {
  color: #6b7178;
}

:root.dora-solid-panels body.dora-mode.dora-dark .dora-shading-flyout,
:root.dora-solid-panels body.dora-mode.dora-dark .dora-paint-flyout,
:root.dora-solid-panels body.dora-mode.dora-dark .dora-render-flyout {
  background: var(--dora-panel-color);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-shading-mode {
  background: rgba(255, 255, 255, 0.08);
}

body.dora-mode.dora-dark .dora-shading-mode button {
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-shading-mode button:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.07);
}

body.dora-mode.dora-dark .dora-shading-mode button:disabled {
  color: #6b7178;
}

body.dora-mode.dora-dark .dora-paint-color-swatch {
  border: 1px solid rgba(255, 255, 255, 0.14);
}

body.dora-mode.dora-dark .dora-shading-tool > button:disabled,
body.dora-mode.dora-dark .dora-paint-tool > button:disabled {
  color: #6b7178;
}

body.dora-mode.dora-dark .dora-part-rename {
  color: #8b9199;
}

body.dora-mode.dora-dark .dora-part-rename:hover {
  color: #e8eaed;
}

body.dora-mode.dora-dark .dora-part-rename-input {
  background: #1e2126;
  color: #e8eaed;
}

body.dora-mode.dora-dark .dora-paint-channels {
  background: rgba(255, 255, 255, 0.08);
}

body.dora-mode.dora-dark .dora-paint-channels button {
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-paint-channels button:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.07);
}

body.dora-mode.dora-dark .dora-paint-channels button:disabled,
body.dora-mode.dora-dark .dora-paint-all-channels:disabled {
  color: #6b7178;
}

body.dora-mode.dora-dark .dora-paint-all-channels {
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-paint-fill,
body.dora-mode.dora-dark .dora-paint-eyedropper {
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-paint-fill:hover:not(:disabled),
body.dora-mode.dora-dark .dora-paint-eyedropper:hover:not(:disabled) {
  color: #d2fb14;
}

body.dora-mode.dora-dark .dora-paint-fill:disabled,
body.dora-mode.dora-dark .dora-paint-eyedropper:disabled {
  color: #6b7178;
}

body.dora-mode.dora-dark .dora-paint-fill-note {
  color: #9aa0a6;
}

body.dora-mode.dora-dark .dora-paint-all-channels:hover:not(:disabled) {
  color: #d2fb14;
}

body.dora-mode.dora-dark .dora-light-gizmo-toggle {
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-light-gizmo-toggle:disabled {
  color: #6b7178;
}

body.dora-mode.dora-dark .dora-light-add {
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-light-add:hover:not(:disabled) {
  color: #d2fb14;
}

body.dora-mode.dora-dark .dora-light-add:disabled,
body.dora-mode.dora-dark .dora-light-actions button:disabled {
  color: #6b7178;
}

body.dora-mode.dora-dark .dora-light-row {
  background: rgba(255, 255, 255, 0.07);
}

body.dora-mode.dora-dark .dora-light-row > button {
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-light-eye {
  color: #8b9199;
}

body.dora-mode.dora-dark .dora-light-eye.dora-active {
  color: #bcd94a;
}

body.dora-mode.dora-dark .dora-light-intensity {
  color: #9aa0a6;
}

body.dora-mode.dora-dark .dora-light-name {
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
}

body.dora-mode.dora-dark .dora-light-attach {
  background: rgba(255, 255, 255, 0.08);
}

body.dora-mode.dora-dark .dora-light-attach button {
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-light-attach button:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.07);
}

body.dora-mode.dora-dark .dora-light-attach button:disabled {
  color: #6b7178;
}

body.dora-mode.dora-dark .dora-light-actions button {
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-light-actions button:hover:not(:disabled) {
  color: #d2fb14;
}

body.dora-mode.dora-dark .dora-light-actions [data-action="light-delete"]:hover:not(:disabled) {
  color: #f2606a;
}

body.dora-mode.dora-dark .dora-paint-hint {
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  color: #9aa0a6;
}

body.dora-mode.dora-dark .dora-paint-presets {
  background: rgba(255, 255, 255, 0.08);
}

body.dora-mode.dora-dark .dora-paint-preset {
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-paint-preset:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.07);
}

body.dora-mode.dora-dark .dora-paint-preset:disabled {
  color: #6b7178;
}

body.dora-mode.dora-dark .dora-paint-preset-note {
  color: #9aa0a6;
}

body.dora-mode.dora-dark .dora-lighting-presets {
  background: rgba(255, 255, 255, 0.08);
}

body.dora-mode.dora-dark .dora-lighting-preset {
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-lighting-preset:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.07);
}

body.dora-mode.dora-dark .dora-lighting-preset:disabled {
  color: #6b7178;
}

body.dora-mode.dora-dark .dora-shading-preset-note,
body.dora-mode.dora-dark .dora-shading-export-note {
  color: #9aa0a6;
}

body.dora-mode.dora-dark .dora-shading-export {
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
  color: #e8eaed;
}

body.dora-mode.dora-dark .dora-shading-export:hover:not(:disabled) {
  color: #d2fb14;
}

body.dora-mode.dora-dark .dora-shading-export:disabled {
  color: #6b7178;
}

body.dora-mode.dora-dark .dora-shading-shadow-note {
  color: #9aa0a6;
}

body.dora-mode.dora-dark .dora-shading-bloom-note {
  color: #9aa0a6;
}

body.dora-mode.dora-dark .dora-shading-ao-note {
  color: #9aa0a6;
}

body.dora-mode.dora-dark .dora-shading-reset {
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: #9aa0a6;
}

body.dora-mode.dora-dark .dora-shading-reset:hover {
  border-color: rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.06);
  color: #e8eaed;
}

body.dora-mode.dora-dark .dora-shading-reset-note {
  color: #8b9199;
}

body.dora-mode.dora-dark .dora-shading-hdri-import {
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-shading-hdri-import:hover {
  color: #d2fb14;
}

body.dora-mode.dora-dark .dora-shading-hdri-note {
  color: #9aa0a6;
}

body.dora-mode.dora-dark .dora-matcap-swatch {
  background: rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-matcap-swatch:hover {
  border-color: rgba(255, 255, 255, 0.14);
}

body.dora-mode.dora-dark .dora-render-tool > button:disabled {
  color: #6b7178;
}

body.dora-mode.dora-dark .nm-trimsplit-popup {
  background: var(--dora-panel-color) !important;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06) !important;
}

body.dora-mode.dora-dark .nm-trimsplit-popup button {
  background: rgba(255, 255, 255, 0.07) !important;
  color: #e8eaed !important;
}

body.dora-mode.dora-dark .nm-trimsplit-popup button:hover {
  background: rgba(255, 255, 255, 0.07) !important;
  border-color: rgba(255, 255, 255, 0.14) !important;
}

body.dora-mode.dora-dark .nm-trimsplit-popup .nm-mask-depth-label {
  color: #a8adb4 !important;
}

body.dora-mode.dora-dark .nm-trimsplit-popup .nm-mask-depth-range {
  background: rgba(255, 255, 255, 0.19) !important;
}

/* --- Dark-theme corrections ------------------------------------------------
   Two classes of bug the generated block above introduces, both caught by a
   contrast audit rather than by eye:

   1. Active pills sit on the LIME fill, which stays lime in dark mode. The
      generated base-colour rule for each control matches at equal specificity
      and lands later in the file, so it was winning over the active state and
      painting light-grey text on lime (measured 1.1:1). Restore the dark ink.
   2. The nav-cube faces are painted with near-white gradients that the
      generator's light-panel patterns did not recognise, so they stayed light
      while their text went light with them. --- */

body.dora-mode.dora-dark .dora-shading-mode button.dora-active,
body.dora-mode.dora-dark .dora-paint-channels button.dora-active,
body.dora-mode.dora-dark .dora-light-attach button.dora-active,
body.dora-mode.dora-dark .dora-paint-preset.dora-active,
body.dora-mode.dora-dark .dora-lighting-preset.dora-active,
body.dora-mode.dora-dark .dora-matcap-swatch.dora-active,
body.dora-mode.dora-dark .dora-mask-actions .dora-active,
body.dora-mode.dora-dark .dora-workspace-tools button[data-action="symmetry"].dora-active {
  color: var(--toolbar-active-icon) !important;
}

body.dora-mode.dora-dark #viewport > div:has(.nav-cube-face) {
  border: 0 !important;
  background: none !important;
  box-shadow: none !important;
}

body.dora-mode.dora-dark .nav-cube-face {
  border: 1px solid rgba(255, 255, 255, 0.14) !important;
  background:
    linear-gradient(180deg, rgba(70, 76, 84, 0.96), rgba(56, 61, 68, 0.9)) !important;
  color: #c3c8ce !important;
  text-shadow: none !important;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06) !important;
}

/* Per-axis idle tints, dark variant -- same hue/chroma family as light mode, lightness
   tuned so they read as lit surfaces on a dark ground instead of washed-out pastels
   (colorize.md: dark mode is not inverted light mode). */
body.dora-mode.dora-dark .nav-cube-face.navcube-axis-x {
  background:
    radial-gradient(circle at 32% 28%, rgba(224, 140, 130, 0.95), rgba(150, 66, 58, 0.92) 75%) !important;
  color: #ffe4e0 !important;
}

body.dora-mode.dora-dark .nav-cube-face.navcube-axis-y {
  background:
    radial-gradient(circle at 32% 28%, rgba(150, 205, 120, 0.95), rgba(66, 110, 46, 0.92) 75%) !important;
  color: #e6ffdc !important;
}

body.dora-mode.dora-dark .nav-cube-face.navcube-axis-z {
  background:
    radial-gradient(circle at 32% 28%, rgba(130, 165, 224, 0.95), rgba(52, 84, 140, 0.92) 75%) !important;
  color: #e2ecff !important;
}

/* The lime active/hover face keeps its dark ink -- lime is unchanged in dark, so this
   mirrors the :root/.dora-mode block's recipe verbatim (same color-mix() percentages)
   instead of the richer toward-black treatment --toolbar-active-* gets in dark mode. This
   block is what actually WINS on screen in dark mode -- body.dora-mode.dora-dark beats the
   plain .dora-mode rule on specificity, so editing only that one silently no-ops here. */
body.dora-mode.dora-dark .nav-cube-face:hover,
body.dora-mode.dora-dark .nav-cube-face.navcube-active {
  border-color: rgba(from color-mix(in srgb, var(--dora-highlight-color) 75%, black) r g b / 0.9) !important;
  background:
    radial-gradient(circle at 32% 28%,
      rgba(from color-mix(in srgb, var(--dora-highlight-color) 30%, white) r g b / 0.98),
      rgba(from color-mix(in srgb, var(--dora-highlight-color) 92%, white) r g b / 0.96) 75%) !important;
  color: #1d2500 !important;
}

body.dora-mode.dora-dark .dora-navcube-lock {
  border-color: rgba(255, 255, 255, 0.14);
  background:
    linear-gradient(180deg, rgba(70, 76, 84, 0.96), rgba(56, 61, 68, 0.9));
  color: #c3c8ce;
}

/* Lime active state is unchanged in dark, same as the faces themselves. */
body.dora-mode.dora-dark .dora-navcube-lock.dora-navcube-lock-active {
  border-color: rgba(210, 251, 20, 0.9);
  background:
    linear-gradient(180deg, rgba(226, 255, 83, 0.98), rgba(206, 247, 22, 0.96));
  color: #1d2500;
}

body.dora-mode.dora-dark .dora-navcube-home {
  border-color: rgba(255, 255, 255, 0.14);
  background:
    linear-gradient(180deg, rgba(70, 76, 84, 0.96), rgba(56, 61, 68, 0.9));
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-navcube-home:hover,
body.dora-mode.dora-dark .dora-navcube-home:focus-visible {
  border-color: rgba(210, 251, 20, 0.9);
  color: #d6ef62;
}

/* The Pieces row's name button never had a background rule, so it fell back to
   the UA default `buttonface` (opaque #f0f0f0). That blended into the light
   row and went unnoticed; on a dark row it reads as a bright grey slab. Reset
   the row's buttons to transparent so they sit on the row's own surface. */
body.dora-mode.dora-dark .dora-part button {
  background: transparent !important;
}

body.dora-mode.dora-dark .dora-part [data-action="part-select"] {
  color: var(--text-primary) !important;
}

/* Range inputs in the flyouts never set a track background, so they inherit
   the UA default (opaque white) -- fine on light chrome, a bright bar on dark.
   accent-color paints the filled portion with the brand lime. */
body.dora-mode.dora-dark input[type="range"] {
  background: rgba(255, 255, 255, 0.14) !important;
  accent-color: var(--accent);
}

/* The reparented Nomad cells keep nomad/theme-dark's own #888 label grey,
   which was fine against the near-black original shell but falls to 2.5:1
   once the shell is lifted to sit above the lighter grey workspace. Use the
   theme's own secondary ink instead. */
body.dora-mode.dora-dark .dora-workspace-tools .nm-label,
body.dora-mode.dora-dark .gui-sidebar.dora-rightbar .nm-label {
  color: var(--text-secondary) !important;
}

/* .dora-mask-tool-label (Symmetry/Detail/Material/Mask/Smooth/Gizmo/Alpha captions --
   a separate class from .nm-label above, same idea, never got the same fix) reads
   `color: var(--dora-text-color)`, which is ONLY ever defined once, as #3f464d for
   light chrome (see :root near the top of this file) -- no dark-mode override existed,
   so this text has been near-invisible dark-gray-on-dark in dark mode all along. Went
   unnoticed while .dora-workspace-group had its own opaque dark card behind it (see the
   "black box" fix above -- everything just read as uniformly dim); removing that fill
   made the broken contrast obvious. Same secondary ink as .nm-label for consistency. */
body.dora-mode.dora-dark .dora-mask-tool-label {
  color: var(--text-secondary) !important;
}

/* Beats the rule above for an ACTIVE bottombar/topbar button specifically (extra .dora-active
   class -- higher specificity, same !important tier): --text-secondary is a muted grey,
   fine on this bar's normal dark chrome but nearly illegible on .dora-active's bright lime
   fill -- read as a faint smudge instead of a caption. Same dark ink every other active-state
   icon/text in the app already uses on that same lime. */
body.dora-mode.dora-dark .dora-topbar button.dora-active .dora-mask-tool-label,
body.dora-mode.dora-dark .dora-bottombar button.dora-active .dora-mask-tool-label {
  color: var(--toolbar-active-icon) !important;
}

body.dora-mode.dora-dark .dora-workspace-tools .nm-tool-cell:not(.nm-on) .nm-label,
body.dora-mode.dora-dark .gui-sidebar.dora-rightbar .nm-tool-cell:not(.nm-on) .nm-label {
  color: var(--dora-text-color) !important;
}

/* Workspace-background picker (Shading flyout). Sits outside the
   dora-shading-lit-only groups so it stays usable in MatCap mode. */
.dora-shading-bg-group .dora-shading-bg-swatch {
  height: 34px;
}

.dora-shading-bg-reset {
  width: 100%;
  margin-top: 4px;
  padding: 6px 8px;
  border: 1px solid rgba(26, 28, 30, 0.12);
  border-radius: 8px;
  background: transparent;
  color: #60676e;
  font-size: 10px;
  cursor: pointer;
}

.dora-shading-bg-reset:hover {
  border-color: rgba(26, 28, 30, 0.22);
  color: #23272b;
}

body.dora-mode.dora-dark .dora-shading-bg-reset {
  border-color: rgba(255, 255, 255, 0.14);
  color: #9aa0a6;
}

body.dora-mode.dora-dark .dora-shading-bg-reset:hover {
  border-color: rgba(255, 255, 255, 0.26);
  color: #e8eaed;
}

/* FaceGroup + full Mask tools (restored from Pro). Same floating-flyout
   recipe as the other top-bar tools; z-index/elevation and the dark-theme
   surface rules already cover .dora-maskfull-flyout via the shared groups
   below, so only the panel's own box needs declaring. */
.dora-maskfull-flyout {
  position: fixed;
  z-index: 160;
  width: 214px;
  max-height: calc(100dvh - 100px);
  overflow-y: auto;
  padding: 8px;
  border: 1px solid var(--glass-border);
  border-radius: 14px;
  background: rgba(250, 251, 252, 0.9);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  box-shadow: 0 16px 42px rgba(44, 51, 63, 0.18), inset 0 1px 0 #fff;
}

.dora-maskfull-flyout strong {
  display: block;
  padding: 4px 6px 8px;
  color: var(--text-primary);
  font-size: 12px;
  font-weight: 600;
}

.dora-maskfull-flyout button {
  width: 100%;
  min-height: 34px;
  margin-top: 4px;
  padding: 0 8px;
  border: 1px solid rgba(26, 28, 30, 0.1);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.55);
  color: #3f464d;
  font-size: 11px;
  text-align: left;
  cursor: pointer;
}

.dora-maskfull-flyout button:hover:not(:disabled) {
  border-color: rgba(159, 190, 14, 0.4);
  background: rgba(210, 251, 20, 0.14);
  color: #4d5f00;
}

.dora-maskfull-flyout button:disabled {
  color: #b7bcc1;
  cursor: not-allowed;
  opacity: 0.5;
}

:root.dora-solid-panels body.dora-mode.dora-dark .dora-maskfull-flyout {
  border-color: rgba(255, 255, 255, 0.14);
  background: var(--dora-panel-color);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-maskfull-flyout button {
  border-color: rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-maskfull-flyout button:hover:not(:disabled) {
  background: rgba(210, 251, 20, 0.18);
  color: #d2fb14;
}

body.dora-mode.dora-dark .dora-maskfull-flyout button:disabled {
  color: #6b7178;
}

/* theme-dark.css declares `body, #viewport, #canvas { background:
   var(--bg-viewport) !important }` -- a near-black (#080808) applied in BOTH
   themes. #viewport and #canvas escape it (dora.css re-declares those with
   !important AND higher specificity), but BODY does not: dora.css's
   `body.dora-mode` gradient carries no !important, so it loses and body stays
   black even in the light workspace. Body is normally hidden behind the
   full-bleed #viewport, so this only shows as black wherever the viewport
   doesn't cover -- e.g. while yagui is re-offsetting the viewport for a
   sidebar, or around the FaceGroup workspace overlay. Point body at the same
   workspace tokens as the viewport so it matches the theme (and any custom
   backdrop colour) instead of being hardcoded near-black. */
body.dora-mode {
  background: linear-gradient(180deg, var(--canvas-top), var(--canvas-bottom)) !important;
}

/* FaceGroup + Mask-tools top-bar triggers. These live one DOM level under
   .dora-topbar (wrapped in their own .dora-X-tool div, like every other tool),
   so the ".dora-topbar > button" direct-child rule does NOT reach them -- the
   same trap the .dora-shading-tool/.dora-paint-tool comment above documents.
   Without these the buttons render with no box and a zero-sized svg, i.e.
   invisible: the tools were in the DOM but nothing showed on the bar. */
.dora-facegroup-tool,
.dora-maskfull-tool {
  position: relative;
}

/* FaceGroup and "Mask tools" are icon-only, same fixed square as every other topbar
   button -- but bigger than the plain-glyph siblings on purpose: they're flat-coloured
   brand icons (ICONS.facegroup / ICONS.mask in GuiDoraPanel.js), not currentColor line
   glyphs, and read as a smudge at the 22px every stroke icon uses. */
.dora-facegroup-tool > button,
.dora-maskfull-tool > button,
.dora-decal-tool > button,
.dora-interface-tool > button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: calc(56px * var(--dora-ui-scale));
  height: calc(56px * var(--dora-ui-scale));
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}

.dora-facegroup-tool > button svg,
.dora-maskfull-tool > button svg,
.dora-decal-tool > button svg,
.dora-interface-tool > button svg {
  width: calc(36px * var(--dora-ui-scale));
  height: calc(36px * var(--dora-ui-scale));
}

/* Still used by the LEFT-RAIL duplicate buttons (Theme/Detail/Gizmo/Material/Smooth/Mask/
   Symmetry/Alpha in GuiDoraPanel.js) -- their captions are unaffected by the topbar going
   icon-only above. */
.dora-mask-tool-label {
  font-size: calc(10px * var(--dora-font-scale));
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  /* Interface > Text color. Was color:inherit (from the parent button's own colour, which is
     var(--toolbar-icon)-derived) -- explicit now so the Text color picker reaches it directly
     instead of depending on what the button happens to inherit. */
  color: var(--dora-text-color);
}

.dora-facegroup-tool > button:hover:not(:disabled),
.dora-maskfull-tool > button:hover:not(:disabled),
.dora-decal-tool > button:hover:not(:disabled),
.dora-interface-tool > button:hover:not(:disabled) {
  border-color: var(--toolbar-hover-border);
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon-hover);
  box-shadow: var(--toolbar-hover-shadow);
}

.dora-facegroup-tool > button.dora-active,
.dora-maskfull-tool > button.dora-active,
.dora-decal-tool > button.dora-active,
.dora-interface-tool > button.dora-active,
.dora-mirror-tool > button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}

.dora-facegroup-tool > button:disabled,
.dora-maskfull-tool > button:disabled,
.dora-decal-tool > button:disabled,
.dora-interface-tool > button:disabled,
.dora-mirror-tool > button:disabled {
  color: #9da3a9;
  cursor: not-allowed;
  opacity: 0.42;
}

/* Brand mark / theme toggle -- moved from the head of the left rail into .dora-bottombar
   per user request. Box model, hover, and active/pressed states all come free from the
   generic ".dora-topbar > button, .dora-bottombar > button" rules above (still a plain
   button direct child, just a different bar) -- only the icon needs its own rule: a
   defensive stroke:none on its fill shapes in case any ambient stroke rule ever reaches
   this slot. Used to run a size premium over its siblings (26px, then 30px, vs their 18-22)
   for readability as a flat-coloured brand icon -- dropped per explicit user request to
   match the right rail's icon size exactly, same as every other bottombar icon now. */
.dora-bottombar > .dora-logo-mark svg {
  width: calc(22px * var(--dora-ui-scale));
  height: calc(22px * var(--dora-ui-scale));
}

.dora-bottombar > .dora-logo-mark svg .nm-logo-fill {
  stroke: none;
}

/* Secondary text that lands just under 3:1 in dark chrome (measured 2.76 and
   2.80 by the contrast audit). These sit on the tinted check/panel fills
   rather than the flat panel, so the generic muted inks are a shade too dark
   there -- lifted just enough to clear the threshold. */
body.dora-mode.dora-dark .dora-mask-check b {
  color: #b4bac1 !important;
}

body.dora-mode.dora-dark .dora-shading-reset-note {
  color: #a8adb4;
}

/* ---- Mask split tools panel (build guide s3) --------------------------- */
.dora-mask-tools-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
  padding: 2px 6px 8px;
}

.dora-mask-tools-title {
  min-width: 0;
}

/* Icon-only reset. Sized like the top-bar tool buttons so it reads as a
   control rather than a label, and given the danger tint on hover since it
   discards work. */
.dora-maskfull-flyout .dora-mask-clear {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  min-height: 30px;
  height: 30px;
  margin-top: 0;
  padding: 0;
  border: 1px solid rgba(26, 28, 30, 0.12);
  border-radius: 8px;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
}

.dora-maskfull-flyout .dora-mask-clear svg {
  width: 15px;
  height: 15px;
}

.dora-maskfull-flyout .dora-mask-clear:hover:not(:disabled) {
  border-color: rgba(226, 67, 78, 0.4);
  background: rgba(226, 67, 78, 0.1);
  color: #a4262f;
}

body.dora-mode.dora-dark .dora-maskfull-flyout .dora-mask-clear {
  border-color: rgba(255, 255, 255, 0.14);
  background: transparent;
  color: #a8adb4;
}

body.dora-mode.dora-dark .dora-maskfull-flyout .dora-mask-clear:hover:not(:disabled) {
  border-color: rgba(242, 96, 106, 0.5);
  background: rgba(242, 96, 106, 0.14);
  color: #f2606a;
}

.dora-mask-tools-head strong {
  display: block;
  color: var(--text-primary);
  font-size: 12px;
  font-weight: 600;
}

.dora-mask-tools-head small {
  color: var(--text-secondary);
  font-size: 10px;
}

/* Two-column grid: Extrude split | Select color regions on row 1, then
   Cancel preview spanning row 2 (auto-placement pushes the full-span item
   onto its own row once row 1 is occupied -- hence the DOM order). */
.dora-maskfull-flyout .dora-mask-actions {
  display: grid;
  grid-template-columns: 0.9fr 1.1fr;
  gap: 6px;
}

.dora-maskfull-flyout .dora-mask-actions > button {
  width: auto;
  min-height: 40px;
  margin-top: 0;
  text-align: center;
}

.dora-maskfull-flyout .dora-mask-actions > .dora-mask-cancel {
  grid-column: 1 / -1;
}

/* Primary action in the group gets the accent tint. */
.dora-maskfull-flyout [data-action="mask-extrude-split"] {
  border-color: rgba(159, 190, 14, 0.5);
  background: rgba(210, 251, 20, 0.14);
  color: #4d5f00;
}

.dora-maskfull-flyout button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
}

.dora-maskfull-flyout .dora-mask-check {
  display: grid;
  grid-template-columns: 18px 1fr auto;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
}

.dora-maskfull-flyout .dora-mask-check b {
  color: var(--text-muted);
  font-size: 9px;
  font-weight: 500;
  text-transform: uppercase;
}

.dora-maskfull-flyout .dora-mask-field {
  display: block;
  margin-top: 8px;
  color: var(--text-secondary);
  font-size: 10px;
}

.dora-maskfull-flyout .dora-mask-field > span {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 3px;
}

.dora-maskfull-flyout .dora-mask-field output {
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  font-size: 10px;
}

.dora-maskfull-flyout .dora-mask-field input[type="range"],
.dora-maskfull-flyout .dora-mask-field select {
  width: 100%;
}

/* Connector settings collapse away until the box is ticked.
   A PRE-EXISTING rule already hides .dora-connector-settings unless a
   [data-mask-setting="connectors"] checkbox is checked -- that is the classic
   mask section's attribute, which this panel does not use, so that rule would
   pin the block shut forever regardless of our own checkbox. Both rules are
   therefore scoped to this flyout and the reveal is made explicit, with
   !important + the extra class to out-rank the foreign one. */
.dora-maskfull-flyout .dora-connector-group:not(:has([data-maskfull-setting="connectors"]:checked)) .dora-connector-settings {
  display: none !important;
}

.dora-maskfull-flyout .dora-connector-group:has([data-maskfull-setting="connectors"]:checked) .dora-connector-settings {
  display: grid !important;
  gap: 2px;
  padding: 6px 2px 2px;
}

.dora-maskfull-flyout .dora-mask-final {
  margin-top: 12px;
}

.dora-maskfull-flyout .dora-mask-split {
  width: 100%;
  min-height: 42px;
  border-color: rgba(159, 190, 14, 0.6);
  background: rgba(210, 251, 20, 0.18);
  color: #4d5f00;
  font-weight: 600;
  text-align: center;
}

.dora-maskfull-flyout .dora-mask-status {
  display: block;
  margin-top: 5px;
  color: var(--text-secondary);
  font-size: 9px;
  line-height: 1.4;
}

body.dora-mode.dora-dark .dora-maskfull-flyout [data-action="mask-extrude-split"],
body.dora-mode.dora-dark .dora-maskfull-flyout .dora-mask-split {
  background: rgba(210, 251, 20, 0.18);
  color: #d2fb14;
}

body.dora-mode.dora-dark .dora-maskfull-flyout .dora-mask-check b {
  color: #8b9199;
}

/* ============================================================
   FACEGROUP MODE -- dark
   FaceGroup mode is two documents. The 3D area is an iframe
   (app/facegroup-workspace/), themed by its own styles.css off a
   .fg-dark class that GuiFaceGroup mirrors across the boundary. The
   visible control panel is NOT in that iframe -- it is
   #nm-mesh-editor-panel / .nmep-*, built by GuiMeshEditorPanel.js in
   THIS document, so it is themed here.

   Two cascade notes before editing:
   1. theme-dark.css:645+ already carries a dark .nmep-* skin, but it is
      the legacy Nomad look (near-black slab, uppercase tracked labels).
      dora-ux.css:1565+ re-skins it light for Dora and wins on load
      order. Getting dark by weakening that light skin would expose the
      Nomad panel, which matches neither theme -- so the dark variant is
      written out in full here instead, in Dora's own palette.
   2. Every selector below is body.dora-mode.dora-dark-prefixed, putting
      it above BOTH the light Dora skin and theme-dark.css on
      specificity, so no !important is needed anywhere in this block.

   Values mirror the panel palette used by the rest of the dark chrome
   (see the token block above) so this reads as the same material. The
   lime accent is untouched: .nmep-done-btn and .nmep-action-btn.nm-on
   keep dark ink on lime, the same correction the other active pills get.
   ============================================================ */

body.dora-mode.dora-dark #nm-mesh-editor-panel {
  border-color: rgba(255, 255, 255, 0.1);
  background:
    radial-gradient(circle at 14% 0%, rgba(255, 255, 255, 0.06), transparent 32%),
    rgba(62, 67, 75, 0.86);
  box-shadow:
    0 16px 46px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);
  color: #e8eaed;
}

body.dora-mode.dora-dark .nmep-header-title {
  color: #e8eaed;
}

/* Lighter than the other secondary text on purpose: the badge sits on its own
   raised pill, which lifts the local background enough that the shared
   secondary grey measured 3.8:1 against it. */
body.dora-mode.dora-dark .nmep-header-badge {
  background: rgba(255, 255, 255, 0.08);
  color: #c3c8ce;
}

body.dora-mode.dora-dark .nmep-back-btn {
  border-color: rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.07);
  color: #d5d9de;
}

body.dora-mode.dora-dark .nmep-back-btn:hover,
body.dora-mode.dora-dark .nmep-done-btn:hover {
  /* brightness(0.97) reads as "pressed" on light fills but as "going
     out" on dark ones -- lift instead of dim. */
  filter: brightness(1.12);
}

/* Dark ink outlines vanish against a dark panel. */
body.dora-mode.dora-dark .nmep-back-btn:focus-visible,
body.dora-mode.dora-dark .nmep-done-btn:focus-visible,
body.dora-mode.dora-dark .nmep-action-btn:focus-visible,
body.dora-mode.dora-dark .nmep-slider:focus-visible {
  outline-color: #d2fb14;
}

body.dora-mode.dora-dark .nmep-section-label,
body.dora-mode.dora-dark .nmep-icon,
body.dora-mode.dora-dark .nmep-slider-value,
body.dora-mode.dora-dark .nmep-action-btn,
body.dora-mode.dora-dark .nmep-action-btn span,
body.dora-mode.dora-dark .nmep-color-sublabel {
  color: #a8adb4;
}

body.dora-mode.dora-dark .nmep-slider-label,
body.dora-mode.dora-dark .nmep-color-name {
  color: #d5d9de;
}

body.dora-mode.dora-dark .nmep-divider {
  background: rgba(255, 255, 255, 0.1);
}

body.dora-mode.dora-dark .nmep-slider-row {
  border-color: rgba(255, 255, 255, 0.08);
  background: rgba(255, 255, 255, 0.05);
}

/* Slider tracks: the unfilled half is a dark-on-light tint that would
   disappear here. The thumb's white ring is kept -- it reads as a light
   knob on a dark track, which is correct -- but its shadow goes darker. */
body.dora-mode.dora-dark .nmep-slider::-webkit-slider-runnable-track {
  background: linear-gradient(
    to right,
    var(--accent) var(--pct, 0%),
    rgba(255, 255, 255, 0.16) var(--pct, 0%)
  );
}

body.dora-mode.dora-dark .nmep-slider::-moz-range-track {
  background: rgba(255, 255, 255, 0.16);
}

body.dora-mode.dora-dark .nmep-slider::-webkit-slider-thumb,
body.dora-mode.dora-dark .nmep-slider::-moz-range-thumb {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
}

body.dora-mode.dora-dark .nmep-action-btn {
  border-color: rgba(255, 255, 255, 0.08);
  background: rgba(255, 255, 255, 0.05);
}

body.dora-mode.dora-dark .nmep-action-btn:hover {
  border-color: rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.09);
  color: #e2e7ed;
}

/* Lime stays lime, so the ink on it stays dark -- same rule the other
   active pills needed once the surrounding text went light. */
body.dora-mode.dora-dark .nmep-action-btn.nm-on,
body.dora-mode.dora-dark .nmep-action-btn.nm-on span {
  color: #1b2200;
}

body.dora-mode.dora-dark .nmep-action-btn.nm-on {
  border-color: rgba(210, 251, 20, 0.9);
  background: rgba(210, 251, 20, 0.85);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.24);
}

/* The overlay sits behind the iframe and is normally covered by it, but
   it does show for the moment before the workspace document paints --
   a white flash on the way into a dark FaceGroup. nomad.css paints it
   #ffffff; theme-dark.css:623 then forces near-black in BOTH themes,
   which is equally wrong in light. Pin each theme to its own backdrop.
   The values match the workspace's own (see its styles.css: they track
   the sculpt viewport's RENDERED colour, not the theme token's hex). */
body.dora-mode .nm-facegroup-workspace-overlay,
body.dora-mode .nm-facegroup-workspace-frame {
  background: #d1d2d3 !important;
}

body.dora-mode.dora-dark .nm-facegroup-workspace-overlay,
body.dora-mode.dora-dark .nm-facegroup-workspace-frame {
  background: #14181f !important;
}

/* ============================================================
   ASSET LIBRARY -- top-bar tool + flyout
   Self-contained (mirrors the shapes tool/flyout values) so it inherits
   nothing that could drift, and carries its own dark override. The trigger is
   wrapped in .dora-asset-tool, so the ".dora-topbar > button" direct-child rule
   never reaches it -- same trap the FaceGroup/Mask block above documents.
   ============================================================ */
.dora-asset-tool {
  position: relative;
}

.dora-asset-tool > button {
  width: calc(56px * var(--dora-ui-scale));
  min-width: calc(56px * var(--dora-ui-scale));
  height: calc(56px * var(--dora-ui-scale));
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}

.dora-asset-tool > button svg {
  width: calc(22px * var(--dora-ui-scale));
  height: calc(22px * var(--dora-ui-scale));
}

.dora-asset-tool > button:hover:not(:disabled) {
  border-color: var(--toolbar-hover-border);
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon-hover);
  box-shadow: var(--toolbar-hover-shadow);
}

.dora-asset-tool > button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}

/* Panel: fixed + z-index 40 + JS-positioned, exactly like the shapes flyout
   (reparented to <body>; see the .dora-shapes-flyout comment for why). */
.dora-asset-flyout {
  position: fixed;
  z-index: 40;
  width: 200px;
  max-height: calc(100dvh - 40px);
  overflow-y: auto;
  padding: 8px;
  border: 1px solid var(--glass-border);
  border-radius: 14px;
  background: rgba(250, 251, 252, 0.9);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  box-shadow: 0 16px 42px rgba(44, 51, 63, 0.18), inset 0 1px 0 #fff;
}

.dora-asset-flyout[hidden] {
  display: none;
}

.dora-asset-flyout strong {
  display: block;
  padding: 4px 6px 8px;
  color: var(--text-primary);
  font-size: 12px;
  font-weight: 600;
}

.dora-asset-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.dora-asset-empty {
  margin: 0;
  padding: 6px;
  color: var(--text-secondary);
  font-size: 11px;
}

.dora-asset-flyout button {
  width: 100%;
  min-height: 44px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 8px;
  border: 1px solid transparent;
  border-radius: 9px;
  background: transparent;
  color: #3f464d;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}

.dora-asset-flyout button:hover:not(:disabled) {
  border-color: rgba(159, 190, 14, 0.4);
  background: rgba(210, 251, 20, 0.14);
  color: #4d5f00;
}

.dora-asset-flyout button:disabled {
  opacity: 0.55;
  cursor: progress;
}

.dora-asset-thumb {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 7px;
  object-fit: cover;
  background: rgba(17, 18, 20, 0.06);
}

/* Placeholder when a manifest entry has no thumbnail: a subtle cube glyph so
   the row still has a consistent leading media slot. */
.dora-asset-thumb-placeholder {
  position: relative;
  border: 1px solid rgba(17, 18, 20, 0.08);
}

.dora-asset-thumb-placeholder::after {
  content: "";
  position: absolute;
  inset: 9px;
  border-radius: 3px;
  border: 1.5px solid rgba(17, 18, 20, 0.22);
  transform: rotate(45deg);
}

.dora-asset-name {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* --- dark --- */
:root.dora-solid-panels body.dora-mode.dora-dark .dora-asset-flyout {
  border-color: rgba(255, 255, 255, 0.14);
  background: var(--dora-panel-color);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-asset-flyout button {
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-asset-flyout button:hover:not(:disabled) {
  border-color: rgba(210, 251, 20, 0.35);
  background: rgba(210, 251, 20, 0.16);
  color: #e2e7ed;
}

body.dora-mode.dora-dark .dora-asset-thumb {
  background: rgba(255, 255, 255, 0.08);
}

body.dora-mode.dora-dark .dora-asset-thumb-placeholder {
  border-color: rgba(255, 255, 255, 0.14);
}

body.dora-mode.dora-dark .dora-asset-thumb-placeholder::after {
  border-color: rgba(255, 255, 255, 0.28);
}

/* ---- Alpha map picker (brush stencil) ----
   Same glass chrome as .dora-asset-flyout, but the swatches lay out as a
   thumbnail grid (like the matcap swatches). */
.dora-alpha-flyout {
  position: fixed;
  z-index: 40;
  width: 214px;
  max-height: calc(100dvh - 40px);
  overflow-y: auto;
  padding: 8px;
  border: 1px solid var(--glass-border);
  border-radius: 14px;
  background: rgba(250, 251, 252, 0.9);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  box-shadow: 0 16px 42px rgba(44, 51, 63, 0.18), inset 0 1px 0 #fff;
}
.dora-alpha-flyout[hidden] { display: none; }
.dora-alpha-flyout strong {
  display: block;
  padding: 4px 6px 8px;
  color: var(--text-primary);
  font-size: 12px;
  font-weight: 600;
}
.dora-alpha-list {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
}
.dora-alpha-swatch {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 5px 3px;
  border: 1px solid transparent;
  border-radius: 10px;
  background: transparent;
  color: #3f464d;
  font-size: 10px;
  cursor: pointer;
}
.dora-alpha-swatch:hover {
  border-color: rgba(159, 190, 14, 0.4);
  background: rgba(210, 251, 20, 0.14);
  color: #4d5f00;
}
.dora-alpha-swatch.dora-active {
  border-color: rgba(159, 190, 14, 0.9);
  background: rgba(210, 251, 20, 0.22);
  color: #3c4a00;
}
.dora-alpha-thumb {
  width: 46px;
  height: 46px;
  border-radius: 8px;
  object-fit: cover;
  background: rgba(17, 18, 20, 0.06);
  border: 1px solid rgba(17, 18, 20, 0.08);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  color: rgba(17, 18, 20, 0.4);
}
.dora-alpha-thumb-import { font-size: 24px; font-weight: 300; }
.dora-alpha-name {
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.dora-alpha-lock {
  width: 100%;
  margin-top: 8px;
  min-height: 34px;
  border: 1px solid var(--glass-border);
  border-radius: 9px;
  background: transparent;
  color: #3f464d;
  font-size: 11px;
  cursor: pointer;
}
.dora-alpha-lock[aria-pressed="true"] {
  border-color: rgba(159, 190, 14, 0.9);
  background: rgba(210, 251, 20, 0.2);
  color: #3c4a00;
}
/* --- dark --- */
:root.dora-solid-panels body.dora-mode.dora-dark .dora-alpha-flyout {
  border-color: rgba(255, 255, 255, 0.14);
  background: var(--dora-panel-color);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
body.dora-mode.dora-dark .dora-alpha-swatch { color: #c3c8ce; }
body.dora-mode.dora-dark .dora-alpha-swatch:hover {
  border-color: rgba(210, 251, 20, 0.35);
  background: rgba(210, 251, 20, 0.16);
  color: #e2e7ed;
}
body.dora-mode.dora-dark .dora-alpha-swatch.dora-active {
  border-color: rgba(210, 251, 20, 0.6);
  background: rgba(210, 251, 20, 0.22);
  color: #eef3d0;
}
body.dora-mode.dora-dark .dora-alpha-thumb {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.14);
  color: rgba(255, 255, 255, 0.4);
}
body.dora-mode.dora-dark .dora-alpha-lock {
  border-color: rgba(255, 255, 255, 0.14);
  color: #c3c8ce;
}
body.dora-mode.dora-dark .dora-alpha-lock[aria-pressed="true"] {
  border-color: rgba(210, 251, 20, 0.55);
  background: rgba(210, 251, 20, 0.18);
  color: #eef3d0;
}

/* Live alpha preview shown ON the left-rail Alpha button (replaces the stencil
   icon once an alpha is picked). A small square swatch centered in the 44px
   button; the border delineates it against both light and dark rails (alpha
   maps are opaque grayscale, so a mostly-black one would otherwise blend in). */
button[data-action="alpha-toggle"] {
  display: flex;
  align-items: center;
  justify-content: center;
}
.dora-alpha-btn-thumb {
  width: 24px;
  height: 24px;
  border-radius: 5px;
  object-fit: cover;
  display: block;
  border: 1px solid rgba(17, 18, 20, 0.2);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}
body.dora-mode.dora-dark .dora-alpha-btn-thumb {
  border-color: rgba(255, 255, 255, 0.25);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.35);
}

/* ---- Export loading overlay (3MF) ----
   Full-screen busy indicator while the exporter builds + zips. The spinner uses a
   transform animation (runs on the compositor) so it keeps turning even while the
   synchronous XML build blocks the main thread. */
.dora-export-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(20, 22, 26, 0.45);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.dora-export-overlay.dora-visible { display: flex; }
.dora-export-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 24px 34px;
  border-radius: 16px;
  background: rgba(250, 251, 252, 0.97);
  box-shadow: 0 18px 46px rgba(0, 0, 0, 0.3), inset 0 1px 0 #fff;
  color: #2c333f;
  font-size: 13px;
  font-weight: 500;
}
.dora-export-spinner {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 3px solid rgba(120, 130, 140, 0.25);
  border-top-color: #9fbe0e;
  animation: dora-export-spin 0.8s linear infinite;
}
@keyframes dora-export-spin { to { transform: rotate(360deg); } }
body.dora-mode.dora-dark .dora-export-card {
  background: rgba(48, 52, 60, 0.97);
  color: #e2e7ed;
  box-shadow: 0 18px 46px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
body.dora-mode.dora-dark .dora-export-spinner {
  border-color: rgba(255, 255, 255, 0.18);
  border-top-color: #d2fb14;
}

/* ---- Export STL parts dialog ----
   Backdrop modal (same shell language as .dora-export-card) holding a per-piece
   checklist + Export/Cancel. Fixed max-height with a scrolling list so a scene with
   many pieces still fits. */
.dora-export-parts {
  position: fixed;
  inset: 0;
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(20, 22, 26, 0.45);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.dora-export-parts > [role="dialog"] {
  position: relative;
  width: 320px;
  max-width: calc(100vw - 32px);
  max-height: calc(100dvh - 48px);
  display: flex;
  flex-direction: column;
  padding: 20px 22px 18px;
  border-radius: 16px;
  background: rgba(250, 251, 252, 0.98);
  box-shadow: 0 18px 46px rgba(0, 0, 0, 0.3), inset 0 1px 0 #fff;
  color: #2c333f;
}
.dora-export-parts h2 {
  margin: 0 0 4px;
  font-size: 15px;
  font-weight: 600;
}
.dora-export-parts p {
  margin: 0 0 12px;
  font-size: 12px;
  color: #6a7178;
}
.dora-export-parts-close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: #6a7178;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.dora-export-parts-close:hover { background: rgba(0, 0, 0, 0.06); color: #2c333f; }
.dora-export-parts-actions-top {
  display: flex;
  gap: 6px;
  margin-bottom: 8px;
}
.dora-export-parts-actions-top button {
  padding: 3px 12px;
  border: 1px solid rgba(26, 28, 30, 0.14);
  border-radius: 7px;
  background: transparent;
  color: #3f464d;
  font-size: 12px;
  cursor: pointer;
}
.dora-export-parts-actions-top button:hover {
  border-color: rgba(159, 190, 14, 0.5);
  background: rgba(210, 251, 20, 0.14);
}
.dora-export-parts-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0 -6px 14px;
  padding: 2px 6px;
}
.dora-export-parts-row {
  display: flex;
  align-items: center;
  gap: 9px;
  min-height: 34px;
  padding: 0 8px;
  border-radius: 8px;
  font-size: 13px;
  cursor: pointer;
}
.dora-export-parts-row:hover { background: rgba(0, 0, 0, 0.04); }
.dora-export-parts-row input {
  width: 16px;
  height: 16px;
  flex: none;
  margin: 0;
  cursor: pointer;
  accent-color: var(--toolbar-active-bg, #d2fb14);
}
.dora-export-parts-row span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dora-export-parts-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.dora-export-parts-actions button {
  min-height: 36px;
  padding: 0 16px;
  border-radius: 9px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.dora-export-parts-cancel {
  border: 1px solid rgba(26, 28, 30, 0.14);
  background: transparent;
  color: #3f464d;
}
.dora-export-parts-cancel:hover { background: rgba(0, 0, 0, 0.05); }
.dora-export-parts-go {
  border: 1px solid rgba(159, 190, 14, 0.6);
  background: rgba(210, 251, 20, 0.92);
  color: #2c333f;
}
.dora-export-parts-go:hover { filter: brightness(1.05); }

body.dora-mode.dora-dark .dora-export-parts > [role="dialog"] {
  background: rgba(48, 52, 60, 0.98);
  color: #e2e7ed;
  box-shadow: 0 18px 46px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
body.dora-mode.dora-dark .dora-export-parts p { color: #9aa0a7; }
body.dora-mode.dora-dark .dora-export-parts-close { color: #9aa0a7; }
body.dora-mode.dora-dark .dora-export-parts-close:hover { background: rgba(255, 255, 255, 0.08); color: #e8eaed; }
body.dora-mode.dora-dark .dora-export-parts-actions-top button { border-color: rgba(255, 255, 255, 0.16); color: #c3c8ce; }
body.dora-mode.dora-dark .dora-export-parts-row:hover { background: rgba(255, 255, 255, 0.06); }
body.dora-mode.dora-dark .dora-export-parts-cancel { border-color: rgba(255, 255, 255, 0.16); color: #c3c8ce; }
body.dora-mode.dora-dark .dora-export-parts-cancel:hover { background: rgba(255, 255, 255, 0.06); }

/* ---- Mask floating menu ----
   A slim horizontal bar shown while the Mask tool is active -- styled with the same
   shell tokens as .dora-topbar/.dora-bottombar so it matches the other workspace bars.
   JS clamps its top/bottom so it never overlaps those bars; max-width keeps it clear of
   the left/right rails. The body is an empty inline row -- controls added later. */
.dora-mask-menu {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  z-index: 24;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
  padding: 5px;
  border: 1px solid var(--toolbar-shell-border);
  border-radius: var(--toolbar-shell-radius);
  background: var(--toolbar-shell-bg);
  -webkit-backdrop-filter: var(--toolbar-shell-blur);
  backdrop-filter: var(--toolbar-shell-blur);
  box-shadow: var(--toolbar-shell-shadow);
  min-width: 44px;
  min-height: 44px;
  max-width: calc(100vw - 170px);
}
.dora-mask-menu[hidden] { display: none; }
.dora-mask-menu-body {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
}
.dora-mask-menu-dock,
.dora-mask-menu-body button {
  flex: none;
  width: 44px;
  min-width: 44px;
  height: 44px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}
.dora-mask-menu-dock:hover,
.dora-mask-menu-body button:hover:not(:disabled) {
  border-color: var(--toolbar-hover-border);
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon-hover);
  box-shadow: var(--toolbar-hover-shadow);
}
/* on-tap toggles this when its region-select mode is on (synced globally) */
.dora-mask-menu-body button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}
.dora-mask-menu-body button:disabled {
  opacity: 0.4;
  cursor: default;
}
.dora-mask-menu-body button[hidden] { display: none; }
.dora-mask-menu-dock svg,
.dora-mask-menu-body button svg { width: 17px; height: 17px; }
/* Icon + caption stack for the six idle-state actions (Clear/Invert/Ontap/Extrude/
   Facegroup/Color). Only these carry .dora-mask-labeled -- the Commit/Cancel/swatch/
   slider controls that replace them mid-workflow keep their own dedicated sizing above. */
.dora-mask-menu-body button.dora-mask-labeled {
  width: auto;
  min-width: 46px;
  height: 44px;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  padding: 0 4px;
}
.dora-mask-menu-body button.dora-mask-labeled svg { flex: none; }
.dora-mask-btn-label {
  font-size: 9px;
  line-height: 1;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
  color: inherit;
  opacity: 0.82;
}
.dora-mask-menu .dora-tool-divider {
  width: 1px;
  height: 24px;
  margin: 0 2px;
}

/* Extrude split Commit/Cancel -- inline in the mask menu body (same bar): shown in place
   of the Extrude-split icon while a live preview is running. Extra class in the selector
   outweighs the base ".dora-mask-menu-body button" rule's element selector. */
.dora-mask-menu-body button.dora-mask-extrude-commit {
  width: auto;
  min-width: 0;
  height: 34px;
  padding: 0 18px;
  border: 1px solid rgba(159, 190, 14, 0.6);
  border-radius: 8px;
  background: rgba(210, 251, 20, 0.92);
  color: #2c333f;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.dora-mask-menu-body button.dora-mask-extrude-commit:hover {
  filter: brightness(1.05);
  border-color: rgba(159, 190, 14, 0.6);
  background: rgba(210, 251, 20, 0.92);
  color: #2c333f;
  box-shadow: none;
}
.dora-mask-menu-body button.dora-mask-extrude-x {
  width: 34px;
  min-width: 34px;
  height: 34px;
}
.dora-mask-menu-body button.dora-mask-extrude-x svg { width: 16px; height: 16px; }

/* Fit tolerance (mm) -- typeable text field (NOT type="number" -- number inputs reject/
   blank mid-typing states like "0.", see onMaskMenuInput) shown next to Commit/Cancel
   while an Extrude-split preview is live. Not a <button>, so it never collides with the
   base ".dora-mask-menu-body button" rule. */
.dora-mask-menu-body input.dora-mask-extrude-tolerance {
  flex: none;
  width: 60px;
  height: 34px;
  padding: 0 8px;
  border: 1px solid var(--toolbar-shell-border);
  border-radius: 8px;
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon-hover);
  font-size: 13px;
  /* left-aligned, not centered -- centered text in a narrow input scrolls/tracks the
     caret unpredictably in some browsers while typing past a couple characters. */
  text-align: left;
}
.dora-mask-menu-body input.dora-mask-extrude-tolerance:focus {
  outline: none;
  border-color: var(--toolbar-active-border);
  box-shadow: var(--toolbar-hover-shadow);
}
.dora-mask-menu-body input.dora-mask-extrude-tolerance[hidden] { display: none; }

/* Split solid -- Commit/Cancel + Thickness field, same three-piece shape as the Extrude trio
   above (own classes rather than sharing theirs, matching how fg-commit/fg-x are separate
   from the extrude pair even though visually identical -- see that block's own comment). */
.dora-mask-menu-body button.dora-mask-split-commit {
  width: auto;
  min-width: 0;
  height: 34px;
  padding: 0 18px;
  border: 1px solid rgba(159, 190, 14, 0.6);
  border-radius: 8px;
  background: rgba(210, 251, 20, 0.92);
  color: #2c333f;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.dora-mask-menu-body button.dora-mask-split-commit:hover {
  filter: brightness(1.05);
  border-color: rgba(159, 190, 14, 0.6);
  background: rgba(210, 251, 20, 0.92);
  color: #2c333f;
  box-shadow: none;
}
.dora-mask-menu-body button.dora-mask-split-cancel {
  width: 34px;
  min-width: 34px;
  height: 34px;
}
/* Gizmo toggle: same 34px square as Commit/Cancel's neighbors, neutral (not the red
   destructive tint those get) -- active state comes for free from the generic
   ".dora-mask-menu-body button.dora-active" rule, same as Wire/Tri/On-tap. */
.dora-mask-menu-body button.dora-mask-split-gizmo {
  width: 34px;
  min-width: 34px;
  height: 34px;
}
.dora-mask-menu-body button.dora-mask-split-gizmo svg { width: 16px; height: 16px; }
.dora-mask-menu-body button.dora-mask-split-cancel svg { width: 16px; height: 16px; }
.dora-mask-menu-body input.dora-mask-split-thickness {
  flex: none;
  width: 60px;
  height: 34px;
  padding: 0 8px;
  border: 1px solid var(--toolbar-shell-border);
  border-radius: 8px;
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon-hover);
  font-size: 13px;
  text-align: left;
}
.dora-mask-menu-body input.dora-mask-split-thickness:focus {
  outline: none;
  border-color: var(--toolbar-active-border);
  box-shadow: var(--toolbar-hover-shadow);
}
.dora-mask-menu-body input.dora-mask-split-thickness[hidden] { display: none; }

/* FaceGroup workflow -- Angle slider + Smooth edges + Commit/Close, shown in place of the
   FaceGroup icon while it is live. Commit reuses the extrude Commit's lime treatment so the
   bar has ONE visual language for "finish this"; the extra class outweighs the base
   ".dora-mask-menu-body button" element selector, same as the extrude pair above. */
.dora-mask-menu-body button.dora-fg-commit {
  width: auto;
  min-width: 0;
  height: 34px;
  padding: 0 18px;
  border-radius: 9px;
  border: 1px solid rgba(159, 190, 14, 0.45);
  background: rgba(198, 240, 16, 0.88);
  color: #2c333f;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.dora-mask-menu-body button.dora-fg-commit:hover {
  filter: brightness(1.05);
  border-color: rgba(159, 190, 14, 0.6);
  background: rgba(210, 251, 20, 0.92);
  color: #2c333f;
  box-shadow: none;
}
.dora-mask-menu-body button.dora-fg-x {
  width: 34px;
  min-width: 34px;
  height: 34px;
}
.dora-mask-menu-body button.dora-fg-x svg { width: 16px; height: 16px; }
/* Cancel/close reads as a destructive step next to the lime Commit button, so both X's in
   this bar (extrude-cancel and facegroup-close) are tinted red instead of the neutral
   toolbar gray -- svg stroke="currentColor", so setting color here is enough. */
.dora-mask-menu-body button.dora-mask-extrude-x,
.dora-mask-menu-body button.dora-fg-x,
.dora-mask-menu-body button.dora-mask-split-cancel {
  color: #e5484d;
}
.dora-mask-menu-body button.dora-mask-extrude-x:hover,
.dora-mask-menu-body button.dora-fg-x:hover,
.dora-mask-menu-body button.dora-mask-split-cancel:hover {
  color: #ff6369;
  border-color: rgba(229, 72, 77, 0.45);
  background: rgba(229, 72, 77, 0.14);
  box-shadow: none;
}
/* Smooth edges and Paint are persistent MODES (each selects a sculpt tool), so they need the
   same lit state the on-tap toggle uses -- that rule is keyed on .dora-active and already
   applies. Sizing deliberately has NO rule here: both now carry .dora-mask-labeled (icon over
   caption, like Clear/Invert/Ontap/Color), and a width/height pair here would sit LATER in the
   file at equal specificity and beat that shared rule, squeezing the caption. */

/* FaceGroup color-pick swatch. An <input>, not a <button>, so the base 44px sizing rule
   above doesn't reach it (same reasoning as the extrude-tolerance field just below) --
   sized to match the bar's other 34px-tall inline controls instead of
   .dora-paint-color-swatch's own full-width/42px flyout sizing. */
.dora-mask-menu-body input.dora-fg-color-swatch,
.dora-mask-menu-body input.dora-fg-paint-swatch {
  flex: none;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--toolbar-shell-border);
  border-radius: 8px;
  background: none;
  cursor: pointer;
}

/* Angle slider. Not a <button>, so the base sizing rule does not reach it. Sized to sit
   inside the 44px bar without stretching it. */
.dora-mask-menu-body input.dora-fg-angle,
.dora-mask-menu-body input.dora-mask-wall-smooth,
.dora-mask-menu-body input.dora-mask-edge-smooth,
.dora-mask-menu-body input.dora-mask-split-thickness-slider {
  flex: none;
  width: 108px;
  height: 34px;
  margin: 0;
  padding: 0;
  background: transparent;
  border: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
.dora-mask-menu-body input.dora-fg-angle[hidden],
.dora-mask-menu-body input.dora-mask-wall-smooth[hidden],
.dora-mask-menu-body input.dora-mask-edge-smooth[hidden],
.dora-mask-menu-body input.dora-mask-split-thickness-slider[hidden] { display: none; }
.dora-mask-menu-body input.dora-fg-angle::-webkit-slider-runnable-track,
.dora-mask-menu-body input.dora-mask-wall-smooth::-webkit-slider-runnable-track,
.dora-mask-menu-body input.dora-mask-edge-smooth::-webkit-slider-runnable-track,
.dora-mask-menu-body input.dora-mask-split-thickness-slider::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: var(--toolbar-hover-border, rgba(255, 255, 255, 0.22));
}
.dora-mask-menu-body input.dora-fg-angle::-webkit-slider-thumb,
.dora-mask-menu-body input.dora-mask-wall-smooth::-webkit-slider-thumb,
.dora-mask-menu-body input.dora-mask-edge-smooth::-webkit-slider-thumb,
.dora-mask-menu-body input.dora-mask-split-thickness-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  margin-top: -5px;
  border-radius: 50%;
  border: none;
  background: rgba(198, 240, 16, 0.95);
}
.dora-mask-menu-body input.dora-fg-angle::-moz-range-track,
.dora-mask-menu-body input.dora-mask-wall-smooth::-moz-range-track,
.dora-mask-menu-body input.dora-mask-edge-smooth::-moz-range-track,
.dora-mask-menu-body input.dora-mask-split-thickness-slider::-moz-range-track {
  height: 4px;
  border-radius: 2px;
  background: var(--toolbar-hover-border, rgba(255, 255, 255, 0.22));
}
.dora-mask-menu-body input.dora-fg-angle::-moz-range-thumb,
.dora-mask-menu-body input.dora-mask-wall-smooth::-moz-range-thumb,
.dora-mask-menu-body input.dora-mask-edge-smooth::-moz-range-thumb,
.dora-mask-menu-body input.dora-mask-split-thickness-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: none;
  background: rgba(198, 240, 16, 0.95);
}
.dora-mask-menu-body input.dora-fg-angle:focus,
.dora-mask-menu-body input.dora-mask-wall-smooth:focus,
.dora-mask-menu-body input.dora-mask-edge-smooth:focus,
.dora-mask-menu-body input.dora-mask-split-thickness-slider:focus { outline: none; }
.dora-mask-menu-body input.dora-fg-angle:focus::-webkit-slider-thumb,
.dora-mask-menu-body input.dora-mask-wall-smooth:focus::-webkit-slider-thumb,
.dora-mask-menu-body input.dora-mask-edge-smooth:focus::-webkit-slider-thumb,
.dora-mask-menu-body input.dora-mask-split-thickness-slider:focus::-webkit-slider-thumb {
  box-shadow: 0 0 0 3px rgba(198, 240, 16, 0.25);
}
/* Degree readout next to the slider. */
.dora-mask-menu-body .dora-fg-angle-out,
.dora-mask-menu-body .dora-mask-wall-smooth-out,
.dora-mask-menu-body .dora-mask-edge-smooth-out {
  flex: none;
  min-width: 30px;
  font-size: 12px;
  font-variant-numeric: tabular-nums; /* stops the bar twitching as the value changes width */
  color: var(--toolbar-icon);
  text-align: right;
}
.dora-mask-menu-body .dora-fg-angle-out[hidden],
.dora-mask-menu-body .dora-mask-wall-smooth-out[hidden],
.dora-mask-menu-body .dora-mask-edge-smooth-out[hidden] { display: none; }

/* Wall/Edge slider name tags -- the two smoothing sliders are otherwise identical, no
   way to tell which is which without hovering for the title tooltip. */
.dora-mask-menu-body .dora-mask-slider-label {
  flex: none;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--toolbar-icon);
  opacity: 0.75;
  white-space: nowrap;
}
.dora-mask-menu-body .dora-mask-slider-label[hidden] { display: none; }

/* ============================================================
   FLEXI CUTTER toolbar (owned by src/editing/FlexiCut.js)
   Self-contained: new selectors only, touches no existing rule. A centered
   floating bar shown only during a live-cut session.
   ============================================================ */
.dora-flexicut-bar {
  position: fixed;
  left: 50%;
  bottom: 84px;
  transform: translateX(-50%);
  z-index: 200;
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 6px;
  padding: 8px 10px;
  border: 1px solid var(--glass-border);
  border-radius: 14px;
  background: rgba(250, 251, 252, 0.92);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  box-shadow: 0 16px 42px rgba(44, 51, 63, 0.2), inset 0 1px 0 #fff;
  font-family: var(--font-sans);
  max-width: calc(100vw - 24px);
  /* Bar can outgrow a narrow viewport with the snap-degree controls added -- scroll sideways
     within the bar itself instead of wrapping (which broke the single-row layout) or clipping
     buttons off-screen. */
  overflow-x: auto;
  scrollbar-width: none;
}
.dora-flexicut-bar::-webkit-scrollbar { display: none; }

.dora-flexicut-title {
  padding: 0 4px 0 2px;
  color: var(--text-secondary);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}

.dora-flexicut-btn {
  flex: none;
  min-height: 38px;
  padding: 0 11px;
  border: 1px solid rgba(26, 28, 30, 0.1);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.6);
  color: #3f464d;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
}

.dora-flexicut-btn:hover {
  border-color: rgba(159, 190, 14, 0.4);
  background: rgba(210, 251, 20, 0.14);
  color: #4d5f00;
}

/* Clicker "2 parts model" A/B asset picker -- a tight pair, not two normal bar buttons, so it
   reads as one compact toggle instead of adding two more full-width items to an already-long bar. */
.dora-clicker-ab {
  display: inline-flex;
  flex: none;
  gap: 3px;
}
.dora-clicker-ab-btn {
  min-width: 30px;
  padding: 0 8px;
  text-align: center;
}

.dora-flexicut-commit {
  border-color: #a5c50c;
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}

/* The clicker bar grows as features are added (Subtract (keep) is the latest) and can outgrow the
   viewport -- .dora-flexicut-bar already scrolls sideways for that, but its scrollbar is
   deliberately hidden (see the overflow-x note above), so nothing tells a user there's more to
   scroll to. That silently pushed Commit clicker off the reachable edge at normal window widths
   (reported: "moved flat base but can't click Commit flat base" -- elementFromPoint at the
   button's own on-screen centre returned nothing; it wasn't disabled, it was off-screen).
   Stick Cancel/Commit to the bar's own right edge so the one button you always need (finish or
   abort) can never be scrolled out of reach by whatever gets added to the bar next. */
.dora-clicker-bar .dora-flexicut-cancel,
.dora-clicker-bar .dora-flexicut-commit {
  position: sticky;
  right: 0;
  z-index: 1;
}
.dora-clicker-bar .dora-flexicut-cancel {
  background: rgba(250, 251, 252, 0.98);
}
body.dora-mode.dora-dark .dora-clicker-bar .dora-flexicut-cancel {
  background: rgba(62, 67, 75, 0.98);
}

/* Inline slider field for the flexicut-family bars (Spring Toy's Stretch). Compact horizontal
   label + range so it sits inside the bar like a button, not a full-width flyout row. */
.dora-flexicut-field {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 6px;
  padding: 0 6px;
  font-size: 12px;
  white-space: nowrap;
}
.dora-flexicut-field > span { display: inline-flex; gap: 4px; align-items: center; }
.dora-flexicut-field input[type="range"] { width: 96px; }

/* Spring Toy bar -- same sticky Cancel/Commit rule as the clicker bar above, so the finish/abort
   buttons never scroll out of reach when the Stretch slider + toggles fill the bar. */
.dora-springtoy-bar .dora-flexicut-cancel,
.dora-springtoy-bar .dora-flexicut-commit {
  position: sticky;
  right: 0;
  z-index: 1;
}
.dora-springtoy-bar .dora-flexicut-cancel {
  background: rgba(250, 251, 252, 0.98);
}
body.dora-mode.dora-dark .dora-springtoy-bar .dora-flexicut-cancel {
  background: rgba(62, 67, 75, 0.98);
}

.dora-flexicut-commit:hover {
  border-color: #a5c50c;
  background: var(--accent);
  filter: brightness(0.97);
  color: var(--accent-ink);
}

.dora-flexicut-cancel {
  color: #dc2626;
  border-color: #dc2626;
}

.dora-flexicut-cancel:hover {
  color: #dc2626;
  border-color: #dc2626;
  background: rgba(220, 38, 38, 0.08);
}

/* Armed: click 1 spawned the copy and handed it the gizmo; click 2 (this state's
   own button) drops it. Accent-filled like Commit so "something is live" reads
   at a glance, but a distinct border so it's not mistaken for Commit itself. */
.dora-flexicut-duplicate-armed {
  border-color: #a5c50c;
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}

.dora-flexicut-duplicate-armed:hover {
  background: var(--accent);
  filter: brightness(0.97);
  color: var(--accent-ink);
}

/* Stamp toggle that rides the gizmo (FlexiCut.buildStampToggle). position:fixed because its
   left/top are written every frame from camera.project() in viewport-client coordinates --
   the same space getBoundingClientRect() reports, so no offset-parent math is involved.
   z-index sits above the canvas but below the flexicut bar (150, see below) so it can never
   cover the Commit/Cancel controls. */
.dora-flexicut-stamp {
  position: fixed;
  z-index: 140;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--glass-border);
  border-radius: 9px;
  background: rgba(250, 251, 252, 0.94);
  color: #3f464d;
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(20, 24, 30, 0.28);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  backdrop-filter: blur(10px) saturate(140%);
}

.dora-flexicut-stamp:hover {
  border-color: rgba(159, 190, 14, 0.5);
  color: #4d5f00;
}

/* ON = stamping armed. Same accent as the bar's armed Duplicate button, so the two faces of
   this one state read identically. */
.dora-flexicut-stamp.dora-flexicut-stamp-on {
  border-color: #a5c50c;
  background: var(--accent);
  color: var(--accent-ink);
}

body.dora-mode.dora-dark .dora-flexicut-stamp {
  border-color: rgba(255, 255, 255, 0.14);
  background: rgba(62, 67, 75, 0.96);
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-flexicut-stamp.dora-flexicut-stamp-on {
  border-color: #a5c50c;
  background: var(--accent);
  color: var(--accent-ink);
}

/* Disabled (e.g. Flat base after the flexi cutter has been moved -- flat base is a
   "do it first" step). Greyed and non-interactive; the button's title says why. */
.dora-flexicut-btn.dora-flexicut-disabled,
.dora-flexicut-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}

/* Snap degree: the pill borrows .dora-flexicut-duplicate-armed for its "on" look (same lit/
   accent-filled state, different button). The editor row is its own inline group so the number
   field and its two actions read as one unit next to the pill that opened them. */
.dora-flexicut-snap-editor {
  display: flex;
  flex: none;
  align-items: center;
  gap: 6px;
}
.dora-flexicut-snap-editor[hidden] { display: none !important; }

/* While the snap editor is open, every OTHER top-level action (Flat base, Duplicate,
   Delete, Cancel, Commit cut) hides -- a half-open number-entry control sitting in a row
   of unrelated buttons read as clutter/confusing. Only the title and the Snap pill itself
   (excluded below) stay, plus .dora-flexicut-snap-editor (a <span>, not a button, so this
   selector never touches it). */
.dora-flexicut-bar.dora-flexicut-snap-editing > button[data-flexicut]:not([data-flexicut="snap-toggle"]) {
  display: none !important;
}

.dora-flexicut-snap-input {
  flex: none;
  width: 52px;
  min-height: 38px;
  padding: 0 8px;
  border: 1px solid rgba(26, 28, 30, 0.1);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.6);
  color: #3f464d;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
/* The built-in up/down spinner reads as a foreign control against this chrome -- the field is
   small and Commit/Enter are the intended ways to change the value anyway. */
.dora-flexicut-snap-input::-webkit-inner-spin-button,
.dora-flexicut-snap-input::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* --- dark --- */
body.dora-mode.dora-dark .dora-flexicut-bar {
  border-color: rgba(255, 255, 255, 0.14);
  background: rgba(62, 67, 75, 0.96);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

body.dora-mode.dora-dark .dora-flexicut-title {
  color: #a8adb4;
}

body.dora-mode.dora-dark .dora-flexicut-btn {
  border-color: rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-flexicut-btn:hover {
  border-color: rgba(210, 251, 20, 0.35);
  background: rgba(210, 251, 20, 0.16);
  color: #e2e7ed;
}

/* BUG: body.dora-mode.dora-dark .dora-flexicut-btn (above) and this rule are the SAME
   specificity (0,3,1) -- without background/border-color repeated here, the generic dark
   button background/border wins on pure source order and Commit silently loses its lime
   fill in dark mode (only `color` survived, giving near-black text on a near-black pill --
   this is why Commit read as a flat grey button instead of the signature lime). Repeating
   background+border-color here, later in the cascade, is what actually wins them back. */
body.dora-mode.dora-dark .dora-flexicut-commit,
body.dora-mode.dora-dark .dora-flexicut-commit:hover {
  background: var(--accent);
  border-color: #a5c50c;
  color: var(--accent-ink);
}

/* Cancel: dark mode has no override at all today, so the generic dark .dora-flexicut-btn
   color/border (higher specificity, see above) silently wins over the light-mode-only
   .dora-flexicut-cancel rule -- red never showed up in dark mode without this block. */
body.dora-mode.dora-dark .dora-flexicut-cancel {
  color: #f87171;
  border-color: #f87171;
}

body.dora-mode.dora-dark .dora-flexicut-cancel:hover {
  color: #f87171;
  border-color: #f87171;
  background: rgba(248, 113, 113, 0.12);
}

/* Same background/border loss as Commit above, affecting every "armed" state that shares
   this class (Duplicate armed, Flat-base armed, Snap armed) -- fixing it here so the whole
   family of accent-filled buttons is consistent, not just Commit. */
body.dora-mode.dora-dark .dora-flexicut-duplicate-armed,
body.dora-mode.dora-dark .dora-flexicut-duplicate-armed:hover {
  background: var(--accent);
  border-color: #a5c50c;
  color: var(--accent-ink);
}

body.dora-mode.dora-dark .dora-flexicut-snap-input {
  border-color: rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.07);
  color: #c3c8ce;
}

body.dora-mode.dora-dark .dora-flexicut-duplicate-armed,
body.dora-mode.dora-dark .dora-flexicut-duplicate-armed:hover {
  color: var(--accent-ink);
}

/* Clicker bar reuses the whole .dora-flexicut-bar look; the only extra is a wait cursor
   while the exact boolean bakes on Commit (setBusy toggles .dora-flexicut-busy). */
.dora-flexicut-bar.dora-flexicut-busy,
.dora-flexicut-bar.dora-flexicut-busy .dora-flexicut-btn { cursor: progress; }

/* ---- Clicker Cut panel (CLICKER_CUT_BUILD_PLAN.md section 3) ---------------
   Same glass chrome as the flexicut bar, but a STACK of numbered steps rather than one row:
   the flow is sequential (split -> press -> slot -> stem -> commit) and each step owns several
   controls, so a single scrolling row would hide the order the user has to follow. */
/* v2 redesign: the panel root is no longer one glass pill -- it is a TRANSPARENT vertical wrapper
   holding TWO independent pills: a thin secondary bar (camera view + display toggles) above, and the
   main step bar below. The .dora-flexicut-bar base still positions it bottom-centre; the pill chrome
   (bg / border / blur / shadow / scroll) is neutralised here and re-applied to each inner bar. */
.dora-clickercut-panel {
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 0;
  min-width: 0;
  border: none;
  background: transparent;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  overflow: visible;
}
/* [hidden] must beat the display:flex we set on the views/step-panels below */
.dora-clickercut-panel [hidden] { display: none !important; }

/* ---- thin secondary bar: camera view (segmented line) + display toggles ---- */
.dora-cc-subbar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 7px;
  border: 1px solid var(--glass-border);
  border-radius: 11px;
  background: rgba(250, 251, 252, 0.9);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  backdrop-filter: blur(16px) saturate(140%);
  box-shadow: 0 8px 22px rgba(44, 51, 63, 0.14), inset 0 1px 0 #fff;
  font-family: var(--font-sans);
  /* Parity with .dora-cc-main: cap to the viewport and scroll SIDEWAYS instead of running the
     view line + toggles off both screen edges. Without this the thin bar was 387px on a 375px
     phone (446px in Vietnamese), pushing Front/X-ray off-screen and unreachable. */
  max-width: calc(100vw - 24px);
  overflow-x: auto;
  scrollbar-width: none;
}
.dora-cc-subbar::-webkit-scrollbar { display: none; }
/* View = one thin segmented line of tiny buttons split by hairline dividers */
.dora-cc-view-seg {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;   /* don't shrink -- overflow so .dora-cc-subbar scrolls (like the main bar) */
  border: 1px solid var(--glass-border);
  border-radius: 8px;
  overflow: hidden;
}
.dora-cc-vbtn {
  min-height: 22px;
  padding: 2px 8px;
  border: 0;
  border-left: 1px solid var(--glass-border);
  background: transparent;
  color: var(--text-secondary);
  font: inherit;
  font-size: calc(10px * var(--dora-font-scale, 1));
  white-space: nowrap;
  cursor: pointer;
}
.dora-cc-vbtn:first-child { border-left: 0; }
.dora-cc-vbtn:hover { background: rgba(210, 251, 20, 0.18); color: #4d5f00; }
.dora-cc-subdiv {
  flex: 0 0 auto;
  width: 1px;
  align-self: stretch;
  min-height: 16px;
  margin: 0 1px;
  background: rgba(128, 138, 152, 0.35);
}
/* X-ray / Blending = small icon + short label, deliberately minimal */
.dora-cc-toggle {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;   /* don't shrink -- keep the label; the subbar scrolls if it overflows */
  gap: 4px;
  min-height: 24px;
  padding: 2px 9px;
  border: 1px solid var(--glass-border);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.55);
  color: #3f464d;
  font: inherit;
  font-size: calc(10px * var(--dora-font-scale, 1));
  white-space: nowrap;
  cursor: pointer;
}
.dora-cc-toggle svg { width: 13px; height: 13px; flex: 0 0 auto; }
.dora-cc-toggle:hover { border-color: rgba(159, 190, 14, 0.4); background: rgba(210, 251, 20, 0.16); color: #4d5f00; }
.dora-cc-toggle.dora-active { border-color: #a5c50c; background: rgba(210, 251, 20, 0.22); color: #4d5f00; }

/* ---- main bar: the step list (sections) OR one open step (stage) ---------- */
.dora-cc-main {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 10px;
  border: 1px solid var(--glass-border);
  border-radius: 14px;
  background: rgba(250, 251, 252, 0.92);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  box-shadow: 0 16px 42px rgba(44, 51, 63, 0.2), inset 0 1px 0 #fff;
  font-family: var(--font-sans);
  max-width: calc(100vw - 24px);
  overflow-x: auto;
  scrollbar-width: none;
}
.dora-cc-main::-webkit-scrollbar { display: none; }
.dora-cc-steps,
.dora-cc-stage {
  display: flex;
  align-items: center;
  gap: 6px;
}
.dora-cc-steps .dora-flexicut-title { margin-right: 2px; }

/* The open step's controls. Was a plain block: buttons/labels/sliders/values flowed inline and
   wrapped raggedly onto ~6 different baselines with no gap (the "spacing/align not ok" report). Now
   a centre-aligned flex row that WRAPS into tidy, evenly-spaced rows on an 8px rhythm, so every step
   tab reads on one grid. stage-body may shrink (min-width:0) so it wraps inside the bar instead of
   forcing horizontal scroll. */
.dora-cc-stage-body {
  display: flex;
  align-items: center;
}
.dora-cc-panel {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  column-gap: 10px;
  row-gap: 8px;
  /* width:max-content = as wide as a single un-wrapped row; the cap makes it WRAP (not overflow the
     pill off-screen) only when a dense step is genuinely wider than the viewport allows. Short steps
     stay a single natural row; step 03 folds into 2-3 tidy rows. The ~260px reserve leaves room for
     Back + the step label + Cancel + padding beside it. */
  width: max-content;
  max-width: calc(100vw - 260px);
}

/* step chip: a numbered badge + label; the Finish chip carries the lime accent */
.dora-cc-chip { display: inline-flex; align-items: center; gap: 7px; padding: 0 13px; }
.dora-cc-chip b {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: calc(11px * var(--dora-font-scale, 1));
  opacity: 0.5;
}
.dora-cc-chip.dora-active { border-color: #a5c50c; background: rgba(210, 251, 20, 0.22); color: #4d5f00; }
.dora-cc-chip.dora-active b { opacity: 0.75; }
.dora-cc-chip-commit {
  border-color: #a5c50c;
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}
.dora-cc-chip-commit b { opacity: 0.7; }
.dora-cc-chip-commit:hover { background: var(--accent); }

/* Back sits left of the open step's controls; the stage label names the step */
.dora-cc-back {
  flex: 0 0 auto;
  min-height: 34px;
  padding: 0 11px;
  border: 1px solid var(--glass-border);
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.5);
  color: var(--text-secondary);
  font: inherit;
  font-size: calc(12px * var(--dora-font-scale, 1));
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.dora-cc-back:hover { border-color: rgba(159, 190, 14, 0.4); background: rgba(210, 251, 20, 0.16); color: #4d5f00; }
.dora-cc-stage-label {
  flex: 0 0 auto;
  padding: 0 2px;
  font-size: calc(12px * var(--dora-font-scale, 1));
  font-weight: 600;
  color: var(--text-secondary);
  white-space: nowrap;
}

/* Keyboard focus: the custom controls (segmented view btns, toggles, Back, chips) are not
   .dora-flexicut-btn and had no visible focus ring. A single accent-outline rule restores it
   for keyboard users in both themes (a11y High: focus-states). */
.dora-cc-vbtn:focus-visible,
.dora-cc-toggle:focus-visible,
.dora-cc-back:focus-visible,
.dora-cc-chip:focus-visible {
  outline: 2px solid #a5c50c;
  outline-offset: 2px;
}

/* ---- v2 dark ---- */
/* The dark `.dora-flexicut-bar` rule (0,3,1) out-specifies the plain `.dora-clickercut-panel`
   reset above (0,1,0), so without this the wrapper keeps the dark pill bg + shadow and the two
   inner bars sit on one merged slab. Re-neutralise the wrapper at matching specificity, later in
   the cascade, so it stays a transparent column and the two pills float apart. */
body.dora-mode.dora-dark .dora-clickercut-panel {
  background: transparent;
  box-shadow: none;
  border-color: transparent;
}
body.dora-mode.dora-dark .dora-cc-subbar,
body.dora-mode.dora-dark .dora-cc-main {
  border-color: rgba(255, 255, 255, 0.14);
  background: rgba(62, 67, 75, 0.96);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
body.dora-mode.dora-dark .dora-cc-view-seg { border-color: rgba(255, 255, 255, 0.16); }
body.dora-mode.dora-dark .dora-cc-vbtn { color: #b8bdc4; border-left-color: rgba(255, 255, 255, 0.14); }
body.dora-mode.dora-dark .dora-cc-vbtn:hover { background: rgba(210, 251, 20, 0.16); color: #e2e7ed; }
body.dora-mode.dora-dark .dora-cc-subdiv { background: rgba(255, 255, 255, 0.16); }
body.dora-mode.dora-dark .dora-cc-toggle { border-color: rgba(255, 255, 255, 0.14); background: rgba(255, 255, 255, 0.07); color: #c3c8ce; }
body.dora-mode.dora-dark .dora-cc-toggle:hover,
body.dora-mode.dora-dark .dora-cc-toggle.dora-active { border-color: rgba(210, 251, 20, 0.4); background: rgba(210, 251, 20, 0.18); color: #e2e7ed; }
/* accent chips lose their fill to the higher-specificity dark .dora-flexicut-btn rule unless
   repeated here (same cascade quirk documented for Commit above) */
body.dora-mode.dora-dark .dora-cc-chip.dora-active {
  border-color: rgba(210, 251, 20, 0.5);
  background: rgba(210, 251, 20, 0.18);
  color: #e2e7ed;
}
body.dora-mode.dora-dark .dora-cc-chip-commit,
body.dora-mode.dora-dark .dora-cc-chip-commit:hover {
  background: var(--accent);
  border-color: #a5c50c;
  color: var(--accent-ink);
}
body.dora-mode.dora-dark .dora-cc-back { border-color: rgba(255, 255, 255, 0.14); background: rgba(255, 255, 255, 0.07); color: #b8bdc4; }
body.dora-mode.dora-dark .dora-cc-back:hover { background: rgba(210, 251, 20, 0.16); color: #e2e7ed; }
body.dora-mode.dora-dark .dora-cc-stage-label { color: #a8adb4; }
.dora-clickercut-panel .dora-cc-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;                 /* view buttons + toggles can wrap on a narrow panel */
  gap: 6px;
  padding-bottom: 6px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--glass-border);
}
/* The title takes the slack so the view group + Cancel/X-ray sit hard right. */
.dora-clickercut-panel .dora-cc-head .dora-flexicut-title { flex: 1 1 auto; }
/* View snap group: a compact inline cluster of Front/Back/Left/Right/Top buttons. */
.dora-clickercut-panel .dora-cc-views {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}
.dora-clickercut-panel .dora-cc-viewbtn {
  padding: 3px 7px;
  font-size: calc(11px * var(--dora-font-scale, 1));
}

.dora-clickercut-panel .dora-cc-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  padding: 5px 0;
}
.dora-clickercut-panel .dora-cc-row + .dora-cc-row { border-top: 1px solid rgba(128, 138, 152, 0.16); }

/* Step number: a fixed-width tabular chip so 01..05 form a straight column no matter the label. */
.dora-clickercut-panel .dora-cc-step {
  flex: 0 0 auto;
  width: 22px;
  font-variant-numeric: tabular-nums;
  font-size: calc(11px * var(--dora-font-scale, 1));
  font-weight: 600;
  opacity: 0.55;
}
.dora-clickercut-panel .dora-cc-label {
  flex: 0 0 auto;
  min-width: 92px;
  font-size: calc(12px * var(--dora-font-scale, 1));
  font-weight: 600;
  color: var(--text-secondary);
}
.dora-clickercut-panel .dora-cc-body {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  flex: 1 1 auto;
}
.dora-clickercut-panel .dora-cc-sub {
  font-size: calc(11px * var(--dora-font-scale, 1));
  opacity: 0.7;
  white-space: nowrap;
}
/* a label + slider (+ value) cluster: one inline-flex unit with a tight internal gap that NEVER
   splits across a wrap, so a label can't be orphaned from its control (whitespace-balance). The
   wider gap BETWEEN clusters comes from the panel's column-gap. */
.dora-clickercut-panel .dora-cc-grp {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  gap: 6px;
  white-space: nowrap;
}
/* numeric readouts (scale ×, press mm, slot clearance mm): aligned tabular figures, tight to their
   slider as part of the cluster, never jammed against the next label (the flex gap handles that). */
.dora-clickercut-panel .dora-cc-press {
  font-size: calc(11px * var(--dora-font-scale, 1));
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  opacity: 0.85;
}
/* thin vertical rule between a step's own controls and the shared model-scale controls */
.dora-clickercut-panel .dora-cc-sep {
  flex: 0 0 auto;
  width: 1px;
  align-self: stretch;
  min-height: 20px;
  margin: 0 4px;
  background: rgba(128, 138, 152, 0.35);
}
body.dora-mode.dora-dark .dora-clickercut-panel .dora-cc-sep { background: rgba(255, 255, 255, 0.16); }
.dora-clickercut-panel .dora-cc-status {
  font-size: calc(11px * var(--dora-font-scale, 1));
  opacity: 0.75;
  white-space: nowrap;
}
.dora-clickercut-panel .dora-cc-num {
  width: 56px;
  padding: 4px 6px;
  border: 1px solid var(--glass-border);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.7);
  color: inherit;
  font: inherit;
  font-size: calc(12px * var(--dora-font-scale, 1));
  text-align: center;
}
.dora-clickercut-panel .dora-cc-slider { width: 120px; }

/* A step whose gate has not been met yet: dimmed and inert, but still READABLE, so the user can
   see what is coming instead of the panel appearing to grow rows out of nowhere. */
.dora-clickercut-panel .dora-cc-row.is-gated { opacity: 0.4; }
.dora-clickercut-panel .dora-cc-row.is-gated * { pointer-events: none; }
.dora-clickercut-panel.is-busy,
.dora-clickercut-panel.is-busy .dora-flexicut-btn { cursor: progress; }

body.dora-mode.dora-dark .dora-clickercut-panel .dora-cc-num {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.14);
}
body.dora-mode.dora-dark .dora-clickercut-panel .dora-cc-row + .dora-cc-row {
  border-top-color: rgba(255, 255, 255, 0.1);
}

/* Loop-cut bar: same chrome, plus a quiet inline instruction (the tool is click-then-drag, so
   the one line of guidance saves a wrong first click). Muted so it reads as help, not a control. */
.dora-flexicut-hint {
  font-size: calc(11px * var(--dora-font-scale, 1));
  opacity: 0.6;
  white-space: nowrap;
  margin-right: 4px;
}

/* ---- Split floating bar --------------------------------------------------
   Left-rail quick-access duplicate of the right rail's Split popup (Line /
   Polygon / 3D Sketch, Connectors, Place manually, Split commit). Same fixed/
   centered/glass shell as .dora-mask-menu; text-pill buttons like
   .dora-conn-menu below since these are labeled (Line/Polygon/...), not icon
   squares. Position (top, below the topbar) is set in JS via
   GuiDoraPanel._positionSplitBar -- the value here is only the pre-JS
   fallback so the bar never flashes at (0,0) between insertion and layout. */
.dora-split-menu {
  position: fixed;
  left: 50%;
  top: 64px;
  transform: translateX(-50%);
  /* SplitTool.arm() creates a FULL-VIEWPORT draw overlay (.nm-split-overlay) at z-index 150
     for pointer capture, and this bar arms the tool as soon as it opens -- z-index 24 (the
     mask bar's value; Mask has no competing overlay) left every button underneath that
     overlay, silently swallowing clicks as sketch input instead. 200 matches the right
     rail's own Split popup (GuiSidebarRight._initSplitPopup: zIndex: '200'), the one other
     UI that already has to sit above this exact overlay. */
  z-index: 200;
  padding: 5px;
  border: 1px solid var(--toolbar-shell-border);
  border-radius: var(--toolbar-shell-radius);
  background: var(--toolbar-shell-bg);
  -webkit-backdrop-filter: var(--toolbar-shell-blur);
  backdrop-filter: var(--toolbar-shell-blur);
  box-shadow: var(--toolbar-shell-shadow);
  min-height: 44px;
  max-width: calc(100vw - 170px);
}
.dora-split-menu[hidden] { display: none; }

.dora-split-menu-body {
  display: flex;
  /* Was "wrap" -- on an auto-width position:fixed shell (no explicit width set on
     .dora-split-menu), a wrapping flex row's shrink-to-fit width calculation settles for
     whatever avoids overflow INCLUDING via a second row, so it wrapped even with plenty of
     horizontal room free. nowrap forces the container to size to the full unwrapped row
     instead, which is what the sibling .dora-mask-menu-body already does (no flex-wrap
     property there at all -- nowrap is the CSS default) -- matching that, not diverging. */
  flex-wrap: nowrap;
  align-items: center;
  gap: 6px;
}

.dora-split-menu-body button {
  flex: none;
  height: 34px;
  padding: 0 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}
.dora-split-menu-body button:hover:not(:disabled) {
  border-color: var(--toolbar-hover-border);
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon-hover);
}
/* Selected shape (Line / Polygon / 3D Sketch). */
.dora-split-menu-body button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}
.dora-split-menu-body button:disabled { opacity: 0.4; cursor: default; }
.dora-split-bar-shape-btn { gap: 6px; }
.dora-split-bar-shape-btn svg { flex: none; width: 15px; height: 15px; }

.dora-split-menu .dora-tool-divider {
  width: 1px;
  height: 24px;
  margin: 0 2px;
}

/* Connector +/- stepper: a compact square instead of the wide text-pill default above --
   extra class beats the base ".dora-split-menu-body button" element selector, same
   specificity trick the mask bar's Extrude commit/X pair already uses. */
.dora-split-menu-body button.dora-split-conn-step {
  width: 30px;
  min-width: 0;
  height: 30px;
  padding: 0;
  font-size: 15px;
  line-height: 1;
}

.dora-split-bar-conn-label {
  font-size: 12px;
  color: var(--toolbar-icon);
  opacity: 0.8;
  white-space: nowrap;
  padding: 0 2px;
}

.dora-split-bar-manual {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  color: var(--toolbar-icon);
  opacity: 0.9;
  white-space: nowrap;
  cursor: pointer;
  padding: 0 2px;
}
.dora-split-bar-manual input[type="checkbox"] {
  width: 15px;
  height: 15px;
  margin: 0;
  accent-color: rgba(210, 251, 20, 0.92);
  cursor: pointer;
}
.dora-split-bar-manual input[type="checkbox"]:disabled { cursor: default; opacity: 0.5; }

/* Primary CTA -- one commit action per bar (ui-ux-pro-max: a screen/panel should have only
   one primary action, everything else subordinate). Same lime-accent recipe as the mask
   bar's Extrude-split commit button, so "the thing that finishes the tool" reads the same
   across every floating bar in the app. */
.dora-split-menu-body button.dora-split-menu-commit {
  border: 1px solid rgba(159, 190, 14, 0.6);
  background: rgba(210, 251, 20, 0.92);
  color: #2c333f;
  font-weight: 600;
}
.dora-split-menu-body button.dora-split-menu-commit:hover:not(:disabled) {
  filter: brightness(1.05);
  border-color: rgba(159, 190, 14, 0.6);
  background: rgba(210, 251, 20, 0.92);
  color: #2c333f;
  box-shadow: none;
}

/* Trim floating bar -- exact same shell/body/button recipe as .dora-split-menu above
   (identical selectors, renamed), per the request that it "show like what split tool menu
   show". Kept as its own rule set rather than sharing .dora-split-menu's classes so the two
   tools stay independently greppable/editable, same call every other floating-bar pair in
   this file already makes (mask vs decal vs connector). */
.dora-trim-menu {
  position: fixed;
  left: 50%;
  top: 64px;
  transform: translateX(-50%);
  z-index: 200; /* matches .dora-split-menu -- same TrimTool overlay z-index (150) to clear */
  padding: 5px;
  border: 1px solid var(--toolbar-shell-border);
  border-radius: var(--toolbar-shell-radius);
  background: var(--toolbar-shell-bg);
  -webkit-backdrop-filter: var(--toolbar-shell-blur);
  backdrop-filter: var(--toolbar-shell-blur);
  box-shadow: var(--toolbar-shell-shadow);
  min-height: 44px;
  max-width: calc(100vw - 170px);
}
.dora-trim-menu[hidden] { display: none; }
/* Individual mode-specific controls toggle the native [hidden] attribute (not this whole
   bar) when switching Line <-> Flat base -- author rules always beat the UA [hidden]
   stylesheet, so without this the button/input display rules below would win instead and
   the "hidden" controls would keep showing. */
.dora-trim-menu-body [hidden] { display: none !important; }

.dora-trim-menu-body {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 6px;
}

.dora-trim-menu-body button {
  flex: none;
  height: 34px;
  padding: 0 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}
.dora-trim-menu-body button:hover:not(:disabled) {
  border-color: var(--toolbar-hover-border);
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon-hover);
}
/* Selected mode (Line / Flat base), and Snap while armed. */
.dora-trim-menu-body button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}
.dora-trim-menu-body button:disabled { opacity: 0.4; cursor: default; }
.dora-trim-bar-mode-btn { gap: 6px; }
.dora-trim-bar-mode-btn svg { flex: none; width: 15px; height: 15px; }

.dora-trim-menu .dora-tool-divider {
  width: 1px;
  height: 24px;
  margin: 0 2px;
}

/* Snap angle degrees -- compact number field, not a wide text pill like the mode/Flip
   buttons either side of it. Same border/radius/ink as the buttons so it reads as one
   family, not a foreign form control dropped into a button row. */
.dora-trim-bar-degree {
  flex: none;
  width: 44px;
  height: 34px;
  padding: 0 4px;
  border: 1px solid var(--toolbar-hover-border);
  border-radius: var(--toolbar-button-radius);
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon);
  font-size: 12px;
  text-align: center;
  -moz-appearance: textfield;
}
.dora-trim-bar-degree::-webkit-outer-spin-button,
.dora-trim-bar-degree::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.dora-trim-bar-degree:disabled { opacity: 0.4; }
.dora-trim-bar-degree:focus { outline: 2px solid var(--toolbar-active-border); outline-offset: 1px; }

/* Flat base height slider -- same accent-color convention as every other range input in
   this file (e.g. .dora-prim-div-slider), just sized for a horizontal bar row instead of a
   vertical toolbar. */
.dora-trim-bar-height {
  flex: none;
  width: 140px;
  height: 24px;
  margin: 0 4px;
  accent-color: var(--toolbar-active-border, #9fbe0e);
  cursor: pointer;
}

/* Primary CTA -- same lime recipe as .dora-split-menu-commit, so "the thing that finishes
   the tool" reads identically across every floating bar in the app. */
.dora-trim-menu-body button.dora-trim-menu-commit {
  border: 1px solid rgba(159, 190, 14, 0.6);
  background: rgba(210, 251, 20, 0.92);
  color: #2c333f;
  font-weight: 600;
}
.dora-trim-menu-body button.dora-trim-menu-commit:hover:not(:disabled) {
  filter: brightness(1.05);
  border-color: rgba(159, 190, 14, 0.6);
  background: rgba(210, 251, 20, 0.92);
  color: #2c333f;
  box-shadow: none;
}

/* Mirror floating bar -- same shell/button system as .dora-split-menu / .dora-trim-menu
   (renamed, own rule set so the three stay independently editable, matching every other
   floating-bar pair in this file). Adds two small segmented pickers (Method, Axis) with
   inline group labels -- a different control shape than the mode-tab buttons the other bars
   use, so it gets its own compact segment-button styling below. */
.dora-mirror-menu {
  position: fixed;
  left: 50%;
  top: 64px;
  transform: translateX(-50%);
  z-index: 200; /* matches the sibling bars; no draw overlay here, but keep the same stacking tier */
  padding: 5px;
  border: 1px solid var(--toolbar-shell-border);
  border-radius: var(--toolbar-shell-radius);
  background: var(--toolbar-shell-bg);
  -webkit-backdrop-filter: var(--toolbar-shell-blur);
  backdrop-filter: var(--toolbar-shell-blur);
  box-shadow: var(--toolbar-shell-shadow);
  min-height: 44px;
  max-width: calc(100vw - 170px);
}
.dora-mirror-menu[hidden] { display: none; }

.dora-mirror-menu-body {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 6px;
}

/* "Method" / "Axis" inline captions -- muted, small, so the segment buttons read as their
   values, not competing labels. */
.dora-mirror-group-label {
  font-size: 11px;
  color: var(--toolbar-icon);
  opacity: 0.75;
  white-space: nowrap;
  padding: 0 1px 0 3px;
}

/* Compact segment buttons (World/Local, X/Y/Z) + the Flip button share one recipe -- smaller
   than the wide text pills the other bars' mode tabs use, since there are more of them in a
   row. Active state uses the shared --toolbar-active-* tokens, same as every other selected
   control in the app. */
.dora-mirror-menu-body button {
  flex: none;
  height: 34px;
  min-width: 34px;
  padding: 0 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}
.dora-mirror-menu-body button.dora-mirror-seg-btn { padding: 0 10px; }
.dora-mirror-menu-body button:hover:not(:disabled) {
  border-color: var(--toolbar-hover-border);
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon-hover);
}
.dora-mirror-menu-body button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}
.dora-mirror-menu-body button:disabled { opacity: 0.4; cursor: default; }

.dora-mirror-menu .dora-tool-divider {
  width: 1px;
  height: 24px;
  margin: 0 2px;
}

/* Primary CTA -- same lime recipe as the Split/Trim commit buttons, so "the thing that
   finishes the tool" reads identically across every floating bar. */
.dora-mirror-menu-body button.dora-mirror-menu-commit {
  border: 1px solid rgba(159, 190, 14, 0.6);
  background: rgba(210, 251, 20, 0.92);
  color: #2c333f;
  font-weight: 600;
}
.dora-mirror-menu-body button.dora-mirror-menu-commit:hover:not(:disabled) {
  filter: brightness(1.05);
  border-color: rgba(159, 190, 14, 0.6);
  background: rgba(210, 251, 20, 0.92);
  color: #2c333f;
  box-shadow: none;
}

/* Cancel. Same red treatment as the mask/facegroup bars' X buttons, so "back out of this
   preview" reads identically everywhere. svg uses stroke="currentColor", so colour is enough. */
.dora-mirror-menu-body button.dora-mirror-x {
  width: 34px;
  min-width: 34px;
  color: #e5484d;
}
.dora-mirror-menu-body button.dora-mirror-x svg { width: 16px; height: 16px; }
.dora-mirror-menu-body button.dora-mirror-x:hover:not(:disabled) {
  color: #ff6369;
  border-color: rgba(229, 72, 77, 0.45);
  background: rgba(229, 72, 77, 0.14);
  box-shadow: none;
}

/* ---- Pivot bar ----------------------------------------------------------
   Same glass shell + segment-button recipe as the Mirror bar (Move / Center / Bottom / Reset
   are the same kind of compact control row), listed as its own selector rather than added to
   the Mirror rules so neither bar can be restyled by accident. */
.dora-pivot-menu {
  position: fixed;
  left: 50%;
  top: 64px;
  transform: translateX(-50%);
  z-index: 200;
  padding: 5px;
  border: 1px solid var(--toolbar-shell-border);
  border-radius: var(--toolbar-shell-radius);
  background: var(--toolbar-shell-bg);
  -webkit-backdrop-filter: var(--toolbar-shell-blur);
  backdrop-filter: var(--toolbar-shell-blur);
  box-shadow: var(--toolbar-shell-shadow);
  min-height: 44px;
  max-width: calc(100vw - 170px);
}
.dora-pivot-menu[hidden] { display: none; }

.dora-pivot-menu-body {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 6px;
}

.dora-pivot-group-label {
  font-size: 11px;
  color: var(--toolbar-icon);
  opacity: 0.75;
  white-space: nowrap;
  padding: 0 1px 0 3px;
}

/* Live pivot coordinates. Tabular figures so the row does not twitch as the numbers change
   while the pivot is dragged. */
.dora-pivot-readout {
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--toolbar-icon);
  opacity: 0.7;
  white-space: nowrap;
  min-width: 96px;
  padding: 0 2px;
}

.dora-pivot-menu-body button {
  flex: none;
  height: 34px;
  min-width: 34px;
  padding: 0 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}
.dora-pivot-menu-body button.dora-pivot-seg-btn { padding: 0 10px; }
.dora-pivot-menu-body button:hover:not(:disabled) {
  border-color: var(--toolbar-hover-border);
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon-hover);
}
.dora-pivot-menu-body button.dora-active {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  box-shadow: var(--toolbar-active-shadow);
}
.dora-pivot-menu-body button:disabled { opacity: 0.4; cursor: default; }

.dora-pivot-menu .dora-tool-divider {
  width: 1px;
  height: 24px;
  margin: 0 2px;
}

.dora-pivot-menu-body button.dora-pivot-x {
  width: 34px;
  min-width: 34px;
  padding: 0;
  color: #e5484d;
}
.dora-pivot-menu-body button.dora-pivot-x svg { width: 16px; height: 16px; }
.dora-pivot-menu-body button.dora-pivot-x:hover:not(:disabled) {
  color: #ff6369;
  border-color: rgba(229, 72, 77, 0.45);
  background: rgba(229, 72, 77, 0.14);
  box-shadow: none;
}

/* ---- Parametric primitive toolbar ---------------------------------------
   Shown while a primitive is pending (PrimitiveToolbar). Title row ("Box" +
   collapse) over a body row of Validate / Mirror / Show / Division stepper /
   Constant density / more -- the Nomad-style editable-primitive toolbar. Same
   glass shell + --toolbar-* tokens as the mask/split bars, pinned top-centre. */
.dora-prim-toolbar {
  position: fixed;
  left: 50%;
  top: 12px;
  transform: translateX(-50%);
  z-index: 200;
  display: flex;
  flex-direction: column;
  min-width: 320px;
  max-width: calc(100vw - 170px);
  border: 1px solid var(--toolbar-shell-border);
  border-radius: var(--toolbar-shell-radius);
  background: var(--toolbar-shell-bg);
  -webkit-backdrop-filter: var(--toolbar-shell-blur);
  backdrop-filter: var(--toolbar-shell-blur);
  box-shadow: var(--toolbar-shell-shadow);
  overflow: hidden;
}
.dora-prim-toolbar[hidden] { display: none; }

/* Follows the app's chosen panel colour the same way every flyout/rail already does (see the
   master :root.dora-solid-panels rule near the top of this file) -- this toolbar was still on
   the plain (unthemed, always-dark) --toolbar-shell-bg only, so it stayed a fixed dark box no
   matter what colour Interface > Panel style set everything else to. New rule, not an edit to
   that existing selector list, per 03_CONSTRAINTS.md §2. Glass mode is unaffected: it already
   reads --toolbar-shell-bg, which already carries its own correct light/dark values. */
:root.dora-solid-panels .dora-prim-toolbar {
  background: var(--dora-panel-color) !important;
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}

.dora-prim-title {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  padding: 4px 8px;
  border-bottom: 1px solid var(--toolbar-shell-border);
}
/* Every size below multiplies by --dora-ui-scale/--dora-font-scale, the same two knobs Interface
   > Overall scale/Font scale already drive on every other toolbar (see
   .dora-workspace-tools .dora-workspace-group > button: 56px box, 22px icon, 10px label). This
   toolbar's numbers were plain px, so at the app's actual default scale (0.66) it rendered at
   its full, unscaled size while everything beside it had already shrunk to ~66% -- the "too big,
   not the same size as other menus" this fixes. */
.dora-prim-title-name {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: calc(13px * var(--dora-font-scale));
  font-weight: 600;
  color: var(--toolbar-icon);
}
.dora-prim-title-name svg { width: calc(16px * var(--dora-ui-scale)); height: calc(16px * var(--dora-ui-scale)); }
.dora-prim-collapse {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  width: calc(30px * var(--dora-ui-scale));
  height: calc(26px * var(--dora-ui-scale));
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}
.dora-prim-collapse svg { width: calc(16px * var(--dora-ui-scale)); height: calc(16px * var(--dora-ui-scale)); }
.dora-prim-collapse:hover { background: var(--toolbar-hover-bg); border-color: var(--toolbar-hover-border); }
.dora-prim-toolbar.dora-prim-collapsed .dora-prim-body { display: none; }
.dora-prim-toolbar.dora-prim-collapsed .dora-prim-collapse svg { transform: rotate(180deg); }

.dora-prim-body {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 2px;
  padding: 4px;
}

.dora-prim-btn {
  flex: none;
  min-width: calc(56px * var(--dora-ui-scale));
  padding: 3px 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  font-size: calc(10px * var(--dora-font-scale));
  line-height: 1.15;
  text-align: center;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}
.dora-prim-btn svg { width: calc(22px * var(--dora-ui-scale)); height: calc(22px * var(--dora-ui-scale)); }
.dora-prim-btn:hover { border-color: var(--toolbar-hover-border); background: var(--toolbar-hover-bg); color: var(--toolbar-icon-hover); }
.dora-prim-btn.dora-active { border-color: var(--toolbar-active-border); background: var(--toolbar-active-bg); color: var(--toolbar-active-icon); }
/* `display:flex` above beats the UA `[hidden]{display:none}`; re-assert it so hiding the
   Constant-density button on sphere/cylinder actually hides it. */
.dora-prim-btn[hidden] { display: none; }

/* Validate is the one primary CTA -- green icon + label, like the reference toolbar. */
.dora-prim-validate { color: #37d67a; }
.dora-prim-validate:hover { color: #37d67a; background: rgba(55, 214, 122, 0.14); border-color: rgba(55, 214, 122, 0.5); }

.dora-prim-more { min-width: calc(38px * var(--dora-ui-scale)); }

/* Division stepper: <- N -> over a label. Goes "hot" (red-tinted, matching the reference's
   red "X Division 32") when constant density is OFF, i.e. the stepper is editing X alone. */
.dora-prim-div {
  flex: none;
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 2px 4px;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  position: relative;
}
.dora-prim-div-label {
  font-size: calc(10px * var(--dora-font-scale));
  color: var(--toolbar-icon);
  opacity: 0.7;
  margin-left: 4px;
  white-space: nowrap;
}
/* Absolute-value companion to the +/- stepper: drag to jump straight to a division count
   instead of clicking N times. Height matches .dora-prim-step (24px * ui-scale) so it sits on
   the stepper's own baseline instead of stretching the row; width is deliberately modest (this
   toolbar is a horizontal strip, not a settings panel a slider can claim a whole row of). */
.dora-prim-div-slider {
  width: calc(64px * var(--dora-ui-scale));
  height: calc(24px * var(--dora-ui-scale));
  margin: 0 2px;
  accent-color: var(--toolbar-active-border, #9fbe0e);
  cursor: pointer;
}
.dora-prim-div.dora-prim-hot .dora-prim-div-slider { accent-color: #ef6b6b; }
.dora-prim-div-val {
  min-width: calc(26px * var(--dora-ui-scale));
  text-align: center;
  font-size: calc(13px * var(--dora-font-scale));
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--toolbar-icon-hover);
}
.dora-prim-step {
  width: calc(24px * var(--dora-ui-scale));
  height: calc(24px * var(--dora-ui-scale));
  padding: 0;
  font-size: calc(14px * var(--dora-font-scale));
  line-height: 1;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}
.dora-prim-step:hover:not(:disabled) { border-color: var(--toolbar-hover-border); background: var(--toolbar-hover-bg); color: var(--toolbar-icon-hover); }
.dora-prim-div.dora-prim-hot {
  border-color: rgba(230, 90, 90, 0.55);
  background: rgba(230, 90, 90, 0.14);
}
.dora-prim-div.dora-prim-hot .dora-prim-div-val,
.dora-prim-div.dora-prim-hot .dora-prim-div-label { color: #ef6b6b; opacity: 1; }

/* ---- Connector placement bar --------------------------------------------
   Shown while a ConnectorPlace session is open. Same shell as .dora-mask-menu
   but pinned to the BOTTOM, so the two bars can be on screen together without
   overlapping. Buttons here are text (Apply / Clear / Part A), not 44px icon
   squares, so they get their own sizing rather than inheriting the mask bar's. */
.dora-conn-menu {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  z-index: 24;
  display: flex;
  align-items: center;
  padding: 5px;
  border: 1px solid var(--toolbar-shell-border);
  border-radius: var(--toolbar-shell-radius);
  background: var(--toolbar-shell-bg);
  -webkit-backdrop-filter: var(--toolbar-shell-blur);
  backdrop-filter: var(--toolbar-shell-blur);
  box-shadow: var(--toolbar-shell-shadow);
  min-height: 44px;
  max-width: calc(100vw - 170px);
}
.dora-conn-menu[hidden] { display: none; }

/* Separates the parameter cluster (Type/Size/Depth/Fit/count) from the action cluster
   (Clear/Switch side/Apply) now that both live in one un-wrapped row -- same divider the
   mask/split bars already use between their own clusters, scaled to this bar's shorter
   30px controls. */
.dora-conn-menu .dora-tool-divider {
  /* flex: none -- without it this collapsed to 0 width. An empty <span>'s automatic flex
     min-width is its min-content size (0, nothing inside it), so under flex-shrink:1 (the
     default) it was the first item sacrificed to fit the row, before any of the buttons or
     fields gave up an inch -- explicit width alone does not survive that. Every OTHER item
     in this bar (.dora-conn-menu-body button) already opts out of shrink/grow the same way. */
  flex: none;
  width: 1px;
  height: 20px;
  margin: 0 2px;
}

.dora-conn-menu-body {
  display: flex;
  /* Was "wrap", contradicting .dora-conn-field's own comment below ("so the bar stays short
     enough to sit in one row") -- same auto-width shrink-to-fit quirk as the split bar's fix
     earlier this session: a wrapping flex row in a position:fixed shell with no explicit
     width settles for a narrower box (wrapping to a 2nd row) rather than sizing to the full
     unwrapped row, even with screen room to spare. nowrap forces the single row every other
     control in this family (label-above-input fields, all sized down for exactly this) was
     already built for. */
  flex-wrap: nowrap;
  align-items: center;
  gap: 8px;
}

.dora-conn-title {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--toolbar-icon);
  padding: 0 2px;
}

.dora-conn-hint {
  font-size: 12px;
  color: var(--toolbar-icon);
  opacity: 0.75;
}

.dora-conn-count {
  font-size: 12px;
  color: var(--toolbar-icon);
  opacity: 0.75;
  min-width: 58px;
}

/* Why a peg refused to grow (or to be placed at all). Amber rather than red: the cut face simply
   has no more room here, which is information, not an error. */
.dora-conn-note {
  font-size: 12px;
  color: #ffc23d;
  max-width: 320px;
  line-height: 1.25;
}

/* The Size field while its value is being capped by the cut face. */
.dora-conn-clamped .dora-conn-out { color: #ffc23d; }
.dora-conn-clamped input[type='range'] { accent-color: #ffc23d; }

.dora-conn-menu-body button {
  flex: none;
  height: 30px;
  padding: 0 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  border: 1px solid transparent;
  border-radius: var(--toolbar-button-radius);
  background: transparent;
  color: var(--toolbar-icon);
  cursor: pointer;
}
.dora-conn-menu-body button:hover:not(:disabled) {
  border-color: var(--toolbar-hover-border);
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon-hover);
}
.dora-conn-menu-body button:disabled { opacity: 0.4; cursor: default; }

.dora-conn-menu-body button.dora-conn-apply {
  border-color: var(--toolbar-active-border);
  background: var(--toolbar-active-bg);
  color: var(--toolbar-active-icon);
  font-weight: 600;
}

.dora-conn-menu-body button.dora-conn-x {
  width: 30px;
  min-width: 30px;
  padding: 0;
}
.dora-conn-menu-body button.dora-conn-x svg { width: 15px; height: 15px; }

/* Clear discards every placed connector -- destructive, same red already used for the
   mask/split bars' Cancel X's, so the color means the same thing everywhere in the app. */
.dora-conn-menu-body button.dora-conn-clear {
  color: #e5484d;
}
.dora-conn-menu-body button.dora-conn-clear:hover:not(:disabled) {
  color: #ff6369;
  border-color: rgba(229, 72, 77, 0.45);
  background: rgba(229, 72, 77, 0.14);
}

/* Same recipe as .dora-split-bar-manual -- inline checkbox + label, not stacked, since
   there's no numeric readout under it to line up with. */
.dora-conn-loose {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  color: var(--toolbar-icon);
  opacity: 0.9;
  white-space: nowrap;
  cursor: pointer;
  padding: 0 2px;
}
.dora-conn-loose input[type="checkbox"] {
  width: 15px;
  height: 15px;
  margin: 0;
  accent-color: rgba(210, 251, 20, 0.92);
  cursor: pointer;
}

/* Label above its control, so the bar stays short enough to sit in one row. */
.dora-conn-field {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 11px;
  color: var(--toolbar-icon);
  opacity: 0.9;
}
.dora-conn-field > span {
  display: flex;
  gap: 4px;
  white-space: nowrap;
}
.dora-conn-field input[type="range"] { width: 92px; }
.dora-conn-field select {
  height: 22px;
  font-size: 11px;
  border-radius: 5px;
  border: 1px solid var(--toolbar-shell-border);
  background: var(--toolbar-hover-bg);
  color: var(--toolbar-icon);
}
.dora-conn-out { font-variant-numeric: tabular-nums; opacity: 0.75; }

.dora-conn-menu [hidden] { display: none !important; }

/* Image decal bar. Same shell as the connector bar (it carries both classes); only the
   pieces the connector bar has no equivalent for are styled here. */

/* Reads out which side of the surface the signed Depth slider is currently on. A caption
   rather than a second control, so depth and direction cannot disagree. */
.dora-decal-dir {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--toolbar-icon);
  opacity: 0.75;
  min-width: 96px;   /* fixed: "cut in (back)" and "raised (front)" differ in width, and a
                        caption that resizes shunts every control after it sideways */
}

/* Wider than the connector bar's 92px: four sliders here are set by eye against the live
   preview, and the extra travel is the difference between usable and fiddly. Full field width,
   not a fixed px, now that the panel is a fixed-width vertical dialog (see .dora-decal-menu
   below) rather than a horizontal wrapping toolbar. */
.dora-decal-menu .dora-conn-field input[type="range"] { width: 100%; }

/* Consolidated stamp panel: fixed to the CENTRE of the screen, detached from its trigger
   button -- deliberately not _positionFlyout's anchor-near-the-button convention every other
   flyout uses, per request. .dora-conn-menu already gives the shell (border/blur/shadow); this
   overrides its horizontal-toolbar layout (left:50%+translateX only) with a vertical dialog
   one, and lifts it above the z-index tier the trim/split popup uses (200) since a centred
   panel has nothing else it could sensibly sit behind. */
.dora-decal-menu {
  top: 50%;
  left: 50%;
  bottom: auto;
  transform: translate(-50%, -50%);
  z-index: 210;
  max-width: 280px;
}
.dora-decal-menu .dora-conn-menu-body {
  flex-direction: column;
  align-items: stretch;
  flex-wrap: nowrap;
  gap: 10px;
  width: 260px;
}
.dora-decal-menu .dora-conn-menu-body > button,
.dora-decal-choose {
  width: 100%;
}
.dora-decal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.dora-decal-head .dora-conn-x { margin-left: 8px; }

.dora-decal-color-row {
  flex-direction: row !important;
  align-items: center;
  justify-content: space-between;
}
.dora-decal-color-row span {
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: normal;
}
.dora-decal-color-swatch {
  width: 30px;
  height: 24px;
  padding: 0;
  border: 1px solid var(--toolbar-shell-border);
  border-radius: 5px;
  background: none;
  cursor: pointer;
}


/* Trailing X, same red-tinted recipe as the Mirror/Pivot bars' own close button. */
.dora-trim-menu-body button.dora-trim-x {
  width: 34px;
  min-width: 34px;
  padding: 0;
  color: #e5484d;
}
.dora-trim-menu-body button.dora-trim-x svg { width: 16px; height: 16px; }
.dora-trim-menu-body button.dora-trim-x:hover:not(:disabled) {
  color: #ff6369;
  border-color: rgba(229, 72, 77, 0.45);
  background: rgba(229, 72, 77, 0.14);
  box-shadow: none;
}

/* ---- Trim bar: method selector + slider-strip takeover ---------------------------------- */

/* Three visible method buttons in a tight segmented group, so Fill / Boolean / Legacy are all
   readable at once instead of hidden behind one cycling label. */
.dora-trim-method-group {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.dora-trim-bar-method-btn {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
  padding: 4px 9px;
  border: 1px solid transparent;
  border-radius: 5px;
  background: none;
  color: inherit;
  cursor: pointer;
  opacity: 0.6;
}

.dora-trim-bar-method-btn:hover { opacity: 0.85; }

/* Active method is fully lit; Fill (safe, keeps geometry behind) reads in the accent colour,
   Boolean/Legacy (both cut through) in the warn colour so the "removes more than you see" pair
   is visually distinct from the front-only default. */
.dora-trim-bar-method-btn.dora-active { opacity: 1; }
.dora-trim-bar-method-btn.dora-active[data-method="fill"] {
  color: var(--dora-accent, #5b9dff);
  border-color: var(--dora-accent, #5b9dff);
}
.dora-trim-bar-method-btn.dora-active[data-method="boolean"],
.dora-trim-bar-method-btn.dora-active[data-method="legacy"] {
  color: var(--dora-warn, #e0a33e);
  border-color: var(--dora-warn, #e0a33e);
}

/* Trim bar REPLACES the left rail's slider strip -- radius/intensity do nothing while a cut is
   being aimed, and two floating menus down the same edge is what the bar was meant to avoid. */
body.dora-trim-bar-open .nm-slidercell { display: none !important; }