/* =========================================================================
   CodingFreaks Overlays — Config Hub  (/einrichten/, /en/setup/)
   -------------------------------------------------------------------------
   Loaded ONLY by public/setup.php, on top of styles.css. The site sheet has
   no form styling at all (grep -c input styles.css == 0), so everything a
   configuration tool needs lives here.

   Rules inherited from the site identity (plans/config-hub/03-ui-guidelines.md):
     · no gradients, no drop shadows, no rounded cards — surfaces are separated
       by --paper / --paper-2 and hairlines (--rule)
     · lime marks exactly ONE thing per surface: the active state or the single
       primary action, never a panel fill
     · dark mode is a pure token swap, inherited from styles.css (no second
       component variant here)

   TYPE FLOOR: the site's floor is 16px; this tool sits at 18px and up
   (--t-body 18-21, controls 18, help text 17). A config screen is read for
   minutes at a time, often on a second monitor while OBS runs on the first.
   Nothing here may fall below 17px.
   ========================================================================= */

/* The hub hides whole blocks with the `hidden` property (empty groups, the
   starter-set box once it is used up). Any class here that sets a display value
   would out-specify the UA's [hidden] rule and leave an empty frame behind, so
   state that once instead of remembering it per component. */
[hidden] { display: none !important; }

/* Form + state tokens. Declared on :root, NOT on .cfg — the settings dialog is
   a <dialog> appended to <body> (top layer), so it is outside .cfg and would
   inherit nothing. Everything else comes from styles.css. */
:root {
  --field-bg:            color-mix(in srgb, var(--paper) 60%, #fff 40%);
  --field-bg-disabled:   var(--paper-2);
  --field-border:        var(--rule);
  --field-border-hover:  color-mix(in srgb, var(--rule) 60%, var(--on-paper) 40%);
  --field-border-focus:  var(--on-paper);
  /* --lime-ink, not --lime, and one token for BOTH themes: this ring is the
     only focus indicator 15 controls have (the input fields additionally get
     --field-border-focus, nothing else does), and outline-offset puts it on the
     SURROUNDING surface rather than on the field fill. Flat lime is a fill
     colour: composited at this alpha it measured 1.05:1 on --paper, 1.02:1 on
     --paper-2 and 1.10:1 on --field-bg in the light theme, against the 3:1 that
     SC 1.4.11 asks of a state indicator. It was built for dark paper (6.3 to
     6.8:1 there) and fell through on light. --lime-ink swaps itself per theme,
     so no dark override is needed; at 85% the worst of the six surfaces is
     3.74:1 light / 8.35:1 dark. Below ~80% the light theme drops under 3:1
     again, so do not soften this further. */
  --field-ring:          color-mix(in srgb, var(--lime-ink) 85%, transparent);
  /* State colours are DARKER than they look like they want to be, on purpose.
     They are read as text on --paper-2 (#e4e0d6), the panel fill, which is the
     darkest of the three surfaces they land on and therefore the one that
     decides. Measured against it: the earlier #2f7a3f scored 4.01:1 and
     #9a6b00 scored 3.56:1, both under the 4.5:1 AA floor for normal text, and
     both of them carry a verdict ("aktuell", "lokal verändert") that a
     streamer acts on. The values below clear it with reserve on all three
     surfaces (paper / paper-2 / field-bg):
       --state-ok    5.85 / 5.33 / 6.29
       --state-warn  5.64 / 5.15 / 6.07
       --state-error 6.01 / 5.48 / 6.46   (was already passing, unchanged)
     Do not lighten them back for looks: on this paper a friendlier green or
     amber is exactly the pair that fails. The dark-theme values further down
     sit at 8.6:1 and up and need no such care. */
  --state-ok:            #256533;
  --state-warn:          #7a5400;
  --state-error:         #a32a1f;
  --ctl-h:               48px;
  --ctl-r:               0;
  --t-help:              clamp(17px, 0.9vw, 19px);
  --t-ctl:               clamp(18px, 0.95vw, 20px);
}

.cfg {
  padding: clamp(28px, 5vw, 64px) 0 clamp(56px, 8vw, 110px);
  font-size: clamp(18px, 1.05vw, 21px);
}
/* Dark mode: only the tokens that are NOT already swapped by styles.css.
   Both blocks must stay in sync (same rule as the site sheet). */
:root[data-theme="dark"] {
  --field-bg:     color-mix(in srgb, var(--paper) 70%, #000 30%);
  --state-ok:     #6fcf87;
  --state-warn:   #e8b64c;
  --state-error:  #ff8a7a;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --field-bg:     color-mix(in srgb, var(--paper) 70%, #000 30%);
    --state-ok:     #6fcf87;
    --state-warn:   #e8b64c;
    --state-error:  #ff8a7a;
  }
}

.cfg-privacy {
  font-family: var(--mono);
  font-size: var(--t-help);
  color: var(--muted);
  margin-top: 14px;
}
.cfg-empty {
  font-family: var(--mono);
  font-size: var(--t-ctl);
  border: 2px solid var(--on-paper);
  padding: clamp(18px, 3vw, 28px);
}

/* ---- Panels: the one structural surface ---------------------------------- */
.cfg-panel {
  border: 2px solid var(--on-paper);
  background: var(--paper-2);
  padding: clamp(22px, 3.4vw, 40px);
}
.cfg-panel + .cfg-panel { margin-top: clamp(18px, 2.5vw, 28px); }
.cfg-panel h2 {
  font-size: var(--t-m);
  line-height: 1.05;
  margin-bottom: 10px;
}
.cfg-panel > p {
  max-width: 62ch;
  color: var(--muted);
  margin-bottom: clamp(18px, 2.5vw, 26px);
}
.cfg-eyebrow {
  display: block;
  font-family: var(--mono);
  font-size: var(--t-help);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}

/* ---- Browser gate: informs, never blocks --------------------------------- */
.cfg-gate {
  border: 2px solid var(--on-paper);
  border-left-width: 10px;
  background: var(--paper-2);
  padding: clamp(20px, 3vw, 32px);
  margin-bottom: clamp(20px, 3vw, 32px);
}
.cfg-gate h2 { font-size: var(--t-m); margin-bottom: 10px; }
.cfg-gate p  { max-width: 62ch; }
.cfg-copyrow {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 18px;
  align-items: flex-end;
}
.cfg-copyrow .cfg-field { flex: 1 1 320px; margin: 0; }

/* ---- Step rail ----------------------------------------------------------- */
.cfg-steps {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  border: 2px solid var(--on-paper);
  border-bottom: 0;
  margin-bottom: 0;
  font-family: var(--mono);
  font-size: var(--t-help);
}
.cfg-step {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px clamp(12px, 1.6vw, 20px);
  border-right: 2px solid var(--on-paper);
  color: var(--muted);
  background: var(--paper);
  white-space: nowrap;
}
.cfg-step:last-child { border-right: 0; }
.cfg-step .n { font-variant-numeric: tabular-nums; }
.cfg-step[data-state="done"]    { color: var(--on-paper); }
.cfg-step[data-state="current"] { color: var(--ink); background: var(--lime); font-weight: 700; }
.cfg-steps + .cfg-panel { border-top-width: 2px; }
/* A completed step is a <button> (the shortest way back). It must not look
   like a button, only behave like one. */
button.cfg-step {
  font: inherit;
  text-align: left;
  cursor: pointer;
}
button.cfg-step:hover { background: var(--paper-2); color: var(--on-paper); }
button.cfg-step:focus-visible { outline: 3px solid var(--field-ring); outline-offset: -3px; }

/* ---- Wizard footer -------------------------------------------------------
   Back on the left, one primary on the right, and a line saying where the
   primary leads. Built once in wizardNav() so no screen can omit the way back. */
.cfg-nav { margin-top: clamp(20px, 3vw, 30px); }
.cfg-nav .cfg-actions { margin-top: 0; }
.cfg-nav-meta {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 14px;
  font-family: var(--mono);
  font-size: var(--t-help);
  color: var(--muted);
}
.cfg-nav-hint::before { content: "→ "; }

/* Sticky variant, set by hub.js on the overlay picker only. That screen is the
   one place where the decision and the way onward are metres apart: with 19
   cards the footer measured +3162px below the fold, so the primary action was
   invisible at the exact moment someone finished choosing.

   It bleeds out to the panel's inner edges so the top line is as wide as the
   panel and, at rest, the bar sits flush on the panel's bottom border instead
   of floating above the panel's own padding. --cfg-panel-pad mirrors
   .cfg-panel's padding; keep the two in step.

   Opaque fill is load-bearing (cards pass UNDER it), the 2px top line is the
   only separator it gets, and it deliberately has no shadow and no transition:
   the rundown look has neither, and a bar that animates itself into place is a
   bar that moves while someone is aiming at the button on it. */
.cfg-nav.is-sticky {
  --cfg-panel-pad: clamp(22px, 3.4vw, 40px);
  position: sticky;
  bottom: 0;
  z-index: 20;
  margin-inline: calc(-1 * var(--cfg-panel-pad));
  margin-bottom: calc(-1 * var(--cfg-panel-pad));
  padding: 14px var(--cfg-panel-pad) var(--cfg-panel-pad);
  background: var(--paper-2);
  border-top: 2px solid var(--on-paper);
}
/* A stack that grows without a ceiling eats the screen it is meant to serve.
   The trigger is the VIEWPORT HEIGHT, not its width: a phone in landscape
   (854x400, 1024x600) is wider than any phone breakpoint and is exactly where
   a 150px bar takes a third of the picture. Below that the meta line goes:
   between knowing the progress and being able to reach the button, the button
   wins. What is left is one row, so it never needs its own scrollbar. */
@media (max-height: 620px) {
  .cfg-nav.is-sticky { padding-top: 10px; padding-bottom: 12px; }
  .cfg-nav.is-sticky .cfg-nav-meta { display: none; }
}
/* Narrow AND tall: the row wraps into a stack. It may grow, but not without
   end, and the bar scrolls rather than the page behind it. */
@media (max-width: 700px) and (min-height: 621px) {
  .cfg-nav.is-sticky {
    max-height: 40vh;
    overflow-y: auto;
    padding-top: 12px;
  }
  .cfg-nav.is-sticky .cfg-nav-meta { margin-top: 10px; }
}

/* Progress, not a step counter. The bar reads 20 % on the very first screen
   (see PROGRESS_FLOOR in hub.js), so the wizard opens as something already
   under way. Same vocabulary as the step rail: hard edges, lime fill. */
.cfg-prog {
  display: flex;
  align-items: center;
  gap: 12px;
}
.cfg-prog-track {
  flex: 1 1 auto;
  height: 10px;
  border: 2px solid var(--on-paper);
  background: var(--paper);
  overflow: hidden;
}
.cfg-prog-fill {
  display: block;
  height: 100%;
  background: var(--lime);
}
.cfg-prog-pct {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--on-paper);
}

