/* ==========================================================================
   studio.css — MapX Editor "v2 chrome" (desktop toolbar system)
   Scoped to body.mx-studio so it NEVER touches the live laser-lake editor.
   Recreates the editor.dc.html handoff: 44px tool rail · 40px contextual bar ·
   300px Map-layers panel (42px icon strip + existing sections) · fixed 292px
   inspector (Layers / Object / Laser). Existing control sections are reparented
   at runtime by js/laser-studio.js — never rewritten.
   ========================================================================== */

body.mx-studio {
  --s-brand: #5f8d74;
  --s-dark: #0c6457;
  --s-tint: #e3f1ee;
  --s-tint-2: #d3e8e1;
  --s-bg: #e9ecea;
  --s-surface: #ffffff;
  --s-well: #f2f4f2;
  --s-text: #212b26;
  --s-muted: #68736d;
  --s-faint: #6e7a74;   /* was #9aa49e = 2.57:1 on white; AA-raised to ~4.5:1, matching the dark theme's own faint fix */
  --s-line: #e6e9e7;
  /* MAP source panel (handoff tokens): a row surface between the panel and the
     well, the flat layout's hairline, the switch's off track, and the row hover
     border. All four are CHROME — the source dots and feed swatches come from
     CutPalette and are written inline as DATA, so they never flip with a theme. */
  --s-row: #fafbfa;
  --s-hair: #edf0ee;
  --s-sw-off: #c7cdc9;
  --s-line-hi: #b9c3bd;
  --s-danger: #b3564d;
  --s-op-engrave: #0c6457;
  --s-op-score: #8a6d2f;
  --s-op-cut: #b3564d;
  /* REQUIREMENT A — inspector width. The handoff README §Sizing calls it
     "inspector 292px (tweakable 264–336)" and the DC exposes it as a range prop
     (default 292, min 264, max 336), so it lives as a token rather than a second
     magic literal. Nothing in JS reads the inspector's width — the collapse /
     reopen strips are their own fixed 26px — so this token is the ONLY place the
     number exists. Border-box: the DC's 292 is content-box + a 1px left border,
     so pixel-exact DC parity would be 293; we ship the spec's own number. */
  --s-insp-w: 292px;
  background: var(--s-bg);
}

/* Hide the legacy floating precision dock if labstudio ever loads alongside */
body.mx-studio #lab-dock { display: none !important; }
/* Phase 2: the engine's own zoom +/- & pan pad (#zoompan) is replaced by the bottom-center zoom pill */
body.mx-studio #zoompan { display: none !important; }

/* Select tool: the cursor becomes a pointing hand over anything you can grab (text / icon /
   marker / shape / line), so it's obvious what's clickable. Only while the Select tool is active. */
body.mx-studio.mx-tool-select #poster-wrap .laser-svg-text,
body.mx-studio.mx-tool-select #poster-wrap .shape-group,
body.mx-studio.mx-tool-select #poster-wrap .shape-hit,
body.mx-studio.mx-tool-select #poster-wrap .laser-marker,
body.mx-studio.mx-tool-select #poster-wrap .edit-line,
body.mx-studio.mx-tool-select #poster-wrap .gen-editable { cursor: pointer !important; }

/* ============ Contextual selection toolbar ============
   Shown ONLY when an object is selected (data-ctx != "tool"), carrying that selection's own
   controls (size / laser op / rotation / X-Y / fill / delete).
   REQUIREMENT B — IT NO LONGER FLOATS. The handoff's ruling is "No floating inspector over the
   artwork", so js/laser-studio.js dockContextualBar() re-parents the bar into #mx-editbar and
   `.in-eb` below strips every floating property: the one docked command row now carries the
   shared actions AND the selection's controls, and the artboard is clear.
   The base rules are kept for a shell with no edit bar (the bar still floats there, unchanged). */
#mx-ctxbar {
  position: absolute; left: 50%; transform: translateX(-50%); top: 14px; z-index: 26;
  max-width: calc(100% - 160px); box-sizing: border-box;
  background: var(--s-surface); border: 1px solid var(--s-line); border-radius: 12px;
  box-shadow: 0 2px 6px rgba(18,32,26,.06), 0 12px 32px rgba(18,32,26,.16);
  display: flex; align-items: center; gap: 6px; padding: 6px 10px;
  overflow-x: auto; overflow-y: hidden; white-space: nowrap;
  font-family: 'Montserrat', system-ui, sans-serif; color: var(--s-text); scrollbar-width: thin;
}
#mx-ctxbar::-webkit-scrollbar { height: 0; }
#mx-ctxbar[data-ctx="tool"] { display: none; }   /* nothing selected → toolbar hidden */
/* REQUIREMENT B — A TEXT SELECTION SHOWS NO BAR AT ALL. Its pane is gone: the actions live on
   #mx-editbar (Align / Arrange incl. centre / Flip / Duplicate) and the properties in the
   inspector Design tab. The bar and its data-ctx attribute STAY — wireMobile() observes that
   attribute as the phone's selection beacon, so it must keep being written. */
#mx-ctxbar[data-ctx="text"] { display: none; }
/* docked into #mx-editbar: no box of its own, and it must not grow the 36px row */
#mx-editbar #mx-ctxbar.in-eb {
  position: static; left: auto; top: auto; transform: none; z-index: auto;
  max-width: none; background: transparent; border: 0; box-shadow: none; border-radius: 0;
  padding: 0; gap: 5px; height: 26px; flex: 0 1 auto; min-width: 0;
}
#mx-editbar #mx-ctxbar.in-eb .ctx-chip,
#mx-editbar #mx-ctxbar.in-eb .ctx-pill,
#mx-editbar #mx-ctxbar.in-eb .ctx-swatch,
#mx-editbar #mx-ctxbar.in-eb .ctx-sel { height: 24px; }
#mx-editbar #mx-ctxbar.in-eb::before {   /* the row's own separator, so the two halves read apart */
  content: ''; width: 1px; height: 18px; background: var(--s-line); margin: 0 4px 0 1px; flex: 0 0 auto;
}
/* The engine's align cluster stays IN THE DOM — #mx-editbar's Align ▾ clicks its [data-align]
   buttons, syncEditBar()/buildEditBar() read and observe its data-* attributes, and it still
   physically holds #btn-group / #btn-ungroup — but it is no longer visible chrome: Align, Group
   and Ungroup are on the edit bar exactly once. HIDDEN, NEVER REMOVED. */
#mx-ctxbar #align-group { display: none !important; }

/* ============ Bottom-center zoom pill (always visible) ============ */
#mx-zoompill {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 16px; z-index: 25;
  display: flex; align-items: center; gap: 2px; box-sizing: border-box;
  background: var(--s-surface); border: 1px solid var(--s-line); border-radius: 999px;
  padding: 4px 6px; box-shadow: 0 2px 6px rgba(18,32,26,.06), 0 8px 24px rgba(18,32,26,.10);
  font-family: 'Montserrat', system-ui, sans-serif; color: var(--s-text);
}
#mx-zoompill .ctx-chip, #mx-zoompill .ctx-pill {
  display: flex; align-items: center; gap: 4px; height: 28px; box-sizing: border-box;
  border: 0; border-radius: 999px; padding: 0 10px; background: transparent;
  font-size: 11.5px; font-weight: 600; color: var(--s-muted); cursor: pointer; user-select: none; flex: 0 0 auto;
}
#mx-zoompill .ctx-chip:hover, #mx-zoompill .ctx-pill:hover { background: var(--s-well); }
#mx-zoompill .ctx-chip.on { background: var(--s-tint); color: var(--s-dark); }
#mx-zoompill .ctx-chip svg { width: 14px; height: 14px; }
#mx-zoompill .ctx-pill { padding: 0 4px; }
#mx-zoompill .ctx-pill .step { color: var(--s-muted); padding: 0 7px; font-size: 15px; line-height: 1; }
#mx-zoompill .ctx-pill .val { font-weight: 700; min-width: 40px; text-align: center; color: var(--s-text); font-size: 12px; }
#mx-zoompill .ctx-units { display: flex; background: var(--s-well); border-radius: 999px; padding: 2px; gap: 0; }
#mx-zoompill .ctx-units .u { height: 22px; display: flex; align-items: center; padding: 0 9px; border-radius: 999px; font-size: 10.5px; font-weight: 700; cursor: pointer; color: var(--s-muted); }
#mx-zoompill .ctx-units .u.on { background: var(--s-surface); color: var(--s-dark); box-shadow: 0 1px 2px rgba(18,32,26,.08); }
#mx-ctxbar .ctx-tag { font-size: 10px; font-weight: 700; letter-spacing: .5px; color: var(--s-dark); flex: 0 0 auto; }
#mx-ctxbar .ctx-tag.muted { color: var(--s-muted); }
#mx-ctxbar .ctx-hint { font-size: 11px; color: var(--s-faint); flex: 0 0 auto; }
#mx-ctxbar .ctx-spring { flex: 1 1 auto; min-width: 8px; }
#mx-ctxbar .ctx-chip,
#mx-ctxbar .ctx-pill {
  display: flex; align-items: center; gap: 4px; height: 28px; box-sizing: border-box;
  border: 1px solid var(--s-line); border-radius: 7px; padding: 0 8px;
  font-size: 11.5px; font-weight: 600; color: var(--s-muted); cursor: pointer; flex: 0 0 auto;
  background: var(--s-surface); user-select: none;
}
#mx-ctxbar .ctx-chip:hover,
#mx-ctxbar .ctx-pill:hover { background: var(--s-well); }
#mx-ctxbar .ctx-chip.on { background: var(--s-tint); color: var(--s-dark); border-color: #a8c6b7; }
#mx-ctxbar .ctx-chip svg { width: 13px; height: 13px; }
#mx-ctxbar .ctx-pill .step { color: var(--s-muted); padding: 0 3px; }
#mx-ctxbar .ctx-pill .val { font-weight: 700; min-width: 44px; text-align: center; color: var(--s-text); }
#mx-ctxbar .ctx-pill .k { color: var(--s-faint); font-size: 10px; }
#mx-ctxbar .ctx-xy { cursor: text; padding: 0 6px; }
#mx-ctxbar .ctx-xyin {
  width: 46px; border: none; background: transparent; outline: none;
  font: 700 11.5px inherit; color: var(--s-text); text-align: center;
  -moz-appearance: textfield;
}
#mx-ctxbar .ctx-xyin::-webkit-outer-spin-button,
#mx-ctxbar .ctx-xyin::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#mx-ctxbar .ctx-danger { color: var(--s-danger); border-color: #e0b9b3; }
#mx-ctxbar .ctx-danger:hover { background: #f6ecea; }
#mx-ctxbar .ctx-units { display: flex; background: var(--s-well); border-radius: 7px; padding: 2px; gap: 0; flex: 0 0 auto; }
#mx-ctxbar .ctx-units .u {
  height: 24px; display: flex; align-items: center; padding: 0 9px; border-radius: 5px;
  font-size: 10.5px; font-weight: 700; cursor: pointer; color: var(--s-muted);
}
#mx-ctxbar .ctx-units .u.on { background: var(--s-surface); color: var(--s-dark); box-shadow: 0 1px 2px rgba(18,32,26,.08); }
/* selection-state panes inside the ctx bar */
#mx-ctxbar .ctx-pane { display: none; align-items: center; gap: 6px; flex: 0 0 auto; }
#mx-ctxbar[data-ctx="tool"]   .ctx-pane.pane-tool,
#mx-ctxbar[data-ctx="object"] .ctx-pane.pane-object,
#mx-ctxbar[data-ctx="shape"]  .ctx-pane.pane-shape { display: flex; }

/* SHAPE pane: fill / border colour swatches in the contextual bar */
#mx-ctxbar .ctx-swatch {
  display: flex; align-items: center; gap: 5px; height: 28px; box-sizing: border-box;
  border: 1px solid var(--s-line); border-radius: 7px; padding: 0 6px 0 9px;
  font-size: 11px; font-weight: 600; color: var(--s-muted); flex: 0 0 auto; cursor: pointer;
}
#mx-ctxbar .ctx-swatch input[type="color"] { width: 22px; height: 20px; border: 0; background: none; padding: 0; cursor: pointer; }

/* full shape property set, relocated into the inspector Object tab */
#mx-inspector #shape-props { margin-top: 0 !important; border-top: 0 !important; padding-top: 0 !important; }
#mx-inspector.shape-sel #text-popup { display: none !important; }   /* shape selected → hide the text inspector */
#mx-inspector .sp-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
#mx-inspector #shape-props .sp-f, #mx-inspector #shape-props .sp-c { display: flex; flex-direction: column; gap: 3px; font-size: 10.5px; color: var(--s-muted); }
#mx-inspector #shape-props input[type="number"], #mx-inspector #shape-props select { width: 100%; box-sizing: border-box; height: 28px; border: 1px solid var(--s-line); border-radius: 7px; padding: 0 7px; font: inherit; font-size: 12px; }
#mx-inspector #shape-props .shape-actions { display: flex; gap: 6px; margin-top: 10px; }
#mx-inspector #shape-props .shape-actions .fbtn { flex: 1; }

#mx-ctxbar select.ctx-sel {
  height: 28px; box-sizing: border-box; border: 1px solid var(--s-line); border-radius: 7px;
  font-size: 11.5px; padding: 0 6px; background: var(--s-surface); color: var(--s-text);
  flex: 0 0 auto; font-family: inherit; font-weight: 600; cursor: pointer; max-width: 150px;
}
/* REQUIREMENT B removed .ctx-seg / .ctx-arc / .ctx-menu-btn / .ctx-care and the whole
   #mx-ctxmenu popover block: every one of them styled an element of the deleted TEXT pane
   (the align segment, the curve slider, and the Position / More dropdowns). #mx-ebmenu is the
   surviving dropdown and has its own styles with the edit bar. */

/* ============ Body row (reuse #free-main as the flex row) ============ */
body.mx-studio #free-main {
  padding: 0; gap: 0;
  height: calc(100% - 52px);   /* only the app bar is in-flow now (the ctx bar floats over the canvas) */
  align-items: stretch;
}

/* ============ Tool rail — DOCKED 68px column (Phase 1) ============
   The approved Studio package docks the tool rail as a real region column: js/laser-studio.js
   inserts #mx-toolcluster as the FIRST flex child of #free-main, so the canvas region starts to
   its right and NOTHING floats over the artboard (it floated at top:14/left:14 before). Same
   element, same buttons, same ids — every existing .tr-btn selector keeps working. It carries
   the design's tool set: Select, Nodes, Pen, Shapes, Text, Upload, Trace, Scissors, Measure,
   Pan, Zoom — plus two COMMAND buttons, Offset and Combine, that open modal dialogs instead of
   entering a mode. Creation tools still fold into their category panels. The node-count slider
   now anchors to the canvas edge beside the Nodes button (see syncNodeSlider), so the column's
   full height costs it nothing. */
#mx-toolcluster {
  position: static; flex: 0 0 68px; width: 68px; z-index: 7;
  display: flex; flex-direction: column; align-items: center; gap: 3px; padding: 8px 4px; box-sizing: border-box;
  background: var(--s-surface); border: 0; border-right: 1px solid var(--s-line); border-radius: 0;
  box-shadow: none;
  max-height: none; overflow-y: auto; overscroll-behavior: contain;
}
#mx-toolcluster .tr-btn {
  position: relative; width: 50px; min-height: 42px; box-sizing: border-box;
  border: 1.5px solid transparent; border-radius: 9px; background: transparent;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  cursor: pointer; color: var(--s-muted); padding: 5px 2px;
}
#mx-toolcluster .tr-btn:hover { background: var(--s-well); color: var(--s-text); }
#mx-toolcluster .tr-btn.on { background: var(--s-tint); color: var(--s-dark); border-color: var(--s-brand); }
#mx-toolcluster .tr-btn svg { width: 17px; height: 17px; }
#mx-toolcluster .tr-btn .tr-lbl { font-size: 8.5px; font-weight: 600; line-height: 1.05; text-align: center; letter-spacing: .01em; }

/* rail flyout popover (bottom-toolbar Combine) — fixed, anchored on demand */
#mx-flyout {
  position: fixed; z-index: 9500; width: 190px;
  background: var(--s-surface); border: 1px solid var(--s-line); border-radius: 10px;
  box-shadow: 0 4px 12px rgba(18,32,26,.08), 0 16px 48px rgba(18,32,26,.14);
  padding: 8px; display: none;
}
#mx-flyout.show { display: block; }
#mx-flyout .fly-title { font-size: 10px; font-weight: 700; color: var(--s-muted); padding: 2px 6px 6px; }
#mx-flyout .fly-item {
  display: flex; align-items: center; gap: 8px; min-height: 30px; padding: 0 9px;
  border-radius: 7px; cursor: pointer; font-size: 12px; font-weight: 500; color: var(--s-text);
}
#mx-flyout .fly-item:hover { background: var(--s-well); }
#mx-flyout .fly-item.on { background: var(--s-tint); color: var(--s-dark); font-weight: 700; }
#mx-flyout .fly-item .fly-note { margin-left: auto; font-size: 9.5px; color: var(--s-faint); }

/* ============ Boolean Assistant (preview before commit) ============
   Bottom-RIGHT, never over the artwork: the whole point of the dialog is watching the
   preview on the canvas, so it must not cover it.
   #mx-bool-backdrop is FUNCTIONAL, not decorative — transparent so the artboard stays
   fully visible, but it blocks input while uncommitted preview geometry is live. That is
   what makes "Cancel restores the originals exactly" a guarantee rather than a hope. */
#mx-bool-backdrop { position: fixed; inset: 0; z-index: 9690; background: transparent; }
#mx-bool-dlg {
  position: fixed; right: 24px; bottom: 96px; z-index: 9700; width: 300px; box-sizing: border-box;
  background: var(--s-surface); border: 1px solid var(--s-line); border-radius: 10px;
  box-shadow: 0 4px 12px rgba(18,32,26,.08), 0 16px 48px rgba(18,32,26,.14);
  padding: 12px; font-family: 'Montserrat', system-ui, sans-serif; color: var(--s-text);
}
#mx-bool-dlg .bd-title { font-size: 10px; font-weight: 700; color: var(--s-muted); padding: 0 2px 6px; letter-spacing: .04em; text-transform: uppercase; }
#mx-bool-dlg .bd-legend { font-size: 10.5px; line-height: 1.45; color: var(--s-muted); background: var(--s-well); border-radius: 7px; padding: 7px 8px; margin-bottom: 9px; }
#mx-bool-dlg .bd-btn {
  display: block; width: 100%; text-align: left; margin-bottom: 5px; min-height: 30px;
  padding: 0 10px; border: 1px solid var(--s-line); border-radius: 7px; cursor: pointer;
  background: var(--s-surface); color: var(--s-text);
  font: 500 12px 'Montserrat', system-ui, sans-serif;
}
#mx-bool-dlg .bd-btn:hover:not([disabled]) { background: var(--s-tint); border-color: var(--s-brand); color: var(--s-dark); }
#mx-bool-dlg .bd-btn[disabled] { opacity: .45; cursor: not-allowed; }
#mx-bool-dlg .bd-btn.bd-reset { margin-top: 4px; color: var(--s-muted); }
#mx-bool-dlg .bd-note { font-size: 10.5px; line-height: 1.45; color: var(--s-faint); margin: 8px 2px 6px; }
#mx-bool-dlg .bd-status { font-size: 10.5px; font-weight: 600; color: var(--s-dark); min-height: 14px; margin: 0 2px 9px; }
#mx-bool-dlg .bd-actions { display: flex; gap: 8px; justify-content: flex-end; }
#mx-bool-dlg .bd-actions button {
  min-height: 30px; padding: 0 16px; border-radius: 7px; cursor: pointer;
  font: 700 12px 'Montserrat', system-ui, sans-serif;
}
#mx-bool-dlg .bd-cancel { background: var(--s-surface); border: 1px solid var(--s-line); color: var(--s-muted); }
#mx-bool-dlg .bd-cancel:hover { background: var(--s-well); color: var(--s-text); }
#mx-bool-dlg .bd-ok { background: var(--s-dark); border: 1px solid var(--s-dark); color: #fff; }
#mx-bool-dlg .bd-ok:hover { background: var(--s-brand); border-color: var(--s-brand); }

/* ============ Offset dialog ============
   Same bottom-RIGHT placement and the same visual language as the Boolean Assistant, so the
   two shape operations do not look like they came from different apps. Uses only the --s-*
   tokens declared at the top of this file — no new hardcoded hex values.
   Unlike the Boolean Assistant this dialog holds NO uncommitted model state (the offset is
   computed and applied on OK), so the backdrop here is a plain dismiss target rather than
   export-safety machinery. */
