/* style-panel.css — Phase 221 (STYLE-04 / FONT-01).
 *
 * All aesthetic styling for components/style-panel.jsx. The panel component
 * carries only layout and position numbers inline (left, top) plus fontFamily
 * on a font row so each family previews in its own face; everything you can
 * see — colours, borders, radii, spacing, type size and weight — lives here.
 * That split is not a preference: `no-inline-aesthetic-styles` is severity
 * ERROR and hard-fails tools/deploy.sh, and the Python audit at
 * tools/audit_inline_style_aesthetics.py --gate is a second, independent gate
 * on the same rule.
 *
 * DESIGN INTENT
 * -------------
 * The panel is not a new visual language. It is the canvas's card-options menu
 * with a different payload, and it deliberately reuses that surface's chrome —
 * the same elevated background, the same hairline border, the same soft drop
 * shadow, the same 13px row rhythm — so opening Style from that menu feels
 * like the menu continuing rather than a different product appearing.
 *
 * The one place it spends any boldness is the font list: each family renders
 * its own name in its own face, with a short sample line underneath. You are
 * choosing a typeface, so the list shows you typefaces instead of a column of
 * identical strings. The theme editor's font picker already works this way, so
 * this is the house pattern rather than a new idea, and it is the reason the
 * rest of this file stays as quiet as it does.
 *
 * PRIM-STYLE rules this file MUST satisfy (tools/audit_primitive_css.py --gate,
 * run by tests/plugin_spec/, which tools/deploy.sh gates via pytest):
 *   1. At least one var(--mt-*) / var(--m-*) consumption — satisfied many
 *      times below; every colour in this file is a token read.
 *   2. Zero hex literals. A hex here silently bypasses the theme system: the
 *      panel would look right on the default theme and wrong on every other.
 *   3. Zero variable declarations. Declaring a --mt-* here would shadow the
 *      theme.css value and break the theme-switch contract; token values are
 *      codegen-owned (tools/build_tokens.py from design-tokens/<theme>.json).
 *
 * Loaded BETWEEN utilities.css and theme.css — see index.html AND
 * dist-index.html.tmpl. It must be in BOTH: a <link> in only one leaves the
 * panel unstyled in production only, which is the worst place to find out
 * (feedback_manifold_dist_template_parity). Bump the ?v= cache-buster on edit,
 * or browsers serve the stale copy and the change appears not to have worked.
 */

/* === The popover shell ==================================================== *
 * position: fixed because the panel is portaled to <body> and anchored at the
 * viewport point the menu was opened at — it must NOT inherit the scaled
 * canvas transform, which would shift and scale it. Mirrors .cb-card-menu.
 * ------------------------------------------------------------------------- */

.msp-panel {
  position: fixed;
  z-index: 51; /* one above .cb-card-menu so it opens over the menu it came from */
  display: flex;
  flex-direction: column;
  width: 300px;
  /* A BACKSTOP, NOT THE CAP. The real cap is an inline max-height written by
   * fitPanelBox in style-panel.jsx: the room actually left underneath the panel
   * at its current top, which is the only number that keeps its bottom edge on
   * screen. This rule cannot know where the panel sits, and on its own it let a
   * panel placed at top:400 on an 800px screen be 784px tall and end 384px
   * below the bottom edge — Karl reported exactly that on 2026-07-27, with Save
   * off screen and unreachable.
   *
   * It stays because it covers the frame before React has measured anything,
   * and it is safe to keep for one reason worth stating: the inline value is
   * `viewportHeight - top - 8` and `top` is never below 8, so the inline cap is
   * always the smaller of the two and this rule can never bind first.
   * tests/222-panel-fit.test.js asserts that relationship across every screen
   * size, so it stays true rather than merely being true today. */
  max-height: calc(100vh - 16px);
  overflow-y: auto;
  padding: 6px;
  background: var(--mt-bg-elev);
  border: 1px solid var(--mt-border-strong);
  border-radius: var(--cb-tb-radius, 8px);
  box-shadow: 0 8px 24px color-mix(in srgb, var(--mt-bg-deep) 50%, transparent);
}

.msp-header {
  padding: 6px 8px 8px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--m-text-muted);
}