/* ---- App bar: help + account --------------------------------------------
   One strip above the step rail, right-aligned, on every screen (appBar() in
   hub.js). It carries the two things that are true everywhere: the explainer
   and who you are. Both used to live inside panels, which meant the "?" showed
   on two screens out of ten and the login state only on the picker.

   Same visual language as the rail below it: mono, 2px hairlines, no rounding,
   lime only on hover of the one thing you can press. */
.cfg-appbar {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.cfg-helpbtn {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 2px solid var(--on-paper);
  background: var(--paper);
  color: var(--on-paper);
  font-family: var(--mono);
  font-size: var(--t-ctl);
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
.cfg-helpbtn:hover { background: var(--lime); color: var(--ink); }
.cfg-helpbtn:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 3px; }

/* Account chip. Signed in it is a readout with one action on it; signed out
   the whole chip IS the action, which is why both states are the same shape. */
.cfg-account {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding: 0 0 0 10px;
  border: 2px solid var(--rule);
  background: var(--paper);
  color: var(--muted);
  font-family: var(--mono);
  font-size: var(--t-help);
  line-height: 1;
}
.cfg-account[data-on="1"] { border-color: var(--on-paper); color: var(--on-paper); }
.cfg-account-ico { flex: 0 0 auto; display: block; width: 17px; height: 17px; }
.cfg-account-ico svg { display: block; width: 100%; height: 100%; }
/* A long display name shortens instead of pushing the "?" off the row. The vw
   half of the cap is what keeps a 25-character Twitch name from filling a phone
   screen on its own — 22ch of mono is wider than a narrow viewport. */
.cfg-account-name {
  max-width: min(22ch, 42vw);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Signed out the chip is an invitation, so its label is a real action colour —
   only the hairline border stays quiet, which is what tells the two states
   apart at a glance. Hover goes lime like the other two pressables in this
   strip; the state readout above never does. */
button.cfg-account {
  padding-right: 12px;
  color: var(--on-paper);
  cursor: pointer;
}
button.cfg-account:hover {
  border-color: var(--on-paper);
  background: var(--lime);
  color: var(--ink);
}
button.cfg-account:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 3px; }

.cfg-signout {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 32px;
  align-self: stretch;
  margin-left: 4px;
  border-left: 2px solid var(--rule);
  color: var(--muted);
}
.cfg-signout svg { display: block; width: 18px; height: 18px; }
.cfg-signout:hover { background: var(--lime); color: var(--ink); }
.cfg-signout:focus-visible { outline: 3px solid var(--field-ring); outline-offset: -3px; }

/* ---- Step 0 explainer ----------------------------------------------------- */
.cfg-intro { margin: 4px 0 0; padding: 0; list-style: none; }
.cfg-intro li {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 14px;
  padding: 14px 0;
  border-top: 1px solid var(--rule);
  max-width: 68ch;
}
.cfg-intro li:last-child { border-bottom: 1px solid var(--rule); }
.cfg-intro .n {
  font-family: var(--mono);
  font-size: var(--t-help);
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

/* ---- "this is what the next click writes" -------------------------------- */
.cfg-review {
  border: 2px solid var(--on-paper);
  border-left-width: 10px;
  background: var(--paper);
  padding: clamp(16px, 2.2vw, 24px);
  margin-top: clamp(18px, 2.5vw, 26px);
}
.cfg-review .cfg-note:first-of-type { margin-top: 12px; }
.cfg-review-list { list-style: none; margin: 10px 0 0; padding: 0; }
.cfg-review-list li {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  align-items: baseline;
  padding: 6px 0;
  border-bottom: 1px solid var(--rule);
}
.cfg-review-list li:last-child { border-bottom: 0; }
.cfg-review-list .nm { font-family: var(--display); font-weight: 700; font-size: var(--t-ctl); }
.cfg-review-list .id { font-family: var(--mono); font-size: var(--t-help); color: var(--muted); }

/* ---- Fields -------------------------------------------------------------- */
.cfg-field { margin-bottom: clamp(18px, 2.4vw, 26px); max-width: 62ch; }
.cfg-field > label,
.cfg-legend {
  display: block;
  font-family: var(--mono);
  font-size: var(--t-ctl);
  font-weight: 700;
  margin-bottom: 8px;
}
.cfg-help {
  display: block;
  font-size: var(--t-help);
  color: var(--muted);
  margin-top: 8px;
  max-width: 60ch;
}
.cfg-field input[type="text"],
.cfg-field input[type="password"],
.cfg-field select,
.cfg-field textarea {
  width: 100%;
  min-height: var(--ctl-h);
  padding: 10px 14px;
  font-family: var(--mono);
  font-size: var(--t-ctl);
  color: var(--on-paper);
  background: var(--field-bg);
  border: 2px solid var(--field-border);
  border-radius: var(--ctl-r);
  appearance: none;
}
.cfg-field select {
  /* Native arrow is hidden by appearance:none, so draw one. currentColor keeps
     it correct in both themes without a second asset. */
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 20px center, right 14px center;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  padding-right: 40px;
}
.cfg-field input:hover,
.cfg-field select:hover { border-color: var(--field-border-hover); }
.cfg-field input:focus-visible,
.cfg-field select:focus-visible,
.cfg-field textarea:focus-visible {
  outline: 3px solid var(--field-ring);
  outline-offset: 2px;
  border-color: var(--field-border-focus);
}
.cfg-field input[readonly] { background: var(--field-bg-disabled); color: var(--muted); }
.cfg-field.is-error input { border-color: var(--state-error); }
.cfg-error {
  display: block;
  font-family: var(--mono);
  font-size: var(--t-help);
  color: var(--state-error);
  margin-top: 8px;
}
/* The counterpart, for the one screen that reports a success instead of asking
   for something: the folder step after the picker closed. Mono and small, so
   it reads as a status line above the heading's answer, not as a second
   headline. The mark is decorative, the words carry the meaning. */
.cfg-ok {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-family: var(--mono);
  font-size: var(--t-help);
  color: var(--state-ok);
  margin: 0 0 14px;
}
.cfg-ok::before { content: "\2713"; font-weight: 700; }

/* secret field + its reveal toggle share one row */
.cfg-secret { display: flex; gap: 8px; }
.cfg-secret input { flex: 1 1 auto; }

/* switch: a checkbox, restyled, still a checkbox.
   `.cfg-field > label` above sets display:block and outranks a bare .cfg-switch,
   so a switch that IS the field's label needs the same weight to stay a row. */
.cfg-switch,
.cfg-field > label.cfg-switch { display: flex; align-items: center; gap: 14px; cursor: pointer; }
.cfg-switch input {
  appearance: none;
  width: 62px; height: 34px;
  flex: 0 0 auto;
  background: var(--field-bg);
  border: 2px solid var(--field-border);
  position: relative;
  cursor: pointer;
}
.cfg-switch input::after {
  content: "";
  position: absolute;
  inset: 3px auto 3px 3px;
  width: 24px;
  background: var(--on-paper);
  transition: transform .12s linear;
}
.cfg-switch input:checked { background: var(--lime); border-color: var(--on-paper); }
.cfg-switch input:checked::after { transform: translateX(28px); background: var(--ink); }
.cfg-switch input:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .cfg-switch input::after { transition: none; } }

/* ---- Bot tiles ------------------------------------------------------------
   One decision instead of two dropdowns. Radio inputs underneath, so arrow
   keys and screen readers behave; the visual selection is the tile border
   plus the lime rail, never colour alone. */
.cfg-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
  gap: 12px;
  margin: 16px 0 clamp(20px, 3vw, 30px);
  max-width: none;
}
.cfg-tile {
  position: relative;
  display: grid;
  grid-template-rows: auto auto 1fr;
  gap: 8px;
  padding: 18px 18px 20px;
  border: 2px solid var(--field-border);
  background: var(--field-bg);
  cursor: pointer;
}
.cfg-tile:hover { border-color: var(--field-border-hover); }
.cfg-tile input {
  position: absolute;
  opacity: 0;
  width: 1px; height: 1px;      /* focusable, not visible: the tile is the control */
}
.cfg-tile .t { font-family: var(--display); font-weight: 700; font-size: var(--t-ctl); line-height: 1.1; }
.cfg-tile .d { font-size: var(--t-help); color: var(--muted); }
.cfg-tile-ico { width: 32px; height: 32px; display: block; }
.cfg-tile-ico svg { width: 32px; height: 32px; }
.cfg-tile:has(input:checked) {
  border-color: var(--on-paper);
  background: var(--paper);
  box-shadow: inset 6px 0 0 0 var(--lime);
  padding-left: 26px;
}
.cfg-tile:has(input:checked) .d { color: var(--on-paper); }
.cfg-tile:has(input:focus-visible) { outline: 3px solid var(--field-ring); outline-offset: 2px; }