#mx-offsetdlg-back { position: fixed; inset: 0; z-index: 9690; background: transparent; }
#mx-offsetdlg {
  position: fixed; right: 24px; bottom: 96px; z-index: 9700; width: 300px; box-sizing: border-box;
  background: var(--s-surface); border: 1px solid var(--s-line); border-radius: 10px;
  box-shadow: 0 4px 12px rgba(18,32,26,.08), 0 16px 48px rgba(18,32,26,.14);
  padding: 12px; font-family: 'Montserrat', system-ui, sans-serif; color: var(--s-text);
}
#mx-offsetdlg .mxo-title { font-size: 10px; font-weight: 700; color: var(--s-muted); padding: 0 2px 8px; letter-spacing: .04em; text-transform: uppercase; }
#mx-offsetdlg .mxo-field { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; font-size: 11.5px; color: var(--s-text); }
#mx-offsetdlg .mxo-field span { flex: 1 1 auto; }
#mx-offsetdlg .mxo-field input[type="number"] {
  flex: 0 0 84px; width: 84px; box-sizing: border-box; height: 28px;
  border: 1px solid var(--s-line); border-radius: 7px; padding: 0 7px;
  font: 600 12px 'Montserrat', system-ui, sans-serif; color: var(--s-text); background: var(--s-surface);
}
#mx-offsetdlg .mxo-lbl { font-size: 10px; font-weight: 700; color: var(--s-muted); letter-spacing: .04em; text-transform: uppercase; margin: 2px 2px 5px; }
#mx-offsetdlg .mxo-seg { display: flex; gap: 5px; margin-bottom: 10px; }
#mx-offsetdlg .mxo-chip {
  flex: 1 1 0; min-height: 28px; padding: 0 6px; cursor: pointer;
  border: 1px solid var(--s-line); border-radius: 7px;
  background: var(--s-surface); color: var(--s-text);
  font: 600 11px 'Montserrat', system-ui, sans-serif;
}
#mx-offsetdlg .mxo-chip:hover { background: var(--s-well); }
#mx-offsetdlg .mxo-chip.on { background: var(--s-tint); color: var(--s-dark); border-color: var(--s-brand); }
#mx-offsetdlg .mxo-row { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--s-text); padding: 3px 2px; cursor: pointer; }
#mx-offsetdlg .mxo-row input { margin: 0; flex: 0 0 auto; }
/* min-height reserved so the dialog does not jump the moment a message appears */
#mx-offsetdlg .mxo-msg { font-size: 10.5px; line-height: 1.4; font-weight: 600; color: var(--s-danger); min-height: 15px; margin: 8px 2px 9px; }
#mx-offsetdlg .mxo-actions { display: flex; gap: 8px; justify-content: flex-end; }
#mx-offsetdlg .mxo-actions button {
  min-height: 30px; padding: 0 16px; border-radius: 7px; cursor: pointer;
  font: 700 12px 'Montserrat', system-ui, sans-serif;
}
#mx-offsetdlg .mxo-cancel { background: var(--s-surface); border: 1px solid var(--s-line); color: var(--s-muted); }
#mx-offsetdlg .mxo-cancel:hover { background: var(--s-well); color: var(--s-text); }
#mx-offsetdlg .mxo-ok { background: var(--s-dark); border: 1px solid var(--s-dark); color: #fff; }
#mx-offsetdlg .mxo-ok:hover { background: var(--s-brand); border-color: var(--s-brand); }

/* ============ Map-layers panel (labeled cat-rail + controls) ============ */
/* `overflow-y: auto` IS A BUG FIX, NOT A STYLE CHOICE. This column was overflow-y:visible inside
   #free-main, which is overflow:hidden — so anything the column laid out past its own box was
   simply CUT OFF, with no scroll port anywhere in the ancestor chain to reach it. MEASURED at
   1440x900 on laser-lake.html: clientHeight 812 against a scrollHeight of 878 in Compact and
   1052 in Expanded, which put ZERO of the twelve source rows on screen in Expanded and ONE of
   twelve in Compact. The caps and floors below keep the common case from ever needing this; it
   is the honest floor under them for the screens and content lengths they do not cover.
   setupDdSync already listens for `scroll` in the CAPTURE phase, so the position:fixed
   "Map & sheet settings" popover re-syncs against this new port with no extra wiring. */
#mx-mappanel {
  width: 336px; flex: 0 0 336px; box-sizing: border-box;
  background: var(--s-surface); border-right: 1px solid var(--s-line);
  display: flex; flex-direction: column; min-height: 0; z-index: 5;
  overflow-y: auto; overflow-x: hidden;
}
body.mx-studio.mx-map-closed #mx-mappanel { display: none; }
#mx-mappanel .mp-head {
  display: flex; align-items: center; gap: 6px; padding: 11px 14px 9px;
  border-bottom: 1px solid var(--s-line); flex: 0 0 auto;
}
#mx-mappanel .mp-title { font-size: 13px; font-weight: 700; flex: 1; color: var(--s-text); }
#mx-mappanel .mp-close {
  width: 26px; height: 26px; border: 0; background: transparent; border-radius: 7px;
  display: flex; align-items: center; justify-content: center; color: var(--s-muted); cursor: pointer;
}
#mx-mappanel .mp-close:hover { background: var(--s-well); }
#mx-mappanel .mp-close svg { width: 13px; height: 13px; }
#mx-mappanel .mp-main { flex: 1; display: flex; min-height: 0; }

/* ---- Order Notes, pinned above the scrolling controls (laser-studio.js buildNotesPin) ----
   Sits between .mp-head and .mp-main, which are the only two fixed-chrome slots in the panel,
   so the customer's requests stay on screen on EVERY category instead of only under Setup. */
#mx-notes-pin {
  flex: 0 0 auto; border-bottom: 1px solid var(--s-line); background: var(--s-well);
  display: flex; flex-direction: column; min-height: 0; max-height: 44%;
}
#mx-notes-pin .mx-notes-hd {
  flex: 0 0 auto; display: flex; align-items: center; gap: 7px; width: 100%;
  padding: 8px 14px; border: 0; background: transparent; cursor: pointer;
  font: inherit; font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--s-muted); text-align: left;
}
#mx-notes-pin .mx-notes-hd:hover { color: var(--s-text); }
#mx-notes-pin .mx-notes-car { font-size: 10px; transition: transform .15s ease; }
#mx-notes-pin.mx-notes-closed .mx-notes-car { transform: rotate(-90deg); }
#mx-notes-pin .mx-notes-ttl { flex: 1; }
#mx-notes-pin .mx-notes-dot { width: 7px; height: 7px; border-radius: 999px; background: var(--s-brand); flex: 0 0 auto; }
#mx-notes-pin .mx-notes-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 0 14px 10px; }
/* the promoted <section> is still a .ctl-group — strip the section chrome, it's a pinned block now */
#mx-notes-pin #order-notes-sec { margin: 0; padding: 0; border: 0; background: transparent; }
#mx-notes-pin #order-notes-sec > h3 { display: none; }        /* the pin header names it */
#mx-notes-pin #order-notes-sec > .ctl-hint { margin: 5px 0 0; font-size: 10.5px; }
/* rows="4" (82px) is what the engine authors, and it is right on the phone sheet and in the
   drilled-in section. In the pin it is 82px of a fixed chrome slot sitting above BOTH the Setup
   block and the source list, where every pixel comes straight off the rows: MEASURED, dropping
   it to two lines is 30px, which is the difference between one Expanded source section being
   fully on screen at 1440x800 and none being. Nothing is hidden by it — the box scrolls, it is
   still resize:vertical so a long note can be dragged open, and the header keeps its dot
   whenever there IS a note. */
#mx-notes-pin #order-notes { min-height: 0; height: 52px; }

/* ---- ONE SETUP, NOT TWO — #mx-setup-pin is now the Setup ROW itself ----
   It used to be a pinned "SETUP" bar with its own chrome, sitting ABOVE the panel while a Setup
   ROW with the red dot sat inside "Map sources" saying the same thing. Two surfaces for one
   thing is the confusion the owner named; the row is what stays. So this id now carries the
   `.msp-row` anatomy every sibling row has (dot · name · live status · chevron) and the rules
   below are only the handful of row-level differences: it is a sibling of #mx-srcpanel rather
   than a child of it, so the .msp-row styling inside that panel cannot reach it.
   Hidden by default and revealed only in the desktop source-LIST state (see the @media block
   below), so the phone sheet and the drilled-in settings view are untouched. */
#mx-setup-pin { display: none; }
#mx-setup-pal { display: none; }
@media (min-width: 761px) {
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #mx-setup-pin {
    position: relative; display: flex; align-items: center; gap: 10px; box-sizing: border-box;
    margin: 8px 14px 0; padding: 8px 10px; width: auto;
    border: 1px solid var(--s-line); border-radius: 9px; background: var(--s-row);
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #mx-setup-pin:hover { border-color: var(--s-line-hi); }
  /* OPEN: the block's own caption, so it recedes rather than reading as a closed row. It is still
     a live toggle (unlike the Expanded variant this replaces, which was chrome only) — the
     disclosure caret below is what carries that, and it is the one disclosure in the block. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #mx-setup-pin.msp-setuprow-open {
    background: transparent; border-color: transparent; margin-bottom: -4px;
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #mx-setup-pin .msp-open {
    position: absolute; inset: 0; z-index: 0; border: 0; padding: 0; margin: 0;
    background: transparent; border-radius: 9px; cursor: pointer;
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #mx-setup-pin .msp-open:focus-visible {
    outline: 2px solid var(--s-brand); outline-offset: -2px;
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #mx-setup-pin .msp-sr {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #mx-setup-pin .msp-txt {
    display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto;
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #mx-setup-pin .msp-nm {
    font: 600 12.5px 'Montserrat', system-ui, sans-serif; color: var(--s-text);
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #mx-setup-pin .msp-st {
    font-size: 10.5px; color: var(--s-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #mx-setup-pin .msp-chev {
    width: 13px; height: 13px; flex: 0 0 auto; color: var(--s-faint); transition: transform .15s ease;
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #mx-setup-pin[data-open="1"] .msp-chev { transform: rotate(90deg); }
  /* the row's own cut-colour bar, in a persistent host of its own so no innerHTML write can
     reach it (the source rows' bars live inside a pane that is rewritten every repaint) */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #mx-setup-pal:not(:empty) {
    display: block; margin: 0 14px; flex: 0 0 auto;
  }
}

/* ---- ASK A — the two SUB-GROUPS inside the inline Setup block (laser-studio.js
   buildSetupGroups) ---- Level 2 of the same collapse pattern: a <button> header + a body div,
   inside the SAME <section>, so every control stays in #free-controls.

   OFF EVERYWHERE BY DEFAULT. The chrome only appears in the desktop source-LIST state; in the
   drilled-in "All Setup settings" view and on the phone sheet the headers are hidden and the
   bodies are FORCE-OPEN, so those two surfaces are byte-for-byte what they shipped as (a
   remembered collapse must never make "All Setup settings" look empty). */
/* ── SETUP: an accordion, not a takeover ─────────────────────────────────────────────
   (owner, 2026-08-26: "when clicking set up dont make the rest disappear, its a odd action
   and would rather have an accordion layout. also map and sheet settings need to be visible
   on the setup at all times"). Open: the Setup body renders IN PLACE and everything below —
   Map sources, the layer sections — simply moves down. Closed: the one-line pin. And the
   "Map & sheet settings" group inside Setup is pinned permanently open: its header (the
   second accordion the owner objected to) is gone, its body always shows. */
@media (min-width: 761px) {
  /* the left column runs to the BOTTOM of the window — it used to end where its content
     did, leaving dead dark space under it */
  body.mx-studio:not(.proof-mode) #mx-mappanel {
    height: 100%; display: flex; flex-direction: column; min-height: 0;
  }
  body.mx-studio:not(.proof-mode) #mx-map-main { flex: 1 1 auto; min-height: 0; }
  /* Map & sheet settings: always open, no toggle */
  body.mx-studio:not(.proof-mode) .mxs-setup-sec .mxs-sgrp[data-sgrp="rest"] > .mxs-sgrp-hd {
    display: none !important;
  }
  body.mx-studio:not(.proof-mode) .mxs-setup-sec .mxs-sgrp[data-sgrp="rest"] > .mxs-sgrp-body {
    display: block !important;
  }
}

#mx-mappanel .mxs-sgrp-hd { display: none; }
#mx-mappanel .mxs-sgrp.mxs-sgrp-closed > .mxs-sgrp-body { display: block; }
@media (min-width: 761px) {
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp-hd {
    display: flex; align-items: center; gap: 7px; width: 100%; box-sizing: border-box;
    margin: 0 0 8px; padding: 5px 0; border: 0; border-bottom: 1px solid var(--s-hair);
    background: transparent; cursor: pointer; text-align: left;
    font: 700 10px 'Montserrat', system-ui, sans-serif; letter-spacing: 1.1px;
    text-transform: uppercase; color: var(--s-muted);
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp-hd:hover { color: var(--s-text); }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp-hd:focus-visible {
    outline: 2px solid var(--s-brand); outline-offset: 2px; border-radius: 4px;
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp.mxs-sgrp-closed > .mxs-sgrp-body { display: none; }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp.mxs-sgrp-closed .mxs-sgrp-hd { margin-bottom: 2px; }
  /* LOCATION, SIZE AND MAP QUALITY HAVE NO HEADER. All three are permanently open
     (setupGrpPref), so a header would be a control that cannot do anything — and each was
     costing a click on something chosen right after a place. The WRAPPERS stay exactly as
     buildSetupGroups made them: no node is re-parented, no id moves, and the drilled-in view and
     the phone sheet still get the shipped chrome from the unscoped rules above. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="loc"] > .mxs-sgrp-hd,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="qual"] > .mxs-sgrp-hd,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] > .mxs-sgrp-hd { display: none; }

  /* A GROUP WITH NO HEADER MUST NEVER RENDER COLLAPSED. Location, Size and Quality hide their
     headers here on purpose — they always show inline, so there is nothing to toggle. But the
     collapse rule above is written for ALL groups, so if any of these three ever picks up
     .mxs-sgrp-closed the body disappears AND the only control that could reopen it is
     display:none. The Setup panel then renders as a blank gap with no way back, which is exactly
     what the owner hit twice ("the set up tab is empty", and earlier "clicked custom size and
     everything went blank"). js/laser-studio.js already carries a repair pass that strips the
     class off setup groups, which says this state was reachable and was being cleaned up after
     the fact rather than prevented. Making it unreachable in CSS is stronger: whatever sets the
     class — a restored preference, a stray click, a future refactor — these three still render. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="loc"].mxs-sgrp-closed > .mxs-sgrp-body,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="qual"].mxs-sgrp-closed > .mxs-sgrp-body,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"].mxs-sgrp-closed > .mxs-sgrp-body { display: grid; }

  /* ---- "MAP & SHEET SETTINGS" AS A DISCLOSURE DROPDOWN ----
     The header is restyled to the Bathymetry "Interval" <select class="msp-lvsel"> metrics — the
     control that already proves a dropdown renders correctly in this panel — and its body is
     lifted OUT OF FLOW by js/laser-studio.js's setupDdSync() (position:fixed, because two
     overflow:auto ancestors would clip an absolute popover). Out of flow it costs the scroll port
     ZERO height whether open or closed, which is what an expander structurally cannot do and what
     keeps Location, Size, Look and Template all on screen at once. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp-hd.mxs-sgrp-dd {
    justify-content: flex-start; min-height: 34px; margin: 10px 0 0; padding: 0 10px;
    border: 1px solid var(--s-line); border-radius: 8px; background: var(--s-well);
    font: 600 11.5px 'Montserrat', system-ui, sans-serif; letter-spacing: 0;
    text-transform: none; color: var(--s-text);
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp.mxs-sgrp-closed .mxs-sgrp-hd.mxs-sgrp-dd { margin-bottom: 0; }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp-hd.mxs-sgrp-dd:hover { border-color: var(--s-line-hi); }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp-hd.mxs-sgrp-dd .mxs-sgrp-car {
    order: 3; margin-left: auto; font-size: 10px; color: var(--s-muted);   /* caret rotates with the fold (rule at the bottom of this block) */
  }
  /* THE ACCORDION BODY (owner 2026-08-19: "more of an accordion action"). In flow, under its
     header: grid-template-rows 1fr↔0fr animates the height without measuring it (Safari 16+ /
     Chrome 107+ animate it; older browsers just snap, which is still correct). visibility keeps
     the ~20 folded controls out of the Tab order; the single inner wrapper (.mxs-sgrp-in, built by
     buildSetupGroups for `rest` only) is what min-height:0 + overflow:hidden clip. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="rest"] > .mxs-sgrp-body {
    display: grid; grid-template-rows: 1fr; overflow: hidden; visibility: visible;
    transition: grid-template-rows .24s cubic-bezier(.4, 0, .2, 1), visibility 0s;
    border: 1px solid var(--s-line); border-top: 0; border-radius: 0 0 8px 8px; background: var(--s-well);
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="rest"].mxs-sgrp-closed > .mxs-sgrp-body {
    display: grid; grid-template-rows: 0fr; visibility: hidden; border-color: transparent;
    transition: grid-template-rows .24s cubic-bezier(.4, 0, .2, 1), visibility 0s .24s;
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="rest"] > .mxs-sgrp-body > .mxs-sgrp-in {
    min-height: 0; overflow: hidden; padding: 10px 12px 14px;
    transition: padding .24s cubic-bezier(.4, 0, .2, 1);
  }
  /* PADDING GOES TOO. A 0fr track collapses the CONTENT box, not the padding box — with the
     padding left on, a folded accordion still measured 25px of empty well under its header. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="rest"].mxs-sgrp-closed > .mxs-sgrp-body > .mxs-sgrp-in { padding-top: 0; padding-bottom: 0; }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="rest"]:not(.mxs-sgrp-closed) > .mxs-sgrp-hd.mxs-sgrp-dd { border-radius: 8px 8px 0 0; margin-bottom: 0; }
  @media (prefers-reduced-motion: reduce) {
    body.mx-studio #mx-mappanel .mxs-sgrp[data-sgrp="rest"] > .mxs-sgrp-body,
    body.mx-studio #mx-mappanel .mxs-sgrp[data-sgrp="rest"] > .mxs-sgrp-body > .mxs-sgrp-in { transition: none; }
  }

  /* THE THREE EXPANDED-ONLY `rest` RULES THAT USED TO SIT HERE ARE DELETED — the inline
     force-open, the inert header, and the hidden caret. They existed because Setup was a ROW IN
     THE LIST and Expanded's contract is that every row shows its controls at once. Setup is now
     its own block ABOVE the list, so [Compact | Expanded] no longer describes it at all, and
     keeping them would have left the panel with two different Setups again — the exact confusion
     this placement was chosen to end. "Map & sheet settings" is the SAME one-click dropdown in
     both layouts now, which is also the only way the advanced pile costs the column zero height
     in both (an inline block costs its full content height whenever it is open — that is what
     put the size and template pickers below the fold in the first place). */
  /* the section's own <h3>Location</h3> is now a THIRD name for this block (the pin says
     "Setup", the header says "Location") — retired in the inline state only */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #free-controls .ctl-group[data-mx-setup-groups] > h3 { display: none; }
}
/* ONE-CLICK LOOKS, small. buildStyleGallery() (js/laser-lake.js:28010) builds the eight real
   MAP_STYLES cards; it sets the grid's columns as an INLINE style (untouchable without
   !important) but each swatch's height as a presentational SVG ATTRIBUTE, which any stylesheet
   rule outranks. Trimming that, and the caption with it, is all it takes to turn eight
   full-height cards into eight small ones inside a ~300px rail. Desktop-only and scoped to the
   studio's own panel, so the phone sheet renders the gallery exactly as it ships. */
@media (min-width: 761px) {
  body.mx-studio #mx-mappanel #style-grid svg { height: 40px; }
  body.mx-studio #mx-mappanel #style-grid > button > span { padding: 4px 6px 5px !important; font-size: 10.5px !important; }
  body.mx-studio #mx-mappanel #style-grid > button > span > span { font-size: 9.5px !important; }
}
#mx-mappanel .mxs-sgrp-car { font-size: 9px; transition: transform .15s ease; flex: 0 0 auto; }
#mx-mappanel .mxs-sgrp.mxs-sgrp-closed .mxs-sgrp-car { transform: rotate(-90deg); }
#mx-mappanel .mxs-sgrp-ttl { flex: 0 0 auto; }
/* collapsed, the header carries what the body would otherwise hide: the picked place, and the
   sheet preset + finished cut width. Empty while the group is open. */
#mx-mappanel .mxs-sgrp-sub {
  flex: 1 1 auto; min-width: 0; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 600 10px 'Montserrat', system-ui, sans-serif; letter-spacing: 0; text-transform: none; color: var(--s-faint);
}
/* THE HIDDEN COST. The engine's how-to VIDEO CARD is 234px, it is appended to #free-controls
   (js/laser-lake.js:31389) and re-pinned to the bottom on every category change — so it is a
   SIBLING of the Setup section, inside the same scroll port. Measured: with it in the port a
   Location-only Setup still sat at the full 325px cap and the source rows gained NOTHING.
   It is hidden here rather than re-parented, because mxUpdateHelp only keeps maintaining it
   while `parentNode === _fc`. `!important` because that same function writes an inline
   `style.display` (''  when there is a video, 'none' when there is not). */