/* === CHANGE: how far this change reaches (Phase 223, STYLE-02) ============ *
 * The first thing in the panel, because it is the first thing to decide. Three
 * choices — This card, This tab, All cards — and the count stays on screen the
 * whole time the tick-boxes below are being set.
 *
 * WHY THIS IS NOT THE OLD PICKER'S LOOK
 * -------------------------------------
 * The shipped .cb-apply-segment is mono, 11px, weight 700, uppercase, with
 * 0.04em tracking. Copying it here would be wrong twice over:
 *
 *   1. It would collide with the heading directly above it. .msp-sub-head is
 *      also 11px/600/uppercase/0.04em, so CHANGE sitting on top of
 *      THIS CARD · THIS TAB (7) · ALL CARDS (46) reads as two headings rather
 *      than as a heading and its control.
 *   2. This panel has no monospace anywhere. One mono control would read as
 *      pasted in from another product — which, until Phase 225 retires the old
 *      picker, it literally would have been.
 *
 * So the segments sit one step down from a row label: 11px, weight 400,
 * sentence case, no tracking. Weight 600 marks the chosen one, which is this
 * panel's own way of saying "this one" (.msp-family.is-selected,
 * .msp-saved-item.is-chosen).
 *
 * The ONE thing lifted from the old picker is its chosen segment's 13% accent
 * wash. Karl already reads that wash as "this segment is chosen" on a control
 * doing the same job, and a second way of saying the same thing is a second
 * thing to learn.
 * ------------------------------------------------------------------------- */

.msp-scope {
  display: flex;
  flex-direction: column;
  /* The one new divider in this phase. The block is a decision and the rows
   * below it are the details; one hairline says so. Same weight and token the
   * footer's own rule uses. */
  border-bottom: 1px solid var(--mt-border);
  padding-bottom: 6px;
}

/* Identical to .msp-sub-head and .msp-parts-head-label — same kind of thing, a
 * name over a group of settings, so it takes the same type rather than a new
 * one. */
.msp-scope-head {
  padding: 8px 8px 2px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--m-text-muted);
}

/* One bordered track, three segments sharing it. Inset 8px so its edges line up
 * with the text of every row above and below it. */
.msp-scope-segments {
  display: flex;
  margin: 2px 8px 0;
  border: 1px solid var(--mt-border-strong);
  border-radius: var(--m-radius-sm, 2px);
  overflow: hidden;
}

/* Every segment is allowed to shrink and none may wrap. At the panel's 300px
 * the three labels measure about 243px together, so there is room to spare even
 * with three-digit counts on both numbers — the ellipsis is a backstop for a
 * font this was not measured in, not the plan. */
.msp-scope-seg {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  padding: 6px 8px;
  border: none;
  background: transparent;
  font-size: 11px;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
  transition: background 120ms ease-out, color 120ms ease-out;
}

/* The dividers between segments, drawn on the second and third rather than as a
 * trailing border on every one — a trailing border on the last segment doubles
 * up with the track's own right edge. */
.msp-scope-seg + .msp-scope-seg {
  border-left: 1px solid var(--mt-border);
}

.msp-scope-seg:hover {
  background: color-mix(in srgb, var(--mt-fg) 8%, transparent);
  color: var(--mt-fg);
}

/* Inset, because the segments sit flush inside a bordered track and an outset
 * ring would be drawn over its neighbour. */
.msp-scope-seg:focus-visible {
  outline: 2px solid var(--mt-accent);
  outline-offset: -2px;
}

.msp-scope-seg.is-active {
  background: color-mix(in srgb, var(--mt-accent) 13%, transparent);
  color: var(--mt-accent);
  font-weight: 600;
}

/* A choice this card cannot make — every dashboard header card is on no tab, so
 * "this tab" and "all cards" have no honest answer for it. The same 35% of the
 * foreground every other greyed thing in this panel uses, with the reason as
 * visible text in the sub-note slot below. One grey, one pattern. */
.msp-scope-seg.is-disabled {
  color: color-mix(in srgb, var(--mt-fg) 35%, transparent);
  cursor: default;
}

.msp-scope-seg.is-disabled:hover {
  background: transparent;
  color: color-mix(in srgb, var(--mt-fg) 35%, transparent);
}

/* The explanatory tail of the block: what a wider choice will do, or why the
 * wider choices are unavailable. Same 11px muted wrapping slot as the palette
 * note and every greyed reason, one step tighter above because it hangs off the
 * control immediately over it. */
.msp-scope-note {
  padding: 2px 8px 6px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--m-text-muted);
}

/* === The locked row ======================================================= *
 * `3 are locked   (Include) (Skip)` — shown only when there is something locked
 * to hold back, and never at This card scope. At zero locked cards there is no
 * row at all: a "0 are locked" placeholder is the kind of noise that makes the
 * lines that matter stop being read.
 *
 * The two actions are mini buttons rather than a second bordered track: they
 * take .msp-btn--small's padding, because that is the size an action inside a
 * settings row already is in this panel.
 * ------------------------------------------------------------------------- */

.msp-locked-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 8px 0;
}

.msp-locked-label {
  flex: 1 1 auto;
  font-size: 11px;
  color: var(--m-text-muted);
}

.msp-locked-segments {
  display: flex;
  flex: 0 0 auto;
  gap: 2px;
}