/* Expert escape hatch: the two raw axes, collapsed. */
.cfg-expert { margin: 6px 0 clamp(18px, 2.4vw, 26px); }
.cfg-expert > summary {
  font-family: var(--mono);
  font-size: var(--t-help);
  cursor: pointer;
  color: var(--muted);
  padding: 6px 0;
}
.cfg-expert > summary:hover { color: var(--on-paper); }
.cfg-expert > summary:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 2px; }
.cfg-expert[open] { border-left: 2px solid var(--rule); padding-left: 16px; }

/* ---- Game picker ---------------------------------------------------------
   Two groups (free, then on request), each a grid of STACKED cards: picture,
   words, action. The card was three columns side by side before — still,
   text, checkbox — which gave it no reading order and squeezed the picture
   into a 150px stamp. Stacking puts the one thing being chosen (a still of
   the overlay running) across the full card and leaves the words a full
   measure underneath.

   min(100%, 300px) caps the count at three on a wide panel and never lets a
   single card exceed the row on a narrow one. */
.cfg-group + .cfg-group { margin-top: clamp(22px, 3vw, 34px); }
.cfg-group > h3 {
  font-family: var(--display); font-weight: 700;
  font-size: var(--t-ctl); line-height: 1.1;
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 4px;
}
/* The lime rule is the "free" marker: the eye finds the group before it reads
   the heading. The on-request group gets the quiet rule instead.

   The custom group is marked by WEIGHT rather than by a second bright colour:
   a full-ink rule, double width. It has to outrank the lime one (this is the
   streamer's own commissioned work, the top of the page) without turning the
   screen into two competing promotional colours — the same reason the badge
   below borrows lime as ink on black instead of another lime fill. */