@media (min-width: 761px) {
  /* TRIGGER CHANGED, RULE UNCHANGED. It used to key on .mxs-sg-rest-closed, which is now
     almost always true — but would release 234px the instant the dropdown opened, i.e.
     exactly when the panel has least room. Unconditional in the LIST state; still released
     in the drilled-in view and on the phone, which is where the card is actually useful. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #free-controls .mx-help { display: none !important; }
}

/* Two-sidebar layout (direction A): #cat-rail lives INSIDE #mx-mappanel as the full-width
   category ROW LIST — the region row is [tools 68px][ONE panel][canvas][inspector]. The
   engine's buttons are re-parented, never rebuilt: same ids, same handlers, new clothes.
   Desktop-gated: the ≤760px block below keeps the phone sheet's chip strip untouched. */
/* zoompan.js injects a « collapse toggle into the rail — the panel has its own collapse */
body.mx-studio #rail-toggle { display: none !important; }
@media (min-width: 761px) {
  body.mx-studio #mx-mappanel #cat-rail {
    width: auto; flex: 1 1 auto; min-height: 0; box-sizing: border-box; z-index: auto;
    border: 0; border-radius: 0; box-shadow: none; margin: 0; max-height: none;
    align-self: stretch;   /* editor.css pins the standalone rail flex-start — full panel width here */
    background: var(--s-surface); overflow-y: auto;
    display: flex; flex-direction: column; align-items: stretch; gap: 2px; padding: 8px 10px 14px;
  }
  body.mx-studio #mx-mappanel #cat-rail .cat-btn {
    width: 100%; min-height: 40px; flex: 0 0 auto; box-sizing: border-box;
    display: flex; flex-direction: row; align-items: center; justify-content: flex-start;
    gap: 10px; padding: 0 8px 0 10px; border: 1.5px solid transparent; border-radius: 9px;
    color: var(--s-text); text-align: left;
  }
  body.mx-studio #mx-mappanel #cat-rail .cat-btn .cat-ico { flex: 0 0 auto; display: flex; color: var(--s-muted); }
  body.mx-studio #mx-mappanel #cat-rail .cat-btn .cat-ico svg { width: 18px; height: 18px; }
  body.mx-studio #mx-mappanel #cat-rail .cat-btn .cat-lbl {
    display: block; flex: 1 1 auto; font-size: 12.5px; font-weight: 600; line-height: 1.2; text-align: left;
  }
  /* the row chevron — pure CSS, no DOM inside the engine's buttons changes */
  body.mx-studio #mx-mappanel #cat-rail .cat-btn::after { content: '›'; font-size: 15px; color: var(--s-faint); flex: 0 0 auto; }
  body.mx-studio #mx-mappanel #cat-rail .cat-btn:hover { background: var(--s-well); color: var(--s-text); }
  body.mx-studio #mx-mappanel #cat-rail .cat-btn.active {
    background: var(--s-tint); color: var(--s-dark); border-color: var(--s-brand); box-shadow: none;
  }
  body.mx-studio #mx-mappanel #cat-rail .cat-btn.active .cat-ico { color: var(--s-dark); }
  /* LIST ↔ DETAIL swap (engine untouched — sections never leave #free-controls; the class
     only decides which half of the panel shows): .mxs-cat-open = the active category's
     controls + the ‹ back affordance; without it = the row list. */
  body.mx-studio #mx-mappanel.mxs-cat-open #cat-rail { display: none; }
  body.mx-studio #mx-mappanel:not(.mxs-cat-open) .mp-main { display: none; }
  body.mx-studio #mx-mappanel.mxs-cat-open .mp-back { display: flex; }
}
/* ============ MAP tab — SOURCE PANEL =======================================
   Toggle-first source rows + a drill-in detail (Compact), or every source open
   at once (Expanded). Geometry, spacing and the 220 ms push are the approved
   handoff prototype's, measured: 30×17 switch with a 13px knob, 9px dots, 47px
   rows on a 4px rhythm, .22s cubic-bezier(.4,0,.2,1) on the track transform.

   DESKTOP ONLY — every rule below sits inside the ≥761px block, so the phone
   MAP sheet keeps its chip strip + controls exactly as it is today.
   The panel never grows: each scroll port is an inner overflow-y:auto box. */
/* the head is a DESCENDANT of #mx-srcpanel again (see js/laser-studio.js buildSourcePanel), so
   this one rule hides the whole list surface — header, track and flat pane together — on the
   phone sheet and in the drilled-in view. It used to need a second base hide of its own because
   it had been hoisted out to be a loose panel child. */