.msp-locked-seg {
  padding: 3px 8px;
  border: 1px solid var(--mt-border-strong);
  border-radius: var(--m-radius-sm, 2px);
  background: transparent;
  font-size: 11px;
  font-weight: 400;
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
  cursor: pointer;
  transition: background 120ms ease-out, color 120ms ease-out;
}

.msp-locked-seg:hover {
  background: color-mix(in srgb, var(--mt-fg) 8%, transparent);
  color: var(--mt-fg);
}

.msp-locked-seg:focus-visible {
  outline: 2px solid var(--mt-accent);
  outline-offset: 1px;
}

/* The same "this one" the scope segments use. Skip is chosen when the row first
 * appears: a padlock that has to be un-chosen to take effect is not a padlock. */
.msp-locked-seg.is-active {
  border: 1px solid var(--mt-accent);
  background: color-mix(in srgb, var(--mt-accent) 13%, transparent);
  color: var(--mt-accent);
  font-weight: 600;
}

/* === The four element rows ================================================ */

.msp-element {
  display: flex;
  flex-direction: column;
}

.msp-element-row {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 28px;
  padding: 6px 8px;
  border-radius: var(--m-radius-sm, 2px);
  font-size: 13px;
  font-weight: 400;
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
  background: transparent;
  cursor: pointer;
  transition: background 120ms ease-out, color 120ms ease-out;
}

.msp-element-row:hover {
  background: color-mix(in srgb, var(--mt-fg) 8%, transparent);
  color: var(--mt-fg);
}

.msp-element-row:focus-visible {
  outline: 2px solid var(--mt-accent);
  outline-offset: -2px;
}

/* A row for an element this card cannot use yet. Greyed and non-interactive,
 * but never silent: the reason renders as visible text below it (D-06). Karl
 * must never have to wonder whether a greyed row is broken. A reason you have
 * to hover to find does not answer that question. */
.msp-element-row.is-disabled {
  color: color-mix(in srgb, var(--mt-fg) 35%, transparent);
  cursor: default;
}

.msp-element-row.is-disabled:hover {
  background: transparent;
  color: color-mix(in srgb, var(--mt-fg) 35%, transparent);
}

.msp-element-reason {
  padding: 0 8px 6px 32px;
  font-size: 12px;
  color: var(--m-text-muted);
}

/* === The per-element mark: this part is the card's own (D-16) ============= *
 * A reserved 12px column at the RIGHT end of every element row, holding a 5px
 * dot when that part is the card's own and nothing when it is not.
 *
 * RESERVED ON EVERY ROW, MARKED OR NOT. The gear's check gutter says the same
 * thing in its own comment and it is the same reason: "the ✓ on the active row
 * and the blank on inactive rows occupy the same width, so every theme name
 * lines up at one indented x". Here it is the rows that must not shift as marks
 * come and go — a column that appeared only on marked rows would move every row
 * under it each time a part was put back.
 *
 * RIGHT-ALIGNED, NOT A LEFT GUTTER, despite the left gutter being the more
 * familiar mechanic. .msp-element-row already opens with a disclosure triangle
 * at a fixed inset, and inserting a gutter before it moves every shipped row. A
 * right column still gives the scannable vertical alignment that made the
 * mechanic worth borrowing. The mechanic transfers; the side does not.
 *
 * NEVER ACCENT, IN ANY STATE. This is STATUS, not interaction — accent is
 * reserved for things you can point at, and card-builder-helpers.css writes the
 * same rule down twice for the lock glyph and the z-badge. 60% here is one step
 * quieter than the canvas mark's 70%, because a 28px panel row is a much
 * smaller field than a card. Do not invent a third grey; 221's 35% stays the
 * only greyed-out value.
 *
 * --m-radius-full may not exist in every theme, so it carries its fallback the
 * same way var(--m-radius-sm, 2px) does everywhere else in this file.
 * ------------------------------------------------------------------------- */

.msp-element-mark {
  display: flex;
  flex: 0 0 12px;
  align-items: center;
  justify-content: flex-end;
}

.msp-element-mark-dot {
  width: 5px;
  height: 5px;
  border-radius: var(--m-radius-full, 9999px);
  background: color-mix(in srgb, var(--mt-fg) 60%, transparent);
}

/* Rendered only when at least one row is marked. A bare dot with a hover-only
 * explanation fails 221 D-06's standing rule, and a legend for an empty set is
 * clutter. Same 11px muted slot as .msp-saved-part-reason. */
.msp-element-mark-legend {
  padding: 2px 8px 6px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--m-text-muted);
}

/* The disclosure triangle, rotated when the row is open. */
.msp-tri {
  flex: 0 0 auto;
  fill: currentColor;
  transition: transform 120ms ease-out;
}

.msp-tri--open {
  transform: rotate(90deg);
}

.msp-element-label {
  flex: 1 1 auto;
}