.cfg-group > h3::before {
  content: ""; width: 18px; height: 0; border-top: 6px solid var(--lime);
}
.cfg-group[data-tone="quiet"] > h3::before { border-top-color: var(--rule); }
.cfg-group[data-tone="own"]   > h3::before { border-top-color: var(--on-paper); width: 34px; }
.cfg-group > p { color: var(--muted); font-size: var(--t-help); margin-bottom: 12px; max-width: 62ch; }

/* Separate bordered cards, not cells hairlined out of one block: the grid never
   divides evenly (5 free overlays into 3 columns), and with the old shared
   background every leftover cell was a hole in an otherwise solid slab. */
.cfg-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
  gap: clamp(12px, 1.4vw, 18px);
}
.cfg-pick {
  display: flex;
  flex-direction: column;
  background: var(--paper);
  border: 2px solid var(--on-paper);
  cursor: pointer;
  min-width: 0;
}
.cfg-pick:hover { background: var(--paper-2); }
/* The still is 16:9 like the overlay itself, so `cover` has nothing to cut, and
   it is full-bleed: no padding, no inner border, the card's own edge frames it.
   The 4:3 product thumbs elsewhere on the site would lose their wordmark here —
   see setup_teaser() for which asset actually gets served. */
.cfg-pick-media {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  background: var(--paper-2);
  border-bottom: 2px solid var(--on-paper);
  overflow: hidden;
}
.cfg-pick-media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.cfg-pick-body {
  display: grid; gap: 5px; min-width: 0;
  padding: 14px 16px 12px;
  flex: 1 1 auto;                     /* footers line up across a row */
}
.cfg-pick .nm { font-family: var(--display); font-weight: 700; font-size: var(--t-ctl); line-height: 1.15; }
.cfg-pick .tg { font-size: var(--t-help); color: var(--muted); line-height: 1.35; }
.cfg-pick .mt { font-family: var(--mono); font-size: var(--t-help); color: var(--muted); align-self: end; }
.cfg-pick-foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 0 16px 14px;
}

/* The pill IS the checkbox: the input stays in the DOM for focus, keyboard and
   screen readers, and CSS swaps the two labels around it. A separate 28px box
   next to a word that says "select" was two controls for one decision. */
.cfg-take {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--mono); font-size: var(--t-help); font-weight: 700;
  letter-spacing: .02em; padding: 9px 14px;
  border: 2px solid var(--on-paper); background: var(--field-bg); color: var(--on-paper);
}
.cfg-take input {
  appearance: none; flex: 0 0 auto; width: 16px; height: 16px;
  border: 2px solid currentColor; background: transparent; cursor: pointer;
}
.cfg-take .on { display: none; }
.cfg-pick:hover .cfg-take { background: var(--paper); }
.cfg-pick:has(input:checked) .cfg-take {
  background: var(--lime); border-color: var(--on-paper); color: var(--ink);
}
.cfg-pick:has(input:checked) .cfg-take .off { display: none; }
.cfg-pick:has(input:checked) .cfg-take .on  { display: inline; }
.cfg-pick:has(input:checked) .cfg-take input { background: var(--ink); }
.cfg-take:focus-within { outline: 3px solid var(--field-ring); outline-offset: 2px; }
/* Same selection language as the bot tiles: a lime rail, never colour alone. */
.cfg-pick:has(input:checked) { background: var(--paper-2); box-shadow: inset 6px 0 0 0 var(--lime); }

/* ---- badges + gated overlays ---------------------------------------------
   Corner badges on the still, and a card that offers an action instead of a
   checkbox when the viewer cannot install it yet. */
.cfg-pay {
  position: absolute; top: 8px; left: 8px;
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 2px;
  /* --on-ink, never --paper: both tokens are near-white on light paper, but
     --paper is the SURFACE token and swaps to #17161b in dark, where this badge
     keeps its --ink fill. Measured on the base rule, which is what "Auf Anfrage"
     wears (data-state="request" has no override): 16.04:1 light and 1.10:1 dark,
     so the whole paid catalogue lost its badge in dark mode. --on-ink is bone in
     both themes: 17.20:1 / 17.65:1. styles.css states the same rule in prose. */
  background: var(--ink); color: var(--on-ink);
}
/* Free is the loud badge, not the quiet one: it is the only word on the card
   that promises no obstacle, and it is what the group above is built around. */
.cfg-pay[data-state="free"],
.cfg-pay[data-state="entitled"] { background: var(--lime); color: var(--ink); }
/* Commissioned: on air black with lime lettering. Distinct from both the lime
   fill (free) and the plain black fill (on request) at a glance, and it reads as
   a stamp on the still rather than as another offer. */
.cfg-pay[data-state="custom"] { background: var(--ink); color: var(--lime); }
/* Already in the folder. Sits opposite the tier badge so the two never collide. */
.cfg-flag {
  position: absolute; top: 8px; right: 8px;
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 2px;
  background: var(--paper); color: var(--on-paper); border: 2px solid var(--on-paper);
}
/* Already in the folder: still readable, visibly done. The dimmed still plus the
   flag do the work — a colour tint would fight the lime that means "selected". */
.cfg-pick[data-installed="1"] { border-color: var(--rule); }
.cfg-pick[data-installed="1"] .cfg-pick-media img { opacity: .55; }
.cfg-pick-gated { cursor: default; }
.cfg-pay-btn {
  display: inline-block; white-space: nowrap; cursor: pointer;
  font-family: var(--mono); font-size: var(--t-help); font-weight: 700;
  letter-spacing: .02em; padding: 9px 14px;
  border: 2px solid var(--on-paper); background: var(--field-bg); color: var(--on-paper);
  text-decoration: none;
}
.cfg-pay-btn:hover { background: var(--lime); border-color: var(--lime); color: var(--ink); }
.cfg-pay-btn:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 2px; }

/* Remove: a text control, never a button-shaped one. It is the one action here
   that deletes something, so it must not compete with the two that add. */