#mx-srcpanel { display: none; }
@media (min-width: 761px) {
  body.mx-studio #mx-mappanel.mxs-src #cat-rail { display: none; }
  /* .mp-head (the "‹ Setup" drill-in header) stays hidden in the list state — there is no
     drilled-in view to come back from, so a back arrow there would point nowhere. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mp-head { display: none; }
  body.mx-studio #mx-mappanel.mxs-src.mxs-src-settings #mx-srcpanel { display: none; }
  body.mx-studio #mx-mappanel.mxs-src #mx-srcpanel { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }

  /* ======== REQUIREMENT A — SETUP RENDERS INLINE AT THE TOP OF THIS PANEL ========
     .mp-main used to be hidden here, which is what made Setup a DRILLED-IN dead end: the panel
     showed either the engine section (with a back arrow as its only exit) or the source rows,
     never both. Now the list state shows BOTH, in one continuous column:
         CONTENT | MAP tabs · Order Notes · SETUP (header + engine controls) · source rows
     #mx-mappanel is already display:flex/column, so plain `order` does the arrangement — no DOM
     node moves, #free-controls never leaves the panel body, and every id-bearing Setup control
     (#export-mm, #border-mm, #margin-*, #size-select …) stays inside #free-controls where
     EditorSave.captureControls sweeps it. WHICH sections are visible is js/laser-studio.js
     applySetupInline()'s job, through the engine's own .mx-cat-off class.
     Height: the Setup section alone is ~1387px in an ~812px panel, so it is BOUNDED and scrolls
     internally (#free-controls is already the inner overflow-y:auto port), and the rows keep a
     floor of their own so they can never be squeezed off the bottom. Collapsing the header hands
     the whole panel back to the rows. */
  /* ── THE FINAL PLACEMENT, DONE BY THE DOM ─────────────────────────────────────────────────
     The owner was given three homes for Setup and picked "above the Map sources list". So the
     column reads, top to bottom:
         ORDER NOTES · SETUP (header + Location, Size, Quality, Template, Map & sheet ▾)
                     · "Map sources — Lake" + [Compact | Expanded] · the source rows

     `order: 1..4` USED TO DO THAT AND HAS BEEN DELETED. It paints in the right sequence and
     leaves the DOM in the wrong one, which is precisely the documented limitation — `order`
     does not affect sequential focus navigation. MEASURED at 1440x900 on laser-lake.html: Tab
     from #order-notes went Compact(y=772) → Expanded(772) → Setup(305), a 467px BACKWARD jump
     on the third stop, where HEAD was monotonic. It also forced .msp-head out of #mx-srcpanel
     (only a direct panel child can be ordered between panel children), which crashed
     tests/studio-source-panel.test.mjs and left the mode switch with no scroll port to stick to.
     js/laser-studio.js now inserts #mx-srcpanel AFTER #mx-map-main and leaves the head inside
     it, so DOM order IS paint order and not one `order` number is needed here. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mp-main {
    /* THE BLOCK IS BOUNDED FROM BOTH ENDS, and both ends changed.

       `flex: 0 0 auto` was the shipped value and it is what turned "too many clicks" into "too
       much height": with shrink pinned to 0 the block took its full content height and the list
       below it collapsed onto its floor. MEASURED at 1440x900 the panel laid out 895px of
       content in an 812px box and #mx-srcpanel got a 77px window — ONE of twelve source rows
       fully visible in Compact, ZERO in Expanded, against 5 of 13 at HEAD.
       `flex: 0 1 auto` hands the shrink back. The reason it mis-behaved when it was tried
       before is gone: the content is ~200px now rather than ~420px (the compact grid below),
       so shrinking rarely binds at all, and where it does #free-controls is an
       overflow-y:auto port with the one foldable control STUCK TO ITS BOTTOM EDGE, so nothing
       can be pushed out of reach by it.

       max-height min(46%, 360px): 46% of the panel is the largest share that still leaves
       #mx-srcpanel the 190px floor its guard asks for at 1440x900, and the 360px ceiling stops
       a tall screen spending space on a block whose content is ~200px. */
    display: flex; flex: 0 1 auto; min-height: 0; max-height: min(46%, 360px);
    border-bottom: 1px solid var(--s-line);
  }
  /* THE EXPANDED-ONLY 560px ALLOWANCE IS GONE, and it was the single biggest cause of the panel
     overflowing its own box. MEASURED at 1440x900 on laser-lake.html, Expanded: the panel has
     812px of room and was laying out 1052px of content, of which this cap alone claimed 560 —
     #mx-srcpanel landed at top 980, i.e. its FIRST ROW started 80px BELOW the panel's bottom
     edge, and #free-main is overflow:hidden so there was no scroll port anywhere in the ancestor
     chain to reach it. ZERO of twelve source rows were reachable. (Re-checked at 1280x800: same
     result, srcTop 933 against a panel bottom of 800.)
     One cap for both modes is now correct BY CONSTRUCTION rather than by compromise: Setup no
     longer lives inside the list, so [Compact | Expanded] does not describe it and there is no
     second layout for it to need a second allowance for. Measured after: Expanded overflow falls
     from 240px to 70px, and the overflow-y:auto on the panel below carries the rest. */
  /* ORDER NOTES LAST — and by DOM order, not by `order`, for the same focus reason as above.
     The engine authors it as the FIRST section in #free-controls, and on laser-studio.html it has
     no #order-notes-sec id so buildNotesPin cannot lift it into its own pin, which put 172px of
     note box between the panel opening and the controls this change exists to expose.
     js/laser-studio.js buildSetupGroups moves the Setup section to the front of #free-controls
     instead. Nothing leaves #free-controls, so save scope is untouched. */
  /* padding-top 12 -> 4: measured, #tpl-select was overshooting the port's bottom edge by 6-9px at
     1280x800 and by nothing at all at 1440x900. Ten pixels of chrome is the entire difference
     between "it is on screen" and "there is a scroll". */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #free-controls { display: flex; flex-direction: column; padding-top: 4px; }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #free-controls > .ctl-group[data-mx-setup-groups] { padding-top: 0; }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #free-controls > .ctl-group { flex: 0 0 auto; }
  /* the two prose hints inside Size & look cost 108px between them and repeat what the labels and
     the group already say. They are still there in "All Setup settings" and on the phone.
     Map quality's hint goes the same way and for the same reason — this block sits ABOVE
     everything now, so its height is the owner's standing complaint, and the <select> already
     names its own options ("Fast preview", "Detailed"). */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] .ctl-hint,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="qual"] .ctl-hint { display: none; }
  /* …and the 57px "picked location" card, whose one fact — WHERE this map is — the Setup row's
     own summary line now states at all times (syncSetupPin). It is still there in "All Setup
     settings" and on the phone; here it was the difference between the template picker being on
     screen when the panel opens and being one scroll below it. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #picked-location { display: none; }

  /* ══ ITEM F — "KEEP IT SHORT". THE FLAT ROWS ARE ONE LINE EACH ═══════════════════════════════
     The approved sketch draws five lines:
         Location  [Lake Minnetonka] [Search]
         Size      [Square (1:1) v]  12 in
         Quality   [Fast Preview v]
         Template  [Cabin standard v]
         Map & sheet settings  v
     The first build stacked every caption ABOVE its control instead, which is what made the
     block 436.8px of an 812px panel and pushed the source list down to a 77px window. A label on
     its own line costs ~20px each and there are five of them.

     ONE GRID, TWO COLUMNS, and the pairing is structural rather than a hand-written list of ids:
     a caption (.ctl-label, or the section's own <h3>) takes the label column and THE ELEMENT
     IMMEDIATELY AFTER IT takes the control column. Anything that is not part of such a pair —
     the Generate button, the finished-width row, the result list — spans both columns and keeps
     its own layout exactly as authored. #mx-style-block is a wrapper div holding two more of the
     same pairs, so it gets the identical grid one level down.
     Scoped to the list state only: "All Setup settings", the drilled-in views and the phone
     sheet all keep the stacked layout, where there is room for it. */
  /* SCOPED TO THE THREE FLAT GROUPS, NEVER TO `rest`. The advanced pile's body is the same
     .mxs-sgrp-body class, and it is a 300px-wide popover holding captions like "Or set each side
     — blank uses 'all sides'": a 70px label column would ellipsize those to nothing. The flat
     groups are the ones whose captions are one or two words, so they are the ones named here. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="loc"] > .mxs-sgrp-body,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] > .mxs-sgrp-body,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="qual"] > .mxs-sgrp-body,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] #mx-style-block {
    /* the label column sizes itself to its OWN longest caption rather than to a magic number:
       each group is a separate grid, so "Location" (55px), "Template" (60px) and "Map quality"
       (68px) each get exactly what they need and hand the rest to the control. A fixed 70px
       squeezed #size-select to 85px and truncated "Square (1:1)" to "Squar". */
    display: grid; grid-template-columns: minmax(0, max-content) minmax(0, 1fr); gap: 5px 8px; align-items: center;
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="loc"] > .mxs-sgrp-body > *,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] > .mxs-sgrp-body > *,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="qual"] > .mxs-sgrp-body > *,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] #mx-style-block > * { grid-column: 1 / -1; min-width: 0; }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="loc"] > .mxs-sgrp-body > h3,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="loc"] > .mxs-sgrp-body > .ctl-label,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] > .mxs-sgrp-body > .ctl-label,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="qual"] > .mxs-sgrp-body > .ctl-label,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] #mx-style-block > .ctl-label {
    grid-column: 1; margin: 0; padding: 0; border: 0;
    font: 600 11px 'Montserrat', system-ui, sans-serif; color: var(--s-muted); line-height: 1.25;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="loc"] > .mxs-sgrp-body > h3 + *,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="loc"] > .mxs-sgrp-body > .ctl-label + *,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] > .mxs-sgrp-body > .ctl-label + *,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="qual"] > .mxs-sgrp-body > .ctl-label + *,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] #mx-style-block > .ctl-label + * { grid-column: 2; margin-top: 0; }
  /* the wrapper itself must not be a grid ITEM with its own padding — it is a pass-through */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] #mx-style-block { margin: 0; }
  /* Generate Map spans the row but does not need to be 38px tall to do it */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp-body #generate-btn { padding: 6px 10px; margin: 0; }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp-body #finished-size-row { margin: 0; }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp-body #search-results { margin: 0; }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp-body #finished-size-row .ctl-label { font-size: 11px; }
  /* ── "SIZE  [Square (1:1) v]  12 in" — ONE LINE, AS DRAWN ────────────────────────────────────
     The approved sketch puts the finished cut width on the SAME line as the sheet preset. It is
     a sibling <div> in the flow, not a child of the size row, so it is pinned to a third column
     of the size group's first grid row — deterministic because the "Size" caption and its
     .mm-row are always that group's first two visible children. Its own "Finished width"
     caption comes off in this state ONLY (it is there in "All Setup settings", on the phone and
     in every drilled-in view), and the row carries a title= so the number is never a bare 12.
     Worth 40px of a block that sits above everything. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] > .mxs-sgrp-body {
    grid-template-columns: minmax(0, max-content) minmax(0, 1fr) auto;
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] > .mxs-sgrp-body > #finished-size-row {
    grid-column: 3; grid-row: 1; margin: 0; gap: 3px; flex-wrap: nowrap; justify-content: flex-end;
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] #finished-size-row > .ctl-label { display: none; }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] #export-mm { width: 46px; flex: 0 0 46px; min-width: 0; padding-left: 5px; padding-right: 2px; }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] #finished-size-row .unitlabel { flex: 0 0 auto; }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] #sizeunits-select { padding-left: 5px; padding-right: 16px; }

  /* ── AND THE ROWS THEMSELVES ARE SHORTER ────────────────────────────────────────────────────
     Six 34px controls, a 30px button and a 12px section margin is 236px before a single caption.
     At the shell's own dense-panel metric (28px, the same as the .msp-lvsel dropdowns already in
     this panel) the block measures ~235px instead of ~317px, which is what puts five source rows
     back on screen at 1440x900 and keeps #quality-select inside the port at 1366x768 — MEASURED,
     it was 'scrolled-out' there before this. Scoped to the three flat groups: the advanced pile
     is a popover with room to breathe and keeps the shell's standard metrics. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #free-controls > .ctl-group[data-mx-setup-groups] { margin-bottom: 0; }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="loc"] select,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="loc"] input,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="loc"] .fbtn,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] select,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="size"] input,
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="qual"] select {
    min-height: 0; height: 28px; padding-top: 2px; padding-bottom: 2px; font-size: 12px;
  }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp-body #generate-btn { min-height: 0; height: 28px; }

  /* ── THE ONE FOLDABLE CONTROL IS STUCK TO THE PORT'S BOTTOM EDGE ─────────────────────────────
     "Map & sheet settings" is the last child of the section and the section scrolls inside
     #free-controls, so on a short screen it simply fell off the bottom: MEASURED at 1366x768 the
     header's visible height was ZERO (top 685.4 against a port bottom of 673.8) and #quality-select
     was clipped too — the advanced pile was unreachable without first scrolling a port most people
     would not know was scrollable. Sticking the wrapper to `bottom: 0` inside that port makes the
     one thing that folds always the one thing you can see, at every viewport, with no height cap
     to tune. The background is required: sticky elements overlap what they scroll over. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) .mxs-sgrp[data-sgrp="rest"].mxs-sgrp-closed {
    position: sticky; bottom: 0; z-index: 3; background: var(--s-surface); padding-bottom: 2px;
  }
  /* open, it is an ordinary block in the flow — the accordion body pushes what follows down and
     the port scrolls through it (an open sticky-bottom wrapper taller than the port would be
     shoved up over Location / Size — exactly the "pops up" look this replaces) */
  /* THE ONE DISCLOSURE. Setup ships OPEN and collapses to its summary line — an escape hatch for
     reclaiming height once the map is set up, never a state the owner has to click out of.
     THE EXPANDED EXEMPTION THAT USED TO SIT HERE IS DELETED. It forced .mp-main back to
     `display: flex` whenever the layout was Expanded, which was defensible while Setup was a row
     IN the list (Expanded's contract is that every row is open); now that Setup is its own block
     ABOVE the list, that rule would leave the collapse control doing visibly nothing in one of
     the two layouts — a dead control, which is the one thing this panel is not allowed to ship. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings).mxs-setup-closed .mp-main { display: none; }
  /* while the accordion is open the Setup block may take a little more of the column (still under
     the 55% the shell test bounds), so more of the expanded settings show before the port scrolls */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings):not(.mxs-sg-rest-closed) .mp-main { max-height: min(54%, 520px); }
  /* THE 190px FLOOR IS BACK, and it is restored rather than re-argued down. It is the shipped
     contract (tests/studio-shell.test.mjs:566 `rowsUsable: src && src.height >= 190`), it is
     four 47px rows, and the previous build's drop to 160 was what let the block above squeeze
     the list to a 77px on-screen window. It can be honoured now because .mp-main shrinks again
     (flex: 0 1 auto above) and its content is ~200px rather than ~420px. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #mx-srcpanel { flex: 1 1 0; min-height: 190px; }
  /* EXPANDED TAKES ITS NATURAL HEIGHT AND LETS THE COLUMN SCROLL, and this is the other half of
     the reachability fix — the panel's own overflow-y:auto alone was not enough.
     MEASURED: in Expanded a source SECTION is 178-207px tall (it carries its whole feature grid),
     and the list port above is pinned to its 160px floor. A box taller than its own scroll port
     can never be brought fully on screen no matter how far it is scrolled, so six of twelve
     sections were still unreachable AFTER the column became scrollable — scrolled to, partly
     seen, never wholly. Compact's rows are 47px and fit that port with room, which is exactly why
     this is scoped to Expanded and why Compact keeps the bounded track its slide animation needs.
     Unbounded, the sections lay out at full height and the panel column is the one thing that
     scrolls — which is what the reachability gate in tests/source-colours.test.mjs now asserts by
     scrolling each section into view and looking at where it lands. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings).mxs-mode-expanded #mx-srcpanel { flex: 0 0 auto; min-height: 0; }
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings).mxs-mode-expanded .msp-flat { flex: 0 0 auto; overflow-y: visible; }
  /* …and in that layout the Setup block must STOP shrinking. Compact and Expanded need opposite
     answers here and it is not a fudge: in Compact the list is `flex: 1 1 0` and takes whatever
     is left, so the block shrinking is what hands the rows their floor. In Expanded the list asks
     for its full 1772px, so a shrinkable block loses the whole fight — MEASURED at 1440x900 with
     `0 1 auto` in both: #mx-map-main came out 1px tall and Location, Size, Quality and Template
     were all off-screen, i.e. the zero-click property held in one layout and not the other.
     Pinned, the block keeps its content height (still under the cap) and the panel column
     scrolls, which is what the sticky header above is for. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings).mxs-mode-expanded .mp-main { flex: 0 0 auto; }
  /* ── THE HEADER STICKS, so the [Compact | Expanded] switch is never scrolled away ──────────
     In Expanded the list takes its natural height and #mx-mappanel is the scroll port, so the
     head was an ordinary sibling in a 2449px column: MEASURED at scrollTop 1617 it sat at
     top -880, i.e. 880px above the panel — the switch that changes the layout was unreachable
     from inside the layout it changes. It is a descendant of #mx-srcpanel again, and
     #mx-srcpanel spans the whole list, so `sticky top:0` pins it for exactly as long as any of
     the list is on screen and releases it the moment the list scrolls past. The background is
     required — a transparent sticky header lets the rows print through it. */
  body.mx-studio #mx-mappanel.mxs-src:not(.mxs-src-settings) #mx-srcpanel .msp-head {
    position: sticky; top: 0; z-index: 4; background: var(--s-surface);
    box-shadow: 0 1px 0 var(--s-line);
  }
  /* the seam line: "where do I go next" answered where the two halves meet. It rides on the
     header's own row now (see .msp-head below) rather than costing a third line of its own. */
  #mx-mappanel .msp-next { margin-top: 0; font-size: 10.5px; font-weight: 600; color: var(--s-brand); }
  /* the Setup row points UP at the block above it, not right into a drill-in that no longer exists */
  #mx-srcpanel .msp-row.msp-anchor .msp-chev { transform: rotate(-90deg); }

  /* ---- header: title and the layout switch on ONE row ----
     They used to be stacked, because the prototype's single row squeezed the title to 44px and
     wrapped it onto three lines. That measurement was taken when the title shared its line with
     a wider control set; at 308px of inner width "Map sources — Lake" (≈105px) and the switch
     (≈150px) fit with room, and stacking them cost 86px of the panel's height in a block that
     now sits ABOVE the source list. Measured after: 57px. `min-width: 0` + ellipsis is the
     honest guard — a longer map kind truncates its title rather than re-wrapping the row. */
  #mx-mappanel .msp-head { flex: 0 0 auto; padding: 8px 14px 6px; display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; }
  #mx-mappanel .msp-title {
    font-size: 11px; font-weight: 600; color: var(--s-muted);
    flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  #mx-mappanel .msp-title b { color: var(--s-text); }
  #mx-mappanel .msp-next { flex: 0 0 100%; }
  #mx-mappanel .msp-modes {
    display: flex; gap: 2px; margin-left: auto; width: max-content; flex: 0 0 auto;
    background: var(--s-well); border: 1px solid var(--s-line); border-radius: 8px; padding: 2px;
  }
  #mx-mappanel .msp-mode {
    display: flex; align-items: center; gap: 5px; padding: 4px 9px; border: 0; border-radius: 6px;
    background: transparent; color: var(--s-muted); cursor: pointer;
    font: 600 10.5px 'Montserrat', system-ui, sans-serif;
  }
  #mx-mappanel .msp-mode svg { width: 11px; height: 11px; flex: 0 0 auto; }
  #mx-mappanel .msp-mode:hover { color: var(--s-text); }
  #mx-mappanel .msp-mode.on { background: var(--s-surface); color: var(--s-dark); box-shadow: 0 1px 2px rgba(18,32,26,.10); }

  /* ---- the push track: two permanently-mounted panes, one transform ---- */
  #mx-srcpanel .msp-track { flex: 1 1 auto; position: relative; overflow: hidden; min-height: 0; }
  #mx-srcpanel[data-mode="expanded"] .msp-track { display: none; }
  #mx-srcpanel .msp-slider {
    position: absolute; inset: 0; display: flex; width: 200%;
    transition: transform .22s cubic-bezier(.4, 0, .2, 1);
  }
  /* The OFF-SCREEN pane must be inert, not merely pushed out of sight. A
     focusable control out there is still in the tab order, and the browser
     scrolls the track sideways to reveal whatever gains focus — which shifts
     the whole panel and never comes back (the push is a transform, so nothing
     resets it). `visibility:hidden` takes the pane out of the tab order and out
     of hit-testing; the .22s delay — exactly the push duration — keeps the
     OUTGOING pane painted for the whole slide, so the detail never blanks
     mid-animation. */
  #mx-srcpanel .msp-pane {
    width: 50%; flex: 0 0 50%; overflow-y: auto; box-sizing: border-box;
    visibility: hidden; transition: visibility 0s linear .22s;
  }
  #mx-srcpanel .msp-pane.on { visibility: visible; transition-delay: 0s; }
  #mx-srcpanel .msp-list { padding: 8px 14px 16px; }
  #mx-srcpanel .msp-detail { padding: 12px 14px 20px; }

  /* ---- compact row ---- */
  #mx-srcpanel .msp-rows { display: flex; flex-direction: column; gap: 4px; }
  #mx-srcpanel .msp-row {
    position: relative; display: flex; align-items: center; gap: 10px; padding: 8px 10px;
    border: 1px solid var(--s-line); border-radius: 9px; background: var(--s-row);
  }
  #mx-srcpanel .msp-row:hover { border-color: var(--s-line-hi); }
  /* the whole row drills in; the switch sits above it so toggling never navigates */
  #mx-srcpanel .msp-open {
    position: absolute; inset: 0; z-index: 0; border: 0; padding: 0; margin: 0;
    background: transparent; border-radius: 9px; cursor: pointer;
  }
  #mx-srcpanel .msp-open:focus-visible { outline: 2px solid var(--s-brand); outline-offset: -2px; }
  #mx-srcpanel .msp-sr {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  #mx-mappanel .msp-dot { width: 9px; height: 9px; border-radius: 3px; flex: 0 0 auto; }
  /* ASK B — the dot becomes a BUTTON (the whole category's cut colour). The .msp-dot node itself
     is untouched, so the row anatomy, its inline hex and the dimmed off-state all still read the
     same; this is only the hit area around it. z-index above .msp-open, exactly like .msp-sw, so
     opening the bar can never navigate. It must not change the 47px row: no extra height, the
     padding is cancelled by an equal negative margin. */
  #mx-mappanel .msp-dotbtn {
    position: relative; z-index: 1; flex: 0 0 auto; display: flex; align-items: center;
    padding: 4px; margin: -4px; border: 0; background: transparent; border-radius: 5px; cursor: pointer;
  }
  #mx-mappanel .msp-dotbtn:hover { background: var(--s-well); }
  #mx-mappanel .msp-dotbtn[aria-expanded="true"] { background: var(--s-tint); }
  #mx-mappanel .msp-dotbtn:focus-visible { outline: 2px solid var(--s-brand); outline-offset: 1px; }
  #mx-srcpanel .msp-txt { flex: 1 1 auto; min-width: 0; pointer-events: none; }
  #mx-srcpanel .msp-nm { display: block; font-size: 12px; font-weight: 600; color: var(--s-text); }
  #mx-srcpanel .msp-st {
    display: block; margin-top: 1px; font-size: 10px; font-weight: 500; color: var(--s-faint);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  #mx-srcpanel .msp-chev { width: 11px; height: 11px; flex: 0 0 auto; color: var(--s-faint); pointer-events: none; }
  /* OFF: the dot dims and the name mutes — the switch itself stays fully live */
  #mx-srcpanel .msp-row.off .msp-dot, #mx-srcpanel .msp-sec.off .msp-dot { opacity: .35; }
  #mx-srcpanel .msp-row.off .msp-nm, #mx-srcpanel .msp-sec.off .msp-secnm { color: var(--s-muted); }
  #mx-srcpanel .msp-dead { opacity: .55; }
  #mx-srcpanel .msp-dead .msp-open { cursor: not-allowed; }

  /* ---- the switch (identical in row, detail head and flat header) ---- */
  #mx-srcpanel .msp-sw {
    position: relative; z-index: 1; width: 30px; height: 17px; flex: 0 0 auto; box-sizing: border-box;
    padding: 2px; border: 0; border-radius: 999px; background: var(--s-sw-off); cursor: pointer;
    transition: background .15s;
  }
  #mx-srcpanel .msp-sw[aria-checked="true"] { background: var(--s-brand); }
  #mx-srcpanel .msp-sw:focus-visible { outline: 2px solid var(--s-brand); outline-offset: 2px; }
  #mx-srcpanel .msp-knob {
    display: block; width: 13px; height: 13px; border-radius: 50%; background: #fff;
    transition: transform .15s; transform: translateX(0);
  }
  #mx-srcpanel .msp-sw[aria-checked="true"] .msp-knob { transform: translateX(13px); }

  /* ---- list furniture ---- */
  #mx-srcpanel .msp-add {
    display: flex; align-items: center; gap: 7px; width: 100%; box-sizing: border-box;
    margin-top: 10px; padding: 9px 10px; border: 1px dashed var(--s-line);
    border-radius: 9px; background: transparent; color: var(--s-muted);
    font: 600 12px 'Montserrat', system-ui, sans-serif; text-align: left; cursor: not-allowed;
  }
  #mx-srcpanel .msp-add svg { width: 12px; height: 12px; }
  #mx-srcpanel .msp-foot { margin-top: 14px; font: 500 10.5px/1.55 'Montserrat', system-ui, sans-serif; color: var(--s-faint); }

  /* ---- detail pane ---- */
  #mx-srcpanel .msp-back {
    display: inline-flex; align-items: center; gap: 6px; margin-bottom: 14px; padding: 0;
    border: 0; background: transparent; color: var(--s-muted); cursor: pointer;
    font: 600 11px 'Montserrat', system-ui, sans-serif;
  }
  #mx-srcpanel .msp-back:hover { color: var(--s-text); }
  /* the drill-in moves focus here, so it is the ring keyboard users see most —
     match the panel's own ring instead of the UA blue */
  #mx-srcpanel .msp-back:focus-visible { outline: 2px solid var(--s-brand); outline-offset: 3px; border-radius: 4px; }
  #mx-srcpanel .msp-back svg { width: 11px; height: 11px; }
  #mx-srcpanel .msp-dhead { display: flex; align-items: center; gap: 10px; }
  #mx-srcpanel .msp-dhead .msp-dot { width: 10px; height: 10px; }
  #mx-srcpanel .msp-dnm { flex: 1 1 auto; font-size: 13px; font-weight: 700; color: var(--s-text); }
  #mx-srcpanel .msp-desc { margin: 7px 0 16px; font: 500 10.5px/1.5 'Montserrat', system-ui, sans-serif; color: var(--s-faint); }
  #mx-srcpanel .msp-warn {
    margin: 8px 0 0; padding: 7px 9px; border: 1px solid var(--s-line); border-radius: 8px;
    background: var(--s-well); font: 500 10.5px/1.45 'Montserrat', system-ui, sans-serif; color: var(--s-muted);
  }
  /* OFF body — the FLAT prototype's semantic, applied in BOTH layouts so the
     off-state means one thing: the source's own settings are unreachable until
     it is switched back on. The inputs also carry `disabled`, which keeps them
     out of the tab order that pointer-events alone would leave reachable. */
  #mx-srcpanel .msp-body, #mx-srcpanel .msp-secbody { transition: opacity .15s; }
  #mx-srcpanel .msp-body.off, #mx-srcpanel .msp-secbody.off { opacity: .35; pointer-events: none; }
  #mx-srcpanel .msp-cap {
    margin-bottom: 7px; font: 700 10px 'Montserrat', system-ui, sans-serif;
    letter-spacing: 1.1px; color: var(--s-muted);
  }
  #mx-srcpanel .msp-feats { display: flex; flex-direction: column; gap: 2px; margin-bottom: 16px; }
  #mx-srcpanel .msp-feat { display: flex; align-items: center; gap: 9px; padding: 7px 8px; border-radius: 8px; cursor: pointer; }
  #mx-srcpanel .msp-feat:hover { background: var(--s-well); }
  #mx-srcpanel .msp-feat input, #mx-srcpanel .msp-gfeat input {
    width: 14px; height: 14px; margin: 0; flex: 0 0 auto; accent-color: var(--s-brand); cursor: pointer;
  }
  #mx-srcpanel .msp-featnm { flex: 1 1 auto; font-size: 12px; font-weight: 600; color: var(--s-text); }
  #mx-srcpanel .msp-soon {
    flex: 0 0 auto; font: 700 8.5px 'Montserrat', system-ui, sans-serif; letter-spacing: .08em;
    text-transform: uppercase; color: var(--s-faint);
  }
  #mx-srcpanel label.msp-dead { cursor: not-allowed; }
  #mx-srcpanel label.msp-dead .msp-featnm { color: var(--s-faint); }

  /* levels — chips (roads' source + detail), the real option list, or a COUNT */
  #mx-srcpanel .msp-lvrow { display: flex; gap: 4px; margin-bottom: 16px; }
  #mx-srcpanel .msp-lv {
    flex: 1 1 0; min-width: 0; padding: 6px 3px; border: 1px solid var(--s-line); border-radius: 7px;
    background: transparent; color: var(--s-muted); cursor: pointer; text-align: center;
    font: 600 11px/1.25 'Montserrat', system-ui, sans-serif; overflow-wrap: anywhere;
  }
  #mx-srcpanel .msp-lv.on { border-color: rgba(95, 141, 116, .5); background: rgba(95, 141, 116, .14); color: var(--s-dark); }
  #mx-srcpanel .msp-lvsel {
    width: 100%; box-sizing: border-box; margin-bottom: 16px; padding: 6px 8px;
    border: 1px solid var(--s-line); border-radius: 7px; background: var(--s-well);
    color: var(--s-text); font: 600 11px 'Montserrat', system-ui, sans-serif;
  }
  /* a COUNT (the bathy/topo layer ceilings). The number rides beside the track
     because the number IS the point of the control — and it is the one readout
     that keeps updating mid-drag while the panel rebuild is held back. */
  #mx-srcpanel .msp-rng { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; }
  #mx-srcpanel .msp-rng input[type="range"] {
    flex: 1 1 auto; min-width: 0; margin: 0; accent-color: var(--s-brand); cursor: pointer;
  }
  #mx-srcpanel .msp-rng input[type="range"][disabled] { cursor: not-allowed; }
  #mx-srcpanel .msp-rngval {
    flex: 0 0 auto; min-width: 18px; text-align: right; color: var(--s-text);
    font: 700 11px/1 'Montserrat', system-ui, sans-serif; font-variant-numeric: tabular-nums;
  }
  /* the caveat under a control — the engine's own live #road-source-note, or a
     standing one. COMPACT only: Expanded is a single 52px-label row with no room
     for prose, so there the same text is the control's title. */
  #mx-srcpanel .msp-lvnote {
    margin: -12px 0 16px; font: 500 10px/1.5 'Montserrat', system-ui, sans-serif; color: var(--s-faint);
  }

  /* feeds layer */
  #mx-srcpanel .msp-feed {
    display: flex; align-items: center; gap: 8px; margin-bottom: 4px; padding: 8px 10px;
    border: 1px solid var(--s-line); border-radius: 9px; background: var(--s-row);
  }
  #mx-srcpanel .msp-feednm { flex: 1 1 auto; font-size: 12px; font-weight: 600; color: var(--s-text); }
  #mx-srcpanel .msp-feedop {
    font: 700 9px 'Montserrat', system-ui, sans-serif; letter-spacing: 1px; color: var(--s-muted);
  }
  #mx-srcpanel .msp-note { margin-top: 8px; font: 500 10.5px/1.55 'Montserrat', system-ui, sans-serif; color: var(--s-faint); }
  #mx-srcpanel .msp-more {
    display: flex; align-items: center; gap: 6px; width: 100%; box-sizing: border-box;
    margin-top: 16px; padding: 9px 10px; border: 1px solid var(--s-line); border-radius: 9px;
    background: var(--s-well); color: var(--s-text); cursor: pointer; text-align: left;
    font: 600 12px 'Montserrat', system-ui, sans-serif;
  }
  #mx-srcpanel .msp-more .msp-chev { margin-left: auto; }
  #mx-srcpanel .msp-more:hover:not([disabled]) { border-color: var(--s-brand); color: var(--s-dark); }
  #mx-srcpanel .msp-more[disabled] { cursor: not-allowed; color: var(--s-faint); }

  /* ---- EXPANDED (flat) ---- */
  #mx-srcpanel .msp-flat { display: none; flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 2px 14px 20px; }
  #mx-srcpanel[data-mode="expanded"] .msp-flat { display: block; }
  #mx-srcpanel .msp-sec { padding: 10px 0 11px; border-bottom: 1px solid var(--s-hair); }
  #mx-srcpanel .msp-sechd { display: flex; align-items: center; gap: 9px; }
  #mx-srcpanel .msp-secnm { flex: 1 1 auto; font-size: 12px; font-weight: 700; color: var(--s-text); }
  #mx-srcpanel .msp-secmore {
    width: 20px; height: 20px; flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 6px; background: transparent; color: var(--s-faint); cursor: pointer; padding: 0;
  }
  #mx-srcpanel .msp-secmore:hover:not([disabled]) { background: var(--s-well); color: var(--s-text); }
  #mx-srcpanel .msp-secmore[disabled] { cursor: not-allowed; opacity: .45; }
  #mx-srcpanel .msp-secst { margin-top: 5px; font: 500 10.5px 'Montserrat', system-ui, sans-serif; color: var(--s-faint); }
  #mx-srcpanel .msp-secbody { margin-top: 7px; }
  /* The prototype's fixed 2-column grid assumed 2-word labels ("Rivers",
     "Streams"). MapX's real ones run to "Tint depth fills (preview)", which a
     nowrap column silently ate — worst case only two thirds of the label was
     legible, and a half-read checkbox label is exactly the honesty problem §8
     is about. Columns now collapse to ONE when the panel is too narrow for two
     readable ones, and anything still too long wraps instead of being clipped. */
  #mx-srcpanel .msp-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(124px, 1fr)); gap: 0 8px; }
  #mx-srcpanel .msp-gfeat { display: flex; align-items: center; gap: 7px; padding: 3px 1px; cursor: pointer; min-width: 0; }
  #mx-srcpanel .msp-gfeat .msp-featnm {
    font-size: 11px; line-height: 1.35; white-space: normal; overflow-wrap: anywhere;
  }
  #mx-srcpanel .msp-lvline { display: flex; align-items: center; gap: 6px; margin-top: 6px; }
  #mx-srcpanel .msp-lvline .msp-lvrow { flex: 1 1 auto; margin-bottom: 0; }
  #mx-srcpanel .msp-lvline .msp-lv { padding: 3px 2px; border-radius: 6px; font-size: 10px; }
  #mx-srcpanel .msp-lvline .msp-lvsel { margin-bottom: 0; padding: 3px 6px; font-size: 10px; }
  #mx-srcpanel .msp-lvline .msp-rng { flex: 1 1 auto; min-width: 0; margin-bottom: 0; gap: 6px; }
  #mx-srcpanel .msp-lvline .msp-rngval { font-size: 10px; }
  #mx-srcpanel .msp-lvlab {
    display: block; flex: 0 0 52px; width: 52px;
    font: 600 10px 'Montserrat', system-ui, sans-serif; color: var(--s-muted);
  }
  #mx-srcpanel .msp-feedline { margin-top: 7px; font: 600 10px/1.5 'Montserrat', system-ui, sans-serif; color: var(--s-faint); }
}

/* the ‹ back-to-rows affordance in the panel head (shown by the rule above; never on phones) */
#mx-mappanel .mp-back {
  display: none; width: 26px; height: 26px; border: 0; background: transparent; border-radius: 7px;
  align-items: center; justify-content: center; color: var(--s-muted); cursor: pointer; padding: 0; flex: 0 0 auto;
}
#mx-mappanel .mp-back:hover { background: var(--s-well); color: var(--s-text); }
#mx-mappanel .mp-back svg { width: 13px; height: 13px; }
/* collapse leaves a slim docked reopen strip — the inspector's exact pattern, mirrored left */
#mx-map-reopen { display: none; }
@media (min-width: 761px) {
  body.mx-studio.mx-map-closed #mx-map-reopen {
    display: flex; flex: 0 0 26px; width: 26px; box-sizing: border-box;
    flex-direction: column; align-items: center; justify-content: center; gap: 7px;
    border: 0; border-right: 1px solid var(--s-line); background: var(--s-surface);
    color: var(--s-muted); cursor: pointer; padding: 0;
  }
  body.mx-studio.mx-map-closed #mx-map-reopen:hover { background: var(--s-well); color: var(--s-text); }
  body.mx-studio.mx-map-closed #mx-map-reopen svg { width: 13px; height: 13px; flex: 0 0 auto; }
  body.mx-studio.mx-map-closed #mx-map-reopen span {
    writing-mode: vertical-rl; font: 600 10px 'Montserrat', system-ui, sans-serif; letter-spacing: .08em;
  }
}
@media (max-width: 760px) { #mx-map-reopen { display: none !important; } }

/* the reparented #free-controls becomes the flush panel body */
body.mx-studio #free-controls {
  width: auto; min-width: 0; flex: 1 1 auto;
  border: 0; border-radius: 0; box-shadow: none; margin: 0;
  max-height: none; background: var(--s-surface);
  padding: 12px 14px 18px; overflow-y: auto;
}