/* === The three font roles ================================================= */

.msp-fonts {
  display: flex;
  flex-direction: column;
  padding: 0 0 4px 8px;
}

.msp-role {
  display: flex;
  flex-direction: column;
}

/* Role row: the name on the left, what the card uses now on the right. The
 * current value is the answer to "what is this set to?", so it reads at a
 * glance without opening the list. */
.msp-role-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  padding: 6px 8px;
  border: none;
  border-radius: var(--m-radius-sm, 2px);
  background: transparent;
  font-size: 13px;
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
  text-align: left;
  cursor: pointer;
  transition: background 120ms ease-out;
}

.msp-role-row:hover {
  background: color-mix(in srgb, var(--mt-fg) 8%, transparent);
}

.msp-role-row:focus-visible {
  outline: 2px solid var(--mt-accent);
  outline-offset: -2px;
}

.msp-role-label {
  flex: 0 0 auto;
}

.msp-role-current {
  flex: 1 1 auto;
  overflow: hidden;
  font-size: 12px;
  color: var(--m-text-muted);
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* === The family list ====================================================== *
 * The signature of this panel. Each family previews in its own face, so
 * picking a typeface is a direct comparison rather than reading a list of
 * names. Capped and scrollable so a long list of uploaded families cannot
 * grow the popover past the viewport.
 * ------------------------------------------------------------------------- */

.msp-family-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 220px;
  overflow-y: auto;
  padding: 2px 4px 6px;
}

.msp-family {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 6px 8px;
  border: 1px solid transparent;
  border-radius: var(--m-radius-sm, 2px);
  background: transparent;
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
  text-align: left;
  cursor: pointer;
  transition: background 120ms ease-out, border-color 120ms ease-out;
}

.msp-family:hover {
  background: color-mix(in srgb, var(--mt-fg) 8%, transparent);
}

.msp-family:focus-visible {
  outline: 2px solid var(--mt-accent);
  outline-offset: -2px;
}

.msp-family.is-selected {
  border: 1px solid var(--mt-accent);
  color: var(--mt-fg);
}

.msp-family-name {
  font-size: 13px;
}

/* The sample line. Deliberately includes digits: the Numbers role is the one
 * Karl changes most, and a sample with no digits in it says nothing about the
 * thing he is actually looking at. */
.msp-family-sample {
  font-size: 11px;
  color: var(--m-text-muted);
}

/* === The palette list ===================================================== *
 * Phase 222 (COLOR-01). Deliberately the family list again rather than a
 * second visual language — same 220px cap, same 2px gaps, same hover and
 * selected treatment — so the panel reads as one list of settings. Twelve rows
 * uncapped is about 364px and would push Save and Cancel off the bottom of a
 * laptop screen.
 *
 * The one difference from .msp-family: a palette row lays its name and its
 * sample out SIDE BY SIDE rather than stacked. A font's sample is a line of
 * text and wants the full width under its name; a palette's sample is a short
 * strip of colour, and stacking it would waste the row's width for nothing.
 * ------------------------------------------------------------------------- */

.msp-palette-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 220px;
  overflow-y: auto;
  padding: 2px 4px 6px;
}

.msp-palette {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 8px;
  border: 1px solid transparent;
  border-radius: var(--m-radius-sm, 2px);
  background: transparent;
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
  text-align: left;
  cursor: pointer;
  transition: background 120ms ease-out, border-color 120ms ease-out;
}

.msp-palette:hover {
  background: color-mix(in srgb, var(--mt-fg) 8%, transparent);
}

.msp-palette:focus-visible {
  outline: 2px solid var(--mt-accent);
  outline-offset: -2px;
}

.msp-palette.is-selected {
  border: 1px solid var(--mt-accent);
  color: var(--mt-fg);
}

/* A saved theme that changes fonts and no colours. Picking it would repaint the
 * card in the palette it already has, so it is greyed — at the same 35% of the
 * foreground every other greyed thing in this panel uses, with its reason on
 * the line underneath. One grey, one pattern. */
.msp-palette.is-disabled {
  color: color-mix(in srgb, var(--mt-fg) 35%, transparent);
  cursor: default;
}

.msp-palette.is-disabled:hover {
  background: transparent;
}

.msp-palette-name {
  flex: 1 1 auto;
  overflow: hidden;
  font-size: 13px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The five chips. Each one is the palette's real colour, so it is the one
 * place in this panel where a literal hex reaches the screen — and it arrives
 * as a custom property set on the element, never as a backgroundColor key in
 * an inline style object. That key is an error-severity lint failure that
 * aborts tools/deploy.sh and leaves yesterday's build live on both targets. */
.msp-swatches {
  display: flex;
  flex: 0 0 auto;
  gap: 2px;
}

.msp-swatch {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 1px solid color-mix(in srgb, var(--mt-fg) 25%, transparent);
  border-radius: var(--m-radius-sm, 2px);
  background: var(--msp-swatch-fill, var(--mt-accent));
}

/* The hairline above is not decoration. Without it, the background chip of a
 * palette that matches the dashboard's own background is an invisible gap in
 * the strip — worst on Deep Sea, which is what Karl runs, and on all three
 * light themes. */

.msp-palette-note {
  padding: 0 8px 6px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--m-text-muted);
}