.cfg-rm {
  font-family: var(--mono); font-size: var(--t-help);
  padding: 6px 8px; border: 0; background: none; color: var(--muted);
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}
.cfg-rm:hover { color: var(--state-warn); }
.cfg-rm:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 2px; }
.cfg-rm.is-yes { color: var(--state-warn); font-weight: 700; }
/* Question on its own line, answers under it: the three parts never fit one
   line inside a 300px card, and a wrapping confirm reads as an accident. */
.cfg-rm-ask {
  display: grid; gap: 2px; width: 100%;
  font-family: var(--mono); font-size: var(--t-help); color: var(--on-paper);
}
.cfg-rm-ask .q { font-weight: 700; }
.cfg-rm-ask .b { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-left: -8px; }
/* In the management list the same control carries .btn-sm as well, and .btn-sm
   sits later in this file, so it used to win the border and the fill back: the
   row ended on a bordered button that was indistinguishable from "Einstellungen"
   next to it, sitting furthest right where the eye finishes. Take the button
   shape away again. Deleting an overlay must never be the obvious thing to do
   on a row that is otherwise about keeping it. */
.cfg-row .cfg-rm {
  padding: 8px 4px;
  border: 0;
  background: none;
  color: var(--muted);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.cfg-row .cfg-rm:hover { background: none; color: var(--state-warn); }
/* Armed, though: once it has been asked the answer IS a button, and a loud one.
   A 2px warn edge is a graphical object, so 3:1 is the bar and --state-warn
   clears it at 5.15:1 on the panel. */
.cfg-row .cfg-rm.is-yes {
  padding: 8px 12px;
  border: 2px solid var(--state-warn);
  color: var(--state-warn);
  font-weight: 700;
  text-decoration: none;
}
.cfg-row .cfg-rm.is-yes:hover { background: var(--state-warn); color: var(--paper); }

/* Ask-for-anything, at the end of the picker. */
.cfg-lead {
  margin-top: clamp(22px, 3vw, 34px);
  padding: clamp(16px, 2.2vw, 24px);
  border: 2px solid var(--on-paper);
  background: var(--paper);
}
.cfg-lead h3 { font-family: var(--display); font-weight: 700; font-size: var(--t-ctl); margin-bottom: 6px; }
.cfg-lead p { color: var(--muted); font-size: var(--t-help); max-width: 58ch; margin-bottom: 12px; }
/* Sign-in line above the catalogue: why signing in is worth it, next to the
   cards it applies to. Only for signed-out visitors, and only when paid
   overlays exist — the signed-in readout lives in the app bar. */
.cfg-authrow { margin: 2px 0 14px; font-size: var(--t-help); color: var(--muted); }
.cfg-authrow .cfg-link { font-size: var(--t-help); }

/* Filter row above the grid: 19 overlays in two columns is a long page. */
.cfg-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 20px;
  align-items: flex-end;
  justify-content: space-between;
  margin-bottom: 16px;
}
.cfg-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.cfg-chip {
  font-family: var(--mono);
  font-size: var(--t-help);
  padding: 6px 12px;
  border: 2px solid var(--rule);
  background: var(--paper);
  color: var(--muted);
  cursor: pointer;
}
.cfg-chip:hover { border-color: var(--on-paper); color: var(--on-paper); }
.cfg-chip[data-on="1"] { border-color: var(--on-paper); background: var(--lime); color: var(--ink); font-weight: 700; }
.cfg-chip:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 2px; }
.cfg-search { margin: 0; flex: 0 1 300px; }
.cfg-search input[type="search"] {
  width: 100%;
  min-height: var(--ctl-h);
  padding: 10px 14px;
  font-family: var(--mono);
  font-size: var(--t-ctl);
  color: var(--on-paper);
  background: var(--field-bg);
  border: 2px solid var(--field-border);
  border-radius: var(--ctl-r);
  appearance: none;
}
.cfg-search input:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 2px; border-color: var(--field-border-focus); }

/* ---- Management list ----------------------------------------------------- */
.cfg-list { border: 2px solid var(--on-paper); }
.cfg-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: clamp(10px, 1.6vw, 20px);
  padding: 14px clamp(12px, 1.8vw, 20px);
  border-bottom: 1px solid var(--rule);
}
.cfg-row:last-child { border-bottom: 0; }
.cfg-row .nm { font-family: var(--display); font-weight: 700; font-size: var(--t-ctl); }
.cfg-row .id { font-family: var(--mono); font-size: var(--t-help); color: var(--muted); }
.cfg-row .acts { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.cfg-badge {
  font-family: var(--mono);
  font-size: var(--t-help);
  padding: 4px 10px;
  border: 2px solid currentColor;
  white-space: nowrap;
}
.cfg-badge[data-s="current"] { color: var(--state-ok); }
.cfg-badge[data-s="update"]  { color: var(--ink); background: var(--lime); border-color: var(--on-paper); }
.cfg-badge[data-s="changed"] { color: var(--state-warn); }
.cfg-badge[data-s="missing"] { color: var(--muted); }

/* The sentence under a row that reads "lokal verändert". The badge alone is a
   verdict without a reason, and this is the state that sends people INTO the
   file it flags, so the explanation belongs on the row and nowhere else: it is
   indented, help-sized and quiet, and the 2px warn stroke on its left edge is
   what ties it to the badge above it rather than to the list as a whole.

   Two placements, because it is a continuation either way: as a cell inside
   .cfg-row (spanning all three columns) or as the row's next sibling in
   .cfg-list. In the second case the row's own hairline would cut the sentence
   away from the line it explains, so it moves to the note. */
.cfg-changed-note {
  font-size: var(--t-help);
  line-height: 1.4;
  color: var(--muted);
  max-width: 70ch;
  margin: 0;
  padding-left: 14px;
  border-left: 2px solid var(--state-warn);
}
.cfg-row > .cfg-changed-note { grid-column: 1 / -1; margin-top: 10px; }
.cfg-row:has(+ .cfg-changed-note) { border-bottom: 0; padding-bottom: 4px; }
.cfg-list > .cfg-changed-note {
  margin: 0 clamp(12px, 1.8vw, 20px) 0 calc(clamp(12px, 1.8vw, 20px) + 6px);
  padding-bottom: 14px;
  border-bottom: 1px solid var(--rule);
}
.cfg-list > .cfg-changed-note:last-child { border-bottom: 0; }

/* ---- Buttons: reuse the site .btn, add sizes the tool needs -------------- */
.cfg-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  margin-top: clamp(20px, 3vw, 30px);
}
.cfg-actions .spacer { flex: 1 1 auto; }
.btn-sm {
  font-family: var(--mono);
  font-size: var(--t-help);
  padding: 8px 14px;
  border: 2px solid var(--on-paper);
  background: var(--paper);
  color: var(--on-paper);
}
.btn-sm:hover { background: var(--on-paper); color: var(--paper); }
.btn-sm[disabled] { opacity: .45; cursor: not-allowed; }
.btn-sm.is-primary { background: var(--lime); color: var(--ink); }
.btn-sm:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 2px; }

