/*
 * Direction C — "Document + margin", at desk distance rather than gym distance,
 * so unlike the board this is sized in rem and is light by default.
 *
 * Layout: a fixed header, then a scrolling `.stage` holding a centred `.sheet`
 * grid — the editor (`.doc-col`, Outline / Text as two tabs) beside a tabbed
 * margin (`.right-col`): a sticky summary band, an [Analysis][Reads as] tab
 * strip, and one bounded, internally-scrolling panel per tab. The movement
 * palette is a summoned `.palette-modal` (⌘K / add-row), not a docked column.
 */

/*
 * Legibility floor (2026-09-02): secondary text is dimmed by COLOUR, never by
 * `opacity`. `--dim` on `--page` / `--panel` clears WCAG AA 4.5:1 in both
 * themes (~4.8:1 light, ~6.3:1 dark), an `opacity` multiplier on top of it does
 * not. `opacity` stays only on things that are not body text: the row-affordance
 * controls (`.node .tools` / `.node .caret` — persistent at a low `opacity`,
 * brightening to full on `.node:hover` / `.node:focus-within`, never fully
 * hidden), the drag ghost, the cycle rails, and `:disabled`. And no text renders
 * below .75rem (12px) — smaller secondary text is still allowed to be smaller,
 * just not sub-12px.
 */
:root {
  --ink: #14161a;
  --dim: #6b7180;
  --page: #fbfbfa;
  --panel: #ffffff;
  --rule: #e3e4e8;
  --accent: #1f6feb;
  --warn: #b3541e;
  --error: #c0392b;
  --ask: #7d4bbd;
  /* The nesting rail in the Outline carries block structure, so it has to be
     perceptible against both --page and --panel — unlike --rule, a hairline
     divider that is deliberately far fainter. Keep --rail clearly darker than
     --rule here and clearly lighter than it in the dark block. */
  --rail: #a3a8b4;
  /* One colour per Outline row kind — a thin left marker on the row, and (R9)
     the Text tab's syntax highlight, so the two views read as one language.
     R9's highlighter consumes these exact tokens; name them for the line kind,
     never the hue. Defined in both themes. */
  --kind-title: #7048e8;
  --kind-directive: #868e96;
  --kind-section: #2f9e44;
  --kind-header: #e8590c;
  --kind-step: #1971c2;
  --kind-rest: #0c8599;
  --kind-comment: #adb5bd;
  /* A cycling palette for repeating blocks — distinct from --accent (which
     stays cycle 0's colour, so a single cycle looks unchanged) and from
     --warn/--error/--ask, which carry their own meaning and would be a false
     alarm if reused here. */
  --cycle-2: #1a7f5c;
  --cycle-3: #a6790a;
  --cycle-4: #b8348f;
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ink: #eceef2;
    --dim: #939aa8;
    --page: #14161a;
    --panel: #1a1d22;
    --rule: #2b2f38;
    --accent: #6ea8ff;
    --warn: #e0a45e;
    --error: #ff7a68;
    --ask: #b79be8;
    --rail: #565c6a;
    --kind-title: #b197fc;
    --kind-directive: #909aa8;
    --kind-section: #69db7c;
    --kind-header: #ffa94d;
    --kind-step: #74c0fc;
    --kind-rest: #66d9e8;
    --kind-comment: #6c7686;
    --cycle-2: #4ddb9e;
    --cycle-3: #e8c34a;
    --cycle-4: #ff8fd0;
    color-scheme: dark;
  }
}

* { box-sizing: border-box; }

/* `hidden` must win over any author `display` (the per-row `modCaret` /
   `blockCaret` / movement-caret are hidden by the attribute, and several rules
   below set `display` on `.caret` / `.tools`). Written as the rule, not a list
   of the selectors that currently collide — see lessons-learned. */
[hidden] { display: none !important; }

body {
  margin: 0;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--page);
  color: var(--ink);
  font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
}

/* --- header --------------------------------------------------------------- */

header {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .55rem 1rem;
  border-bottom: 1px solid var(--rule);
  background: var(--panel);
}

.mark { font-size: .78rem; font-weight: 700; letter-spacing: .12em; color: var(--dim); }

/* The document title, read off `parse(text()).workout.title` and repainted by
   `render()`. Centred, and allowed to shrink before the controls do. */
.doc-title {
  flex: 1;
  min-width: 0;
  text-align: center;
  font-size: .9rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.file-cluster {
  display: flex;
  gap: .3rem;
  padding: .15rem .3rem;
  border: 1px solid var(--rule);
  border-radius: .4rem;
}

header .identity { flex: none; }

header button, header .button { white-space: nowrap; }

/* --- the stage and the sheet ------------------------------------------------ */

.stage {
  flex: 1;
  min-height: 0;
  overflow: auto;
  display: flex;
  justify-content: center;
}

.sheet {
  width: 100%;
  max-width: 1600px;
  height: 100%;
  min-height: 0;
  display: grid;
  /* editor | splitter | margin. The margin width is a drag handle away — JS
     sets `--right-col-w` on this element and persists it; without a stored
     value the per-breakpoint fallback below stands. The editor keeps a 22rem
     floor so a drag can't crush it. */
  grid-template-columns: minmax(22rem, 1fr) 6px var(--right-col-w, 24rem);
}

.doc-col, .right-col {
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.right-col { background: var(--panel); }

/* The editor/margin splitter. A 6px track carrying the hairline that used to be
   `.doc-col`'s right border; the whole width is the grab target. */
.col-resize {
  border-left: 1px solid var(--rule);
  background: transparent;
  cursor: col-resize;
  touch-action: none;
}
.col-resize:hover,
.col-resize.dragging { background: var(--accent); border-left-color: var(--accent); }
.col-resize:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

body.col-resizing { cursor: col-resize; user-select: none; }

/* h2 bars, in the margin panel and the palette modal — the same quiet
   uppercase label treatment. */
.right-col h2, .palette-modal h2 {
  margin: 0;
  padding: .5rem 1rem;
  border-bottom: 1px solid var(--rule);
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--dim);
}
/* Inside the scrolling Analysis panel the section bars need a little air above
   them and no full-bleed borders fighting the panel padding. */
#view-analysis h2 {
  padding: .6rem 0 .4rem;
  border-bottom: 1px solid var(--rule);
  margin-top: .4rem;
}
#view-analysis h2:first-child { margin-top: 0; }

.dirty { color: var(--accent); text-transform: none; letter-spacing: 0; font-weight: 400; }

/* --- the editor: Outline / Text as two tabs of one cell ------------------- */

/* The tab strip stands in for a pane `<h2>` bar, so it carries the same
   metrics. `Canonicalise` rides on the end of it — it acts on the editor
   buffer, so it belongs with the editor rather than in the top-level nav. */