/* ============ REQUIREMENT B — canvas COLUMN (#mx-canvascol) ============
   The canvas region of the handoff is a COLUMN, not a single pane: the artboard on top, then the
   production strip + status bar underneath — all inside the canvas' own width, so the tool rail,
   the content panel and the inspector run the full row height beside them (measured on the DC:
   the strip starts at the canvas column's left edge and tracks it when a panel collapses).
   Built by js/laser-studio.js buildCanvasColumn(), which re-parents the existing #free-preview
   into it. Every colour here is a chrome token, so both themes get it for free. */
body.mx-studio #mx-canvascol {
  flex: 1 1 auto; min-width: 0; min-height: 0;
  display: flex; flex-direction: column; background: var(--s-bg);
}

/* ============ Canvas (reparented #free-preview, flush) ============ */
body.mx-studio #free-preview {
  flex: 1 1 auto; min-width: 0; min-height: 0; padding: 0 !important; background: var(--s-bg);
  transition: none; position: relative;   /* anchors the floating tool cluster + bottom toolbar */
}
body.mx-studio.mx-tp-open #free-preview,
body.mx-studio #free-preview.mx-tp-open { padding-right: 0 !important; }

/* ============ In-place text editor "Done" commit button (mobile only) ============ */
/* Touch has no Esc / reliable click-away / blur, so a clear commit affordance is needed.
   Desktop keeps Esc / click-away / blur, so this button stays hidden there. Sits just below
   the top bar (52px) + contextual bar (40px) so it never covers the text toolbar. */
.mx-inline-done { position: fixed; top: 100px; right: 12px; z-index: 80; display: none;
  background: var(--s-brand); color: #fff; border: none; border-radius: 999px;
  padding: 11px 22px; font-size: 15px; font-weight: 700; letter-spacing: .01em;
  box-shadow: 0 6px 18px rgba(24, 40, 32, .34); cursor: pointer; -webkit-tap-highlight-color: transparent; }
.mx-inline-done:active { transform: scale(.95); }
@media (max-width: 760px) { .mx-inline-done.show { display: block; top: calc(100px + env(safe-area-inset-top)); } }

/* ============ Right inspector (fixed --s-insp-w, 292px per the handoff) ============ */
#mx-inspector {
  width: var(--s-insp-w); flex: 0 0 var(--s-insp-w); box-sizing: border-box;
  background: var(--s-surface); border-left: 1px solid var(--s-line);
  display: flex; flex-direction: column; min-height: 0; z-index: 5;
}
/* ── Increment 4: auto-collapse the right inspector when nothing is selected ──────────────────
   The inspector only does something with a selection (or when the owner opens Layers/Laser). With
   nothing selected it collapses to a slim reveal tab and hands its 292px to the canvas — which is
   what keeps the artboard usable at tablet / small-laptop widths. Selecting an object, clicking the
   reveal, or opening Layers brings it straight back. Purely additive: the panel and every control
   in it are untouched when shown. Respects reduced-motion. */
body.mx-ins-empty #mx-inspector { width: 0 !important; flex-basis: 0 !important; border-left: 0 !important; overflow: hidden !important; }
#mx-ins-reveal { display: none; }
body.mx-ins-empty #mx-ins-reveal {
  display: flex; position: fixed; top: 50%; right: 0; transform: translateY(-50%);
  align-items: center; gap: 8px; writing-mode: vertical-rl;
  background: var(--s-surface, #fff); border: 1px solid var(--s-line, #e4ded2); border-right: 0;
  border-radius: 10px 0 0 10px; box-shadow: 0 4px 16px rgba(16,24,40,.12);
  padding: 13px 8px; font: 600 12px Montserrat, system-ui, sans-serif; color: var(--s-muted, #6b6557);
  cursor: pointer; z-index: 40;
}
body.mx-ins-empty #mx-ins-reveal:hover { color: var(--s-dark, #2b2820); background: var(--s-tint, #eef1f4); }
#mx-ins-reveal:focus-visible { outline: 2px solid var(--s-accent, #0c6457); outline-offset: 2px; }
.mx-ins-collapse-btn { border: 0; background: transparent; cursor: pointer; color: var(--s-muted, #6b6557); font-size: 15px; line-height: 1; padding: 2px 7px; border-radius: 6px; }
.mx-ins-collapse-btn:hover { background: var(--s-tint, #eef1f4); color: var(--s-dark, #2b2820); }
.mx-ins-collapse-btn:focus-visible { outline: 2px solid var(--s-accent, #0c6457); outline-offset: 2px; }
#mx-inspector .insp-tabs { display: flex; gap: 4px; padding: 10px 12px 8px; border-bottom: 1px solid var(--s-line); flex: 0 0 auto; }
#mx-inspector .insp-tab {
  flex: 1; height: 28px; display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 7px; background: transparent; cursor: pointer;
  font-size: 11.5px; font-weight: 600; color: var(--s-muted); font-family: inherit;
}
#mx-inspector .insp-tab.on { background: var(--s-tint); color: var(--s-dark); }
#mx-inspector .insp-body { flex: 1; overflow-y: auto; min-height: 0; }
#mx-inspector .insp-pane { display: none; padding: 12px 14px 18px; }
/* ONE scrollbar in the inspector, not two. #text-popup is authored as a floating popup with
   its own overflow, and the shell REPARENTS it into the inspector — so the text properties
   scrolled inside .insp-body, which was already scrolling. Two bars, side by side, on the
   maker's own editor (owner, 2026-08-25: "see the double scroll bar? its on desktop on our
   admin side"). .insp-body is the scroller; everything inside it grows. */
#mx-inspector #text-popup,
#mx-inspector .insp-pane { overflow: visible; max-height: none; }
#mx-inspector[data-tab="layers"] .insp-pane.pane-layers,
#mx-inspector[data-tab="laser"]  .insp-pane.pane-laser { display: block; }
/* Object pane hosts the reparented #text-popup — control its visibility via the pane wrapper */
#mx-inspector[data-tab="object"] .insp-pane.pane-object { display: block; }
#mx-inspector .insp-foot { flex: 0 0 auto; padding: 8px 14px; border-top: 1px solid var(--s-well); font-size: 9.5px; color: #c9cdca; text-align: center; }

/* neutralize the docked #text-popup so it flows inside the Object pane */
body.mx-studio #text-popup {
  position: static; inset: auto; top: auto; right: auto; left: auto; z-index: auto;
  width: auto; max-width: none; box-shadow: none; border: 0; border-radius: 0;
  padding: 0; background: transparent; display: flex;
  flex-direction: column; gap: 13px; align-items: stretch;   /* stack sections vertically (engines w/o the inline style) */
}
body.mx-studio #mx-inspector[data-tab="object"] #text-popup { display: flex; }
body.mx-studio #mx-inspector:not([data-tab="object"]) .insp-pane.pane-object { display: none; }

/* ---------- REQUIREMENT B — OBJECT ACTIONS (the floating TEXT pane's properties) ----------
   Built by js/laser-studio.js buildObjectActions() immediately AFTER #text-popup, so the block
   follows the engine's own show/hide with a sibling selector instead of a second observer:
   visible for a text / marker / icon selection, and for a SHAPE selection too, because
   #mx-combine lives here and Combine is a shape operation. The four per-item rows (.oa-item)
   stand down for shapes — they write LabBridge.selected(), which a shape never is. */
#mx-inspector .pane-objacts { display: none; margin-top: 14px; padding-top: 13px; border-top: 1px solid var(--s-well); }
#mx-inspector[data-tab="object"] #text-popup.show ~ .pane-objacts,
#mx-inspector[data-tab="object"].shape-sel .pane-objacts { display: block; }
#mx-inspector.shape-sel .pane-objacts .oa-item { display: none; }
#mx-inspector .pane-objacts .oa-row { display: flex; align-items: center; gap: 6px; margin-top: 8px; }
#mx-inspector .pane-objacts .oa-btn {
  flex: 1 1 0; min-width: 0; height: 30px; box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  border: 1px solid var(--s-line); border-radius: 8px; background: var(--s-surface);
  font: 600 11.5px 'Montserrat', system-ui, sans-serif; color: var(--s-text); cursor: pointer;
}
#mx-inspector .pane-objacts .oa-btn:hover { background: var(--s-well); }
#mx-inspector .pane-objacts .oa-btn.on { background: var(--s-tint); color: var(--s-dark); border-color: var(--s-brand); }
#mx-inspector .pane-objacts .oa-btn svg { width: 14px; height: 14px; flex: 0 0 14px; }
#mx-inspector .pane-objacts .oa-line { display: flex; align-items: center; gap: 8px; margin-top: 9px; }
#mx-inspector .pane-objacts .oa-lab { flex: 0 0 92px; font-size: 11.5px; color: var(--s-muted); }
#mx-inspector .pane-objacts .oa-lab i { font-style: normal; color: var(--s-faint); font-size: 10px; display: block; }
#mx-inspector .pane-objacts .oa-opacity { flex: 1 1 auto; min-width: 0; }
#mx-inspector .pane-objacts #mx-combine.oa-combine {
  display: flex; align-items: center; justify-content: center; gap: 6px; width: 100%;
  height: 30px; box-sizing: border-box; margin-top: 12px;
  border: 1px solid var(--s-line); border-radius: 8px; background: var(--s-surface);
  font: 600 11.5px 'Montserrat', system-ui, sans-serif; color: var(--s-text); cursor: pointer;
}
#mx-inspector .pane-objacts #mx-combine.oa-combine:hover { background: var(--s-well); }
#mx-inspector .pane-objacts #mx-combine.oa-combine svg { width: 14px; height: 14px; flex: 0 0 14px; }

/* Layers tab rows */
#mx-inspector .lyr-row {
  display: flex; align-items: center; gap: 9px; min-height: 36px; padding: 0 8px;
  border-radius: 8px; cursor: pointer;
}
#mx-inspector .lyr-row:hover { background: #f7faf9; }
#mx-inspector .lyr-row.on { background: var(--s-tint); }
#mx-inspector .lyr-sw { width: 9px; height: 9px; border-radius: 2px; flex: 0 0 9px; }
#mx-inspector .lyr-name { flex: 1; min-width: 0; font-size: 12px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#mx-inspector .lyr-kind { font-size: 10px; color: var(--s-faint); flex: 0 0 auto; }
#mx-inspector .lyr-maprow { display: flex; align-items: center; gap: 9px; min-height: 38px; padding: 0 8px; border-top: 1px solid var(--s-well); margin-top: 4px; color: var(--s-muted); }
#mx-inspector .lyr-maprow svg { width: 14px; height: 14px; flex: 0 0 auto; color: var(--s-faint); }
#mx-inspector .lyr-empty,
#mx-inspector .laser-note { text-align: center; padding: 26px 10px; color: var(--s-faint); font-size: 11.5px; line-height: 1.6; }

/* ---- Layers pane: laser layers + what the map actually drew -------------
   EVERY colour here is CHROME and comes from a --s-* token, so the dark theme
   re-points it for free. The one thing that is DATA — .lyr-sw's background, a
   laser layer's colour / the LightBurn hex a map layer cuts on — is written
   inline by js/laser-studio.js and is never selected here, in studio-dark.css,
   or anywhere else. It must read identically in both themes: it is the colour
   that ends up in the cut file. */
/* five cells in a 292px column: the row is tight on purpose so the NAME keeps as
   much of the width as possible before it has to ellipsis. */
#mx-inspector .lyr-row { gap: 5px; }
/* the row label inherited the document's ink, which the dark theme never re-points —
   black-on-charcoal. Bind it to the panel's own token so it reads in both. */
#mx-inspector .lyr-name { color: var(--s-text); font-size: 11.5px; }
#mx-inspector .lyr-sec {
  font-size: 9.5px; font-weight: 800; letter-spacing: .09em; text-transform: uppercase;
  color: var(--s-faint); padding: 14px 8px 5px; border-top: 1px solid var(--s-line); margin-top: 8px;
}
#mx-inspector .insp-pane.pane-layers > .lyr-sec:first-child { border-top: 0; margin-top: 0; padding-top: 4px; }
/* the colour chip is DATA — give it a chrome-coloured hairline so a black or
   white layer colour still reads as a chip against either theme's surface */
#mx-inspector .lyr-sw { box-shadow: 0 0 0 1px var(--s-line) inset, 0 0 0 1px rgba(128, 128, 128, .35); }
#mx-inspector .lyr-num { font-size: 8.5px; font-weight: 800; color: var(--s-faint); margin-right: 4px; letter-spacing: .02em; }
#mx-inspector .lyr-nm { overflow: hidden; text-overflow: ellipsis; }
#mx-inspector .lyr-count { flex: 0 0 auto; font-size: 9.5px; font-weight: 700; color: var(--s-muted); background: var(--s-well); border-radius: 999px; padding: 1px 5px; margin-left: 5px; }
#mx-inspector .lyr-badge {
  flex: 0 0 auto; font-size: 8px; font-weight: 800; letter-spacing: .03em;
  color: var(--s-muted); background: var(--s-well); border: 1px solid var(--s-line);
  border-radius: 4px; padding: 2px 4px;
}
#mx-inspector .lyr-badge.lyr-code { font-variant-numeric: tabular-nums; letter-spacing: .02em; }
#mx-inspector .lyr-eye, #mx-inspector .lyr-lock {
  flex: 0 0 auto; width: 18px; height: 18px; padding: 0; display: flex; align-items: center; justify-content: center;
  background: transparent; border: 0; border-radius: 5px; color: var(--s-muted); cursor: pointer;
}
#mx-inspector .lyr-eye svg, #mx-inspector .lyr-lock svg { width: 14px; height: 14px; }
#mx-inspector .lyr-eye:hover:not([disabled]), #mx-inspector .lyr-lock:hover:not([disabled]) { background: var(--s-well); color: var(--s-text); }
#mx-inspector .lyr-lock.on { color: var(--s-dark); background: var(--s-tint); }
/* §8: an affordance with no real handler is visibly dead, never decorative */
#mx-inspector .lyr-eye[data-off], #mx-inspector .lyr-off { opacity: .38; cursor: not-allowed; }
#mx-inspector .lyr-off { text-decoration: line-through; }
#mx-inspector .lyr-row.off .lyr-name, #mx-inspector .lyr-row.off .lyr-sw { opacity: .45; }
#mx-inspector .lyr-srow { cursor: default; }
#mx-inspector .lyr-acts { display: flex; gap: 6px; padding: 8px 8px 0; }
#mx-inspector .lyr-btn {
  flex: 1; min-width: 0; height: 28px; border: 1px solid var(--s-line); border-radius: 7px;
  background: var(--s-surface); color: var(--s-muted); font: inherit; font-size: 10.5px; font-weight: 700;
  cursor: pointer; padding: 0 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#mx-inspector .lyr-btn:hover:not([disabled]) { background: var(--s-tint); border-color: var(--s-brand); color: var(--s-dark); }
#mx-inspector .lyr-btn[disabled] { opacity: .45; cursor: not-allowed; }
#mx-inspector .lyr-help { padding: 8px 9px 2px; color: var(--s-faint); font-size: 10px; line-height: 1.55; }

/* ---- per-layer cut colour ------------------------------------------------
   Read the standing rule above before touching anything here: the .lyr-sw chip
   and every .lyr-pal-cell carry a LightBurn hex written INLINE by
   js/laser-studio.js. That hex is the colour that ends up in the cut file, so
   NOTHING in this block (or in studio-dark.css) may set `background` on either
   of them — only chrome around them. */
/* the sheet chip becomes a button; the chip itself is unchanged, so the row's
   cell order (eye · .lyr-sw · name · .lyr-badge) still reads the same */
#mx-inspector .lyr-swbtn {
  flex: 0 0 auto; display: flex; align-items: center; padding: 3px; margin: -3px;
  border: 0; background: transparent; border-radius: 5px; cursor: pointer;
}
#mx-inspector .lyr-swbtn:hover { background: var(--s-well); }
#mx-inspector button.lyr-code { font-family: inherit; cursor: pointer; }
#mx-inspector button.lyr-code:hover { border-color: var(--s-brand); color: var(--s-dark); background: var(--s-tint); }
#mx-inspector .lyr-swbtn[aria-expanded="true"], #mx-inspector button.lyr-code[aria-expanded="true"] { background: var(--s-tint); }
/* the 32 LightBurn slots, 8 across — the laser's own layer bar, not a dropdown */
/* ASK B — the SAME bar opens from the source panel's left dot, so these selectors carry both
   hosts rather than a second set of rules being written for it. Only the chrome is shared: the
   cell backgrounds are still the inline LightBurn hex, and the rule above still applies — nothing
   here or in studio-dark.css may set `background` on a .lyr-pal-cell. */
#mx-inspector .lyr-pal, #mx-mappanel .msp-cpal { padding: 8px; margin: 1px 0 5px; background: var(--s-well); border-radius: 9px; }
#mx-inspector .lyr-pal-head, #mx-mappanel .lyr-pal-head { font-size: 9.5px; font-weight: 700; color: var(--s-muted); padding: 0 1px 6px; }
#mx-inspector .lyr-pal-grid, #mx-mappanel .lyr-pal-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px; }
#mx-inspector .lyr-pal-cell, #mx-mappanel .lyr-pal-cell {
  height: 20px; padding: 0; cursor: pointer; border: 0; border-radius: 4px;
  box-shadow: 0 0 0 1px rgba(128, 128, 128, .45) inset;
}
#mx-inspector .lyr-pal-cell.on, #mx-mappanel .lyr-pal-cell.on { box-shadow: 0 0 0 1px rgba(128, 128, 128, .45) inset, 0 0 0 2px var(--s-brand); }
#mx-inspector .lyr-pal-reset, #mx-mappanel .lyr-pal-reset { width: 100%; margin-top: 8px; }

/* ---- the DRAWN-colour bar (js/laser-studio.js dcHtml) ----
   A sibling of the LightBurn-layer grid above, deliberately shaped differently: that one is 32
   fixed machine slots, this one is the map's own colour space and has no slots at all. Keeping
   them visually distinct is what stops "the coloured square" and "the C-code" reading as the
   same control again. */
#mx-inspector .lyr-dc-modes { display: flex; gap: 5px; margin-bottom: 7px; }
#mx-inspector .lyr-dc-modes .lyr-btn { flex: 1 1 0; min-width: 0; }
#mx-inspector .lyr-dc-modes .lyr-btn.on { background: var(--s-tint); border-color: var(--s-brand); color: var(--s-dark); }
#mx-inspector .lyr-dc-rows { display: flex; flex-direction: column; gap: 4px; }
#mx-inspector .lyr-dc-row { display: flex; align-items: center; gap: 8px; cursor: pointer; }
#mx-inspector .lyr-dc-row input[type="color"] {
  flex: 0 0 auto; width: 26px; height: 20px; padding: 0; border: 1px solid var(--s-line);
  border-radius: 4px; background: none; cursor: pointer;
}
#mx-inspector .lyr-dc-row span {
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 11px; color: var(--s-text);
}
/* the READ-ONLY chip: a row whose colour is fixed in the renderer, or lives on the object.
   Marked rather than merely inert — a plain square that does nothing is indistinguishable from
   a broken one. */
#mx-inspector .lyr-sw-ro { display: flex; align-items: center; justify-content: center; }
#mx-inspector .lyr-sw-ro i {
  font-style: normal; font-size: 8px; line-height: 1; color: #fff;
  mix-blend-mode: difference; opacity: .85;
}
/* NO COLOUR TO SHOW — hatched, and deliberately not a colour. A row whose colour lives on the
   OBJECT has no single colour, and a fixed colour this panel cannot resolve (url(#…),
   currentColor, a named colour) is not one either. Borrowing the LightBurn palette hex for these
   — what the shipped build did — made the square claim a colour the map is not drawn in, under a
   banner that says every square IS the drawn colour. */
#mx-inspector .lyr-sw-none {
  background: repeating-linear-gradient(45deg, var(--s-line) 0 2px, transparent 2px 4px);
}
#mx-inspector .lyr-sw-none i { mix-blend-mode: normal; color: var(--s-muted); opacity: .9; }
#mx-mappanel .lyr-btn {
  display: block; width: 100%; min-width: 0; height: 28px; border: 1px solid var(--s-line); border-radius: 7px;
  background: var(--s-surface); color: var(--s-muted); font: inherit; font-size: 10.5px; font-weight: 700;
  cursor: pointer; padding: 0 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#mx-mappanel .lyr-btn:hover:not([disabled]) { background: var(--s-tint); border-color: var(--s-brand); color: var(--s-dark); }