/* ---- Per-game keys (schema from config.js.example) ------------------------
   These fields are labelled with the KEY itself and the game's own comment,
   so they read like the file a hand-editor would open. Mono throughout: a
   config key is code, never prose (03-ui-guidelines.md, "Typografie"). */
.cfg-adv { margin-top: clamp(22px, 3vw, 32px); border-top: 2px solid var(--on-paper); padding-top: 18px; }
.cfg-adv > summary {
  font-family: var(--mono);
  font-size: var(--t-ctl);
  font-weight: 700;
  cursor: pointer;
  padding: 8px 0;
  list-style-position: inside;
}
.cfg-adv > summary:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 2px; }
.cfg-adv .cfg-eyebrow { margin-top: 26px; border-top: 1px solid var(--rule); padding-top: 14px; }
/* The left edge is the ONLY carrier of "you changed this" against "default":
   the row is otherwise identical, no marker, no weight change. That makes it a
   meaningful graphic object (3:1), and flat --lime is a fill colour that
   measured 1.05:1 on --paper-2 in the light theme, so on roulette's 98 text
   rows nobody could see what they had touched. --lime-ink swaps per theme:
   4.98:1 light, 11.13:1 dark. Same reasoning as .cfg-ok-banner below, and the
   three sibling rules (.cfg-cfield, .cfg-cmd-row, .cfg-txt-row) carry it too. */
.cfg-gfield { padding-left: 0; border-left: 4px solid transparent; }
.cfg-gfield.is-changed { border-left-color: var(--lime-ink); }
.cfg-key { font-family: var(--mono); }
/* A generic field labelled by its documented sentence instead of its config key
   (gameField()): prose, so it does not read as code, and a shade lighter than
   the mono key label because these run to a full line or two. Outranks the
   `.cfg-field > label` mono rule on specificity, order-independently. */
.cfg-field > label.cfg-name {
  font-family: inherit;
  font-weight: 600;
  line-height: 1.35;
}
.cfg-def {
  display: block;
  font-family: var(--mono);
  font-size: var(--t-help);
  color: var(--muted);
  margin-top: 6px;
}
.cfg-field input[type="number"] {
  width: 100%;
  min-height: var(--ctl-h);
  padding: 10px 14px;
  font-family: var(--mono);
  font-size: var(--t-ctl);
  color: var(--on-paper);
  background: var(--field-bg);
  border: 2px solid var(--field-border);
  border-radius: var(--ctl-r);
}
.cfg-field textarea { resize: vertical; line-height: 1.5; }

/* ---- Curated settings (hub.json) -----------------------------------------
   These read like settings, not like a config file: a name in plain words, the
   right control, the unit the streamer thinks in. The raw key list above keeps
   its mono, code-like look for the other forty keys. */
.cfg-curated { margin: 4px 0 clamp(18px, 2.4vw, 26px); }
.cfg-cfield { padding-left: 14px; border-left: 4px solid transparent; }
.cfg-cfield.is-changed { border-left-color: var(--lime-ink); }
.cfg-cfield > label { font-family: var(--display); font-weight: 700; text-transform: none; letter-spacing: 0; }

.cfg-slider { display: flex; align-items: center; gap: 16px; }
.cfg-slider input[type="range"] { flex: 1 1 auto; min-height: 44px; accent-color: var(--lime); }
.cfg-slider-val {
  font-family: var(--mono);
  font-size: var(--t-ctl);
  font-variant-numeric: tabular-nums;
  min-width: 6ch;
  text-align: right;
}

/* Payout table: one input per slot, so nobody has to type JSON.
   The selector carries .cfg-field on purpose: `.cfg-field input[type="number"]`
   further up sets width:100% and would otherwise stack the slots vertically. */
.cfg-table { display: flex; flex-wrap: wrap; gap: 6px; }
.cfg-field .cfg-table input[type="number"],
.cfg-table input {
  width: 7ch;
  min-height: 44px;
  padding: 6px 8px;
  font-family: var(--mono);
  font-size: var(--t-help);
  text-align: center;
  color: var(--on-paper);
  background: var(--field-bg);
  border: 2px solid var(--field-border);
}
.cfg-table input:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 2px; }
.cfg-table .btn-sm { min-width: 44px; }

.cfg-reset {
  font-family: var(--mono);
  font-size: var(--t-help);
  color: var(--muted);
  background: none;
  border: 0;
  border-bottom: 1px solid currentColor;
  padding: 0;
  cursor: pointer;
}
.cfg-reset:hover { color: var(--on-paper); }
.cfg-reset:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 2px; }

/* Theme tiles reuse the bot-tile geometry; a theme with no description is a
   single line, so the rows must not stretch to a fixed height. */
.cfg-themes .cfg-tile { grid-template-rows: auto; }

/* ---- Feinschliff step: the delivery, and the way past it ------------------
   The tuning step is the first screen after the files exist on disk. Two things
   have to be on it before anything else: that it worked, and that nothing here
   is compulsory. */

/* The one moment in the whole tool where the product has DELIVERED, so it is
   the one place a status line may use the accent. A 4px lime edge, not a lime
   fill: the fill is the primary button, and two lime surfaces on one screen
   means neither of them points anywhere. */
.cfg-ok-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  max-width: 70ch;
  margin: 0 0 clamp(16px, 2.2vw, 22px);
  padding: 10px 0 10px 14px;
  /* --lime-ink, not --lime. The flat lime is a FILL colour: on the light paper
     it measures 1.05:1, so the one mark that says "this worked" would have been
     invisible on exactly the surface it sits on. --lime-ink exists for lime
     used as text or as a graphic (4.98:1 here) and flips to the bright value in
     the dark theme on its own. */
  border-left: 4px solid var(--lime-ink);
  font-family: var(--mono);
  font-size: var(--t-ctl);
  color: var(--on-paper);
}

/* "Feinschliff überspringen", top right of the step. Everything below it is
   optional, and a screen full of settings does not say so on its own.
   Optically a .cfg-link, positioned by the grid rule underneath. */
.cfg-skip {
  font: inherit;
  font-family: var(--mono);
  font-size: var(--t-help);
  color: var(--muted);
  background: none;
  border: 0;
  border-bottom: 1px solid currentColor;
  padding: 0;
  cursor: pointer;
  text-decoration: none;
  /* Right-aligned without float, and harmless wherever it ends up: as a flex or
     grid item it pushes itself over, as a plain block child it right-aligns its
     own box on its own line. */
  width: fit-content;
  margin-left: auto;
  align-self: baseline;
  justify-self: end;
}
.cfg-skip:hover { color: var(--on-paper); }
.cfg-skip:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 3px; }
/* Puts it on the heading's line when it directly follows the heading: the panel
   becomes a two-column grid in which EVERY other child still spans the full
   width, so only that one pair shares a row. Nothing else in the file needs the
   panel to be a grid, hence the :has() gate. One consequence to know: grid kills
   margin collapsing, so this panel's vertical rhythm is the sum of the margins
   rather than the larger of two. */