/* Something true about a control that WORKS — today, only the count of colours
 * already set by hand on this card's widgets.
 *
 * Same size, same inset and same rhythm as a greyed reason, and deliberately
 * one step brighter than one: a reason belongs to a dead control and can fade
 * into it, while this line sits under a live tick-box Karl is meant to use. If
 * the two looked identical, the sentence that stops Colors from reading as
 * broken would itself read as Colors being unavailable. */
.msp-element-note {
  padding: 0 8px 4px;
  font-size: 11px;
  line-height: 1.4;
  color: color-mix(in srgb, var(--mt-fg) 60%, transparent);
}

/* Under a tick-box the same line steps in to the label's own column, so it
 * reads as belonging to that box rather than to the group. Exactly the inset
 * .msp-saved-part-reason already uses, because they sit in the same slot. */
.msp-saved-parts .msp-element-note {
  padding: 0 0 2px 22px;
}

/* === Frame / Surface / Watermark settings ================================= *
 * Deliberately NOT a second visual language. Every setting row uses the same
 * shape the font roles already established — the setting's name on the left,
 * what it is currently set to on the right — so all four elements read as one
 * list of settings rather than four little dialects. The controls themselves
 * stay quiet: this panel spends its one moment of boldness on the font list,
 * and the live card is where you look to see what a change did.
 * ------------------------------------------------------------------------- */

.msp-section {
  display: flex;
  flex-direction: column;
}

/* Nested groups (a gradient's stops, the grid's settings) step in so the
 * hierarchy is legible without a box or a rule around them. */
.msp-sub {
  display: flex;
  flex-direction: column;
  padding-left: 8px;
}

/* A named group of settings inside a section: "Frame fill", "Grid". Same
 * treatment as the panel header so the two levels of heading agree. */
.msp-sub-head {
  padding: 8px 8px 2px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--m-text-muted);
}

.msp-field {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px 8px 6px;
}

.msp-field-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.msp-field-label {
  flex: 0 0 auto;
  font-size: 13px;
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
}

/* What this setting is currently set to. Same role, same treatment and same
 * side of the row as .msp-role-current in the font list. */
.msp-field-value {
  flex: 1 1 auto;
  overflow: hidden;
  font-size: 12px;
  color: var(--m-text-muted);
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* One line of explanation under a setting whose name cannot carry the whole
 * idea (the frame's flip-direction toggle is the only one so far). */
.msp-field-hint {
  padding-left: 22px;
  font-size: 12px;
  color: var(--m-text-muted);
}

.msp-range {
  width: 100%;
  height: 16px;
  margin: 0;
  accent-color: var(--mt-accent);
  background: transparent;
  cursor: pointer;
}

.msp-range:focus-visible {
  outline: 2px solid var(--mt-accent);
  outline-offset: 2px;
}

.msp-select {
  width: 100%;
  padding: 4px 6px;
  border: 1px solid var(--mt-border-strong);
  border-radius: var(--m-radius-sm, 2px);
  background: var(--mt-bg);
  font-size: 12px;
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
  cursor: pointer;
}

.msp-select:focus-visible {
  outline: 2px solid var(--mt-accent);
  outline-offset: 1px;
}

.msp-check {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
  cursor: pointer;
}

.msp-check input {
  accent-color: var(--mt-accent);
  cursor: pointer;
}

.msp-check-label {
  flex: 1 1 auto;
}

.msp-color-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.msp-color-input {
  width: 44px;
  height: 24px;
  padding: 0;
  border: 1px solid var(--mt-border-strong);
  border-radius: var(--m-radius-sm, 2px);
  background: transparent;
  cursor: pointer;
}

.msp-color-input:focus-visible {
  outline: 2px solid var(--mt-accent);
  outline-offset: 1px;
}

/* === Saving this card's look under a name (STYLE-05 / D-05) =============== *
 * The naming step sits inside the panel rather than floating over it, so a
 * click in it is a click inside the panel and the panel's own outside-press
 * dismiss does not fire. It reads as the panel's last row growing a little,
 * which is what it is — not a second window opening on top.
 *
 * Set apart from the settings above it by a hairline and a slightly recessed
 * background, the same two devices the footer already uses. No new visual
 * idea: the one place this panel spends any boldness is the font list, and a
 * second bold element would spend it twice.
 * ------------------------------------------------------------------------- */

.msp-saved-dialog {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 4px;
  padding: 8px;
  border-top: 1px solid var(--mt-border);
  background: color-mix(in srgb, var(--mt-bg-deep) 35%, transparent);
  border-radius: var(--m-radius-sm, 2px);
}

.msp-saved-dialog-head {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--m-text-muted);
}

.msp-saved-name {
  width: 100%;
  box-sizing: border-box;
  padding: 5px 8px;
  border: 1px solid var(--mt-border-strong);
  border-radius: var(--m-radius-sm, 2px);
  background: var(--mt-bg);
  font-size: 13px;
  color: var(--mt-fg);
}

.msp-saved-name:focus-visible {
  outline: 2px solid var(--mt-accent);
  outline-offset: -1px;
}

/* A save that did not happen, said in one plain line. It is a statement of
 * fact in the interface's own voice, not an alert and not an apology, and it
 * stays put next to the box Karl is about to try again in. */
.msp-saved-message {
  font-size: 12px;
  line-height: 1.4;
  color: var(--mt-warn);
}

.msp-saved-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
}