#mx-mappanel .lyr-btn[disabled] { opacity: .45; cursor: not-allowed; }
#mx-mappanel .msp-cback { margin-top: 5px; }
/* the CATEGORY choice, for a source that draws onto more than one cut layer (Places feeds six) —
   the feed rows the panel already renders, made pickable. Never an invented "Places colour". */
#mx-srcpanel .msp-cpick {
  display: flex; align-items: center; gap: 8px; width: 100%; box-sizing: border-box;
  margin-bottom: 4px; padding: 7px 9px; border: 1px solid var(--s-line); border-radius: 8px;
  background: var(--s-surface); cursor: pointer; text-align: left; font: inherit;
}
#mx-srcpanel .msp-cpick:hover { border-color: var(--s-brand); }
#mx-mappanel .msp-cpal-note, #mx-inspector .msp-cpal-note { margin-top: 7px; font: 500 10px/1.5 'Montserrat', system-ui, sans-serif; color: var(--s-faint); }
/* the master gate, restated where the pick happens — the SAME fact the Layers pane states, and
   never a second switch: #machinecolor-check is the only store, and it lives in that pane */
#mx-mappanel .msp-cpal-gate {
  margin-top: 7px; padding: 7px 8px; border: 1px solid var(--s-line); border-radius: 8px;
  background: var(--s-surface); font: 500 10px/1.5 'Montserrat', system-ui, sans-serif; color: var(--s-muted);
}
#mx-mappanel .msp-cpal-gate.on { border-color: var(--s-brand); background: var(--s-tint); color: var(--s-dark); }
/* the master gate — the one control that decides whether any of this reaches
   the cut file, so it reads as a statement, not a stray tickbox */
#mx-inspector .lyr-gate {
  display: flex; gap: 7px; align-items: flex-start; width: calc(100% - 16px); margin: 2px 8px 4px; padding: 8px 9px;
  border: 1px solid var(--s-line); border-radius: 9px; background: var(--s-well);
  color: var(--s-muted); font: inherit; font-size: 10px; line-height: 1.5; text-align: left; cursor: pointer;
}
#mx-inspector .lyr-gate.on { border-color: var(--s-brand); background: var(--s-tint); color: var(--s-dark); }
/* the tickbox is drawn, not an <input>: the gate's real state lives on
   #machinecolor-check and this is only ever a proxy for it */
#mx-inspector .lyr-gate-box {
  flex: 0 0 13px; width: 13px; height: 13px; margin-top: 1px; border-radius: 3px;
  border: 1px solid var(--s-line); background: var(--s-surface);
  display: flex; align-items: center; justify-content: center; color: #fff;
}
#mx-inspector .lyr-gate.on .lyr-gate-box { background: var(--s-brand); border-color: var(--s-brand); }
#mx-inspector .lyr-gate-box svg { width: 9px; height: 9px; }
/* the account-wide save is the only thing in this pane undo cannot take back */
#mx-inspector .lyr-btn.lyr-warnbtn { border-color: var(--s-brand); background: var(--s-tint); color: var(--s-dark); }
#mx-inspector .lyr-help.lyr-warn { color: var(--s-muted); }

/* Laser tab op cards */
#mx-inspector .op-card { border: 1px solid var(--s-line); border-radius: 9px; padding: 10px 12px; margin-bottom: 10px; }
#mx-inspector .op-head { display: flex; align-items: center; gap: 8px; }
#mx-inspector .op-sw { width: 10px; height: 10px; border-radius: 3px; }
#mx-inspector .op-name { font-size: 12px; font-weight: 700; flex: 1; }
#mx-inspector .op-count { font-size: 10px; color: var(--s-faint); }
#mx-inspector .op-row { display: flex; gap: 8px; margin-top: 8px; }
#mx-inspector .op-cell { flex: 1; border: 1px solid var(--s-well); border-radius: 7px; padding: 5px 9px; }
#mx-inspector .op-cell .k { font-size: 9px; color: var(--s-faint); }
#mx-inspector .op-cell .v { font-size: 11.5px; font-weight: 700; }
/* §8: power/speed/passes presets don't exist yet — cells render visibly disabled, never faked */
#mx-inspector .op-cell.op-off { opacity: .55; cursor: not-allowed; }
#mx-inspector .op-cell.op-off .v { color: var(--s-faint); }

/* ---- inspector collapse / reopen (desktop only; Phase 1) ----
   Collapse hides the inspector column and swaps in a slim docked reopen strip — a real flex
   sibling in the #free-main row, nothing floats. All rules are ≥761px-gated so the mobile
   sheets (which key off body.mx-insp-open, a different class) are untouched. */
#mx-inspector .insp-collapse {
  flex: 0 0 24px; width: 24px; height: 28px; border: 0; background: transparent; border-radius: 7px;
  color: var(--s-faint); cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0;
}
#mx-inspector .insp-collapse:hover { background: var(--s-well); color: var(--s-text); }
#mx-inspector .insp-collapse svg { width: 13px; height: 13px; }
#mx-insp-reopen { display: none; }
@media (min-width: 761px) {
  body.mx-studio.mx-insp-closed #mx-inspector { display: none; }
  body.mx-studio.mx-insp-closed #mx-insp-reopen {
    display: flex; flex: 0 0 26px; width: 26px; box-sizing: border-box;
    flex-direction: column; align-items: center; justify-content: center; gap: 7px;
    border: 0; border-left: 1px solid var(--s-line); background: var(--s-surface);
    color: var(--s-muted); cursor: pointer; padding: 0;
  }
  body.mx-studio.mx-insp-closed #mx-insp-reopen:hover { background: var(--s-well); color: var(--s-text); }
  body.mx-studio.mx-insp-closed #mx-insp-reopen svg { width: 13px; height: 13px; flex: 0 0 auto; }
  body.mx-studio.mx-insp-closed #mx-insp-reopen span {
    writing-mode: vertical-rl; font: 600 10px 'Montserrat', system-ui, sans-serif; letter-spacing: .08em;
  }
}
@media (max-width: 760px) {
  #mx-insp-reopen { display: none !important; }
  #mx-inspector .insp-collapse { display: none !important; }   /* the sheet grip/✕ owns closing */
}

/* ============ Bottom status bar + production-views strip (Phase 1) ============
   REQUIREMENT B: a real docked row at the BOTTOM OF THE CANVAS COLUMN (built by
   js/laser-studio.js buildStatusBar, only when an adapter is registered) — it spans the canvas'
   width only, never beneath the side panels, and it costs the column's height rather than the
   region row's, so no #free-main height maths is involved. Left: the adapter's live save status.
   Right: the production-views strip — until an adapter reports real views, the chips are disabled
   with a "coming later" title (§8). Desktop only: the phone's bottom edge belongs to #mx-tabbar. */
#mx-statusbar {
  height: 26px; box-sizing: border-box; display: flex; align-items: center; gap: 6px;
  padding: 0 12px; background: var(--s-surface); border-top: 1px solid var(--s-line);
  font: 600 10.5px 'Montserrat', system-ui, sans-serif; color: var(--s-muted);
  white-space: nowrap; overflow-x: auto; scrollbar-width: none;
}
#mx-statusbar::-webkit-scrollbar { display: none; }
#mx-statusbar .sb-save { color: var(--s-muted); overflow: hidden; text-overflow: ellipsis; max-width: 40%; }
#mx-statusbar .sb-spring { flex: 1 1 auto; min-width: 8px; }
#mx-statusbar .sb-lab { color: var(--s-faint); font-size: 9.5px; letter-spacing: .06em; text-transform: uppercase; flex: 0 0 auto; }
#mx-statusbar .pv-chip {
  height: 19px; box-sizing: border-box; border: 1px solid var(--s-line); border-radius: 999px;
  background: var(--s-surface); color: var(--s-muted); flex: 0 0 auto;
  font: 600 10px 'Montserrat', system-ui, sans-serif; padding: 0 9px;
}
#mx-statusbar .pv-chip[disabled] { opacity: .5; cursor: not-allowed; }
#mx-statusbar .pv-chip:not([disabled]) { cursor: pointer; }
#mx-statusbar .pv-chip:not([disabled]):hover { background: var(--s-well); }
#mx-statusbar .pv-chip.on { background: var(--s-tint); color: var(--s-dark); border-color: var(--s-brand); }
#mx-statusbar .sb-note { color: var(--s-faint); font-size: 9.5px; flex: 0 0 auto; }

/* ---------- REQUIREMENT C — the zoom / view utilities, docked in this row ----------
   js/laser-studio.js mountZoomControls() re-parents the whole #mx-zoompill CONTAINER (never the
   six children — see the note there about phones) to the FRONT of this bar, which is where the
   approved concept puts them: [zoom − % + Fit] … ——spring—— … [views]. `.in-sb` strips the
   floating pill's own chrome (absolute position, pill background, border, shadow, padding) and
   compacts the chips so the row STAYS EXACTLY 26px — any growth would come straight out of
   #free-preview and could change the fitted artboard. Measured at 1440x900 and 1280x800:
   scrollWidth === clientWidth, i.e. zero overflow. */
#mx-statusbar #mx-zoompill.in-sb {
  position: static; left: auto; bottom: auto; transform: none;
  background: transparent; border: 0; box-shadow: none; border-radius: 0;
  padding: 0; gap: 1px; height: 26px; flex: 0 0 auto;
}
#mx-statusbar #mx-zoompill.in-sb .ctx-chip,
#mx-statusbar #mx-zoompill.in-sb .ctx-pill { height: 20px; padding: 0 7px; font-size: 10.5px; }
#mx-statusbar #mx-zoompill.in-sb .ctx-pill { padding: 0 2px; }
#mx-statusbar #mx-zoompill.in-sb .ctx-pill .step { padding: 0 5px; font-size: 13px; }
#mx-statusbar #mx-zoompill.in-sb .ctx-pill .val { min-width: 32px; font-size: 10.5px; }
#mx-statusbar #mx-zoompill.in-sb .ctx-chip svg { width: 12px; height: 12px; }
#mx-statusbar #mx-zoompill.in-sb .ctx-units { padding: 1px; }
#mx-statusbar #mx-zoompill.in-sb .ctx-units .u { height: 16px; padding: 0 7px; font-size: 9.5px; }
/* On a 13" laptop the canvas column is only ~584px wide, so the two purely decorative labels
   stand down first — the CONTROLS never scroll off, and .sb-save (overflow:hidden) absorbs the
   rest with its full text kept readable through a title (buildStatusBar → syncSave). */
@media (min-width: 761px) and (max-width: 1366px) {
  #mx-statusbar .sb-lab, #mx-statusbar .sb-note { display: none; }
}

/* REQUIREMENT B: the bar used to be a full-width row under #free-main, so the region row had to
   shrink by 26px (body.mxs-statusbar → height: calc(100% - 52px - 26px), plus a second variant
   for the edit bar). Inside the canvas column it costs the CANVAS' height instead, which flexbox
   does on its own — both of those calc() rules are gone and no new height maths replaces them. */
@media (max-width: 760px) {
  #mx-statusbar { display: none !important; }
}

/* the shell-built tri-state chrome toggle (behaviour: js/studio-theme.js; dark values:
   css/studio-dark.css). Sizing here so it reads right in the two shells without .mx-hdr2. */
body.mx-studio #theme-toggle svg { width: 15px; height: 15px; }
body.mx-studio #theme-toggle .tt-auto,
body.mx-studio #theme-toggle .tt-sun, body.mx-studio #theme-toggle .tt-moon { display: flex; align-items: center; }

/* keep the app-bar tidy in studio: hide the legacy in-toolbar align cluster (it relocates to the
   floating bottom toolbar). lock/text/del now live in the header .fh-left, so they're no longer
   caught by this rule; leave New/undo/redo/export/save/account. */
body.mx-studio .editor-tools #align-group,
body.mx-studio .editor-tools #rail-lock,
body.mx-studio .editor-tools #rail-text,
body.mx-studio .editor-tools #rail-del { display: none; }

/* ============ Header — mockup layout (scoped to .mx-hdr2 so other editors are untouched) ============
   home · Resize · | · undo redo · | · lock add-text delete · saved  —  centered TITLE  —  Preview Share Export */
#free-header.mx-hdr2 {
  background: var(--s-surface); border-bottom: 1px solid var(--s-line);
  padding: 0 10px; gap: 6px; font-family: 'Montserrat', system-ui, sans-serif;
}
#free-header.mx-hdr2 .fh-left { display: flex; align-items: center; gap: 3px; flex: 0 0 auto; min-width: 0; }
#free-header.mx-hdr2 .fh-center { flex: 1 1 auto; display: flex; justify-content: center; min-width: 80px; }
#free-header.mx-hdr2 .fh-right { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }
#free-header.mx-hdr2 .fh-right .editor-tools { display: flex; align-items: center; gap: 4px; }

/* centered, editable title */
#free-header.mx-hdr2 .mx-filebar { width: 100%; max-width: 300px; display: flex; justify-content: center; }
#free-header.mx-hdr2 #mapname {
  width: 100%; height: 32px; box-sizing: border-box; text-align: center;
  border: 1px solid transparent; border-radius: 8px; background: transparent;
  font: 600 13.5px 'Montserrat', system-ui, sans-serif; color: var(--s-text); outline: none; text-overflow: ellipsis;
}
#free-header.mx-hdr2 #mapname:hover { border-color: var(--s-line); background: var(--s-well); }
#free-header.mx-hdr2 #mapname:focus { border-color: var(--s-brand); background: var(--s-surface); }

/* brand → compact home mark */
#free-header.mx-hdr2 #free-brand { display: flex; align-items: center; gap: 5px; text-decoration: none; color: var(--s-dark); font-weight: 700; margin-right: 2px; }
#free-header.mx-hdr2 #free-brand .brand-mark { width: 22px; height: 22px; }

/* generic header buttons */
#free-header.mx-hdr2 .tool-btn {
  height: 32px; box-sizing: border-box; border: 0; background: transparent; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center; gap: 5px; padding: 0 8px;
  font: 600 12.5px 'Montserrat', system-ui, sans-serif; color: var(--s-text); cursor: pointer;
}
#free-header.mx-hdr2 .tool-btn:hover { background: var(--s-well); }
#free-header.mx-hdr2 .tool-btn.tool-btn-icon { width: 32px; padding: 0; }
#free-header.mx-hdr2 .tool-btn svg { width: 15px; height: 15px; }
#free-header.mx-hdr2 .tool-btn[disabled] { color: var(--s-faint); cursor: default; opacity: .55; }
#free-header.mx-hdr2 .tool-btn[disabled]:hover { background: transparent; }
#free-header.mx-hdr2 .hdr-chip { border: 1px solid var(--s-line); padding: 0 10px; }
#free-header.mx-hdr2 .hdr-care { font-size: 9px; color: var(--s-muted); }
#free-header.mx-hdr2 #rail-del:hover { background: #f6ecea; color: var(--s-danger); }
#free-header.mx-hdr2 #rail-lock .cat-lbl { display: none; }       /* lock is icon-only like the mockup */
/* phone-only header Save mirror (see the ≤760px block) — the desktop header shows the real one */
#free-header.mx-hdr2 .mx-hdr-save { display: none; }
#free-header.mx-hdr2 .mx-hdr-save.is-dirty { background: var(--s-tint, #e3f1ee); box-shadow: 0 0 0 3px rgba(95,141,116,.18); }
#free-header.mx-hdr2 #rail-lock.locked, #free-header.mx-hdr2 #rail-lock.on { background: var(--s-tint); color: var(--s-dark); }
#free-header.mx-hdr2 .mx-sep { width: 1px; height: 20px; background: var(--s-line); margin: 0 3px; flex: 0 0 auto; }

/* saved status */
#free-header.mx-hdr2 .autosave-status { font-size: 11.5px; color: var(--s-muted); white-space: nowrap; padding: 0 4px; }

/* Export → green primary */
#free-header.mx-hdr2 #export-menu-btn.tool-btn-accent {
  background: var(--s-brand); color: #fff; border: 0; padding: 0 12px; font-weight: 600;
}
#free-header.mx-hdr2 #export-menu-btn.tool-btn-accent:hover { background: var(--s-dark); }
#free-header.mx-hdr2 #export-menu-btn .tm-caret { font-size: 9px; }

/* the injected Create button → neutral "File"-style chip; New Map hidden (Create covers it) */
#free-header.mx-hdr2 #create-btn { background: transparent; color: var(--s-text); border: 1px solid var(--s-line); border-radius: 8px; height: 32px; margin: 0 4px 0 0; padding: 0 10px; font-weight: 600; box-shadow: none; }
#free-header.mx-hdr2 #create-btn:hover { background: var(--s-well); }
#free-header.mx-hdr2 #new-map-btn { display: none; }

/* on-canvas inline text editor (created lazily on body by laser-studio.js) — README #10.
   Font-family/size/weight/style/decoration/spacing/align/rotation/color/position are all
   set per-item by styleInlineEditor() in JS; this just supplies the constant chrome. */
.mx-inline-edit {
  display: none;
  position: fixed;
  z-index: 9000;
  background: rgba(255, 255, 255, 0.75);
  outline: 1.5px solid #5f8d74;
  outline-offset: 2px;
  border-radius: 3px;
  padding: 1px 3px;
  min-width: 1ch;
  white-space: pre-wrap;
  word-break: break-word;
  cursor: text;
}
.mx-inline-edit.show { display: inline-block; }

/* the mobile chrome (bottom tab bar + slide-up sheets) is built in JS but must stay hidden on
   desktop — the tab bar / grip / close are display:none until the phone breakpoint activates them. */
#mx-tabbar, .mx-grip, body.mx-studio .mx-sheet-close { display: none; }

/* ==========================================================================
   MOBILE (≤760px) — bottom TAB BAR + slide-up bottom SHEETS (matches
   design/mobile-editor.dc.html). Everything here is media-gated + scoped to
   body.mx-studio, so desktop (≥761px) is byte-for-byte the Phase 2 layout.
   The existing panels are REPARENTED, never rebuilt: #mx-mappanel (the
   category controls, with #cat-rail re-hosted as a horizontal chip strip) and
   #mx-inspector (Design / Layers / Export) become the two sheet surfaces.
   ========================================================================== */