.editor-tabs { display: flex; align-items: stretch; border-bottom: 1px solid var(--rule); }

.editor-tab {
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  background: transparent;
  padding: .5rem 1rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--dim);
  cursor: pointer;
}

.editor-tab:hover { color: var(--ink); border-bottom-color: var(--rule); }
.editor-tab[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--accent); }

.toolbar-btn {
  margin: .3rem .5rem .3rem auto;
  align-self: center;
  font-size: .75rem;
  padding: .25rem .6rem;
}

/* One view shows; the other is `display: none` so it reserves no height.
   Each is a flex column so `#outline` (scrolls) and `#text` fill it. */
.editor-view { display: none; flex: 1; min-height: 0; flex-direction: column; }
.editor-view.active { display: flex; }

/* --- outline -------------------------------------------------------------- */

.outline { flex: 1; min-height: 0; overflow: auto; padding: .75rem 0 1.5rem; }

/* The Outline reads as a document, not an IDE gutter: a generous row rhythm,
   the `#` title set as a heading, the `@` directives as a dim preamble block
   above the workout body. The inputs stay exactly as editable as before — this
   is presentation only, the write-back path is unchanged. */
.node {
  display: flex;
  align-items: center;
  gap: .35rem;
  padding: .2rem 1rem .2rem 0;
}

/* Nesting is shown with a rule per level rather than blank space, so the shape
   of a block is visible when its header has scrolled out of view. The rail
   carries meaning, so it takes --rail (perceptible against the sheet) rather
   than the --rule hairline; stretched to the row's full height so the levels
   read as continuous lines down the document. */
.node .rails { display: flex; flex: none; align-self: stretch; }
.node .rail { width: 1.1rem; border-left: 1px solid var(--rail); }
.node .rail:first-child { border-left: 0; width: .9rem; }

/* The per-kind marker: a thin coloured edge on the row's wrap, one --kind-*
   colour per line kind (set from `.node[data-kind]`). Orthogonal to the rails —
   they carry nesting, this carries what the line *is* — and quiet by design: an
   edge, not a full-row wash. Survives `.node.bad`, so a broken step still reads
   as a step (the marker) that is broken (the red input border). */
.node .line-wrap {
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  border-left: 3px solid transparent;
  padding-left: .45rem;
}

/* A transient one-liner under the input after a per-implement load pick,
   explaining the seeded `@ Nx` until the weight is typed (see `showModHint`).
   Full-width so it breaks below the input; the panels above it are all
   position:absolute, so this is the only wrapping child. */
.node .line-wrap .mod-hint {
  flex-basis: 100%;
  margin: .15rem 0 0;
  font: 400 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--accent);
}
.node[data-kind="title"]     .line-wrap { border-left-color: var(--kind-title); }
.node[data-kind="directive"] .line-wrap { border-left-color: var(--kind-directive); }
.node[data-kind="section"]   .line-wrap { border-left-color: var(--kind-section); }
.node[data-kind="header"]    .line-wrap { border-left-color: var(--kind-header); }
.node[data-kind="step"]      .line-wrap { border-left-color: var(--kind-step); }
.node[data-kind="rest"]      .line-wrap { border-left-color: var(--kind-rest); }
.node[data-kind="comment"]   .line-wrap { border-left-color: var(--kind-comment); }

.node input.line {
  flex: 1;
  min-width: 0;
  padding: .35rem 4.4rem .35rem .5rem;
  border: 1px solid transparent;
  border-radius: .3rem;
  background: transparent;
  color: inherit;
  font: 400 13.5px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
}

.node input.line:hover { border-color: var(--rule); }
.node input.line:focus { outline: none; border-color: var(--accent); background: var(--panel); }

.node.block input.line { font-weight: 650; }
.node.comment input.line { color: var(--dim); font-style: italic; }
.node.bad input.line { border-color: var(--error); }

/* The title row is the document's heading — larger and heavier, with air under
   it before the preamble or the body begins. */
.node[data-kind="title"] { padding-bottom: .4rem; }
.node[data-kind="title"] input.line { font-size: 1.05rem; font-weight: 700; letter-spacing: .01em; }

/* The `@` directives describe the workout rather than being part of it — a dim
   preamble block, sitting a notch under the title and above the body. */
.node[data-kind="directive"] { padding-top: .04rem; padding-bottom: .04rem; }
.node[data-kind="directive"] input.line { color: var(--dim); font-size: .82rem; }
.node[data-kind="title"] + .node[data-kind="directive"] { margin-top: .1rem; }
.node[data-kind="directive"] + .node:not([data-kind="directive"]) { margin-top: .7rem; }

/* The movement caret opens a live-filtered list of curated movements matching
   whatever is being typed, in place of the browser's native (and useless, since
   a row's value is never just a movement name) datalist popup. Persistent at a
   low opacity so a first-time user sees it before touching the row (and a touch
   user, who has no hover, can reach it); the reveal rule below brings the whole
   affordance set to full strength on row hover / focus. A ≥32px hit box (glyph
   stays small) for WCAG 2.2 target size — the box is centred on the row's right
   edge. Per-row relevance is still the `hidden` attribute, set in JS. */
.node .caret {
  position: absolute;
  right: .1rem;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--dim);
  font-size: .65rem;
  line-height: 1;
  border-radius: .2rem;
  cursor: pointer;
}

.node .caret:hover { background: var(--rule); color: var(--ink); }

/* The modifier / block caret sits left of the movement caret, same treatment,
   and is hidden per-row (not just faded) when the row names no curated movement
   or block — see `movementModifiers` / `blockOf`. Offset by one hit box so the
   two 32px targets do not overlap. */
.node .caret.modcaret { right: 2.1rem; }

/* One reveal rule for the whole row-affordance set: dim-but-present at rest,
   full on row hover and on any focus into the row (a keyboard user tabbing the
   tool strip, or a touch user who has tapped a control — the buttons take focus
   on tap even at tabindex -1, so `:focus-within` covers the no-hover case). */
.node .tools,
.node .caret { opacity: .6; transition: opacity .12s ease; }
.node:hover .tools,
.node:focus-within .tools,
.node:hover .caret,
.node:focus-within .caret { opacity: 1; }

.node .suggestions {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  z-index: 20;
  margin-top: .2rem;
  max-height: 12rem;
  overflow: auto;
  padding: .25rem;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: .35rem;
  box-shadow: 0 .4rem 1rem rgba(0, 0, 0, .3);
}

.node .suggestions .suggestion {
  display: block;
  width: 100%;
  text-align: left;
  padding: .3rem .5rem;
  border: 0;
  border-radius: .25rem;
  background: transparent;
  color: inherit;
  font: 400 13px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  cursor: pointer;
}