.cfg-panel:has(> h2 + .cfg-skip) {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  align-content: start;
}
.cfg-panel:has(> h2 + .cfg-skip) > *            { grid-column: 1 / -1; }
.cfg-panel:has(> h2 + .cfg-skip) > h2           { grid-column: 1; }
.cfg-panel:has(> h2 + .cfg-skip) > h2 + .cfg-skip { grid-column: 2; }

/* The overlay picker in the tuning step: one overlay is edited at a time. */
.cfg-tune-pick { max-width: 44ch; margin-bottom: clamp(18px, 2.4vw, 26px); }

/* ---- Progress + toast ---------------------------------------------------- */
.cfg-progress { margin-top: 18px; font-family: var(--mono); font-size: var(--t-help); }
.cfg-progress progress { width: 100%; height: 14px; }
.cfg-progress-label { display: block; margin-bottom: 6px; }
.cfg-toast {
  position: fixed;
  left: 50%;
  /* Clears the sticky action bar on the picker. The bar is the whole point of
     that screen, and a toast parked on top of the primary button is the same
     defect the missing-consent toast was removed for. --cfg-toast-lift carries
     the bar's MEASURED height (set by render() in hub.js) and is 0 with no bar
     on screen. A constant was tried and is wrong: the bar wraps, so its height
     depends on the viewport and on how long the counter text is. */
  bottom: calc(24px + var(--cfg-toast-lift, 0px));
  transform: translateX(-50%);
  z-index: 60;
  background: var(--ink);
  color: var(--on-ink);
  border: 2px solid var(--lime);
  font-family: var(--mono);
  font-size: var(--t-help);
  padding: 12px 18px;
  max-width: min(92vw, 60ch);
}
/* The border is the only thing separating a failure from a success here (the
   text colour is --on-ink either way), so it has to carry the meaning on its
   own. The toast surface is --ink in BOTH themes, which is why this one value
   is hardcoded instead of taking --state-error: the light theme's red is tuned
   for light paper and measures 2.67:1 on --ink, under the 3:1 a graphic needs.
   This is the dark theme's red, 8.6:1 on --ink, correct in both. */
.cfg-toast[data-kind="error"] { border-color: #ff8a7a; }

/* ---- Secondary actions as links -------------------------------------------
   A row of equal-weight buttons reads as a row of equally likely actions. Only
   the step's primary action (and the rare update-all) stays a button; every
   other route on the screen is a text link. */
.cfg-links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  margin-top: 16px;
  font-family: var(--mono);
  font-size: var(--t-help);
}
.cfg-link {
  font: inherit;
  color: var(--muted);
  background: none;
  border: 0;
  border-bottom: 1px solid currentColor;
  padding: 0;
  cursor: pointer;
  text-decoration: none;
}
.cfg-link:hover { color: var(--on-paper); }
.cfg-link:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 3px; }
.cfg-help .cfg-link { display: inline-block; margin-top: 6px; }
/* The worked example under a field: what to paste, and what it becomes. */
.cfg-example {
  display: block;
  font-family: var(--mono);
  margin-top: 6px;
  color: var(--on-paper);
  opacity: .85;
}

/* ---- Numbered instructions ------------------------------------------------
   The OBS step and the manual path give the same kind of thing (do this, then
   this) and now share one class instead of one of them inheriting a look from
   the container it happened to sit in. Carrying the descendant selector too, so
   the manual panel keeps working if a list there is ever built without the
   class. styles.css resets every margin, so the spacing has to be stated. */
.cfg-manual ol,
.cfg-steplist { padding-left: 1.4em; margin: 18px 0 0; }
.cfg-manual li,
.cfg-steplist li { list-style: decimal; margin-bottom: 10px; max-width: 62ch; }
.cfg-note {
  font-family: var(--mono);
  font-size: var(--t-help);
  color: var(--muted);
  margin-top: 18px;
  border-left: 4px solid var(--rule);
  padding-left: 14px;
  max-width: 62ch;
}

/* ---- Content-bank import (spreadsheet -> questions.js) -------------------- */
.cfg-banks { margin-top: clamp(20px, 3vw, 30px); }
.cfg-bank { border: 2px solid var(--on-paper); }
.cfg-bank-count { font-family: var(--mono); font-size: var(--t-help); color: var(--muted); white-space: nowrap; }

/* The import screen is two numbered rows and nothing else. Number, one line of
   what it is, the control on the right: the eye lands on the buttons, and the
   copy is short enough to be read on the way past rather than studied. */
.cfg-imp {
  margin-top: 18px;
  border: 2px solid var(--on-paper);
  background: var(--field-bg);
}
.cfg-imp-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
}
.cfg-imp-row + .cfg-imp-row { border-top: 1px solid var(--rule); }
.cfg-imp-n {
  font-family: var(--mono);
  font-size: var(--t-help);
  font-weight: 700;
  line-height: 1;
  padding: 6px 9px;
  color: var(--ink);
  background: var(--lime);
}
.cfg-imp-body .cfg-help { display: block; max-width: 60ch; margin-top: 3px; }
.cfg-imp-ctl .btn-sm { white-space: nowrap; }
.cfg-file { font-family: var(--mono); font-size: var(--t-help); }
/* Expanded preview: bounded so a 500-row bank cannot push the save button off
   the screen it belongs to. */
.cfg-imp-more { margin-top: 12px; }
.table-scroll.is-tall { max-height: 60vh; overflow-y: auto; }

/* Preview: the mapping row is the load-bearing part, so give the table room
   and let a wide sheet scroll inside its own box rather than the page. */