@media (max-width: 760px) {
  /* ---- compact top bar: back(brand) · title · undo · redo · saved · Export ---- */
  body.mx-studio #free-header.mx-hdr2 { height: 50px; min-height: 50px; padding: 0 6px; gap: 2px; }
  body.mx-studio #free-header.mx-hdr2 .fh-left { gap: 1px; }
  body.mx-studio #free-header.mx-hdr2 #free-brand span,
  body.mx-studio #free-header.mx-hdr2 #hdr-resize,
  body.mx-studio #free-header.mx-hdr2 #rail-text,
  body.mx-studio #free-header.mx-hdr2 #rail-del,
  body.mx-studio #free-header.mx-hdr2 #new-map-btn,
  body.mx-studio #free-header.mx-hdr2 #create-btn,
  body.mx-studio #free-header.mx-hdr2 #align-group,
  body.mx-studio #free-header.mx-hdr2 #hdr-preview,
  body.mx-studio #free-header.mx-hdr2 #hdr-share,
  body.mx-studio #free-header.mx-hdr2 #save-top-btn,
  body.mx-studio #free-header.mx-hdr2 #refresh-data-btn,
  body.mx-studio #free-header.mx-hdr2 #account-slot,
  body.mx-studio #free-header.mx-hdr2 .fh-left .mx-sep,
  body.mx-studio #free-header.mx-hdr2 .fh-right .mx-sep { display: none !important; }
  /* the theme toggle stays desktop-only for now (no .mx-hdr2 guard — covers all three shells) */
  body.mx-studio #free-header #theme-toggle { display: none !important; }
  body.mx-studio #free-header.mx-hdr2 #free-brand { margin: 0 2px 0 0; }
  body.mx-studio #free-header.mx-hdr2 #free-brand .brand-mark { width: 24px; height: 24px; }
  body.mx-studio #free-header.mx-hdr2 .fh-center { min-width: 40px; }
  body.mx-studio #free-header.mx-hdr2 #mapname { font-size: 13px; height: 34px; }
  body.mx-studio #free-header.mx-hdr2 .autosave-status { font-size: 10.5px; max-width: 66px; overflow: hidden; text-overflow: ellipsis; }
  body.mx-studio #free-header.mx-hdr2 .tool-btn { height: 34px; }
  /* owner 2026-08-21: "See the undo and redo buttons? Make lock and unlock and save there, too."
     The real Save lives in .fh-right behind the Export button, which is the busiest corner of a
     phone header — so the phone gets an id-less MIRROR next to undo/redo, and the lock button
     (already sitting there in .fh-left) stops being hidden. */
  body.mx-studio #free-header.mx-hdr2 .mx-hdr-save { display: inline-flex !important; }
  body.mx-studio #free-header.mx-hdr2 #rail-lock { padding: 0 7px; }
  body.mx-studio #free-header.mx-hdr2 #rail-lock .cat-lbl { display: none; }
  body.mx-studio #free-header.mx-hdr2 #export-menu-btn.tool-btn-accent { padding: 0 12px; font-size: 12.5px; }

  /* ---- canvas fills the space between the top bar and the bottom tab bar ---- */
  /* REQUIREMENT B is desktop-only. On phones #free-main is a block containing-block and
     #free-preview is position:absolute; inset:0 against it — so the canvas column must generate
     NO box at all, or it would become the containing block and break that. display:contents does
     exactly that: the wrapper vanishes from layout and #free-preview's geometry is byte-identical
     to the un-wrapped baseline (measured at 390x844). The status bar is display:none here anyway. */
  body.mx-studio #mx-canvascol { display: contents; }
  body.mx-studio #free-main { display: block; position: relative; height: calc(100% - 50px); overflow: hidden; }
  body.mx-studio #free-preview { position: absolute; inset: 0; width: 100%; height: 100%; padding: 0 !important; overflow: auto; -webkit-overflow-scrolling: touch; }
  body.mx-studio #poster-wrap { padding-bottom: calc(60px + env(safe-area-inset-bottom)); box-sizing: border-box; }

  /* ---- hide the desktop-only floating chrome (rail cluster + selection toolbar) ---- */
  body.mx-studio #mx-toolcluster { display: none !important; }
  body.mx-studio #mx-ctxbar { display: none !important; }   /* object editing happens in the inspector sheet */
  /* the zoom / fit / grid / snap / units / lock pill floats just above the tab bar, right-aligned */
  body.mx-studio #mx-zoompill {
    left: auto; right: 10px; transform: none; bottom: calc(60px + env(safe-area-inset-bottom) + 8px);
    max-width: calc(100vw - 20px); overflow-x: auto; z-index: 40;
  }
  body.mx-studio #mx-zoompill::-webkit-scrollbar { display: none; }

  /* ---- BOTTOM TAB BAR (Setup · Location · Design · Text · Icons · Layers · Export) ---- */
  #mx-tabbar {
    display: flex; align-items: stretch; position: fixed; left: 0; right: 0; bottom: 0; z-index: 70;
    background: var(--s-surface); border-top: 1px solid var(--s-line);
    padding-bottom: env(safe-area-inset-bottom); box-sizing: border-box;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    font-family: 'Montserrat', system-ui, sans-serif;
  }
  #mx-tabbar::-webkit-scrollbar { display: none; }
  #mx-tabbar .mx-tab {
    flex: 1 0 52px; min-height: 54px; border: 0; background: transparent; cursor: pointer;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    color: var(--s-muted); font-family: inherit; -webkit-tap-highlight-color: transparent;
  }
  #mx-tabbar .mx-tab svg { width: 20px; height: 20px; }
  #mx-tabbar .mx-tab span { font-size: 9.5px; font-weight: 600; letter-spacing: .01em; }
  #mx-tabbar .mx-tab.on { color: var(--s-dark); background: var(--s-tint); }
  #mx-tabbar .mx-tab:active { background: var(--s-well); }

  /* ---- SHEETS: #mx-mappanel + #mx-inspector slide up above the tab bar ---- */
  body.mx-studio #mx-mappanel, body.mx-studio #mx-inspector {
    display: flex !important; flex-direction: column;
    position: fixed !important; left: 0; right: 0; top: auto;
    bottom: calc(56px + env(safe-area-inset-bottom)); width: auto !important; flex: none !important;
    height: 52vh; max-height: calc(100vh - 96px); box-sizing: border-box;
    background: var(--s-surface); border: 0; border-top: 1px solid var(--s-line); border-radius: 16px 16px 0 0;
    box-shadow: 0 -8px 30px rgba(18,32,26,.20); z-index: 60;
    transform: translateY(112%); overflow: hidden;
    transition: transform .24s ease, height .22s ease;
  }
  /* detents (cycled by tapping the grip) — half is the default, no class */
  body.mx-studio.mx-sheet-full #mx-mappanel, body.mx-studio.mx-sheet-full #mx-inspector { height: calc(100vh - 92px); }
  body.mx-studio.mx-sheet-collapsed #mx-mappanel, body.mx-studio.mx-sheet-collapsed #mx-inspector { height: 148px; }
  /* map sheet is OPEN when not collapsed-away (default down on phones via JS); inspector opens on tab/selection */
  body.mx-studio:not(.mx-map-closed) #mx-mappanel { transform: translateY(0); }
  body.mx-studio.mx-insp-open #mx-inspector { transform: translateY(0); }

  /* the drag-handle strip (tap → cycle collapsed / half / full) */
  .mx-grip { display: flex; flex-direction: column; align-items: center; gap: 3px; flex: 0 0 auto;
    padding: 8px 0 4px; cursor: grab; -webkit-tap-highlight-color: transparent; }
  .mx-grip-bar { width: 38px; height: 4px; border-radius: 999px; background: #d5dad7; }
  .mx-grip-lbl { font-size: 9.5px; font-weight: 500; color: var(--s-faint); }

  /* sheet body layout: heads pinned, controls scroll */
  body.mx-studio #mx-mappanel .mp-head, body.mx-studio #mx-inspector .insp-tabs { flex: 0 0 auto; }
  body.mx-studio #mx-mappanel .mp-head { padding: 4px 14px 8px; border-bottom: 0; }
  body.mx-studio #mx-mappanel .mp-close { display: none; }   /* the injected ✕ handles close */
  body.mx-studio #mx-mappanel .mp-back { display: none !important; }  /* list/detail is desktop-only */
  body.mx-studio #mx-mappanel .mxs-railtabs { padding: 2px 14px 8px; border-bottom: 0; }
  body.mx-studio #mx-mappanel #mx-map-main { display: block; flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; }
  body.mx-studio #mx-inspector .insp-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; }

  /* #cat-rail re-hosted inside the map sheet as a horizontal CHIP STRIP (all 15 categories reachable).
     The !important + position:static overrides neutralize the base free.css (max-740) mobile drawer,
     which otherwise pins #cat-rail / #free-controls position:fixed to the viewport bottom. */
  body.mx-studio #mx-mappanel #cat-rail {
    position: static !important; inset: auto !important; z-index: auto; box-shadow: none;
    flex: 0 0 auto; width: 100% !important; min-width: 0; max-height: none; height: auto;
    flex-direction: row; align-items: center; gap: 6px; padding: 2px 14px 12px;
    overflow-x: auto; overflow-y: hidden; border: 0; background: transparent;
  }
  body.mx-studio #mx-mappanel #cat-rail::-webkit-scrollbar { display: none; }
  body.mx-studio #mx-mappanel #cat-rail .cat-btn {
    flex: 0 0 auto; width: auto !important; min-width: 0; min-height: 34px; flex-direction: row; gap: 6px;
    padding: 0 13px !important; border-radius: 999px; border: 1px solid var(--s-line); background: var(--s-surface);
  }
  body.mx-studio #mx-mappanel #cat-rail .cat-btn .cat-ico svg { width: 15px; height: 15px; }
  body.mx-studio #mx-mappanel #cat-rail .cat-btn .cat-lbl { font-size: 12px; white-space: nowrap; }
  body.mx-studio #mx-mappanel #cat-rail .cat-btn.active { background: var(--s-tint); border-color: var(--s-brand); }
  body.mx-studio #mx-mappanel #free-controls {
    position: static !important; inset: auto !important; transform: none !important; z-index: auto;
    width: auto !important; max-width: none !important; height: auto !important; max-height: none !important;
    box-shadow: none !important; border-radius: 0; padding: 2px 14px 24px; overflow: visible;
  }
  /* Order Notes pin inside the bottom sheet: sits under the cat-rail chip strip, still fixed
     chrome (the scroll port is #mx-map-main here). Sheet height is tight, so cap it harder and
     let the collapsed header cost ~32px — JS defaults it collapsed on phones. */
  body.mx-studio #mx-notes-pin {
    max-height: 38%; border-top: 1px solid var(--s-line); background: transparent;
  }
  body.mx-studio #mx-notes-pin .mx-notes-hd { padding: 7px 14px; }
  body.mx-studio #mx-notes-pin .mx-notes-body { padding: 0 14px 8px; }
  body.mx-studio #mx-notes-pin #order-notes { min-height: 44px; font-size: 16px; }   /* 16px: iOS won't zoom the sheet */

  /* the studio sheet has its own grip + ✕ — hide the legacy free.css mobile drawer chrome */
  body.mx-studio #mm-sheet-head, body.mx-studio #mm-panel-close, body.mx-studio #mm-sheet-scrim { display: none !important; }
  /* the close (✕) injected into each sheet */
  body.mx-studio .mx-sheet-close {
    display: flex; position: absolute; top: 8px; right: 12px; width: 30px; height: 30px; border-radius: 999px;
    border: 0; background: var(--s-well); color: var(--s-muted); font-size: 15px; line-height: 1;
    align-items: center; justify-content: center; cursor: pointer; z-index: 4;
  }
}

/* ── #text-popup inner styling for chrome engines that don't inline it (street/golf/ski).
   laser-lake.html inlines the same rules; duplication is idempotent. Positioning is handled by
   the neutralize block above; these are layout + the empty/content/is-text visibility toggles. ── */
body.mx-studio #text-popup .tp-empty { display: none; flex-direction: column; align-items: center; text-align: center; gap: 8px; padding: 24px 6px; color: #8a857a; }
body.mx-studio #text-popup:not(.show) > *:not(.tp-empty) { display: none; }
body.mx-studio #text-popup:not(.show) .tp-empty { display: flex; }
body.mx-studio #text-popup:not(.is-text) .tp-text-only { display: none; }
body.mx-studio #text-popup .tp-empty-ico { width: 42px; height: 42px; border-radius: 11px; background: #f2f4f2; display: flex; align-items: center; justify-content: center; }
body.mx-studio #text-popup .tp-empty-ico svg { width: 20px; height: 20px; color: #9aa49e; }
body.mx-studio #text-popup .tp-empty-title { font-size: 13px; font-weight: 700; color: #58534a; }
body.mx-studio #text-popup .tp-empty-hint { font-size: 12px; line-height: 1.5; }
body.mx-studio #text-popup .tp-head { display: flex; align-items: center; justify-content: space-between; }
body.mx-studio #text-popup .tp-title { font-size: 13px; font-weight: 700; }
body.mx-studio #text-popup .tp-head-actions { display: flex; gap: 2px; }
body.mx-studio #text-popup .tp-ico { width: 28px; height: 28px; padding: 0; display: flex; align-items: center; justify-content: center; border: 1px solid transparent; border-radius: 8px; background: transparent; color: #68736d; cursor: pointer; }
body.mx-studio #text-popup .tp-ico:hover { background: #f2f4f2; color: #23201a; }
body.mx-studio #text-popup .tp-ico svg { width: 15px; height: 15px; }
body.mx-studio #text-popup .tp-ico-danger:hover { background: #f6ecea; color: #b3564d; }
body.mx-studio #text-popup .tp-sec { display: flex; flex-direction: column; gap: 6px; }
body.mx-studio #text-popup .tp-lab { font-size: 10px; font-weight: 700; letter-spacing: .5px; color: #8a857a; }
body.mx-studio #text-popup .tp-lab-row { display: flex; align-items: center; justify-content: space-between; }
body.mx-studio #text-popup .tp-val { font-size: 11.5px; font-weight: 600; color: #23201a; }
body.mx-studio #text-popup textarea#tp-text { min-height: 46px; border: 1px solid #e6e4dc; border-radius: 8px; padding: 8px 10px; font: inherit; font-size: 12.5px; resize: vertical; box-sizing: border-box; outline: none; color: #23201a; width: 100%; }
body.mx-studio #text-popup textarea#tp-text:focus { border-color: #5f8d74; }
body.mx-studio #text-popup select#tp-font { height: 32px; width: 100%; box-sizing: border-box; border: 1px solid #e6e4dc; border-radius: 8px; background: #fff; font: inherit; font-size: 12.5px; padding: 0 8px; color: #23201a; cursor: pointer; }
body.mx-studio #text-popup .tp-weight { height: 30px; width: 100%; box-sizing: border-box; border: 1.5px solid #e6e4dc; border-radius: 8px; background: #fff; font: inherit; font-size: 13px; cursor: pointer; color: #23201a; }
body.mx-studio #text-popup .tp-weight.on { border-color: #5f8d74; background: #e3f1ee; color: #0c6457; }
body.mx-studio #text-popup .tp-style { display: flex; gap: 6px; }
body.mx-studio #text-popup .tp-style .tp-weight { flex: 1; width: auto; }
body.mx-studio #text-popup input[type=range] { width: 100%; accent-color: #5f8d74; margin: 0; }
body.mx-studio #text-popup .tp-seg { display: flex; gap: 6px; }
body.mx-studio #text-popup .tp-seg button { flex: 1; height: 30px; display: flex; align-items: center; justify-content: center; border: 1.5px solid #e6e4dc; border-radius: 8px; background: #fff; color: #68736d; cursor: pointer; padding: 0; }
body.mx-studio #text-popup .tp-seg button svg { width: 15px; height: 15px; }
body.mx-studio #text-popup .tp-seg button.on { border-color: #5f8d74; background: #e3f1ee; color: #0c6457; }
body.mx-studio #text-popup .tp-swatches { display: flex; gap: 6px; }
body.mx-studio #text-popup .tp-swatches button { flex: 1; aspect-ratio: 1; border-radius: 7px; border: 1px solid rgba(0,0,0,.08); padding: 0; cursor: pointer; box-sizing: border-box; }
body.mx-studio #text-popup .tp-swatches button.on { outline: 2px solid #0c6457; outline-offset: 2px; }
body.mx-studio #text-popup input[type=color]#tp-color { width: 100%; height: 28px; padding: 2px; border: 1px solid #e6e4dc; border-radius: 7px; background: #fff; cursor: pointer; box-sizing: border-box; }
body.mx-studio #text-popup .tp-action { height: 32px; width: 100%; box-sizing: border-box; border: 1px solid #e6e4dc; border-radius: 8px; background: #faf9f6; font: inherit; font-size: 12.5px; cursor: pointer; color: #2b2b2b; }
body.mx-studio #text-popup .tp-action:hover { background: #fff; border-color: #5f8d74; }
body.mx-studio #text-popup .tp-sep { width: 100%; height: 1px; background: #eceae2; margin: 2px 0; }

/* laser-lake: layer-option sections (Places / Bathy / Elevation / Golf / Ski) always show their
   options — the user toggles the layer's own checkbox (or an item) to render it live. Overrides the
   inline display:none the reveal handlers set. Parent .ctl-group still hides by cat-rail category. */
body.mx-studio #places-controls,
body.mx-studio #bathy-controls,
body.mx-studio #topo-controls,
body.mx-studio #golf-controls,
body.mx-studio #ski-controls { display: block !important; }

/* MARKER/ICON tool → inspector pane (icon choices + add-text + place-add + import, relocated) */
body.mx-studio #mx-inspector .pane-marker { display: none; }
body.mx-studio #mx-inspector.marker-tool[data-tab="object"] .pane-marker { display: block; }
body.mx-studio #mx-inspector.marker-tool[data-tab="object"] #text-popup,
body.mx-studio #mx-inspector.marker-tool[data-tab="object"] #shape-props { display: none !important; }
body.mx-studio .pane-marker .mk-sec { margin-bottom: 16px; }
body.mx-studio .pane-marker .mk-sec .tp-lab { font-size: 10px; font-weight: 700; letter-spacing: .5px; color: var(--s-faint); margin-bottom: 7px; }
body.mx-studio .pane-marker .mk-addtext { display: flex; gap: 6px; }
body.mx-studio .pane-marker .mk-addtext input { flex: 1; min-width: 0; height: 30px; border: 1px solid var(--s-line); border-radius: 7px; padding: 0 8px; font: inherit; font-size: 12px; box-sizing: border-box; }
body.mx-studio .pane-marker .marker-row { display: flex; flex-wrap: wrap; gap: 6px; }
body.mx-studio .pane-marker #place-input, body.mx-studio .pane-marker #place-label { width: 100%; box-sizing: border-box; margin-bottom: 5px; }

/* item 6 — the left ".mx-textpanel" duplicates the right #text-popup (the text INPUT + size/style/
   spacing/curve/erase-gap). Hide it so the left Text section is settings-only (add-text button,
   match-all-text, erase-roads) and the ONE text editor is the right-side Canva #text-popup. The
   small T tool adds a text + selects it → the right editor shows (see selectTool). */
body.mx-studio .mx-textpanel { display: none; }

/* item 7 — COVER-BLOCK pane: tick options show in the inspector when a block is selected */
body.mx-studio #mx-inspector .pane-block { display: none; }
body.mx-studio #mx-inspector.block-sel[data-tab="object"] .pane-block { display: block; }
body.mx-studio #mx-inspector.block-sel[data-tab="object"] #text-popup,
body.mx-studio #mx-inspector.block-sel[data-tab="object"] #shape-props,
body.mx-studio #mx-inspector.block-sel[data-tab="object"] .pane-textadd,
body.mx-studio #mx-inspector.block-sel[data-tab="object"] .pane-marker { display: none !important; }
body.mx-studio .pane-block .tp-lab { font-size: 10px; font-weight: 700; letter-spacing: .5px; color: var(--s-faint); margin-bottom: 8px; }
body.mx-studio .pane-block .ctl-check { display: flex; gap: 7px; margin-bottom: 8px; font-size: 12px; }

/* ============================================================================
   Studio shell — Content / Map panel tabs                (direction A)
   The editor ships one flat rail of 22 categories. The approved Studio package
   separates map data from design content. Categories are HIDDEN, never moved or
   rebuilt, so every existing handler, id and piece of state survives untouched.
   The tabs sit at the TOP OF #mx-mappanel now (panel-width row), with the panel
   collapse chevron (#mx-map-close, relocated by mountStudioRegions) at their
   right end. Scoped to body.mx-studio so nothing here can reach account or
   marketplace pages.
   ========================================================================== */