/* === The looks already saved ============================================== *
 * Listed by name with the date they were saved underneath. The date is not
 * decoration: Karl has two saved looks called "Gold Test" in his own backup
 * history, and the date is the only thing on screen that tells them apart. An
 * id would too, but an id means nothing to the person reading it.
 * ------------------------------------------------------------------------- */

/* Collapsed by default and set off from the four element rows by a hairline:
 * this panel's first job is changing THIS card, and the saved looks are the
 * second thing you come here for, not the first. */

.msp-saved-block {
  display: flex;
  flex-direction: column;
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px solid var(--mt-border);
}

/* Deliberately the same row rhythm and hover as an element row above, so the
 * panel reads as one list rather than two stacked ideas. */
.msp-saved-head {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 28px;
  padding: 6px 8px;
  border: none;
  border-radius: var(--m-radius-sm, 2px);
  background: transparent;
  font-size: 13px;
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
  cursor: pointer;
  transition: background 120ms ease-out, color 120ms ease-out;
}

.msp-saved-head:hover {
  background: color-mix(in srgb, var(--mt-fg) 8%, transparent);
  color: var(--mt-fg);
}

.msp-saved-head:focus-visible {
  outline: 2px solid var(--mt-accent);
  outline-offset: -2px;
}

/* Capped and scrollable, the same way the font and palette lists are. Karl has
 * two saved looks today, so this is a guard rather than a fix — but the block
 * above it is now four tick rows and a heading taller, and this is the one part
 * of the panel that grows without limit as he uses the feature. */
.msp-saved-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 200px;
  overflow-y: auto;
  padding: 2px 0;
}

.msp-saved-empty {
  padding: 6px 8px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--m-text-muted);
}

.msp-saved-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  width: 100%;
  padding: 6px 8px;
  border: none;
  border-radius: var(--m-radius-sm, 2px);
  background: transparent;
  text-align: left;
  cursor: pointer;
  transition: background 120ms ease-out;
}

.msp-saved-item:hover {
  background: color-mix(in srgb, var(--mt-fg) 8%, transparent);
}

.msp-saved-item:focus-visible {
  outline: 2px solid var(--mt-accent);
  outline-offset: -2px;
}

/* The look currently on the card. Marked, not shouted about — the card itself
 * is the real answer to "which one is on", and this is only the reminder of
 * which row produced it. */
.msp-saved-item.is-chosen {
  background: color-mix(in srgb, var(--mt-accent) 15%, transparent);
}

.msp-saved-item.is-chosen .msp-saved-item-name {
  color: var(--mt-accent);
}

.msp-saved-item-name {
  font-size: 13px;
  color: var(--mt-fg);
}

.msp-saved-item-date {
  font-size: 11px;
  color: var(--m-text-muted);
}

/* The row: pick target on the left taking the slack, controls on the right at
 * their natural width.
 *
 * min-width:0 on the pick target is what lets a long name truncate instead of
 * pushing the controls off the panel — without it a flex child refuses to
 * shrink below its own content. 225.1 fixed this exact wrap on the gear row;
 * do not let it back in here. */
.msp-saved-row {
  display: flex;
  align-items: center;
  gap: 2px;
}

.msp-saved-row > .msp-saved-item {
  flex: 1 1 auto;
  min-width: 0;
}

.msp-saved-item-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* Always visible, and that is the whole point of this phase rather than a
 * styling preference: these controls were findable, then were not. A control
 * revealed on hover is unfindable on a touch screen and undiscoverable
 * everywhere else. No opacity transition, no reveal-on-hover rule — the only
 * :hover here changes colour on something already on screen. */