.cfg-preview { margin-top: 20px; }
.cfg-preview-notes { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
.cfg-bank-ok   { font-family: var(--mono); font-size: var(--t-help); color: var(--state-ok); font-weight: 700; }
.cfg-bank-warn { font-family: var(--mono); font-size: var(--t-help); color: var(--state-warn); max-width: 70ch; }
.cfg-bank-err  { font-family: var(--mono); font-size: var(--t-help); color: var(--state-error); max-width: 70ch; }
.cfg-preview-table {
  border-collapse: collapse;
  font-size: var(--t-help);
  min-width: 100%;
}
.cfg-preview-table th,
.cfg-preview-table td {
  border: 1px solid var(--rule);
  padding: 6px 10px;
  text-align: left;
  vertical-align: top;
  max-width: 40ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cfg-preview-table td { font-family: var(--mono); color: var(--muted); }
.cfg-colmap {
  font-family: var(--mono);
  font-size: var(--t-help);
  padding: 4px 6px;
  border: 2px solid var(--on-paper);
  background: var(--paper);
  color: var(--on-paper);
  max-width: 22ch;
}

/* ---- Commands (commandsBlock) ---------------------------------------------
   A rename is one input, so the row is one line: what it does, the chat word,
   who may. The header row labels the columns once instead of repeating three
   labels fifteen times. */
.cfg-cmds { margin-top: 14px; }
.cfg-cmd-head,
.cfg-cmd-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 16ch 13ch auto;
  gap: 12px;
  align-items: center;
}
.cfg-cmd-head {
  font-family: var(--mono);
  font-size: var(--t-help);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--rule);
}
.cfg-cmd-row {
  padding: 10px 0 10px 14px;
  border-left: 4px solid transparent;
  border-bottom: 1px solid var(--rule);
}
.cfg-cmd-row.is-changed { border-left-color: var(--lime-ink); }
.cfg-cmd-lbl { display: block; cursor: pointer; }
.cfg-cmd-lbl .nm { font-family: var(--display); font-weight: 700; }
.cfg-cmd-lbl .cfg-help { display: block; margin-top: 2px; }
.cfg-cmd-name,
.cfg-cmd-perm {
  width: 100%;
  min-height: 44px;
  padding: 8px 10px;
  font-family: var(--mono);
  font-size: var(--t-ctl);
  color: var(--on-paper);
  background: var(--field-bg);
  border: 2px solid var(--field-border);
  border-radius: var(--ctl-r);
}
.cfg-cmd-name:focus-visible,
.cfg-cmd-perm:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 2px; }

/* ---- Texts (textsBlock) ---------------------------------------------------
   The key stays mono (it is the name in lang.js and in config.js), the value is
   an ordinary text field, and the row under it holds the placeholders that
   entry's own default uses. Those buttons are the whole point: they are how
   someone who has never written a template gets {player} in without a typo. */
.cfg-txt-search {
  width: 100%;
  max-width: 34ch;
  min-height: 44px;
  margin: 12px 0 6px;
  padding: 8px 12px;
  font-family: var(--mono);
  font-size: var(--t-ctl);
  color: var(--on-paper);
  background: var(--field-bg);
  border: 2px solid var(--field-border);
  border-radius: var(--ctl-r);
}
.cfg-txt-row {
  padding: 10px 0 10px 0;
  border-left: 4px solid transparent;
  border-bottom: 1px solid var(--rule);
}
.cfg-txt-row.is-changed { border-left-color: var(--lime-ink); }
.cfg-txt-row[hidden] { display: none; }
.cfg-txt-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.cfg-txt-key { font-family: var(--mono); font-size: var(--t-help); color: var(--muted); }
.cfg-txt-in {
  width: 100%;
  margin-top: 4px;
  padding: 8px 12px;
  font-family: var(--mono);
  font-size: var(--t-ctl);
  line-height: 1.5;
  resize: vertical;
  /* One line for "BEREIT", four for a long status template. Without this every
     one of the 39 to 98 rows is the height of the longest, and the block reads
     as a wall. field-sizing is Chromium-only, which is the same browser the
     directory API already requires; elsewhere rows="1" plus min-height is the
     honest fallback. */
  field-sizing: content;
  min-height: 44px;
  max-height: 12em;
  color: var(--on-paper);
  background: var(--field-bg);
  border: 2px solid var(--field-border);
  border-radius: var(--ctl-r);
}
.cfg-txt-in:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 2px; }
.cfg-txt-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 6px; }
.cfg-var {
  font-family: var(--mono);
  font-size: var(--t-help);
  padding: 4px 8px;
  min-height: 32px;
  color: var(--on-paper);
  background: var(--paper);
  border: 2px solid var(--on-paper);
  cursor: pointer;
}
/* Lime carries dark text in BOTH themes, so the hover state has to switch the
   colour too: `--on-paper` is near-black on paper and near-white in dark mode,
   and only the second one lands on lime. This was the one lime surface in the
   file that inherited its text colour instead of pairing with `--ink`. */
.cfg-var:hover { background: var(--lime); color: var(--ink); }
.cfg-var:focus-visible { outline: 3px solid var(--field-ring); outline-offset: 2px; }
.cfg-txt-warn { font-family: var(--mono); font-size: var(--t-help); color: var(--state-warn); }
.cfg-txt-warn:empty { display: none; }

/* ---- Install consent ------------------------------------------------------
   Sits directly above the button that writes to disk, so the thing being
   agreed to and the thing being done are on the same screen. */
.cfg-consent {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 12px;
  align-items: start;
  margin-top: 16px;
  padding: 14px;
  border: 2px solid var(--on-paper);
  background: var(--paper);
  cursor: pointer;
}
.cfg-consent input[type="checkbox"] {
  width: 24px;
  height: 24px;
  margin-top: 2px;
  accent-color: var(--lime);
}
.cfg-consent-links { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 6px; }
.cfg-consent .cfg-help { display: block; margin-top: 4px; }

/* Already agreed. It stays on screen, because a consent nobody can find again
   is a consent nobody can withdraw, but it stops being a box: an open question
   and a settled one must not look alike, or the settled one keeps asking. Same
   surface as the panel, no frame, help-sized, muted. The withdraw control next
   to it is a .cfg-link and needs nothing here. */
.cfg-consent.is-done {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 14px;
  max-width: 70ch;
  margin-top: 14px;
  padding: 0;
  border: 0;
  background: none;
  cursor: auto;
  font-size: var(--t-help);
  color: var(--muted);
}
.cfg-consent.is-done .cfg-help { margin-top: 0; }
/* Only bites if the settled state keeps the box: a 24px control is the size of
   a question, and this one has already been answered. */
.cfg-consent.is-done input[type="checkbox"] { width: 18px; height: 18px; margin-top: 0; }

/* ---- Small screens ------------------------------------------------------- */
@media (max-width: 620px) {
  /* Label on its own line, then the two controls side by side under it. */
  .cfg-cmd-head { display: none; }
  .cfg-cmd-row { grid-template-columns: 1fr 1fr; }
  .cfg-cmd-lbl { grid-column: 1 / -1; }
  .cfg-row { grid-template-columns: 1fr; align-items: start; }
  .cfg-row .acts { justify-content: flex-start; }
  /* Number stays on the text, the control drops under both. */
  .cfg-imp-row { grid-template-columns: auto 1fr; }
  .cfg-imp-ctl { grid-column: 1 / -1; }
  /* The card is already stacked at every width, so a phone only needs the
     footer to stop trying to sit on one line. */
  .cfg-pick-foot { align-items: stretch; flex-direction: column; }
  .cfg-take, .cfg-pay-btn { justify-content: center; text-align: center; }
  .cfg-step { flex: 1 1 100%; border-right: 0; border-bottom: 1px solid var(--rule); }
  .cfg-filter { flex-direction: column; align-items: stretch; }
  .cfg-search { flex: 1 1 auto; }
}