.node .suggestions .suggestion:hover,
.node .suggestions .suggestion.active { background: var(--rule); }

.node .suggestions .modifier-info {
  padding: .3rem .5rem;
  color: var(--dim);
  font: 400 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  border-top: 1px solid var(--rule);
  margin-top: .2rem;
}

/* X2: the "not in the database" flag at the top of the `»` panel — reuses
   `.modifier-info`'s dim treatment but anchors to the top, not the bottom. */
.node .suggestions .db-note {
  border-top: 0;
  border-bottom: 1px solid var(--rule);
  margin: 0 0 .2rem;
}

/* Reorder / indent / clone / delete. Persistent at the shared low opacity (the
   reveal rule is up by `.caret`); ≥32px targets for WCAG 2.2. */
.node .tools { display: flex; gap: .1rem; flex: none; }

.node .tools button {
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: .25rem;
  background: transparent;
  color: var(--dim);
  font-size: .85rem;
  line-height: 1;
  cursor: pointer;
}

.node .tools button:hover { background: var(--rule); color: var(--ink); }

.add-row { display: flex; gap: .4rem; padding: .6rem 1rem; border-top: 1px solid var(--rule); }

/* --- text ----------------------------------------------------------------- */

/* The Text tab is a transparent <textarea> over a syntax-highlight layer, so
   paste / undo / spellcheck / IME all keep working — the real control is still
   a textarea. The layer (#text-hl) holds the same text, re-tokenised on every
   input, one --kind-* colour per line kind (shared with the Outline's rails, so
   the two views read as one language). The two elements MUST agree on every
   text-metric property or the caret drifts off the glyphs — hence the shared
   rule below. `scrollbar-gutter` is reserved on both so a vertical scrollbar
   does not rewrap one and not the other. */
.text-wrap {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

#text, #text-hl {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: .75rem 1rem;
  border: 0;
  font: 400 13.5px/1.65 ui-monospace, SFMono-Regular, Menlo, monospace;
  tab-size: 2;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  scrollbar-gutter: stable;
}

#text {
  background: transparent;
  color: transparent;
  caret-color: var(--ink);
  resize: none;
  overflow: auto;
}

/* Transparent glyphs would take the selection colour with them; keep a visible
   wash so a drag-select still reads. */
#text::selection { background: color-mix(in srgb, var(--accent) 25%, transparent); }
#text:focus { outline: none; }

#text-hl {
  overflow: hidden;
  pointer-events: none;
  color: var(--ink);
}

#text-hl .k-title     { color: var(--kind-title); }
#text-hl .k-directive { color: var(--kind-directive); }
#text-hl .k-section   { color: var(--kind-section); }
#text-hl .k-header    { color: var(--kind-header); }
#text-hl .k-step      { color: var(--kind-step); }
#text-hl .k-rest      { color: var(--kind-rest); }
#text-hl .k-comment   { color: var(--kind-comment); font-style: italic; }

/* --- the margin: summary band + tabbed panels --------------------------- */

/* The band spans the top of the margin and stays put while a panel scrolls
   under it. `title / cues / duration / cap / score` as a compact <dl>. */
.band {
  position: sticky;
  top: 0;
  z-index: 2;
  flex: none;
  border-bottom: 1px solid var(--rule);
  background: var(--panel);
}

/* A workout with several repeating blocks grows a `cues N–M repeat` row per
   cycle; past a few they should scroll inside the band rather than pushing the
   tabs and the analysis panel down. */
.summary { padding: .6rem 1rem; font-size: .82rem; max-height: 9rem; overflow-y: auto; }
.summary dl { display: grid; grid-template-columns: auto 1fr; gap: .1rem .75rem; margin: 0; }
.summary dt { color: var(--dim); }
.summary dd { margin: 0; font-variant-numeric: tabular-nums; }
.summary:empty { display: none; }

/* --- the directive metadata block ---------------------------------------------
   The `@key value` preamble read off `workout.directives`. It *describes* the
   workout, so it reads as a bordered aside above the cue list, with the shared
   `--kind-directive` accent the Outline and Text tabs give a directive line.
   Read-only; hidden entirely for a workout that sets no directives. */
.directives {
  margin-bottom: .7rem;
  padding: .45rem .6rem;
  font-size: .8rem;
  border: 1px solid var(--rule);
  border-left: 3px solid var(--kind-directive);
  border-radius: .3rem;
  background: var(--page);
}
.directives dl { display: grid; grid-template-columns: auto 1fr; gap: .15rem .7rem; margin: 0; }
.directives dt { color: var(--dim); }
.directives dd { margin: 0; color: var(--ink); overflow-wrap: anywhere; }
.directives .chips { margin: 0; }
.directives .chip { cursor: default; }

.right-tabs { display: flex; gap: .25rem; padding: .4rem .75rem 0; border-bottom: 1px solid var(--rule); flex: none; }
.right-tab {
  border: 1px solid var(--rule);
  border-bottom: 0;
  border-radius: .35rem .35rem 0 0;
  background: var(--page);
  padding: .35rem .8rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--dim);
  cursor: pointer;
}
.right-tab[aria-selected="true"] { color: var(--ink); background: var(--panel); }

/* The panels: only one shows, and it is the bounded scroll container the
   read pane never had — a long analysis scrolls here instead of pushing the
   page. */
.rpanel { display: none; flex: 1; min-height: 0; overflow: auto; padding: .75rem 1rem 1.5rem; }
.rpanel.active { display: block; }
.placeholder { color: var(--dim); font-size: .85rem; }

/* --- the run: cue list (Reads-as tab, second subsection) -------------- */

.steps .cue { display: flex; gap: .5rem; padding: .1rem 0; }
.steps .cue .n { color: var(--dim); font-variant-numeric: tabular-nums; min-width: 1.6rem; text-align: right; }
.steps .cue.rest { color: var(--dim); }
/* A half-line between rounds, set in JS on the first cue of each new round. */
.steps .cue.round-start { margin-top: .5rem; }

/* Cues inside a cycle are marked by a rail down their left, closed at both
   ends: the shape says "these come round again" without a word per row.
   Each cycle in the workout gets its own colour from a small cycling
   palette (`--rail`, set per row by `.cycle-hue-N`) — otherwise two
   repeating blocks back to back read as one continuous cycle. */