.msp-saved-act {
  flex: 0 0 auto;
  padding: 4px 6px;
  background: none;
  border: none;
  color: var(--m-text-muted);
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
}

.msp-saved-act:hover { color: var(--mt-fg); }
.msp-saved-act:disabled { cursor: default; opacity: 0.5; }

.msp-saved-row--editing { gap: 4px; }
.msp-saved-row--editing > .msp-saved-name { flex: 1 1 auto; min-width: 0; }

/* The question. Its text takes the slack so both answers keep their natural
 * width and neither wraps at 300px. */
.msp-saved-row--confirm { gap: 4px; padding: 2px 0; }

.msp-saved-confirm-text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  color: var(--mt-fg);
}

/* --mt-warn is what .msp-saved-message already uses, so the answer that cannot
 * be taken back and the line that reports bad news speak with one colour. */
.msp-btn--danger { color: var(--mt-warn); }

/* The eight tick-boxes that decide which parts of a chosen look get put on this
 * card. A part the chosen look does not carry greys with its reason visible,
 * the same treatment a greyed element row gets (D-06) — one pattern, not two. */

/* The heading and the All / None pair above the boxes (D-15). It sits OUTSIDE
 * .msp-saved-parts rather than inside it so its text starts at the same 8px as
 * the checkboxes below — nested, it would inherit that group's own 8px and sit
 * indented from the column it is the heading for. The group keeps its
 * accessible label either way.
 *
 * Same padding, size, weight and tracking as .msp-sub-head, because it is the
 * same kind of thing: a name over a group of settings. Eight boxes is the point
 * at which that group needs one; four did not. */
.msp-parts-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 8px 2px;
}

.msp-parts-head-label {
  flex: 1 1 auto;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--m-text-muted);
}

/* All and None. Never disabled: pressing All when everything is already ticked
 * does nothing, which is a quieter answer than a greyed button that leaves you
 * working out why it is greyed. */
.msp-parts-head-actions {
  display: flex;
  flex: 0 0 auto;
  gap: 4px;
}

.msp-saved-parts {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px 8px 6px;
}

.msp-saved-part.is-disabled {
  color: color-mix(in srgb, var(--mt-fg) 35%, transparent);
  cursor: default;
}

.msp-saved-part-reason {
  padding: 0 0 2px 22px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--m-text-muted);
}

/* === Footer: three distinct actions (D-05) ================================ *
 * "Save as Style…" sits apart from Save/Cancel because it does a different
 * job — it captures this card's whole look under a name for use on other
 * cards. One button doing both would accumulate a named Style every time
 * Karl nudged one card's border.
 * ------------------------------------------------------------------------- */

.msp-footer {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  padding: 8px 4px 2px;
  border-top: 1px solid var(--mt-border);
}

.msp-footer-spacer {
  flex: 1 1 auto;
}

.msp-btn {
  padding: 5px 10px;
  border: 1px solid var(--mt-border-strong);
  border-radius: var(--m-radius-sm, 2px);
  background: transparent;
  font-size: 12px;
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
  cursor: pointer;
  transition: background 120ms ease-out, color 120ms ease-out;
}

.msp-btn:hover {
  background: color-mix(in srgb, var(--mt-fg) 8%, transparent);
  color: var(--mt-fg);
}

.msp-btn:focus-visible {
  outline: 2px solid var(--mt-accent);
  outline-offset: 1px;
}

/* An action that is not built yet. Greyed and non-interactive, with its reason
 * on the control itself — never a button that looks live and does nothing. */
.msp-btn.is-disabled {
  color: color-mix(in srgb, var(--mt-fg) 35%, transparent);
  border: 1px solid color-mix(in srgb, var(--mt-border-strong) 50%, transparent);
  cursor: default;
}

.msp-btn.is-disabled:hover {
  background: transparent;
  color: color-mix(in srgb, var(--mt-fg) 35%, transparent);
}

/* The one inline action inside a settings row ("Use the theme color"). */
.msp-btn--small {
  padding: 3px 8px;
  font-size: 11px;
}

.msp-btn--primary {
  border: 1px solid var(--mt-accent);
  color: var(--mt-accent);
}

.msp-btn--primary:hover {
  background: color-mix(in srgb, var(--mt-accent) 15%, transparent);
  color: var(--mt-accent);
}