body.mx-studio .mxs-railtabs {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 5px;
  width: auto;
  box-sizing: border-box;
  padding: 9px 10px 8px;
  background: var(--s-surface, #fff);
  border-bottom: 1px solid var(--s-line, #e6e9e7);
  flex: 0 0 auto;
}
body.mx-studio .mxs-railtab {
  flex: 1 1 0;
  min-width: 0;
  height: 28px;
  padding: 0 4px;
  font: 700 11px/1 Montserrat, system-ui, sans-serif;
  letter-spacing: .02em;
  color: var(--s-muted, #68736d);
  background: var(--s-well, #f2f4f2);
  border: 0;
  border-radius: 7px;
  cursor: pointer;
}
body.mx-studio .mxs-railtab:hover { color: var(--s-text, #212b26); }
body.mx-studio .mxs-railtab.on {
  color: #fff;
  background: var(--s-brand, #5f8d74);
}
body.mx-studio .mxs-railtab:focus-visible {
  outline: 2px solid var(--s-brand, #5f8d74);
  outline-offset: -2px;
}
/* the relocated panel collapse control riding the tabs row */
body.mx-studio .mxs-railtabs .mp-close { flex: 0 0 26px; }
body.mx-studio .cat-btn.mxs-cat-hidden { display: none !important; }

/* ============================================================================
   Top edit-actions bar (direction B) — a REAL docked row between the app bar
   and the region row. Wired buttons proxy engine controls; unwired ones render
   visibly disabled with a "coming later" title (§8 — never faked). Desktop
   only: the phone top bar is already at its minimum height.
   ========================================================================== */
#mx-editbar {
  height: 36px; box-sizing: border-box; display: flex; align-items: center; gap: 4px;
  padding: 0 10px; background: var(--s-surface); border-bottom: 1px solid var(--s-line);
  font-family: 'Montserrat', system-ui, sans-serif; color: var(--s-text);
  white-space: nowrap; overflow-x: auto; scrollbar-width: none;
}
#mx-editbar::-webkit-scrollbar { display: none; }
#mx-editbar .eb-btn {
  height: 26px; box-sizing: border-box; display: inline-flex; align-items: center; gap: 4px;
  border: 0; border-radius: 7px; background: transparent; padding: 0 9px; flex: 0 0 auto;
  font: 600 11.5px 'Montserrat', system-ui, sans-serif; color: var(--s-text); cursor: pointer;
}
#mx-editbar .eb-btn:hover:not([disabled]) { background: var(--s-well); }
#mx-editbar .eb-btn[disabled] { color: var(--s-faint); cursor: not-allowed; opacity: .6; }
#mx-editbar .eb-btn.on { background: var(--s-tint); color: var(--s-dark); }
#mx-editbar .eb-car { font-size: 8px; color: var(--s-muted); }
#mx-editbar .eb-sep { width: 1px; height: 18px; background: var(--s-line); margin: 0 3px; flex: 0 0 auto; }
#mx-editbar .eb-spring { flex: 1 1 auto; min-width: 8px; }
#mx-editbar .eb-pv {
  flex: 0 0 auto; font: 600 10.5px 'Montserrat', system-ui, sans-serif; color: var(--s-muted);
  border: 1px solid var(--s-line); border-radius: 999px; padding: 4px 10px;
}
#mx-editbar .eb-pv.off { opacity: .55; cursor: not-allowed; }
/* Align / Arrange dropdowns — fixed, one at a time, closed on Esc / outside click */
#mx-ebmenu {
  position: fixed; z-index: 9500; width: 208px; box-sizing: border-box;
  background: var(--s-surface); border: 1px solid var(--s-line); border-radius: 10px;
  box-shadow: 0 4px 12px rgba(18,32,26,.08), 0 16px 48px rgba(18,32,26,.14);
  padding: 6px; display: none; font-family: 'Montserrat', system-ui, sans-serif;
}
#mx-ebmenu.show { display: block; }
#mx-ebmenu .ebm-item {
  display: flex; align-items: center; min-height: 30px; padding: 0 9px;
  border-radius: 7px; cursor: pointer; font-size: 12px; font-weight: 500; color: var(--s-text);
}
#mx-ebmenu .ebm-item:hover { background: var(--s-well); }
#mx-ebmenu .ebm-item.ebm-off { color: var(--s-faint); cursor: not-allowed; }
#mx-ebmenu .ebm-item.ebm-off:hover { background: transparent; }
#mx-ebmenu .ebm-sep { height: 1px; background: var(--s-line); margin: 5px 4px; }
/* the bar is a region row — the region-row height makes room for it (desktop only).
   REQUIREMENT B removed the second variant here: the status bar no longer comes out of the
   region row's height, so the edit bar is the only in-flow row left to subtract. */
@media (min-width: 761px) {
  body.mx-studio.mxs-editbar #free-main { height: calc(100% - 52px - 36px); }
}
@media (max-width: 760px) {
  #mx-editbar { display: none !important; }
  #mx-ebmenu { display: none !important; }
}

/* ---- SAVE button (owner 2026-08-19: "make the save button larger and place it more prominent") ----
   Was a 32px icon-only button AFTER Export. Now a labeled, outlined-green pill BEFORE Export — same
   height as the Export pill, distinct from it (outline vs fill) so "Save" and "Export" read as two
   first-class actions. The narrow-width hide rules above still target #save-top-btn by id. */
#free-header #save-top-btn.tool-btn-save {
  display: inline-flex; align-items: center; gap: 7px;
  height: 36px; padding: 0 14px; margin-right: 4px;
  font: 700 13px 'Montserrat', system-ui, sans-serif; letter-spacing: .01em;
  color: var(--s-dark, #0c6457); background: #fff;
  border: 1.5px solid var(--s-dark, #0c6457); border-radius: 8px; cursor: pointer;
}
#free-header #save-top-btn.tool-btn-save svg { width: 17px; height: 17px; flex: none; }
#free-header #save-top-btn.tool-btn-save:hover { background: var(--s-tint, #e3f1ee); }
#free-header #save-top-btn.tool-btn-save.is-dirty { background: var(--s-tint, #e3f1ee); box-shadow: 0 0 0 3px rgba(95,141,116,.18); }
body.mx-studio #free-header.mx-hdr2 #save-top-btn.tool-btn-save { height: 34px; padding: 0 12px; font-size: 12.5px; }

/* ---- family WIDTH row + ▸ in the Layers-tab colour popover (all elevation / all depth lines) ---- */
#mx-inspector .lyr-dc-w { margin-top: 6px; }
#mx-inspector .lyr-dc-w input[type="range"] { flex: 1 1 auto; min-width: 0; }
#mx-inspector .lyr-dc-w span { font-size: 11px; color: var(--s-muted, #8b8578); white-space: nowrap; }
#mx-inspector .lyr-dc-w .lyr-dc-wval { font: 600 11px 'IBM Plex Mono', Menlo, monospace; min-width: 2.4em; text-align: right; }
#mx-inspector .lyr-dc-panel { margin-top: 6px; width: 100%; }

/* "+ Add text box" at the top of the Text panel (owner 2026-08-19). A primary-looking full-width
   button: the Text tool only opens this panel on desktop, so this is THE way to add one. */
body.mx-studio #mx-addtext { display: block; width: 100%; margin: 6px 0 2px; padding: 9px 12px; font-weight: 600; font-size: 13px;
  color: #fff; background: #4f7f66; border: 1px solid #3f6b54; border-radius: 8px; cursor: pointer; }
body.mx-studio #mx-addtext:hover { background: #457259; }
body.mx-studio #mx-addtext:focus-visible { outline: 2px solid #9cc5ad; outline-offset: 2px; }
body.mx-studio .mx-addtext-hint { margin: 2px 0 10px; }

/* per-text ERASE options in the inspector (owner 2026-08-19: "change what type of erasing each
   text block does behind it"). Two columns of small checkboxes + a "map default" reset that only
   shows once this text has an override of its own. */
body.mx-studio #text-popup .tp-erase-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; margin-top: 4px; }
body.mx-studio #text-popup .tp-erase-chk { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--s-text); cursor: pointer; }
body.mx-studio #text-popup .tp-erase-chk input { margin: 0; }
body.mx-studio #text-popup .tp-mini { border: 1px solid var(--s-line); background: var(--s-well); color: var(--s-muted);
  font: 600 9.5px 'Montserrat', system-ui, sans-serif; letter-spacing: .4px; text-transform: uppercase;
  padding: 2px 6px; border-radius: 6px; cursor: pointer; }
body.mx-studio #text-popup .tp-mini:hover { color: var(--s-text); border-color: var(--s-line-hi); }
body.mx-studio #text-popup .tp-erase-sec.is-custom .tp-lab { color: var(--s-brand); }

/* pin caption placement (owner 2026-08-19: "can we adjust where the label will go — above, side,
   below — and how much padding it has"). Five little segmented buttons + the gap slider. */
body.mx-studio #text-popup .tp-labelpos-row { display: flex; gap: 4px; margin: 4px 0 6px; }
body.mx-studio #text-popup .tp-labelpos-row button { flex: 1; min-width: 0; padding: 4px 2px; font: 600 10px 'Montserrat', system-ui, sans-serif;
  border: 1px solid var(--s-line); border-radius: 6px; background: var(--s-well); color: var(--s-muted); cursor: pointer; }
body.mx-studio #text-popup .tp-labelpos-row button:hover { color: var(--s-text); border-color: var(--s-line-hi); }
body.mx-studio #text-popup .tp-labelpos-row button.on { background: #e3f1ee; border-color: #5f8d74; color: #0c6457; }
/* the "Everything behind it" master spans both columns above its kinds */
body.mx-studio #text-popup .tp-erase-chk.tp-erase-all { grid-column: 1 / -1; padding-bottom: 2px; border-bottom: 1px solid var(--s-line); margin-bottom: 2px; }

/* the map-lock pill (owner 2026-08-20: "make the unlock and lock button more prominent") —
   labelled, coloured by state: green = locked (safe), amber = unlocked (the map itself moves) */
#mx-zoompill .ctx-chip.mx-lockpill { gap: 6px; padding: 4px 12px; font-weight: 700; font-size: 12px; border-radius: 999px; border: 1.5px solid transparent; }
#mx-zoompill .ctx-chip.mx-lockpill.on { background: #e3f1ee; color: #2e5d49; border-color: #9cc5ad; }
#mx-zoompill .ctx-chip.mx-lockpill.mx-unlocked { background: #fdf1dc; color: #8a5d13; border-color: #ecc98a; }
#mx-zoompill .ctx-chip.mx-lockpill .mx-lock-lbl { white-space: nowrap; letter-spacing: .2px; }

/* ── Detail chip + popover in the edit bar (owner 2026-08-21 #3) ───────────────── */
#mx-editbar .eb-detail { color: var(--s-dark, #0c6457); font-weight: 700; }
#mx-editbar .eb-detail:not([disabled]):hover, #mx-editbar .eb-detail.on { background: var(--s-tint, #e3f1ee); }
#mx-editbar .eb-detail .dp-dot { color: #2e9e6b; font-size: 9px; vertical-align: 1px; }
.mx-detailpop {
  display: none; position: fixed; z-index: 9500; width: 260px; padding: 10px;
  background: var(--s-surface, #fff); border: 1px solid var(--s-line, #e6e9e7); border-radius: 10px;
  box-shadow: 0 4px 12px rgba(18,32,26,.08), 0 16px 48px rgba(18,32,26,.14);
  font: 500 12px Montserrat, system-ui, sans-serif; color: var(--s-text, #212b26);
}
.mx-detailpop.show { display: block; }
.mx-detailpop .dp-row { margin-bottom: 9px; }
.mx-detailpop .dp-lab { font-weight: 700; font-size: 11px; margin-bottom: 4px; }
.mx-detailpop .dp-chips { display: flex; gap: 4px; flex-wrap: wrap; }
.mx-detailpop .dp-chip {
  flex: 1 1 auto; min-width: 52px; height: 28px; border: 1px solid var(--s-line, #e6e9e7);
  background: var(--s-surface, #fff); border-radius: 7px; font: 600 11.5px Montserrat, system-ui, sans-serif;
  color: var(--s-muted, #68736d); cursor: pointer;
}
.mx-detailpop .dp-chip:hover { background: var(--s-well, #f2f4f2); }
.mx-detailpop .dp-chip.on { background: var(--s-tint, #e3f1ee); color: var(--s-dark, #0c6457); border-color: #a8c6b7; }
.mx-detailpop .dp-hint { font-size: 10.5px; color: var(--s-faint, #6e7a74); margin-top: 3px; line-height: 1.3; }
.mx-detailpop .dp-check { display: flex; align-items: center; gap: 6px; font-weight: 600; font-size: 11.5px; cursor: pointer; }
.mx-detailpop .dp-more {
  width: 100%; height: 30px; margin-top: 2px; border: 1px solid var(--s-line, #e6e9e7);
  background: var(--s-well, #f2f4f2); border-radius: 7px; font: 600 11.5px Montserrat, system-ui, sans-serif;
  color: var(--s-text, #212b26); cursor: pointer;
}
.mx-detailpop .dp-more:hover { background: var(--s-tint, #e3f1ee); }

/* ── Text presets grid — the text twin of the graphics palette (owner 2026-08-21 #8) ───────── */
.mx-txtpre { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; margin: 6px 0 10px; }
.mx-txtpre-cell {
  height: 46px; border: 1px solid var(--s-line, #e6e9e7); background: var(--s-surface, #fff);
  border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center;
  color: var(--s-text, #212b26); overflow: hidden; padding: 0 6px;
}
.mx-txtpre-cell:hover { background: var(--s-well, #f2f4f2); border-color: var(--s-line-hi, #b9c3bd); }
.mx-txtpre-prev { font-family: 'Montserrat', system-ui, sans-serif; font-size: 13px; white-space: nowrap; }

@media (max-width: 760px) {
  /* ── Phone declutter (owner 2026-08-21 #5/#6) ───────────────────────────────────────────────
     TWO mobile shells were live at once: this file's bottom tab bar + sheets, and the older
     floating cluster from js/mobile.js. Setup and Undo/Redo were duplicated (they are in the tab
     bar and the header), so the cluster was five big cards sitting on top of the map. Keep the
     two it uniquely owns — Hold to pan (pen/finger routing) and Delete — and drop the rest. */
  body.mx-studio #mm-touch-tools #mm-setup-btn,
  body.mx-studio #mm-touch-tools #mm-undo-btn,
  body.mx-studio #mm-touch-tools #mm-redo-btn,
  /* Delete now lives on the floating selection bar, right next to the thing it deletes —
     a permanent Delete card parked over the map is the opposite of that. */
  body.mx-studio #mm-touch-tools #mm-del-btn { display: none !important; }
  body.mx-studio #mm-touch-tools { gap: 6px; }
  body.mx-studio #mm-touch-tools .mm-tt-btn { transform: scale(.88); transform-origin: right center; }
  /* the zoom pill ran off a 390px screen — let it scroll instead of overflowing */
  body.mx-studio #mx-zoompill { max-width: calc(100vw - 20px); overflow-x: auto; scrollbar-width: none; }
  body.mx-studio #mx-zoompill::-webkit-scrollbar { display: none; }
  .mx-txtpre { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 760px) {
  /* live drag height wins over the detent rules while the finger is down (owner #7) */
  body.mx-studio.mx-sheet-drag #mx-mappanel,
  body.mx-studio.mx-sheet-drag #mx-inspector { height: var(--mx-sheet-h, 52vh) !important; transition: none !important; }
  /* the handle is the grab target — make it worth grabbing, and stop the page scrolling with it */
  body.mx-studio .mx-grip { touch-action: none; cursor: grab; padding-top: 9px; padding-bottom: 7px; }
  body.mx-studio.mx-sheet-drag .mx-grip { cursor: grabbing; }
  body.mx-studio .mx-grip-bar { width: 44px; height: 5px; border-radius: 999px; }
}

/* ── Floating selection actions (owner 2026-08-21, the Canva behaviour) ───────────────────── */
#mx-selbar {
  display: none; position: fixed; z-index: 9400; gap: 2px; padding: 4px;
  background: var(--s-surface, #fff); border: 1px solid var(--s-line, #e6e9e7); border-radius: 999px;
  box-shadow: 0 4px 12px rgba(18,32,26,.10), 0 12px 32px rgba(18,32,26,.16);
}
#mx-selbar.show { display: flex; }
#mx-selbar .mx-sb-btn {
  width: 40px; height: 36px; border: 0; background: transparent; border-radius: 999px;
  font-size: 16px; line-height: 1; color: var(--s-text, #212b26); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
#mx-selbar .mx-sb-btn:active { background: var(--s-tint, #e3f1ee); }
/* the "customize presets" affordance under the quick-add grid, and its dialog */
.mx-txtpre-edit { grid-column: 1 / -1; border: 1px dashed var(--s-line, #d9ddd8); background: none;
  border-radius: 8px; padding: 7px; font: 600 11.5px 'Montserrat', system-ui, sans-serif;
  color: var(--s-mut, #68736d); cursor: pointer; }
.mx-txtpre-edit:hover { color: var(--s-dark, #0c6457); border-color: var(--s-accent, #5f8d74); }
#mx-tpe { position: fixed; inset: 0; z-index: 99999; background: rgba(20,18,14,.5);
  display: flex; align-items: center; justify-content: center; padding: 20px; }
#mx-tpe .tpe-card { background: var(--s-surface, #fff); color: var(--s-text, #212b26); border-radius: 14px;
  max-width: 680px; width: 100%; max-height: 84vh; overflow-y: auto; padding: 20px 22px;
  font-family: 'Montserrat', system-ui, sans-serif; box-shadow: 0 12px 40px rgba(0,0,0,.3); }
#mx-tpe h3 { margin: 0 0 4px; font-family: 'Playfair Display', Georgia, serif; font-size: 18px; }
#mx-tpe p { margin: 0 0 14px; font-size: 12.5px; color: var(--s-mut, #68736d); }
#mx-tpe .tpe-row { display: flex; align-items: center; gap: 10px; padding: 8px 0;
  border-top: 1px solid var(--s-line, #eee9de); font-size: 12.5px; flex-wrap: wrap; }
#mx-tpe .tpe-row > b { flex: 0 0 86px; font-size: 12.5px; }
#mx-tpe .tpe-row select { flex: 1 1 150px; min-width: 130px; padding: 6px 8px; font: inherit;
  border: 1px solid var(--s-line, #d9ddd8); border-radius: 7px; background: var(--s-surface, #fff); color: inherit; }
#mx-tpe .tpe-row label { display: inline-flex; align-items: center; gap: 5px; color: var(--s-mut, #68736d); }
#mx-tpe .tpe-row input[type="number"] { width: 64px; padding: 6px; font: inherit;
  border: 1px solid var(--s-line, #d9ddd8); border-radius: 7px; background: var(--s-surface, #fff); color: inherit; }
#mx-tpe .tpe-acts { display: flex; gap: 10px; margin-top: 16px; align-items: center; }
#mx-tpe .tpe-acts button { border: 1px solid var(--s-line, #d9ddd8); background: var(--s-surface, #fff);
  color: inherit; border-radius: 9px; padding: 9px 15px; font: 600 13px 'Montserrat', sans-serif; cursor: pointer; }
#mx-tpe .tpe-save { background: var(--s-dark, #0c6457) !important; color: #fff !important; border: 0 !important; }
/* The EDIT button carries a word and the accent, because a lone pencil only reads as "edit"
   to someone who already knows (owner, 2026-08-25, comparing it to Canva). Retyping the words
   is the likeliest thing anyone wants from a selected text, so it looks like the main action. */
#mx-selbar .mx-sb-btn.mx-sb-primary {
  width: auto; padding: 0 14px; gap: 6px; font-weight: 700;
  background: var(--s-accent, #5f8d74); color: #fff;
}
#mx-selbar .mx-sb-btn.mx-sb-primary:active { background: var(--s-dark, #0c6457); }
#mx-selbar .mx-sb-lab { font: 700 13px 'Montserrat', system-ui, sans-serif; }
@media (min-width: 761px) { #mx-selbar { display: none !important; } }

@media (max-width: 760px) {
  /* Essentials first — the rest is one tap away (owner 2026-08-21 "#6 don't overwhelm").
     Media-gated on purpose: the desktop panel keeps every row exactly where it was. */
  body.mx-studio #free-controls .mx-folded { display: none !important; }
  body.mx-studio #free-controls .mx-fold-open > .mx-folded { display: revert !important; }
  body.mx-studio #free-controls .mx-foldbtn {
    width: 100%; margin: 8px 0 4px; background: var(--s-well, #f2f4f2);
    border: 1px solid var(--s-line, #e6e9e7); color: var(--s-text, #212b26); font-weight: 700;
  }
}

/* ── Text property strip (owner 2026-08-22, the Canva bottom row) ─────────────────────────── */
#mx-txtbar { display: none; }
@media (max-width: 760px) {
  #mx-txtbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 9450;
    align-items: center; gap: 6px; padding: 7px 10px;
    background: var(--s-surface, #fff); border-top: 1px solid var(--s-line, #e6e9e7);
    box-shadow: 0 -6px 18px rgba(18,32,26,.10);
    overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  #mx-txtbar::-webkit-scrollbar { display: none; }
  #mx-txtbar.show { display: flex; }
  /* the strip takes the tab bar's place — they are the same piece of screen */
  body.mx-studio.mx-txtbar-on #mx-tabbar { display: none !important; }
  #mx-txtbar .mx-tb-chip {
    flex: 0 0 auto; min-width: 40px; height: 34px; border: 1px solid var(--s-line, #e6e9e7);
    background: var(--s-surface, #fff); border-radius: 9px; font: 600 14px/1 Montserrat, system-ui, sans-serif;
    color: var(--s-text, #212b26); cursor: pointer;
  }
  #mx-txtbar .mx-tb-chip.on { background: var(--s-tint, #e3f1ee); border-color: var(--s-brand, #5f8d74); color: var(--s-dark, #0c6457); }
  /* Done must always be reachable. The row scrolls, so margin-left:auto puts it past the right
     edge the moment the content overflows — which it does on a 390 px screen. Sticky pins it to
     the edge while everything else scrolls under it. */
  #mx-txtbar .mx-tb-done {
    background: var(--s-brand, #5f8d74); border-color: var(--s-brand, #5f8d74); color: #fff;
    position: sticky; right: 0; margin-left: auto; box-shadow: -10px 0 10px -6px rgba(18,32,26,.18);
  }
  #mx-txtbar .mx-tb-col { flex: 0 0 auto; width: 36px; height: 34px; padding: 2px; border: 1px solid var(--s-line, #e6e9e7); border-radius: 9px; background: var(--s-surface, #fff); }
  #mx-txtbar .mx-tb-font { flex: 0 1 118px; min-width: 88px; height: 34px; border: 1px solid var(--s-line, #e6e9e7); border-radius: 9px; font-size: 12.5px; background: var(--s-surface, #fff); color: var(--s-text, #212b26); }
  #mx-txtbar .mx-tb-szv { flex: 0 0 auto; min-width: 46px; text-align: center; font: 700 12.5px Montserrat, system-ui, sans-serif; color: var(--s-muted, #68736d); }
}

/* basemap toggle in the edit bar (owner 2026-08-23) */
#mx-editbar .eb-base { font-weight: 700; color: var(--s-muted, #68736d); }
#mx-editbar .eb-base.on { background: var(--s-tint, #e3f1ee); color: var(--s-dark, #0c6457); border-color: #a8c6b7; }
#mx-editbar .eb-base:not([disabled]):hover { background: var(--s-well, #f2f4f2); }