.steps .cue.looping { position: relative; --rail: var(--accent); }
.steps .cue.cycle-hue-1 { --rail: var(--cycle-2); }
.steps .cue.cycle-hue-2 { --rail: var(--cycle-3); }
.steps .cue.cycle-hue-3 { --rail: var(--cycle-4); }
.steps .cue.looping::before {
  content: ''; position: absolute; left: 1.9rem; top: 0; bottom: 0;
  border-left: 2px solid var(--rail); opacity: .5;
}
.steps .cue.cycle-start::before { top: .35rem; border-top-left-radius: 3px; }
.steps .cue.cycle-end::before { bottom: .35rem; border-bottom-left-radius: 3px; }
.steps .cue.cycle-start::after,
.steps .cue.cycle-end::after {
  content: ''; position: absolute; left: 1.9rem; width: .4rem;
  border-top: 2px solid var(--rail); opacity: .5;
}
.steps .cue.cycle-start::after { top: .35rem; }
.steps .cue.cycle-end::after { bottom: .35rem; }

/* --- the run: compressed reading (Reads-as tab) ----------------------- */

.reads { font-size: .9rem; line-height: 1.5; }
.reads-group { margin: 0 0 .7rem; }
.reads-group .reads-group {
  margin: .35rem 0 .35rem .9rem;
  padding-left: .7rem;
  border-left: 2px solid var(--rule);
}
.reads-head { font-weight: 600; color: var(--ink); }
.reads-body { color: var(--dim); margin-top: .1rem; }
.reads-line { color: var(--ink); padding: .05rem 0; }
.reads-line.rest { color: var(--dim); }
/* The second subsection: a quiet label, then the full per-cue list under it. */
.reads-cues-head {
  margin-top: .8rem;
  padding-top: .6rem;
  border-top: 1px solid var(--rule);
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--dim);
}
.reads-cues { margin-top: .3rem; }

.diagnostics { margin: .4rem 0 0; padding: 0; list-style: none; font-size: .85rem; }
.diagnostics li { padding: .35rem 0; border-top: 1px solid var(--rule); }
.diagnostics .where { color: var(--dim); font-variant-numeric: tabular-nums; }
.diagnostics .error { color: var(--error); }
.diagnostics .warning { color: var(--warn); }
.diagnostics .question { color: var(--ask); }
/* A `note` says what the language did not read. Nothing is wrong and nothing is
   being asked, so it takes the dim colour the margin uses rather than one of the
   three that mean something is up — visible, and claiming nothing. */
.diagnostics .note { color: var(--dim); }
.diagnostics .candidates { color: var(--dim); padding-left: 1rem; }

/* --- controls -------------------------------------------------------------- */

button, .button {
  padding: .35rem .7rem;
  border: 1px solid var(--rule);
  border-radius: .35rem;
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: .82rem;
  text-decoration: none;
  cursor: pointer;
}

button:hover, .button:hover { border-color: var(--dim); }
.button { display: inline-flex; align-items: center; }

/* --- responsive --------------------------------------------------------- */

/* The margin narrows before it stacks. Lower fallback width; a stored
   `--right-col-w` still overrides it, and the editor floor relaxes to 0. */
@media (max-width: 1100px) {
  .sheet { grid-template-columns: minmax(0, 1fr) 6px var(--right-col-w, 20rem); }
}

/* Narrow: one column. The doc column and the margin panel each keep a bounded
   height and their own scroll, and the stage scrolls the pair. The palette
   modal is centred at every width, so nothing about it changes here. */
@media (max-width: 820px) {
  .sheet { grid-template-columns: 1fr; height: auto; max-width: 40rem; }
  .col-resize { display: none; }
  .doc-col { border-bottom: 1px solid var(--rule); }
  .doc-col, .right-col { overflow: visible; }
  .outline { max-height: 52vh; }
  .text-wrap { min-height: 40vh; }
  .rpanel { max-height: 78vh; }
  .band { position: static; }
}

/* On a phone the 32px tool strip (six buttons) would crush the line input to a
   few characters. Drop it to its own full-width row under the input — the
   targets stay big (the point, for touch), the input gets the whole width, and
   vertical space is the cheap axis here. The two carets stay on the input row,
   over its right padding. */
@media (max-width: 560px) {
  .node { flex-wrap: wrap; }
  .node .tools { flex-basis: 100%; justify-content: flex-end; }
}

/* --- the movement palette modal ------------------------------------------- */

/* Summoned by ⌘K / "+ movement" / Enter on a blank outline row — a centred
   panel, not a docked column. Same inner DOM the drawer held. */