/* === GO BACK: the way back, kept apart from the way forward (D-11) ======== *
 * "Give this card that look" and "throw this card's look away" are opposite
 * actions, and putting the second one in the footer beside the first is how
 * somebody presses the wrong one. Three things keep them apart, and all three
 * are load-bearing rather than decorative:
 *
 *   BELOW the footer, behind its own rule — a second block of the same kind,
 *   which is why the border, the margin and the padding here match .msp-footer
 *   rather than being chosen. The two sit one above the other and have to read
 *   as two blocks of the same kind.
 *
 *   FULL WIDTH AND STACKED, not the footer's right-aligned row. A different
 *   shape is what the eye catches before it has read anything.
 *
 *   NEITHER IS THE PANEL'S PRIMARY, AND NOTHING HERE IS RED. Both keep plain
 *   .msp-btn. The panel's one primary action is Save, and a second accent
 *   button in the same 300px column makes neither of them the primary. Red is
 *   wrong for a different reason: a confirm stands in front of both wider
 *   choices, one Ctrl-Z takes the whole thing back and the strip afterwards
 *   carries an Undo, so a red button would say "this might not come back",
 *   which is false. --m-error appears nowhere in this phase.
 *
 * NOTHING IN HERE IS position: sticky OR position: fixed. The panel's own fixed
 * position plus the inline max-height fitPanelBox writes are what keep it on
 * screen; a sticky block inside a capped scroller pins over the content it
 * heads, and this block is the furthest thing down a 300px column that already
 * scrolls inside itself on a short screen.
 * ------------------------------------------------------------------------- */

.msp-goback {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 4px;
  padding: 6px 4px 2px;
  border-top: 1px solid var(--mt-border);
}

/* Identical to .msp-scope-head, .msp-sub-head and .msp-parts-head-label — the
 * same kind of thing, a name over a group, so it takes the same type rather
 * than a new one. This phase introduces no new size anywhere. */
.msp-goback-head {
  padding: 8px 8px 2px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--m-text-muted);
}

/* `Based on Midnight · edited` (D-15). Same 11px slot as .msp-scope-note, and
 * the same truncation as the button below it for the same reason: the name is
 * user-supplied with no length cap, and a 60-character one would wrap the line
 * and shift everything under it.
 *
 * IT IS NOT MUTED, AND THAT IS THE POINT. The suffix below is, and the step
 * between the two is what says ` · edited` is a note ABOUT the name rather than
 * part of it. Take the colour off this rule and the two collapse into one
 * sentence that reads as a Style called "Midnight · edited". The 85% is
 * .msp-element-row's — the panel's ordinary text colour, not a new one. */
.msp-basedon {
  padding: 2px 8px 6px;
  font-size: 11px;
  line-height: 1.4;
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.msp-basedon-edited {
  color: var(--m-text-muted);
}

/* Full width and stacked. The truncation is not fussiness: `Back to <name>`
 * carries a user-supplied Style name with no length cap on the save dialog, and
 * a 60-character name would wrap a stacked button to two lines and shift every
 * row under it — including, on a short screen, out of the panel's capped
 * scroller. It truncates instead. */
.msp-goback-btn {
  width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Why a greyed button is greyed, as VISIBLE TEXT under it — never a title,
 * never a tooltip. 221 D-06's standing rule: a reason you have to hover to find
 * does not answer the question. Same 11px muted wrapping slot as every other
 * reason in this panel. */
.msp-goback-reason {
  padding: 0 8px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--m-text-muted);
}

/* BACKLOG-05. Not a reason — a report. The panel's refusal line and its
   "here is what just happened" line are two different jobs and they get two
   different treatments, because one line doing both is the whole defect.

   NO GREEN AND NO ACCENT, deliberately. Green means committed, and this change
   is NOT committed — it lives in a draft and dies on Cancel. A tick would push
   exactly the wrong way. The accent in this panel is reserved for selection and
   focus (.msp-family.is-selected, every focus ring, accent-color on the boxes)
   and spending it here would break that reservation.

   The one device is a CONTRAST STEP, and it is borrowed rather than invented:
   .msp-basedon already uses this exact 85% value to say "this line is ordinary
   text, not a muted note", against .msp-goback-reason's --m-text-muted. Same
   11px slot, same 1.4, same 8px column as every other line in this block. This
   phase introduces no new size and no new colour. */
.msp-goback-done {
  padding: 0 8px;
  font-size: 11px;
  line-height: 1.4;
  color: color-mix(in srgb, var(--mt-fg) 85%, transparent);
}

/* The nudge half. Weight and nothing else — the panel's own way of saying
   "this one" (.msp-goback-head, .manifold-gear-theme-row.is-previewing both
   use 600 alone). It is a separate span because it does a separate job:
   the sentence before it reports, this one asks. */
.msp-goback-done-cta {
  font-weight: 600;
}

/* Someone who has asked their system to reduce motion should not get the
 * disclosure rotation or the hover fades. */
@media (prefers-reduced-motion: reduce) {
  .msp-scope-seg,
  .msp-locked-seg,
  .msp-element-row,
  .msp-role-row,
  .msp-family,
  .msp-palette,
  .msp-btn,
  .msp-goback-btn,
  .msp-select,
  .msp-saved-head,
  .msp-saved-item,
  .msp-tri {
    transition: none;
  }
}