.palette-modal {
  width: min(30rem, 92vw);
  max-height: min(40rem, 84vh);
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: var(--panel);
  color: var(--ink);
  box-shadow: 0 1rem 3rem rgba(0, 0, 0, .3);
}
.palette-modal[open] {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.palette-modal::backdrop { background: rgba(0, 0, 0, .3); }

.palette-modal h2 { display: flex; align-items: center; }
.palette-close {
  margin-left: auto;
  border: 0;
  background: transparent;
  color: var(--dim);
  font-size: .9rem;
  line-height: 1;
  padding: .1rem .3rem;
  cursor: pointer;
}
.palette-close:hover { color: var(--ink); }

.palette-modal > :not(h2):not(#palette) { flex: none; margin: 0 1rem; }
.palette-modal .palette-controls { margin-top: .6rem; }

.palette-controls { display: grid; gap: .35rem; margin-bottom: .4rem; }
.palette-controls input,
.palette-controls select { width: 100%; font: inherit; padding: .3rem .4rem; }
.row.tight { display: flex; flex-wrap: wrap; gap: .35rem; }
/* Two buttons share the width; four in a narrow column would each get a quarter
   and wrap their labels mid-phrase, so only a pair is stretched. */
.row.tight:not(.goal-tools):not(.repertoire-tools) button { flex: 1; }

.hint { margin: 0 0 .4rem; font-size: .75rem; color: var(--dim); }

.palette { flex: 1; min-height: 0; overflow-y: auto; display: grid; gap: 2px; align-content: start; padding: 0 1rem 1rem; }

/* The name hugs the left; the muscle meta and any trailing glyphs are pushed to
   the right by `.meta { margin-left: auto }` — NOT by `justify-content:
   space-between`, which distributed the slack *around* the name and left it
   floating mid-row (dead-centre in compose mode, where the row is tick · name ·
   meta). See `.meta` / `.db-mark` below. */
.movement {
  display: flex; align-items: baseline; gap: .5rem;
  text-align: left; font: inherit; padding: .25rem .45rem;
  border: 1px solid transparent; border-radius: 3px; background: none; cursor: grab;
}
.movement:hover { border-color: currentColor; }
.movement:active { cursor: grabbing; }
.movement .name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.movement .meta { margin-left: auto; padding-left: .5rem; font-size: .75rem; color: var(--dim); white-space: nowrap; }
/* `.tick` is a state glyph (＋ / ✓), not text — opacity is its off/on. */
.movement .tick { flex: none; width: 1em; text-align: center; opacity: .4; font-size: .8em; }
.movement.in-set { border-color: currentColor; }
.movement.in-set .tick { opacity: 1; }
/* P2: the card for the movement a search resolves to, pinned above the folded
   list. The `↳ matches "…"` marker sits between the name and the muscle meta. */
.movement.pinned { border-color: var(--accent); }
.movement .pin-mark { flex: none; font-size: .7rem; color: var(--accent); white-space: nowrap; }

/* X2: a quiet "not in the movement database" marker. A DB-backed movement has
   none. `●` curated-static (in registry/movements.json, no DB row yet — worth
   promoting); `○` canonical-only (only in the 5-source merge). Dimmed by
   colour, not a new token; a glyph rather than body text, and aria-hidden (the
   card `title` carries the words), so .75rem is a fine floor and the opacity on
   the fainter tier is the same class of use as `.tick`. It packs tight to the
   name on its own (the right cluster is pushed away by `.meta`'s auto margin);
   a card without one is unchanged. Non-interactive — no hit-target rule. */
.movement .db-mark { flex: none; font-size: .75rem; line-height: 1; color: var(--dim); }
.movement .db-mark--canonical-only { opacity: .55; }

.compose-hint { color: var(--accent); }

/* --- the repertoire of sets --------------------------------------------- */

/* A first-class section at the top of the modal: a full-bleed "Sets" bar in the
   same treatment as the "Movements" head, its whole-repertoire tools inline on
   the right, then the (scrolling) list of set cards. When a set is open it is
   the subject of the dialog; the movement search sits below it. */
#palette-modal > #repertoire {
  margin: 0;
  border-bottom: 1px solid var(--rule);
  font-size: .85em;
}
.repertoire-head {
  display: flex; align-items: center; flex-wrap: wrap; gap: .3rem .5rem;
  border-bottom: 1px solid var(--rule);
  padding-bottom: .35rem;
}
.repertoire-head h2 {
  margin: 0; flex: 1 1 6rem; border: 0;   /* the section border is on .repertoire-head */
  padding-bottom: 0;
}
.repertoire h2 span { color: var(--dim); font-weight: 400; letter-spacing: 0; }

/* "Act on the whole repertoire" — New set / Save showing as primary buttons,
   Import · Export as a low-key pair (DEC-027: the movement-sets.json carrier
   stays, kept understated). */
.repertoire-tools { display: flex; align-items: center; flex-wrap: wrap; gap: .3rem; padding-right: 1rem; }
.repertoire-tools > button {
  font: inherit; font-size: .72rem; line-height: 1;
  padding: .28rem .5rem; border-radius: 3px;
}
.repertoire-file { display: flex; gap: .5rem; margin-left: .3rem; padding-left: .5rem; border-left: 1px solid var(--rule); }
.repertoire-file .link { font-size: .72rem; }

.set-list {
  display: grid; gap: .3rem;
  margin: .5rem 1rem;
  max-height: 13rem; overflow-y: auto;
}
.set-card { border: 1px solid var(--rule); border-radius: 4px; background: var(--panel); }
.set-card[data-drop] { outline: 2px solid var(--accent); outline-offset: -2px; }
.set-card.composing { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
/* A set you cannot edit (org-library, or another person's public one) reads as
   reference, not a workbench: recessed ground, dimmed name, a LIBRARY tag. */
.set-card.readonly { background: color-mix(in srgb, var(--ink) 4%, var(--panel)); }
.set-card.readonly .set-name { color: var(--dim); font-weight: 500; }

/* Fixed count + action columns so the numbers and the glyph cluster line up
   straight down the list — an editable card (five actions) and a read-only one
   (just ↧) share the same right-hand geometry. */
.set-card > summary {
  display: grid; align-items: center;
  grid-template-columns: minmax(0, 1fr) 1.6rem 8.6rem;
  column-gap: .4rem;
  padding: .35rem .5rem; cursor: pointer; list-style: none;
}
.set-card > summary::-webkit-details-marker { display: none; }
.set-card .set-name {
  font-weight: 600; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The composing card edits its name in place — "New set" lands here, not in a
   prompt(). Fills the name column of the summary grid. */
.set-card > summary .set-name-input {
  font: inherit; font-weight: 600;
  width: 100%; min-width: 0;
  margin: -.15rem 0; padding: .1rem .35rem;
  border: 1px solid var(--accent); border-radius: 3px;
  background: var(--panel); color: var(--ink);
}
#set-done { border-color: var(--accent); color: var(--accent); }
.set-tag {
  font-size: .62rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--dim); border: 1px solid var(--rule); border-radius: 3px;
  padding: 0 .3rem; margin-left: .4rem; font-weight: 600;
}
.set-card .set-n {
  color: var(--dim); font-size: .72rem; font-variant-numeric: tabular-nums;
  text-align: right;
}
.set-actions { display: flex; gap: .1rem; justify-content: flex-end; }
.set-actions button, .set-movements button {
  font: inherit; font-size: .8rem; line-height: 1;
  min-width: 1.6rem; height: 1.6rem; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 1px solid transparent; border-radius: 3px;
  cursor: pointer; color: var(--dim);
}
.set-actions button:hover, .set-movements button:not(:disabled):hover {
  border-color: var(--dim); color: var(--ink);
}
.set-actions button[aria-pressed="true"] { border-color: currentColor; color: var(--accent); }
.set-actions .danger:hover { border-color: var(--error); color: var(--error); }
.set-movements button:disabled { opacity: .3; cursor: default; }

/* The movement list: a numbered rail on the left, name flexing, the
   reorder/remove cluster in a fixed column so it lines up down the card. */
.set-movements {
  list-style: none; margin: 0; padding: .15rem .5rem .4rem;
  border-top: 1px solid var(--rule);
  display: grid; gap: .1rem;
  counter-reset: setmv;
}
.set-movements li {
  display: grid; align-items: center;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: .35rem;
}
.set-movements li > .mv-name { counter-increment: setmv; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.set-movements li > .mv-name::before {
  content: counter(setmv) ".";
  display: inline-block; width: 1.5rem;
  color: var(--dim); font-variant-numeric: tabular-nums;
}
.set-movements li > .mv-tools { display: flex; gap: .1rem; justify-content: flex-end; }
.set-movements li.empty { grid-template-columns: 1fr; color: var(--dim); font-style: italic; padding: .1rem 0 .1rem 1.5rem; }

/* --- drag and drop -------------------------------------------------------- */

/* The line shows where the thing will land, on the edge it will land against,
   so the target is the gap rather than the row. */
.node[data-drop] { position: relative; }
.node[data-drop]::after {
  content: ''; position: absolute; left: 0; right: 0; height: 2px;
  background: currentColor;
}
.node[data-drop="before"]::after { top: -1px; }
.node[data-drop="after"]::after { bottom: -1px; }
.node.dragging { opacity: .4; }

/* --- the body map ----------------------------------------------------------- */

.body-head { display: flex; align-items: baseline; gap: .5rem; justify-content: space-between; }
.body-head select { font: inherit; font-size: .8rem; }
#bodymap { margin-bottom: .2rem; }

/* The map grows with the margin. The shared stylesheet pins each figure at
   78px; here the two figures split the panel width, each capped so a very wide
   margin doesn't blow them up. Click-to-enlarge (`.mm-zoomable`) is untouched. */
#bodymap.bodymap { width: 100%; }
#bodymap.bodymap .mm-view { flex: 1 1 0; min-width: 0; }
#bodymap.bodymap svg { width: 100%; max-width: 190px; height: auto; }

/* --- per-section muscle "adds" -------------------------------------------
   One terse line per top-level named section, below the body map: what that
   section brings in. Only present for workouts with named sections. */
.block-adds:not(:empty) { margin: .1rem 0 .5rem; }
.adds-line {
  font-size: .8em;
  line-height: 1.5;
  color: var(--dim);
  border-top: 1px solid var(--rule);
  padding-top: .25rem;
}
.adds-line + .adds-line { border-top: 0; padding-top: 0; }
.adds-label { color: var(--ink); font-weight: 600; }

/* --- the goals block -------------------------------------------------------
   A titled unit in the Analysis panel: the set picker and the rule-management
   actions (Edit rules / Open… / Save as… / .wdlg / Delete) grouped as one
   toolbar under the heading, the .wdlg editor revealing inline beneath it, and
   errors / notes / findings reading as the block's output. All behaviour is in
   builder.js already — this is layout and rules only, both themes. */

.goals-block { margin-top: .4rem; }
.goals-block .goals-head { margin-top: 0; }

.goals-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem .5rem;
  margin: .5rem 0 .1rem;
}
.goals-bar #goal-pick {
  flex: 1 1 9rem;
  min-width: 0;
  font: inherit;
  font-size: .8rem;
  padding: .25rem .3rem;
  border: 1px solid var(--rule);
  border-radius: .35rem;
  background: var(--panel);
  color: var(--ink);
}
.findings .ok { color: var(--dim); }

/* The findings / notes read as this block's output, set a touch below the bar. */
.goals-out { margin-top: .3rem; }

/* --- the muscle filter ---------------------------------------------------- */

.muscles { margin-bottom: .4rem; font-size: .85em; }
.muscles summary { cursor: pointer; color: var(--dim); }
.muscles summary span { color: var(--ink); }
.chips { display: flex; flex-wrap: wrap; gap: .2rem; margin: .35rem 0 .2rem; align-items: baseline; }
.chip-region {
  width: 100%; font-size: .75rem; text-transform: uppercase;
  letter-spacing: .04em; color: var(--dim); margin-top: .2rem;
}
.chip {
  font: inherit; font-size: .8rem; padding: .1rem .4rem; cursor: pointer;
  border: 1px solid var(--rule); border-radius: 999px; background: none;
}
.chip[aria-pressed="true"] { border-color: currentColor; font-weight: 600; }
.link { font: inherit; font-size: .8rem; background: none; border: 0; padding: 0; cursor: pointer; color: var(--dim); text-decoration: underline; }

/* --- writing your own goals ----------------------------------------------- */

/* The action toolbar: buttons sized to their labels, wrapping as a group when
   the pane is narrow. `Save as…` broken across two lines is not a smaller
   button, it is a taller one. */
.goal-tools { display: flex; flex-wrap: wrap; gap: .25rem; margin: 0; }
.goal-tools button { flex: 0 0 auto; white-space: nowrap; padding: .25rem .5rem; font-size: .82rem; }
.goal-text {
  width: 100%; min-height: 9rem; resize: vertical; font: inherit;
  font-size: .85em; padding: .4rem; margin: .1rem 0 .4rem;
  border: 1px solid var(--rule); border-radius: .35rem;
  background: transparent; color: inherit;
}

/* A note is not a finding: it says how to read the list, so it does not look
   like an item in it. Dim and unlaboured, the way a margin remark should be. */
.notes {
  margin: .3rem 0 .1rem;
  font-size: .8rem;
  line-height: 1.45;
  color: var(--dim);
}

/* Notes stack; each is its own fact. */
.notes div + div { margin-top: .2rem; }

/* --- accounts: identity, Workouts menu, sharing ------------------------- */

.identity { display: inline-flex; align-items: center; gap: .4rem; }
.identity .who {
  font-size: .82rem; color: var(--dim);
  max-width: 10rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.identity .avatar { width: 1.4rem; height: 1.4rem; border-radius: 999px; object-fit: cover; }

/* The @handle beside the signed-in identity + its inline editor (task L1). */
.handle { display: inline-flex; align-items: center; gap: .25rem; position: relative; }
.handle-label {
  font-size: .78rem; color: var(--dim);
  max-width: 9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.handle.pending { font-size: .75rem; color: var(--dim); font-style: italic; }
.handle-input {
  font: inherit; font-size: .8rem; width: 8rem;
  padding: .15rem .35rem; border: 1px solid var(--rule); border-radius: .3rem;
  background: var(--page); color: var(--ink);
}
.handle-btn {
  font: inherit; font-size: .76rem; padding: .15rem .4rem;
  border: 1px solid var(--rule); border-radius: .3rem;
  background: var(--panel); color: var(--ink); cursor: pointer;
}
.handle-btn:hover { border-color: var(--dim); }
.handle-error {
  position: absolute; top: 100%; right: 0; margin-top: .25rem;
  font-size: .75rem; color: var(--error); white-space: nowrap;
}
.handle-error:empty { display: none; }

/* The "Workouts" dropdown: a <details> whose panel is absolutely positioned
   so opening it never reflows the header or the panes below. Same visual
   language as the outline's suggestion popups. */
.menu { position: relative; flex: none; }
.menu > summary {
  list-style: none;
  padding: .35rem .7rem;
  border: 1px solid var(--rule);
  border-radius: .35rem;
  background: var(--panel);
  font-size: .82rem;
  cursor: pointer;
}
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary::after { content: " ▾"; color: var(--dim); }
.menu[open] > summary { border-color: var(--dim); }
.menu > summary:hover { border-color: var(--dim); }

.menu-panel {
  position: absolute;
  right: 0;
  top: calc(100% + .3rem);
  z-index: 30;
  width: 24rem;
  max-width: 90vw;
  max-height: 72vh;
  overflow: auto;
  padding: .5rem;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: .4rem;
  box-shadow: 0 .5rem 1.4rem rgba(0, 0, 0, .3);
  display: flex;
  flex-direction: column;
  gap: .4rem;
}

.menu-row { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
.menu-row button, .menu-row select { font: inherit; font-size: .82rem; }
.menu-row select { padding: .25rem .3rem; border: 1px solid var(--rule); border-radius: .3rem;
  background: var(--panel); color: var(--ink); }

.menu-hint { margin: 0; font-size: .78rem; color: var(--dim); line-height: 1.45; }

.share-row { border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  padding: .4rem 0; }
.share-row .share-label {
  font-size: .75rem; text-transform: uppercase; letter-spacing: .08em; color: var(--dim);
}

.mine-search {
  width: 100%;
  font: inherit; font-size: .82rem;
  padding: .3rem .4rem;
  border: 1px solid var(--rule); border-radius: .3rem;
  background: var(--panel); color: var(--ink);
}
.mine-search::placeholder { color: var(--dim); }
.mine-search:focus { outline: none; border-color: var(--accent); }

.mine-list { display: flex; flex-direction: column; gap: 1px; }

.mine-row { display: flex; align-items: stretch; gap: .15rem; }
.mine-row.current .mine-open { border-color: var(--accent); }

.mine-open {
  flex: 1; min-width: 0;
  display: flex; justify-content: space-between; align-items: baseline; gap: .6rem;
  text-align: left;
  padding: .35rem .45rem;
  border: 1px solid transparent; border-radius: .3rem;
  background: transparent; color: inherit; font: inherit; cursor: pointer;
}
.mine-open:hover { border-color: var(--rule); background: var(--page); }
.mine-open .mine-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mine-open .mine-when { flex: none; font-size: .75rem; color: var(--dim); font-variant-numeric: tabular-nums; }
.mine-open .mine-label { flex: none; font-size: .75rem; color: var(--dim); }
.mine-open .mine-label.is-mine { color: var(--accent); }

.mine-examples { align-items: baseline; row-gap: .3rem; }
.mine-examples .examples-label {
  font-size: .75rem; text-transform: uppercase; letter-spacing: .08em; color: var(--dim);
}
.mine-chip {
  font: inherit; font-size: .78rem;
  padding: .12rem .55rem;
  border: 1px solid var(--rule); border-radius: 999px;
  background: var(--panel); color: var(--ink); cursor: pointer;
}
.mine-chip:hover { border-color: var(--dim); }
.mine-chip.active { background: var(--accent); border-color: var(--accent); color: var(--page); }

.menu-icon {
  flex: none; width: 1.7rem;
  border: 1px solid transparent; border-radius: .3rem;
  background: transparent; color: var(--dim); font-size: .8rem; cursor: pointer;
}
.menu-icon:hover { border-color: var(--dim); color: var(--ink); }

/* --- the movement editor modal (M5) ------------------------------------- */

/* The reusable `apps/shared/movement-editor.js` form, hosted in a <dialog>.
   A form over one movement's curated fields; writes a `scope: "global"` row via
   /api. Styling stays on the shared --ink/--dim/--rule/--panel tokens so the
   widget carries no host-specific look. */
.movement-editor-modal {
  width: min(40rem, 94vw);
  max-height: 90vh;
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: var(--panel);
  color: var(--ink);
  box-shadow: 0 1rem 3rem rgba(0, 0, 0, .3);
}
.movement-editor-modal::backdrop { background: rgba(0, 0, 0, .3); }
#movement-editor-host { overflow-y: auto; max-height: 90vh; }
/* The editor opens on the DB row, so there is a fetch between the click and the
   form. This is what the modal shows meanwhile, and where a failed read is
   reported — the same box, so an error cannot land somewhere the user is not
   already looking. */
#movement-editor-host.me-loading { padding: 1.4rem 1.1rem; color: var(--dim); font-size: .88rem; }

.movement-editor { display: flex; flex-direction: column; gap: .7rem; padding: 1rem 1.1rem 1.1rem; }
.movement-editor .me-title { margin: 0; font-size: 1rem; }
/* X2: dim line under the title when the editor opened on a movement with no DB
   row (both the static-registry and merged-corpus cases). Same treatment as
   `.me-hint`. */
.movement-editor .me-provenance { margin: -.2rem 0 0; font-size: .8rem; color: var(--dim); }
.movement-editor .me-row { display: flex; flex-wrap: wrap; gap: .7rem; }
.movement-editor .me-row > .me-field { flex: 1 1 10rem; min-width: 0; }
/* The container a token input is mounted into — a plain flex item, but one that
   must be allowed to shrink or a long chip widens the whole modal. */
.me-tokens { min-width: 0; }

.me-field { display: flex; flex-direction: column; gap: .2rem; }
.me-label { font-size: .75rem; text-transform: uppercase; letter-spacing: .06em; color: var(--dim); }
.me-hint { font-size: .75rem; color: var(--dim); }
.me-input, .me-select {
  font: inherit; font-size: .88rem; padding: .3rem .4rem;
  border: 1px solid var(--rule); border-radius: .3rem;
  background: var(--page); color: var(--ink);
}
.me-input:read-only { color: var(--dim); background: var(--panel); }
.me-num { width: 6rem; }

.me-checks { display: flex; flex-wrap: wrap; gap: .1rem .9rem; padding-top: .15rem; }
.me-check { display: inline-flex; align-items: center; gap: .3rem; font-size: .85rem; text-transform: none; letter-spacing: 0; }
.me-check input { margin: 0; }

.me-errors { margin: 0; padding-left: 1.1rem; color: var(--error); font-size: .82rem; }
.me-status { margin: 0; font-size: .85rem; }
.me-status[data-kind="error"] { color: var(--error); }
.me-status[data-kind="ok"] { color: var(--accent); }
.me-status[data-kind="busy"] { color: var(--dim); }

.me-actions { display: flex; gap: .5rem; margin-top: .2rem; }
.me-save { border-color: var(--accent); color: var(--accent); }
/* Admin-only soft-delete (D5). Set off to the right, away from Save/Cancel, and
   destructive on hover — the same red-on-hover the set-card `✕` uses. */
.me-delete { margin-left: auto; color: var(--dim); }
.me-delete:hover { border-color: var(--error); color: var(--error); }
.me-delete:disabled { opacity: .4; cursor: default; }
/* Admin-only "Duplicate movement" (real DB rows only). Sits with Save/Cancel on
   the left; not destructive, so a plain button treatment — dim by default, an
   accent hint on hover to mark it as the constructive shortcut it is. */
.me-copy { color: var(--dim); }
.me-copy:hover { border-color: var(--accent); color: var(--accent); }
.me-copy:disabled { opacity: .4; cursor: default; }

/* The per-card "edit this movement" affordance in the palette, and the
   "edit its curated fields" row in the step `»` panel. */
.movement-edit {
  flex: none; margin-left: .35rem; padding: 0 .25rem;
  color: var(--dim); font-size: .75rem; cursor: pointer; border-radius: 3px;
}
.movement-edit:hover { color: var(--accent); }
/* The palette card's ✎ (edit) + ⧉ (duplicate) pair — the wrapper carries the gap. */
.movement-actions { flex: none; display: inline-flex; margin-left: .35rem; }
.movement-actions .movement-edit { margin-left: 0; }
.suggestion.modifier-edit { border-top: 1px solid var(--rule); }
.suggestion.modifier-edit + .suggestion.modifier-edit { border-top: none; }

/* "This needs you signed in / an admin" — `requireAccount` in builder.js. */
.auth-notice {
  width: min(26rem, 92vw);
  padding: 1rem 1.1rem;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: var(--panel);
  color: var(--ink);
  box-shadow: 0 1rem 3rem rgba(0, 0, 0, .3);
}
.auth-notice::backdrop { background: rgba(0, 0, 0, .3); }
.auth-notice p { margin: 0 0 .9rem; font-size: .92rem; line-height: 1.4; }
.auth-notice-actions { display: flex; gap: .5rem; justify-content: flex-end; }

/* --- the token input (V4) ------------------------------------------------ */

/* `apps/shared/token-input.js` — chips + type-ahead + a filter/select picker,
   the movement editor's seven list fields. Styled here on the shared
   --ink/--dim/--rule/--accent/--page/--panel tokens only, so the widget carries
   no host-specific look and a second host restyles nothing.

   One composition at every vocabulary size: no count threshold, no "show N then
   More" (DEC-024). Size only changes how far the two bounded lists scroll.

   Both the type-ahead menu and the picker sit in normal flow rather than
   floating, so neither can be clipped by, or overflow, the modal's own
   scroll container at any width. They are shown/hidden by the `hidden`
   attribute, which the `[hidden] { display: none !important }` rule at the top
   of this file makes win over the `display`s below. */
.ti { display: flex; flex-direction: column; gap: .3rem; min-width: 0; }

.ti-field {
  display: flex; flex-wrap: wrap; align-items: center; gap: .25rem;
  min-width: 0; padding: .2rem .2rem .2rem .3rem;
  border: 1px solid var(--rule); border-radius: .3rem;
  background: var(--page); cursor: text;
}
.ti-field:focus-within { border-color: var(--accent); }

.ti-chips { display: contents; }
.ti-chip {
  display: inline-flex; align-items: center; gap: .1rem;
  min-height: 2rem; max-width: 100%;
  padding-left: .5rem;
  border: 1px solid var(--rule); border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 12%, var(--panel));
  color: var(--ink); font-size: .8rem;
}
.ti-chip > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A 2rem square hit box for WCAG 2.2 target size; the glyph stays small. */
.ti-x {
  flex: none; width: 2rem; align-self: stretch;
  border: none; border-radius: 0 999px 999px 0;
  background: transparent; color: var(--dim);
  font: inherit; font-size: .95rem; line-height: 1; cursor: pointer;
}
.ti-x:hover { color: var(--error); }

.ti-entry {
  flex: 1 1 7rem; min-width: 4rem; min-height: 2rem;
  border: none; outline: none; background: transparent;
  color: var(--ink); font: inherit; font-size: .88rem;
}
.ti-entry::placeholder { color: var(--dim); }

.ti-browse {
  flex: none; margin-left: auto; width: 2rem; height: 2rem;
  border: 1px solid transparent; border-radius: .3rem;
  background: transparent; color: var(--dim);
  font: inherit; font-size: .8rem; line-height: 1; cursor: pointer;
}
.ti-browse:hover { border-color: var(--rule); color: var(--ink); }

.ti-menu {
  margin: 0; padding: 0; list-style: none;
  max-height: 11rem; overflow-y: auto;
  border: 1px solid var(--rule); border-radius: .3rem;
  background: var(--panel);
}
.ti-opt {
  display: flex; align-items: center; min-height: 2rem;
  padding: .1rem .5rem; font-size: .82rem; cursor: pointer;
}
.ti-opt:hover, .ti-opt.on { background: var(--accent); color: var(--page); }
.ti-add { color: var(--dim); font-style: italic; }
.ti-add:hover, .ti-add.on { color: var(--page); font-style: normal; }

.ti-picker {
  display: flex; flex-direction: column; gap: .25rem;
  min-width: 0; padding: .3rem;
  border: 1px solid var(--rule); border-radius: .3rem;
  background: var(--panel);
}
.ti-filter {
  min-width: 0; min-height: 2rem; padding: .2rem .4rem;
  border: 1px solid var(--rule); border-radius: .3rem;
  background: var(--page); color: var(--ink); font: inherit; font-size: .82rem;
}
.ti-filter::placeholder { color: var(--dim); }
.ti-filter:focus { outline: none; border-color: var(--accent); }

/* V9: the "＋ new term" row — an admin mints a global vocabulary term without
   leaving the editor. In normal flow inside the picker, so the modal's scroll
   container can never clip it. */
.ti-new {
  min-height: 2rem; padding: .2rem .5rem; text-align: left;
  border: 1px dashed var(--rule); border-radius: .3rem;
  background: var(--page); color: var(--dim);
  font: inherit; font-size: .82rem; font-style: italic; cursor: pointer;
}
.ti-new:hover:not(:disabled) { border-color: var(--accent); color: var(--ink); font-style: normal; }
.ti-new:disabled { cursor: default; }
.ti-error { margin: 0; padding: .1rem .25rem; font-size: .78rem; color: var(--error); }

/* Bounded and scrolling, so the widget's height is the same for 11 terms as for
   1100 — the reason no size threshold is needed. Columns fill the width a wide
   modal offers without letting a long vocabulary run off the bottom. */
.ti-list {
  max-height: 9rem; overflow-y: auto;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); gap: 0 .4rem;
}
.ti-term {
  display: flex; align-items: center; gap: .35rem;
  min-height: 2rem; min-width: 0; padding: 0 .25rem;
  font-size: .82rem; text-transform: none; letter-spacing: 0; cursor: pointer;
}
.ti-term:hover { color: var(--accent); }
.ti-term input { flex: none; margin: 0; }
.ti-term span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ti-empty { margin: 0; padding: .3rem .25rem; font-size: .78rem; color: var(--dim); }
