/* ============================================================================
 * settings-tabs.css — the interior of every Settings tab.
 *
 * NEW FILE, 2026-08-30. Written from empty as part of the ground-up rebuild of
 * all eighteen tabs. It is NOT a pruned copy of `settings-shell.css`, and it
 * shares no selector with it.
 *
 * WHAT OWNS WHAT
 *   settings.css        the page shell, the section rail, the phone drawer, and
 *                       the `.settings-section` panel surface. KEPT AS IS.
 *   settings-tabs.css   everything INSIDE a tab. This file.
 *   settings-shell.css  the `.set-modal` dialog system only, for the nine real
 *                       dialogs on other pages. The Settings page no longer
 *                       loads it.
 *
 * WHY THE PREVIOUS LANGUAGE WAS RETIRED RATHER THAN TIDIED
 * The `.set-*` vocabulary was designed for the inside of a dialog, and its own
 * selectors said so — 231 rules were written `:is(.set-modal, .settings-section)`,
 * so page content was styled by the modal system. On top of that it had drifted
 * to 84 spacing literals, 28 font sizes, 13 radii and 249 hardcoded colours.
 * Three separate attempts to normalise it left the shell uniform and the
 * interiors untouched, which is what the page looked like before this rebuild.
 *
 * THE RULES THIS FILE HOLDS ITSELF TO
 *   1. No ID selectors. 142 of them are why nothing could be overridden before.
 *   2. No colour literals. Every value is a token, so all five palettes
 *      (`theme.js`: warm / cool / contrast / ocean / sage / sandstone) work.
 *      `--ipi-*` is used in preference to `:root` because a settings section is
 *      inside `.inspections-page`, where those tokens are defined per palette.
 *   3. One breakpoint: 900px. `desktop.css` / `tablet.css` / `mobile.css`
 *      partition at 768 and 1367 and can never line up with this page.
 *   4. Spacing comes from a 6-step scale, radius from a 2-step scale. Declared
 *      once, below, and never written as a literal in a rule.
 *   5. ⛔ NEVER `--ipi-ink-4` (#9a9188) or `--ipi-accent` (#c49510) for text.
 *      Measured on the live page before this rebuild: the gold eyebrow was
 *      2.74:1 and the faint field label 3.1:1 — both fail WCAG AA, and the
 *      label failure appeared 19 times on a single tab. `--ipi-ink-3` (#6b6458,
 *      ~5.4:1) is the quietest colour text may use. Gold is for marks and
 *      borders only.
 *   6. No dark-mode rules. Out of scope by standing project rule.
 * ========================================================================= */

/* ⛔ `.clients-modal` IS IN THIS LIST AND MUST STAY IN BOTH OF THEM (2026-09-04).
 * The Edit Client modal's Pricing tab renders _components_editor.php as its third
 * host, so it uses the same `--cfg-*` vocabulary — 550 uses across this file with
 * only 4 fallbacks. Out of scope, `border: 1px solid var(--cfg-rule-strong)`
 * is invalid at computed-value time and `.cfg-list` renders with NO border at all;
 * `.cfg-toggle__track` goes fully transparent; every gap collapses.
 * ⚠ THE SECOND BLOCK IS THE ONE THAT GETS MISSED: the `@media (max-width: 900px)`
 * block below re-declares `--cfg-field-w`, and omitting `.clients-modal` there
 * leaves the 460px desktop field cap in force on a 360px phone. */
.settings-section,
.integrations-section,
.help-section,
.set-modal,
.clients-modal {
  /* Spacing scale. Six steps, and every rule below uses one of them. */
  --cfg-1: 4px;
  --cfg-2: 8px;
  --cfg-3: 12px;
  --cfg-4: 16px;
  --cfg-5: 24px;
  --cfg-6: 32px;

  /* Radius. Two steps: controls, and containers. */
  --cfg-r-control: 8px;
  --cfg-r-box: 12px;

  /* Semantic surfaces, so a rule never reaches for a raw token. */
  --cfg-ink: var(--ipi-ink, #1a1815);
  --cfg-ink-2: var(--ipi-ink-2, #3d3a34);
  --cfg-ink-3: var(--ipi-ink-3, #6b6458);
  --cfg-rule: var(--ipi-rule, rgba(0, 0, 0, 0.07));
  --cfg-rule-strong: var(--ipi-rule-h, rgba(0, 0, 0, 0.11));
  --cfg-surface: var(--ipi-card, #ffffff);
  --cfg-inset: var(--ipi-bg-inset, #edeae3);
  --cfg-mark: var(--ipi-accent, #c49510);
  --cfg-mark-bg: var(--ipi-accent-bg, rgba(196, 149, 16, 0.08));
  --cfg-danger: var(--ipi-red, #b03030);
  --cfg-danger-bg: var(--ipi-red-bg, rgba(176, 48, 48, 0.07));
  --cfg-good: var(--ipi-green, #1e7a45);
  --cfg-good-bg: var(--ipi-green-bg, rgba(30, 122, 69, 0.08));

  /* The control metrics, stated once. Every input, select and textarea in
     every tab is this height, so a row reads as a row. */
  --cfg-control-h: 40px;
  --cfg-field-w: 460px;
}

/* ⛔ THE GLOBAL BARE-`form` RULE HAS TO BE NEUTRALISED HERE.
 * `style.css` gives every `form` a card background, 16px padding, a 12px radius
 * and `display: grid; gap: 12px`. Inside a dialog `.modal form` already undoes
 * it — but a settings tab is NOT inside `.modal`, so without this every tab
 * renders a second white card behind its own content, with mismatched padding.
 * The old file carried the same neutraliser for the same reason. */
.settings-section form,
.integrations-section form,
.help-section form {
  display: block;
  margin: 0;
  padding: 0;
  gap: 0;
  width: 100%;
  background: transparent;
  border-radius: 0;
}

/* ===========================================================================
 * 1. cfg-head — the tab's title block
 *
 * Replaces `ipi-panel__head` + three incompatible "tools" slots. The eyebrow
 * is INK, not gold: it failed AA at 2.74:1 on every tab, and its job is to say
 * which group of the rail you are in, not to decorate.
 *
 * `data-cfg-head` is the guided tour's spotlight target on all 15 settings
 * steps (config/auto_tour_steps.php + the Fire Stopping twin). Keep the
 * attribute if this markup ever moves.
 * ======================================================================== */
.cfg-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--cfg-4);
  padding-bottom: var(--cfg-4);
  margin-bottom: var(--cfg-5);
  border-bottom: 1px solid var(--cfg-rule-strong);
}

.cfg-head__copy {
  min-width: 0;
}

.cfg-head__eyebrow {
  margin: 0 0 var(--cfg-1);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--cfg-ink-3);
}

.cfg-head__title {
  margin: 0;
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.25;
  color: var(--cfg-ink);
}

/* The rail focuses this heading when you switch tab, so a screen reader
   announces where it landed. It is a heading, not a control — a focus ring on it
   reads as "this text is selected" and was visibly wrong on every switch. Real
   keyboard focus on the controls below is untouched. */
.cfg-head__title:focus {
  outline: none;
}

.cfg-head__lede {
  margin: var(--cfg-2) 0 0;
  max-width: 68ch;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--cfg-ink-3);
}

.cfg-head__lede a {
  color: inherit;
  text-decoration: underline;
}

.cfg-head__tools {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  flex-shrink: 0;
}

/* ===========================================================================
 * 2. cfg-group — a titled group of settings
 *
 * ⛔ A GROUP IS NOT A CARD. The single biggest structural fault in the old page
 * was cards inside cards inside a panel — four different corner radii on one
 * screen (20px option groups, 16px checklist items, 14px preset cards, 18px
 * price rows). A group is separated by space and one hairline. The only
 * bordered container inside a tab is `.cfg-list`.
 * ======================================================================== */
.cfg-body {
  display: flex;
  flex-direction: column;
  gap: var(--cfg-6);
  min-width: 0;
}

.cfg-group {
  display: flex;
  flex-direction: column;
  gap: var(--cfg-4);
  min-width: 0;
}

/* Second and subsequent groups carry the rule. The first sits under the head's
   own border, so giving it one too would double the line. */
.cfg-group + .cfg-group {
  padding-top: var(--cfg-6);
  border-top: 1px solid var(--cfg-rule);
}

.cfg-group__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--cfg-3);
  flex-wrap: wrap;
}

.cfg-group__copy {
  min-width: 0;
}

.cfg-group__title {
  margin: 0;
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--cfg-ink);
}

.cfg-group__hint {
  margin: var(--cfg-1) 0 0;
  max-width: 72ch;
  font-size: 13px;
  line-height: 1.55;
  color: var(--cfg-ink-3);
}

/* QR codes & labels: no label format to choose while a mode that prints no labels
   (own codes) is checked. Keyed on the radio's data-shows-labels, which the view
   writes from TagModes metadata. `display: none`, not [hidden], so it follows the
   radio live without script. */
.settings-section:has(input[data-shows-labels="0"]:checked) [data-label-format-group] {
  display: none;
}

.cfg-group__actions {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  flex-shrink: 0;
}

/* ===========================================================================
 * 3. cfg-grid + cfg-row — one setting, one shape
 *
 * Replaces six competing label patterns, including one whose element was chosen
 * at runtime (`$isCustom ? 'div' : 'label'`).
 *
 * The label is 12.5px sentence case in `--cfg-ink-2`. It was 10px uppercase 600
 * letter-spaced in a colour that failed AA — a micro-label treatment inherited
 * from the dialog system, which also meant anything richer than a bare control
 * placed inside a field came out UPPERCASE, one word per line.
 * ======================================================================== */
.cfg-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--cfg-4) var(--cfg-5);
  min-width: 0;
}

.cfg-grid--two {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.cfg-grid--three {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.cfg-row--wide {
  grid-column: 1 / -1;
}

.cfg-row {
  display: flex;
  flex-direction: column;
  gap: var(--cfg-2);
  min-width: 0;
}

.cfg-row__label {
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.4;
  color: var(--cfg-ink-2);
}

.cfg-row__opt {
  font-weight: 400;
  color: var(--cfg-ink-3);
}

.cfg-row__hint {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--cfg-ink-3);
  max-width: 62ch;
}

.cfg-row__hint code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.92em;
  background: var(--cfg-inset);
  border-radius: var(--cfg-1);
  padding: 0 var(--cfg-1);
}

/* --- controls -------------------------------------------------------------
 * Every text-shaped control in every tab, one declaration. `:where()` keeps the
 * specificity at (0,1,0) so a tab can still adjust one without an ID or an
 * `!important` — which is what 142 ID-anchored rules existed to work around. */
/* ⛔ ANCHORED ON `.cfg-row`, NOT ON A PAGE-ONLY ANCESTOR. This editor is also
   rendered by the Fire Stopping setup wizard inside a `.set-modal`, where no
   `.settings-section` exists — scoping these to the page would have left the
   wizard's copy of the capture-vocabularies editor unstyled. (0,1,1) already
   beats the bare-tag `input` / `select` / `textarea` rules in style.css. */
.cfg-row input[type="text"],
.cfg-row input[type="email"],
.cfg-row input[type="tel"],
.cfg-row input[type="url"],
.cfg-row input[type="number"],
.cfg-row input[type="password"],
.cfg-row input[type="search"],
.cfg-row input[type="date"],
.cfg-row select,
.cfg-row textarea {
  width: 100%;
  max-width: var(--cfg-field-w);
  min-height: var(--cfg-control-h);
  padding: var(--cfg-2) var(--cfg-3);
  font: inherit;
  font-size: 13.5px;
  line-height: 1.4;
  color: var(--cfg-ink);
  background: var(--cfg-surface);
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-control);
  box-shadow: none;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

.cfg-row textarea {
  min-height: 96px;
  max-width: 72ch;
  resize: vertical;
}
.cfg-row--wide input,
.cfg-row--wide select,
.cfg-row--wide textarea {
  max-width: 100%;
}

/* A MEASURE, for a row whose control holds PROSE rather than a value.
 *
 * ⚠ `--wide` means "span the grid", and on a 1440px screen that made the Terms &
 * conditions textareas 1,057px — roughly 150 characters a line — for wording
 * that prints in a narrow PDF column. Long measures are hard to read and harder
 * to proofread, and the box looked nothing like the output it describes.
 *
 * ⛔ IT MUST STAY BELOW THE `--wide` OVERRIDE ABOVE. Both are (0,1,1), so the
 * later one wins; written above it, this rule lost silently and the boxes
 * measured 971px with the class applied and doing nothing. Found by measuring in
 * a browser, not by reading the file.
 *
 * ⛔ AND IT MUST NOT NAME `input[type="text"]`. It did, and that selector is
 * (0,2,1) — enough to beat `.cfg-row input[type="text"] { max-width:
 * var(--cfg-field-w) }` — so it WIDENED all 18 single-line heading inputs from
 * the system's 460px control width to 695px. A 12-character heading in a 695px
 * box is the exact inverse of a measure. The bullet inputs are still covered,
 * via their `.dt-bullets` container.
 */
.cfg-row--measure > textarea,
.cfg-row--measure .dt-bullets {
  max-width: 78ch;
}

.cfg-row input:focus-visible,
.cfg-row select:focus-visible,
.cfg-row textarea:focus-visible {
  outline: none;
  border-color: var(--cfg-mark);
  box-shadow: 0 0 0 3px var(--cfg-mark-bg);
}

.cfg-row input:disabled,
.cfg-row select:disabled,
.cfg-row textarea:disabled {
  background: var(--cfg-inset);
  color: var(--cfg-ink-3);
  cursor: not-allowed;
}

/* A currency or unit sits with the control, not floating beside it. */
.cfg-affix {
  display: flex;
  align-items: stretch;
  width: 100%;
  max-width: var(--cfg-field-w);
  background: var(--cfg-surface);
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-control);
  overflow: hidden;
}

.cfg-affix__mark {
  display: flex;
  align-items: center;
  padding: 0 var(--cfg-3);
  font-size: 13px;
  font-weight: 600;
  color: var(--cfg-ink-3);
  background: var(--cfg-inset);
  border-right: 1px solid var(--cfg-rule-strong);
}

.cfg-affix input {
  border: 0;
  border-radius: 0;
  max-width: none;
  flex: 1 1 auto;
  min-width: 0;
}

.cfg-affix:focus-within {
  border-color: var(--cfg-mark);
  box-shadow: 0 0 0 3px var(--cfg-mark-bg);
}

.cfg-affix input:focus-visible {
  box-shadow: none;
}

/* The logo preview on Organisation profile. Was `.brand-logo-preview`, whose two
   rules lived in settings-shell.css and died with the rest of its settings half. */
.cfg-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 132px;
  max-width: 320px;
  padding: var(--cfg-4);
  background: var(--cfg-inset);
  border: 1px solid var(--cfg-rule);
  border-radius: var(--cfg-r-box);
}

.cfg-logo img {
  max-width: 200px;
  max-height: 100px;
  height: auto;
  width: auto;
}

/* ⛔ DO NOT PUT `height: auto` ON THE PLACEHOLDER SVG. `style.css` sizes it with
   `.logo-placeholder--large svg { height: 120px }`, and an SVG that has a
   viewBox but no intrinsic size collapses to 0x0 the moment something overrides
   that with `auto`. A `.cfg-logo svg` rule did exactly that and rendered an
   empty grey box — caught in the browser, not by any test. Cap it, never
   re-declare it. */
.cfg-logo .logo-placeholder--large svg {
  height: 96px;
  max-width: 200px;
}

/* --- toggle ---------------------------------------------------------------
 * One boolean control for the whole page. The old page had `set-toggle` for a
 * standalone boolean and a bare `toggle-switch` inside a list row — two classes
 * for one job — plus raw browser checkboxes on the Components tab. */
.cfg-toggle {
  /* Containing block for the visually-hidden input below. Without it the
     absolutely-positioned control resolves against a far-off ancestor and the
     browser's focus scroll-into-view jumps the whole page. */
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--cfg-3);
  cursor: pointer;
  min-width: 0;
}

.cfg-toggle input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  pointer-events: none;
}

.cfg-toggle__track {
  position: relative;
  flex: 0 0 auto;
  width: 40px;
  height: 24px;
  margin-top: 1px;
  border-radius: 999px;
  background: var(--cfg-inset);
  border: 1px solid var(--cfg-rule-strong);
  transition: background 0.14s ease, border-color 0.14s ease;
}

.cfg-toggle__track::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--cfg-surface);
  box-shadow: var(--ipi-shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.06));
  transition: transform 0.14s ease;
}

.cfg-toggle input:checked + .cfg-toggle__track {
  background: var(--cfg-ink);
  border-color: var(--cfg-ink);
}

.cfg-toggle input:checked + .cfg-toggle__track::after {
  transform: translateX(16px);
}

.cfg-toggle input:focus-visible + .cfg-toggle__track {
  box-shadow: 0 0 0 3px var(--cfg-mark-bg);
  border-color: var(--cfg-mark);
}

.cfg-toggle__copy {
  min-width: 0;
}

.cfg-toggle__label {
  display: block;
  font-size: 13px;
  font-weight: 500;
  color: var(--cfg-ink);
}

.cfg-toggle__hint {
  display: block;
  margin-top: var(--cfg-1);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--cfg-ink-3);
}

/* ===========================================================================
 * 4. cfg-list — every repeating thing
 *
 * Replaces five bespoke card families: `component-settings-item`,
 * `email-template-row`, `fire-door-pricing-grid__item`, `preset-form-card` and
 * `fss-opt-item`. ONE bordered container; rows separated by rules inside it,
 * never a stack of individually-bordered cards.
 * ======================================================================== */
.cfg-list {
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-box);
  background: var(--cfg-surface);
  overflow: hidden;
  min-width: 0;
  /* ⚠ Load-bearing in a flex column: without it the list is squashed to the
     column's height and its own scroll dies. The list keeps its natural
     height; the page scrolls. */
  flex-shrink: 0;
}

.cfg-list__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cfg-3);
  padding: var(--cfg-2) var(--cfg-4);
  background: var(--cfg-inset);
  border-bottom: 1px solid var(--cfg-rule-strong);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--cfg-ink-3);
}

.cfg-list__row {
  display: flex;
  align-items: center;
  gap: var(--cfg-3);
  padding: var(--cfg-3) var(--cfg-4);
  min-width: 0;
}

.cfg-list__row + .cfg-list__row,
.cfg-list__group + .cfg-list__group,
.cfg-list__group .cfg-list__row + .cfg-list__row {
  border-top: 1px solid var(--cfg-rule);
}

.cfg-list__main {
  flex: 1 1 auto;
  min-width: 0;
}

.cfg-list__title {
  margin: 0;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--cfg-ink);
}

.cfg-list__meta {
  margin: var(--cfg-1) 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--cfg-ink-3);
}

.cfg-list__aside {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
}

.cfg-list__actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
}

.cfg-list__foot {
  padding: var(--cfg-3) var(--cfg-4);
  border-top: 1px solid var(--cfg-rule);
  background: var(--cfg-inset);
}

/* An expanding row. `<details>` so it works with no JS, which is what
   /demo/settings and a no-JS visitor get. */
.cfg-list__group {
  min-width: 0;
}

.cfg-list__summary {
  display: flex;
  align-items: center;
  gap: var(--cfg-3);
  padding: var(--cfg-3) var(--cfg-4);
  cursor: pointer;
  list-style: none;
  min-width: 0;
}

/* ⛔ A SETTINGS SECTION IS AN `.ipi-panel`, AND BOTH TENANT SHELLS PUT A CHEVRON
   ON EVERY `<summary>` INSIDE ONE:

       .ipi-panel summary::after { content: ""; position: absolute; right: 8px;
                                   border-right/bottom; transform: rotate(45deg) }

   (inspections_shell.css and fire-stopping-shell.css, both of them). So every
   `<details>` row in this file rendered a SECOND chevron, hard against the right
   edge past the toggle, while this markup draws its own on the left. It is a
   shared primitive that Security's disclosures rely on, so it is neutralised
   here rather than edited there — and it needs `.ipi-panel` in the selector to
   out-specify (0,1,1).

   ⚠ Found by looking at the page. `::marker` and `::-webkit-details-marker`
   were both already suppressed and neither was the culprit; the first two
   attempts at this fixed the wrong pseudo-element. */
.ipi-panel .cfg-list__summary::after,
/* ⚠ THE TIER HEADERS ARE `<summary>` TOO, and inherit the same stray chevron
   from the shell. Caught by looking at the page — a `13` count with a second
   arrow jammed against it. Any new `<summary>` in this file needs adding here. */
.ipi-panel .cfg-tier__summary::after {
  content: none;
}

.cfg-list__summary::-webkit-details-marker {
  display: none;
}

.cfg-list__summary::marker {
  content: "";
}

.cfg-list__summary:hover {
  background: var(--cfg-inset);
}

.cfg-list__summary:focus-visible {
  outline: 2px solid var(--cfg-mark);
  outline-offset: -2px;
}

.cfg-list__chev {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  color: var(--cfg-ink-3);
  transition: transform 0.14s ease;
}

.cfg-list__group[open] > .cfg-list__summary .cfg-list__chev {
  transform: rotate(90deg);
}

.cfg-list__panel {
  padding: 0 var(--cfg-4) var(--cfg-4);
  border-top: 1px solid var(--cfg-rule);
  padding-top: var(--cfg-4);
}

/* A stack of editable rows inside a panel (Capture vocabularies, 2026-09-29). The rows are
   `.cfg-toolbar`s — an input and its Remove — and sat edge to edge with nothing between them,
   so each input box touched the next and the last touched "Add item". */
.cfg-stack {
  display: grid;
  gap: var(--cfg-2);
  min-width: 0;
}

/* One line per row at every width: the toolbar wraps by default, and on a phone each Remove
   dropped under its input, the same distance from the next row's input as from its own. */
.cfg-stack > .cfg-toolbar {
  flex-wrap: nowrap;
}

.cfg-stack > .cfg-toolbar > .cfg-toolbar__search {
  flex: 1 1 0;
}

/* Out-specifies the ≤900px `.cfg-toolbar__actions { width: 100% }`, which would otherwise take
   the whole row and squeeze the input to nothing. */
.cfg-stack > .cfg-toolbar > .cfg-toolbar__actions {
  flex: 0 0 auto;
  width: auto;
}

.cfg-list__panel > .cfg-stack ~ .cfg-empty,
.cfg-list__panel > .cfg-stack ~ .cfg-group__actions {
  margin-top: var(--cfg-3);
}

/* Count / status marks. `nowrap` because a shrinking badge once wrapped
   "4 options" onto two lines on some rows and not others. */
.cfg-count {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--cfg-2);
  border-radius: 999px;
  background: var(--cfg-inset);
  font-size: 11.5px;
  font-weight: 500;
  color: var(--cfg-ink-3);
  white-space: nowrap;
}

.cfg-count--on {
  background: var(--cfg-good-bg);
  color: var(--cfg-good);
}

.cfg-count--off {
  background: var(--cfg-danger-bg);
  color: var(--cfg-danger);
}

.cfg-count--mark {
  background: var(--cfg-mark-bg);
  color: var(--cfg-ink-2);
}

/* Empty state. Every list has one — two of the old lists had a filter and no
   empty state at all, so filtering to nothing showed a blank box. */
.cfg-empty {
  margin: 0;
  padding: var(--cfg-6) var(--cfg-4);
  text-align: center;
  font-size: 13px;
  color: var(--cfg-ink-3);
}

/* ===========================================================================
 * 5. cfg-toolbar — search and actions above a list
 *
 * Replaces `set-filter-rail` and its two incompatible mobile variants (a tray
 * that hid controls behind a button, and an `--inline-mobile` modifier that
 * opted out of it). One shape: it wraps.
 * ======================================================================== */
.cfg-toolbar {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  flex-wrap: wrap;
  min-width: 0;
}

.cfg-toolbar__search {
  position: relative;
  flex: 1 1 240px;
  min-width: 0;
  display: flex;
  align-items: center;
}

.cfg-toolbar__search svg {
  position: absolute;
  left: var(--cfg-3);
  width: 15px;
  height: 15px;
  color: var(--cfg-ink-3);
  pointer-events: none;
}

.cfg-toolbar__search input {
  width: 100%;
  min-height: var(--cfg-control-h);
  padding: var(--cfg-2) var(--cfg-3) var(--cfg-2) 34px;
  font: inherit;
  font-size: 13.5px;
  color: var(--cfg-ink);
  background: var(--cfg-surface);
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-control);
}

.cfg-toolbar__search input:focus-visible {
  outline: none;
  border-color: var(--cfg-mark);
  box-shadow: 0 0 0 3px var(--cfg-mark-bg);
}

.cfg-toolbar__actions {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  flex: 0 0 auto;
}

/* Segmented filter. Black and white, like every other control in the app —
   the old filter pill was the page's only coloured control system. */
.cfg-seg {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  background: var(--cfg-inset);
  border: 1px solid var(--cfg-rule);
  border-radius: var(--cfg-r-control);
  flex: 0 0 auto;
}

.cfg-seg__btn {
  appearance: none;
  border: 0;
  background: none;
  padding: 6px var(--cfg-3);
  border-radius: 6px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--cfg-ink-3);
  cursor: pointer;
  white-space: nowrap;
}

.cfg-seg__btn:hover {
  color: var(--cfg-ink);
}

.cfg-seg__btn.is-active {
  background: var(--cfg-surface);
  color: var(--cfg-ink);
  box-shadow: var(--ipi-shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.06));
}

.cfg-seg__btn:focus-visible {
  outline: 2px solid var(--cfg-mark);
  outline-offset: 1px;
}

/* ===========================================================================
 * 6. cfg-actions — the save bar
 *
 * One save affordance and one place feedback appears, for all eighteen tabs.
 * The old page had six feedback mechanisms (page flash, toast, a toast deferred
 * through sessionStorage across a reload, inline text, button-label mutation,
 * and an aria-live span) and seven ways of submitting.
 * ======================================================================== */
.cfg-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cfg-4);
  flex-wrap: wrap;
  margin-top: var(--cfg-6);
  padding-top: var(--cfg-4);
  border-top: 1px solid var(--cfg-rule-strong);
}

.cfg-actions__copy {
  min-width: 0;
  flex: 1 1 auto;
}

.cfg-actions__note {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--cfg-ink-3);
}

.cfg-actions__buttons {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  flex: 0 0 auto;
}

/* The status region. Reserved space, so a tab does not jump when it fills. */
.cfg-status {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  min-height: 20px;
  margin: 0;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--cfg-ink-3);
}

.cfg-status:empty {
  display: none;
}

.cfg-status--ok {
  color: var(--cfg-good);
}

.cfg-status--bad {
  color: var(--cfg-danger);
}

.cfg-status--busy {
  color: var(--cfg-ink-3);
}

/* A saving button keeps its width so the bar does not reflow mid-save. */
.settings-section .btn[data-loading],
.integrations-section .btn[data-loading],
.help-section .btn[data-loading],
.set-modal .btn[data-loading] {
  opacity: 0.7;
  pointer-events: none;
}

/* ===========================================================================
 * 7. cfg-disclose — the one dismissible-in-flow primitive
 *
 * ⛔ THERE ARE NO MODALS ON THIS PAGE. Everything that used to open a dialog,
 * a popover or a native `confirm()` / `prompt()` is now an in-flow panel:
 * help text, the pricing line-item editor, the presets form, the placeholder
 * inserter, the app-store and install content, and every destructive confirm.
 *
 * No overlay, no body scroll lock, no focus trap. Escape closes it and focus
 * returns to the trigger — handled in features/settings.js.
 * ======================================================================== */
.cfg-disclose {
  margin-top: var(--cfg-3);
  padding: var(--cfg-4);
  background: var(--cfg-inset);
  border: 1px solid var(--cfg-rule);
  border-radius: var(--cfg-r-box);
  min-width: 0;
}

.cfg-disclose__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--cfg-3);
  margin-bottom: var(--cfg-3);
}

.cfg-disclose__title {
  margin: 0;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--cfg-ink);
}

.cfg-disclose ul,
.cfg-disclose ol {
  margin: 0;
  padding-left: 18px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--cfg-ink-2);
}

.cfg-disclose li + li {
  margin-top: var(--cfg-1);
}

/* The `?` trigger in a tab head. */
.cfg-help-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--cfg-rule-strong);
  border-radius: 50%;
  background: var(--cfg-surface);
  color: var(--cfg-ink-3);
  cursor: pointer;
}

.cfg-help-btn:hover {
  color: var(--cfg-ink);
  border-color: var(--cfg-ink-3);
}

.cfg-help-btn:focus-visible {
  outline: 2px solid var(--cfg-mark);
  outline-offset: 1px;
}

.cfg-help-btn svg {
  width: 15px;
  height: 15px;
}

/* --- inline destructive confirm -------------------------------------------
 * Replaces five native `confirm()` dialogs and one `.set-modal--danger`.
 * Modelled on Delete workspace, which already asked for a typed name and a
 * password inline and was the best interaction on the old page. */
.cfg-confirm {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cfg-3);
  flex-wrap: wrap;
  margin-top: var(--cfg-3);
  padding: var(--cfg-3) var(--cfg-4);
  background: var(--cfg-danger-bg);
  border: 1px solid var(--ipi-red-border, rgba(176, 48, 48, 0.18));
  border-radius: var(--cfg-r-box);
}

.cfg-confirm__copy {
  min-width: 0;
  flex: 1 1 240px;
}

.cfg-confirm__title {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--cfg-danger);
}

.cfg-confirm__hint {
  margin: var(--cfg-1) 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--cfg-ink-2);
}

.cfg-confirm__actions {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  flex: 0 0 auto;
}

/* ===========================================================================
 * 8. cfg-keyvals — read-only state
 *
 * Payouts & Stripe, Offline cache, The app, Sample data. These tabs report
 * state rather than collect input.
 * ======================================================================== */
.cfg-keyvals {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-box);
  overflow: hidden;
}

.cfg-keyvals li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cfg-4);
  padding: var(--cfg-3) var(--cfg-4);
  min-width: 0;
}

.cfg-keyvals li + li {
  border-top: 1px solid var(--cfg-rule);
}

.cfg-keyvals__k {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--cfg-ink-3);
  flex: 0 0 auto;
}

.cfg-keyvals__v {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--cfg-ink);
  text-align: right;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Click-to-copy value. ⚠ `.cfg-copy-hint` must stay the NEXT ELEMENT SIBLING of
   the button — features/settings.js writes into `nextElementSibling` and bails
   if it is not that class. */
.cfg-copy {
  appearance: none;
  font: inherit;
  font-size: 13px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--cfg-ink);
  background: var(--cfg-inset);
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-control);
  padding: var(--cfg-1) var(--cfg-2);
  cursor: pointer;
}

.cfg-copy:hover {
  border-color: var(--cfg-ink-3);
}

.cfg-copy:focus-visible {
  outline: 2px solid var(--cfg-mark);
  outline-offset: 1px;
}

.cfg-copy-hint {
  margin-left: var(--cfg-2);
  font-size: 12px;
  color: var(--cfg-good);
}

/* ===========================================================================
 * 9. cfg-tabs — an inner tab strip
 *
 * For a tab that genuinely holds alternative VIEWS of one thing. It is NOT a
 * navigation layer — `document-terms` used to nest a second rail plus a tab
 * strip inside the tab, three levels deep before you reached a field.
 *
 * ⚠ IT HAS NO CONSUMER IN THE MARKUP AS OF 3.8.7, and that is worth knowing
 * before assuming a change here is exercised. The email editor moved to its own
 * `etpl-*` segmented control (section 12), and the Terms & conditions rebuild
 * took the last one: its Fire door / Fire stopping strip was a THIRD level of
 * navigation inside a disclosure inside a rail, and those two sets are now peer
 * entries in a `.cfg-split` nav instead.
 *
 * Kept rather than deleted because it is a documented primitive of this system
 * and the shape recurs; delete it if a release passes without one appearing.
 * ======================================================================== */
.cfg-tabs {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  background: var(--cfg-inset);
  border: 1px solid var(--cfg-rule);
  border-radius: var(--cfg-r-control);
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}

.cfg-tabs::-webkit-scrollbar {
  display: none;
}

.cfg-tab {
  appearance: none;
  border: 0;
  background: none;
  padding: 6px var(--cfg-3);
  border-radius: 6px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--cfg-ink-3);
  cursor: pointer;
  white-space: nowrap;
}

.cfg-tab:hover {
  color: var(--cfg-ink);
}

.cfg-tab.is-active,
.cfg-tab[aria-selected="true"] {
  background: var(--cfg-surface);
  color: var(--cfg-ink);
  box-shadow: var(--ipi-shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.06));
}

.cfg-tab:focus-visible {
  outline: 2px solid var(--cfg-mark);
  outline-offset: 1px;
}

/* ===========================================================================
 * 10. cfg-split — master / detail
 *
 * The right shape for choosing one of several things and editing it, expressed
 * in this system rather than in bespoke classes and non-standard breakpoints.
 *
 * ⚠ THIS SAID "Email templates only" AND HAD NOT BEEN TRUE FOR TWO RELEASES.
 * The email tab moved to its own three-pane `etpl-*` layout (section 12, whose
 * own 900px comment already says so). The real consumers are Fire door presets
 * (`_presets_editor.php`) and, since 3.8.7, Terms & conditions
 * (`_document_terms_section.php`).
 * ======================================================================== */
.cfg-split {
  display: grid;
  grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  gap: var(--cfg-5);
  align-items: start;
  min-width: 0;
}

.cfg-split__nav {
  min-width: 0;
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-box);
  overflow: hidden;
  background: var(--cfg-surface);
}

.cfg-split__scroll {
  max-height: 62vh;
  overflow-y: auto;
  scrollbar-width: thin;
}

.cfg-split__label {
  padding: var(--cfg-2) var(--cfg-4);
  background: var(--cfg-inset);
  border-bottom: 1px solid var(--cfg-rule);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--cfg-ink-3);
  position: sticky;
  top: 0;
  z-index: 1;
}

.cfg-split__item {
  display: flex;
  align-items: center;
  gap: var(--cfg-3);
  width: 100%;
  padding: var(--cfg-3) var(--cfg-4);
  text-align: left;
  background: none;
  border: 0;
  border-left: 2px solid transparent;
  font: inherit;
  cursor: pointer;
  min-width: 0;
}

.cfg-split__item + .cfg-split__item {
  border-top: 1px solid var(--cfg-rule);
}

.cfg-split__item:hover {
  background: var(--cfg-inset);
}

.cfg-split__item.is-active {
  background: var(--cfg-mark-bg);
  border-left-color: var(--cfg-mark);
}

.cfg-split__item:focus-visible {
  outline: 2px solid var(--cfg-mark);
  outline-offset: -2px;
}

.cfg-split__main {
  min-width: 0;
}

.cfg-split__back {
  display: none;
}

/* The preview frame. Sandboxed iframe fed by srcdoc from the live draft. */
.cfg-preview {
  width: 100%;
  min-height: 320px;
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-box);
  background: var(--cfg-surface);
}

.cfg-preview--text {
  padding: var(--cfg-4);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--cfg-ink-2);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  min-height: 0;
}

/* ===========================================================================
 * 8b. cfg-fieldset — a titled group that is actually a CONTAINER
 *
 * The system's law is "A GROUP IS NOT A CARD… separated by space and one
 * hairline. The only bordered container inside a tab is `.cfg-list`." That is
 * right for a tab with two or three flat groups, and it is the whole of
 * section 2 above.
 *
 * It broke on Terms & conditions, and this is what broke it: that tab nests a
 * fourth level (document → wording section → field), so four levels competed
 * for two heading treatments and one 7%-black hairline. Measured in a browser
 * before the 3.8.7 rebuild — a section's `<h4>` rendered at 14.5px inside a
 * `<summary>` at 13.5px, the CHILD heading larger than its parent, and
 * "Document types" was byte-identically styled to a section heading two levels
 * below it.
 *
 * `.cfg-fieldset` is therefore a third, container-shaped treatment: a border
 * and a tinted head — the `.cfg-list` / `.cfg-list__head` pair — but titled by
 * a real heading and carrying that section's own actions in the head.
 *
 * ⛔ IT IS FOR A GROUP OF FIELDS THAT IS ITSELF INSIDE SOMETHING. Do not reach
 * for it in a flat tab: `.cfg-group` is still correct there, and using this
 * instead would put a box round something with nothing to be distinguished
 * from. Invoicing and Capture layout are the shapes `.cfg-group` was designed
 * for and they must stay on it.
 * ======================================================================== */
.cfg-fieldset {
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-box);
  background: var(--cfg-surface);
  min-width: 0;
  /* Load-bearing in a flex column, same reason as `.cfg-list`: without it the
     fieldset is squashed to the column's height. */
  flex-shrink: 0;
}

.cfg-fieldset__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cfg-3);
  padding: var(--cfg-2) var(--cfg-3) var(--cfg-2) var(--cfg-4);
  background: var(--cfg-inset);
  border-bottom: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-box) var(--cfg-r-box) 0 0;
  min-width: 0;
}

/* ⚠ NOT `.cfg-group__title`'s 14.5px. A section heading sits BELOW the pane
   title, and the whole failure this primitive exists to fix was a deeper
   heading rendering larger than a shallower one. 13px plus the tinted band is
   what marks it; size is not asked to carry the hierarchy alone. */
.cfg-fieldset__title {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--cfg-ink);
  min-width: 0;
}

.cfg-fieldset__actions {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  flex-shrink: 0;
}

.cfg-fieldset__body {
  padding: var(--cfg-4);
  min-width: 0;
}

/* ===========================================================================
 * 8c. The Terms & conditions tab
 *
 * Everything below is `.dt-*` and belongs to that one tab. `.cfg-split`,
 * `.cfg-fieldset`, `.cfg-row` and `.cfg-actions` do the rest.
 * ======================================================================== */

/* One document's pane. Six exist; five are `hidden`.
 *
 * ⛔ `form.dt-pane`, NOT `.dt-pane`. The pane IS a `<form>` inside
 * `.settings-section`, and the bare-form neutraliser at the top of this file
 * (`.settings-section form { display: block; gap: 0 }`) is (0,1,1) while a lone
 * class is (0,1,0) — so a plain `.dt-pane` rule loses on specificity no matter
 * how far down the file it sits, and this whole declaration was inert. Measured:
 * `display` computed `block` and `gap` computed `0px`, so the 16px rhythm
 * between head, notes, body, preview and actions did not exist and the pane
 * notes sat flush against the first fieldset.
 *
 * Found by two independent reviewers, one screen below the comment where the
 * same class of silent loss was caught on `.cfg-row--measure`. Tying at (0,1,1)
 * and winning on source order is enough; it does not need `!important`.
 */
form.dt-pane {
  display: flex;
  flex-direction: column;
  gap: var(--cfg-4);
  min-width: 0;
}

/* ⛔ STICKY, because Save belongs to the document you are editing. The old tab
   had ONE static Save at the foot of a form measured at 8,977px with every
   group open. Panes are far shorter now — 13 fields at worst — but a long one
   still must not put its own Save below the fold. */
.dt-pane__head {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--cfg-4);
  flex-wrap: wrap;
  padding: var(--cfg-3) 0;
  background: var(--cfg-surface);
  border-bottom: 1px solid var(--cfg-rule-strong);
}

.dt-pane__copy {
  min-width: 0;
}

.dt-pane__title {
  margin: 0;
  font-size: 15.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--cfg-ink);
}

.dt-pane__meta {
  margin: var(--cfg-1) 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--cfg-ink-3);
}

.dt-pane__actions {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  flex-shrink: 0;
}

/* The status region sits in the head with the Save it reports on, so "Saved."
   appears next to the button that was pressed rather than a screen away. */
.dt-pane__actions .cfg-status {
  margin: 0;
}

.dt-pane__notes {
  display: flex;
  flex-direction: column;
  gap: var(--cfg-2);
}

.dt-pane__body {
  display: flex;
  flex-direction: column;
  gap: var(--cfg-4);
  min-width: 0;
}

/* A placeholder token, shown inline in the pane note. Until 3.8.7 nothing in
   this tab explained `{visit}` or `{property}` anywhere at all, on any surface,
   while four shipped defaults contain one. */
.dt-token {
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--cfg-inset);
  border: 1px solid var(--cfg-rule);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  color: var(--cfg-ink-2);
  white-space: nowrap;
}

/* "Prints on …" under a field label.
   ⚠ NOT `.cfg-row__hint`. It used to share that class with the character
   counter directly beneath it, so a CONSEQUENCE ("this edits two client PDFs")
   and a CONSTRAINT ("522 of 522 characters") were the same 12.5px grey. */
.dt-appears {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  align-self: flex-start;
  max-width: 100%;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--cfg-inset);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--cfg-ink-3);
}

.dt-appears--shared {
  background: var(--cfg-mark-bg);
  color: var(--cfg-ink-2);
}

.dt-appears--shared strong {
  font-weight: 600;
}

/* --- bullet rows -----------------------------------------------------------
 * ⚠ THEY USED TO BE `.cfg-toolbar__search`. That primitive reserves 34px of
 * left padding for a search icon, which the template cancelled with an inline
 * `style="padding-left:12px"` — and the JS row builder carried the same inline
 * override, so the two had to be kept in step by hand. A bullet is not a search
 * box; it has its own row now.
 */
.dt-bullets {
  display: flex;
  flex-direction: column;
  gap: var(--cfg-2);
  min-width: 0;
}

.dt-bullet {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  min-width: 0;
}

.dt-bullet__dot {
  flex: 0 0 auto;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--cfg-ink-3);
}

.dt-bullet__input {
  flex: 1 1 auto;
  min-width: 0;
}

.dt-bullet__remove {
  flex: 0 0 auto;
}

.dt-bullets__add {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
}

/* A reset button that has been armed and is one press from discarding wording.
 *
 * ⛔ NOT `.is-over-limit`. That is what the JS reached for first, and on a
 * `.btn.btn-secondary` it is a (0,1,0) colour against a (0,2,0) button rule, so
 * it lost: measured before and after arming, the text stayed rgb(61,58,52) and
 * the only cue the armed state had was its own label. `.btn` in the selector
 * takes this to (0,2,0) and later source order does the rest.
 */
.btn.dt-armed {
  border-color: var(--cfg-danger);
  background: var(--cfg-danger-bg);
  color: var(--cfg-danger);
  font-weight: 600;
}

/* --- character counters + the terms preview -------------------------------
 * `is-at-limit` warns BEFORE the browser silently stops accepting keystrokes:
 * hitting a maxlength with no feedback reads as a broken text box.
 *
 * 🔴 IT IS APPLIED ONLY ONCE THE FIELD HAS BEEN CUSTOMISED. 21 of the 39
 * counters read 100% of their cap on a workspace that had changed nothing —
 * for a `fixed` field the cap IS the shipped wording's own length — so an
 * untouched page rendered 21 counters in this emphasised treatment, every one
 * of them meaningless. The rule lives in two places, the template and
 * features/settings/report-wording.js; change both or it reappears on the
 * first keystroke.
 */
.cfg-row__hint .is-at-limit,
.cfg-row__hint[data-wording-bullet-count].is-at-limit {
  color: var(--cfg-ink-2);
  font-weight: 600;
}

.is-at-limit {
  color: var(--cfg-ink-2);
  font-weight: 600;
}

.is-over-limit {
  color: var(--cfg-danger);
  font-weight: 600;
}

/* The wording preview "paper". Not `--text`: it holds real block elements, and
   `white-space: pre-wrap` would turn the whitespace between them into gaps. */
.cfg-preview--paper {
  min-height: 0;
  padding: var(--cfg-4);
  font-size: 13px;
  line-height: 1.6;
  color: var(--cfg-ink-2);
}

.cfg-preview--paper > * + * {
  margin-top: var(--cfg-4);
}

.cfg-dt-block__head {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--cfg-ink-3);
  margin-bottom: var(--cfg-2);
}

.cfg-dt-block p {
  margin: 0 0 var(--cfg-2);
}

.cfg-dt-block ul {
  margin: 0;
  padding-left: 18px;
}

/* --- cfg-grid-table: the merged reason + price editor -----------------------
 * The one table in the settings system, and it earns it: a failure reason now
 * carries a risk level, a price, the wording that price prints as, and a status,
 * and those are columns, not a stack. Line item mapping used to be a separate
 * tab for the last three.
 *
 * ⛔ IT SCROLLS INSIDE ITS OWN BOX. The wrapper is `.cfg-scroll-x`; the page body
 * must never scroll sideways, and this table cannot fit a phone.
 */
.cfg-grid-table {
  width: 100%;
  min-width: 760px;
  border-collapse: collapse;
  font-size: 12.5px;
}

.cfg-grid-table th {
  text-align: left;
  padding: 6px 8px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--cfg-ink-3);
  border-bottom: 1px solid var(--cfg-rule-strong);
  white-space: nowrap;
}

.cfg-grid-table td {
  padding: 8px;
  border-bottom: 1px solid var(--cfg-rule);
  vertical-align: middle;
}

.cfg-grid-table tbody tr:last-child td {
  border-bottom: 0;
}

.cfg-grid-table__pick { width: 30px; }
.cfg-grid-table__risk { width: 116px; }
.cfg-grid-table__price { width: 146px; }
.cfg-grid-table__status { width: 122px; }
.cfg-grid-table__act { width: 94px; text-align: right; }

/* Controls inside the table are the same controls as everywhere else, just
   dense. (0,2,1) so they beat the bare-tag rules without an ID or !important. */
.cfg-grid-table td input[type="text"],
.cfg-grid-table td input[type="number"],
.cfg-grid-table td select {
  width: 100%;
  min-height: 34px;
  padding: var(--cfg-1) var(--cfg-2);
  font: inherit;
  font-size: 12.5px;
  color: var(--cfg-ink);
  background: var(--cfg-surface);
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-control);
}

.cfg-grid-table td input:focus-visible,
.cfg-grid-table td select:focus-visible {
  outline: none;
  border-color: var(--cfg-mark);
  box-shadow: 0 0 0 3px var(--cfg-mark-bg);
}

.cfg-grid-table td .cfg-affix {
  max-width: none;
}

.cfg-grid-table td .cfg-affix input {
  min-height: 34px;
}

.cfg-grid-table tr[data-reason-row].is-selected td {
  background: var(--cfg-mark-bg);
}

/* ------------------------------------------------ the price cell ----------
 * 2026-09-28, the price builder. A failure reason is TWO LINES on a desktop:
 *
 *   ☐  Failure reason · Risk · Wording on the quote · Quote line · Remove   (headed)
 *      £118.50 · Materials £74.75 · Standard, 0.75h · Edit price           (the price cell)
 *
 * The price line sits on the same white ground as the reason (owner, 2026-09-28:
 * no grey panels), indented under the reason. The row is a grid laid out by the
 * cells' own `data-rc-area`s -- the same names table-cards.css uses for the phone
 * and tablet cards -- so ONE markup serves every width. Scoped to `html.rc-wide`:
 * below 901px (and on touch wrappers) table-cards.css owns these rows.
 *
 * The client tab's own grid (`reason-client-split`) and the two-figure
 * Materials / Labour / Total cells went with the price builder: a client's
 * prices are now the "what's different" list (price-builder.css, .pe-*).
 * ------------------------------------------------------------------------- */
/* A reason row is the reason, its risk and Remove, on one line (the evening of 2026-09-29).
   Which fix a fault gets, and its price, is on Settings → Pricing. */
html.rc-wide .cfg-grid-table[data-rc-editor="reason-risk"] :is(thead, tbody) > tr {
  display: grid;
  align-items: center;
  column-gap: 0;
  grid-template-columns: minmax(200px, 1fr) 132px 94px;
}

html.rc-wide .cfg-grid-table[data-rc-editor="reason-risk"] tbody > tr[data-reason-row] {
  padding: 6px 0;
  border-bottom: 1px solid var(--cfg-rule-strong);
  grid-template-areas: "reason risk x";
}

html.rc-wide .cfg-grid-table[data-rc-editor="reason-risk"] :is(th, td) {
  border-bottom: 0;
  min-width: 0;
  /* The column classes (__price 146px, __effect 130px...) size TABLE columns; in
     this grid the template does, and a fixed cell width overhangs its track. */
  width: auto;
}

html.rc-wide .cfg-grid-table[data-rc-editor="reason-risk"] tbody > tr:last-child {
  border-bottom: 0;
}

html.rc-wide .cfg-grid-table[data-rc-editor="reason-risk"] td[data-rc-area] {
  grid-area: var(--cfg-area);
}
html.rc-wide .cfg-grid-table td[data-rc-area="pick"] { --cfg-area: pick; }
html.rc-wide .cfg-grid-table td[data-rc-area="reason"] { --cfg-area: reason; }
html.rc-wide .cfg-grid-table td[data-rc-area="id"] { --cfg-area: id; }
html.rc-wide .cfg-grid-table td[data-rc-area="risk"] { --cfg-area: risk; }
html.rc-wide .cfg-grid-table td[data-rc-area="words"] { --cfg-area: words; }
html.rc-wide .cfg-grid-table td[data-rc-area="qline"] { --cfg-area: qline; }
html.rc-wide .cfg-grid-table td[data-rc-area="x"] { --cfg-area: x; }

/* A unit that follows the figure (%), rather than a currency before it. */
.cfg-affix--suffix {
  flex-direction: row-reverse;
}

.cfg-affix--suffix .cfg-affix__mark {
  border-right: 0;
  border-left: 1px solid var(--cfg-rule-strong);
}

/* --- the email placeholder picker ------------------------------------------
 * ⛔ THESE FIVE CLASSES ARE BUILT IN JAVASCRIPT, which is why the rebuild lost
 * them: a class-inventory sweep over the MARKUP cannot see a name that only
 * exists in `features/settings/email-templates.js`. Their rules lived in
 * settings-shell.css, top-level and un-scoped; that file was pruned AND the
 * Settings page stopped loading it, so "Insert a field" rendered as bare UA
 * buttons with the Copy control wrapped onto its own line.
 * Both verification passes found this independently. Emitters:
 * email-templates.js buildPlaceholderList().
 */
.email-placeholder-group {
  margin-bottom: var(--cfg-4);
}

.email-placeholder-group > p {
  margin: 0 0 var(--cfg-2);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--cfg-ink-3);
}

.email-placeholder-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--cfg-2);
  padding: var(--cfg-2) 0;
}

.email-placeholder-row + .email-placeholder-row {
  border-top: 1px solid var(--cfg-rule);
}

.email-placeholder {
  appearance: none;
  justify-self: start;
  max-width: 100%;
  padding: var(--cfg-1) var(--cfg-2);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  text-align: left;
  color: var(--cfg-ink);
  background: var(--cfg-surface);
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-control);
  cursor: pointer;
  overflow-wrap: anywhere;
}

.email-placeholder:hover {
  border-color: var(--cfg-mark);
}

.email-placeholder:focus-visible {
  outline: 2px solid var(--cfg-mark);
  outline-offset: 1px;
}

.email-placeholder-row__meta {
  font-size: 12px;
  line-height: 1.4;
  color: var(--cfg-ink-3);
}

.email-placeholder-copy {
  flex: 0 0 auto;
  white-space: nowrap;
}

/* Also JS-built, in the clipboard fallback path. Off-screen by design. */
.clipboard-fallback-textarea {
  position: absolute;
  left: -9999px;
  top: 0;
  opacity: 0;
  pointer-events: none;
}

/* ===========================================================================
 * 11. cfg-danger — the irreversible tab
 * ======================================================================== */
.cfg-danger {
  padding: var(--cfg-4);
  background: var(--cfg-danger-bg);
  border: 1px solid var(--ipi-red-border, rgba(176, 48, 48, 0.18));
  border-radius: var(--cfg-r-box);
}

.cfg-danger__title {
  margin: 0 0 var(--cfg-1);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--cfg-danger);
}

.cfg-danger__hint {
  margin: 0 0 var(--cfg-4);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--cfg-ink-2);
  max-width: 72ch;
}

/* ===========================================================================
 * 12. Utilities
 * ======================================================================== */

/* A one-button form that must sit inline beside its siblings. Payouts & Stripe
   renders up to three of them in one row. */
form.cfg-inline {
  display: inline-flex;
  flex: 0 0 auto;
  width: auto;
}

.cfg-note {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--cfg-ink-3);
  max-width: 72ch;
}

.cfg-hidden {
  display: none !important;
}

/* Wide content scrolls inside its own box; the page never scrolls sideways. */
.cfg-scroll-x {
  overflow-x: auto;
  min-width: 0;
}

/* ===========================================================================
 * 13. 900px — the one breakpoint
 * ======================================================================== */
@media (max-width: 900px) {
  /* ⛔ `.clients-modal` BELONGS HERE TOO — see the note on the base token block at
   * the top of this file. Miss this one and the desktop 460px field cap survives
   * on a 360px phone. */
  .settings-section,
  .integrations-section,
  .help-section,
  .set-modal,
  .clients-modal {
    --cfg-field-w: 100%;
  }

  /* 🔴 THE MAGNIFIER KEEPS ITS ROOM INSIDE A SHEET MODAL.
   *
   * `style.css`'s canonical mobile rule is
   * `.modal.modal--sheet input[type="search"] { padding: 9px 12px }` — (0,3,1),
   * which out-specifies `.cfg-toolbar__search input` (0,1,1) and wins outright.
   * That is invisible on the Settings page (not a modal) and on the setup
   * wizard's `.set-modal` (not `--sheet` chrome at the time this was written),
   * and it lands squarely on the client Pricing tab, where the search box IS
   * inside a sheet modal: measured at 390px the icon sat on top of the
   * placeholder text with 12px of padding instead of 34px.
   *
   * ⚠ This is padding for an icon that is really there — content styling, which
   * the canonical-modal contract explicitly leaves to page CSS. It does NOT
   * touch the 16px font size or the touch-target height that rule exists for
   * (the iOS zoom guard), and it must not: only the left inset moves. */
  .modal.modal--sheet .cfg-toolbar__search input {
    /* ⚠ `!important` IS REQUIRED, not preferred. The canonical rule sets
       `padding: 9px 12px !important`, so an ordinary declaration at the same
       specificity loses to it however late it appears. Only the LEFT inset is
       reclaimed: the 16px font size, the 8px radius and the rest of the mobile
       input spec are left exactly as the canonical rule sets them. */
    padding-left: 34px !important;
  }

  .cfg-body {
    gap: var(--cfg-5);
  }

  .cfg-group + .cfg-group {
    padding-top: var(--cfg-5);
  }

  /* --- Terms & conditions -------------------------------------------------
   * 🔴 THE PANE HEAD STAYS STICKY ON A PHONE, and it is TRIMMED rather than
   * unstuck.
   *
   * It was `position: static` here, on the reasoning that a head carrying a
   * title, a "Prints on …" line, a status region and three buttons is most of a
   * phone viewport. That reasoning was right about the height and wrong about
   * the fix: a reviewer measured the consequence at 390px — the footer Save sat
   * 2,997px below the only `[data-cfg-status]` in the pane, so pressing it
   * painted "Saving…", "Saved." and every error message three screens above the
   * fold, on the one width where scrolling back is most expensive.
   *
   * So the head keeps its sticky, and the "Prints on …" line — which is
   * reference, not feedback, and is also on the nav item you just came from —
   * is what gives up the space instead. Title plus one wrapped action row is
   * roughly 90px of an 800px viewport.
   */
  .dt-pane__head {
    flex-direction: column;
    gap: var(--cfg-2);
  }

  .dt-pane__meta {
    display: none;
  }

  .dt-pane__actions {
    flex-wrap: wrap;
    width: 100%;
  }

  /* A section title and its Reset button will not sit on one line on a phone
     once the title runs past a couple of words. */
  .cfg-fieldset__head {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--cfg-2);
    padding: var(--cfg-3) var(--cfg-3);
  }

  .cfg-fieldset__body {
    padding: var(--cfg-3);
  }

  /* ⚠ The bullet row must WRAP, not overflow. Input plus a labelled Remove
     button does not fit 390px, and this tab already carries an unfixed
     right-edge spill from an earlier shape (handoff log, 2026-08-30). */
  .dt-bullet {
    flex-wrap: wrap;
  }

  .dt-bullet__input {
    flex: 1 1 100%;
  }

  .cfg-head {
    flex-direction: column;
    gap: var(--cfg-3);
    margin-bottom: var(--cfg-4);
  }

  .cfg-head__tools {
    width: 100%;
  }

  .cfg-grid--two,
  .cfg-grid--three {
    grid-template-columns: 1fr;
  }

  /* 16px minimum on controls stops iOS zooming the page on focus. */
  .cfg-row input,
  .cfg-row select,
  .cfg-row textarea,
  .cfg-toolbar__search input {
    min-height: 44px;
    font-size: 16px;
  }

  .cfg-toolbar__search {
    flex: 1 1 100%;
  }

  .cfg-toolbar__actions {
    width: 100%;
  }

  .cfg-toolbar__actions .btn {
    flex: 1 1 auto;
  }

  .cfg-seg {
    width: 100%;
  }

  .cfg-seg__btn {
    flex: 1 1 auto;
    min-height: 36px;
  }

  /* A list row becomes a stack, with its actions on their own line. */
  .cfg-list__row,
  .cfg-list__summary {
    flex-wrap: wrap;
  }

  .cfg-list__main {
    flex: 1 1 100%;
  }

  /* ⚠ EXCEPT IN A `<summary>`, where 100% pushes the disclosure chevron onto a
     line of its own above the title — measured at 500px, it read as a stray
     glyph rather than as the control that opens the row. A row's actions still
     stack; a summary's title just shares the line with its own chevron. */
  .cfg-list__summary .cfg-list__main {
    flex: 1 1 auto;
  }

  .cfg-list__actions,
  .cfg-list__aside {
    flex: 1 1 auto;
  }

  .cfg-list__actions .btn {
    flex: 1 1 auto;
  }

  .cfg-keyvals li {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--cfg-1);
  }

  .cfg-keyvals__v {
    text-align: left;
  }

  .cfg-actions {
    flex-direction: column;
    align-items: stretch;
    margin-top: var(--cfg-5);
  }

  .cfg-actions__buttons {
    flex-direction: column-reverse;
    align-items: stretch;
  }

  .cfg-actions__buttons .btn {
    width: 100%;
    min-height: 44px;
  }

  .cfg-confirm__actions {
    width: 100%;
  }

  .cfg-confirm__actions .btn {
    flex: 1 1 auto;
    min-height: 44px;
  }

  /* Master/detail becomes list-then-detail, driven by `data-cfg-view` on the
     root. Below 900px only one pane is on screen at a time. */
  .cfg-split {
    grid-template-columns: 1fr;
  }

  .cfg-split__scroll {
    max-height: none;
  }

  /* ⚠ CORRECTED 2026-08-30. This said `data-email-mobile-view` was "what the
     email editor's own module writes", and that is no longer true: the Email
     templates tab was rebuilt onto its own three-pane `etpl-*` layout with its
     own 900px rules (section 12), and it writes the value "editor", not
     "detail". The `data-email-mobile-view` selectors below therefore match
     nothing today. They are kept only so a tab that still carries the older
     attribute keeps working; `data-cfg-view` is the name to use for any new
     split. Do not add the email tab back to this rule — it has no
     `.cfg-split__nav` or `.cfg-split__main` to hide. */
  [data-cfg-view="list"] .cfg-split__main,
  [data-email-mobile-view="list"] .cfg-split__main,
  [data-cfg-view="detail"] .cfg-split__nav,
  [data-email-mobile-view="detail"] .cfg-split__nav {
    display: none;
  }

  [data-cfg-view="detail"] .cfg-split__back,
  [data-email-mobile-view="detail"] .cfg-split__back {
    display: inline-flex;
    margin-bottom: var(--cfg-3);
  }
}

@media (prefers-reduced-motion: reduce) {
  .settings-section *,
  .integrations-section *,
  .help-section *,
  .settings-section *::before,
  .integrations-section *::before,
  .help-section *::before,
  .settings-section *::after,
  .integrations-section *::after,
  .help-section *::after,
  .set-modal *,
  .set-modal *::before,
  .set-modal *::after {
    transition-duration: 0.01ms !important;
  }
}

/* ⛔ THIS BLOCK MUST STAY BELOW THE SHARED CONTROL RULES.
 * They use the `background:` and `padding:` SHORTHANDS at the same specificity,
 * so wherever they appear after this one they reset the chevron image and the
 * right padding that clears it. It was written above them first and the
 * wizard's Risk and Status selects still rendered as plain boxes.
 *
 * ⚠ IT DOES NOT APPLY BELOW 900px INSIDE A DIALOG, and that is not a bug to
 * chase: style.css's mobile input spec sets `background` and `padding` with
 * `!important` on every `.modal.modal--sheet` control, so no select in any
 * sheet modal has a painted chevron on a phone. Consistent app-wide; leave it.
 */
/* Every cfg select draws its OWN chevron.
 *
 * 🔴 WITHOUT THIS THE CONTROL LOOKS LIKE A TEXT BOX ON ONE OF THE TWO HOSTS.
 * Measured in the browser 2026-08-30: on ?page=settings these get the native
 * arrow, but the SETUP WIZARD also loads settings-shell.css, whose
 * `.set-modal select` sets `appearance: none` and paints a chevron as a
 * background-image — and the `background:` shorthand in the rules above wipes
 * that image out while `appearance: none` survives. Result: Risk and Status
 * rendered as plain boxes with no affordance at all inside the wizard dialog.
 *
 * ⛔ SO DO NOT "SIMPLIFY" THIS BY DROPPING `appearance`. Owning both halves is
 * what makes the two hosts identical whatever else is on the page. `padding-right`
 * has to clear the chevron, so it is stated after the shared padding above.
 */
.cfg-row select,
.cfg-grid-table td select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 30px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%239a9188' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 5l3 3 3-3'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  cursor: pointer;
}

/* ===========================================================================
 * The merged components table INSIDE A DIALOG, below 900px.
 *
 * 🔴 style.css's canonical mobile input spec is `!important` ON PURPOSE — every
 * `.modal.modal--sheet` control is forced to `font-size: 16px` and `padding:
 * 9px 12px` under 900px, because a sub-16px field makes iOS zoom the viewport
 * on focus. That is right, and this file must not fight it.
 *
 * ⛔ BUT IT BREAKS A TABLE SIZED FOR 12.5px TEXT. Measured in the browser at
 * 818px: every cell inflated, so "Surface damage, gouges, or delamination
 * visible" rendered as "Surface (", and Remove wrapped onto two lines — while
 * the table still claimed to fit, so `.cfg-scroll-x` never scrolled.
 *
 * The fix is to let it be as wide as its own content needs and scroll, which is
 * what the wrapper is for. Scoped to `.set-modal` because the Settings page has
 * no dialog and keeps its 12.5px controls, where 700px is correct.
 * ======================================================================== */
@media (max-width: 900px) {
  /* ⛔ `:not([data-reason-table])` IS LOAD-BEARING — do not simplify it away.
     The reason table is the ONLY `.cfg-grid-table` a `.set-modal` currently
     hosts (the wizard renders `_components_editor.php` and `_presets_editor.php`;
     the latter has no table, and `_overrides_editor.php` is not in the wizard at
     all), so without this the rule applies to exactly the one table it must not.
     It STACKS below 900px now, and stacking is what actually fixes the problem
     this 880px was working around: with one control per line, 16px text cannot
     inflate a shared horizontal strip. Forcing 880px on top of the stacked grid
     just puts the sideways scroll back — reported from the onboarding modal,
     where the Settings page had already been fixed and the wizard had not,
     because this rule sits AFTER the stacking block and matches at the same
     specificity, so source order handed it the win.
     The exclusion is kept rather than deleted so any FUTURE table in a dialog
     still gets the widen-and-scroll treatment it was written for. */
  .set-modal .cfg-grid-table:not([data-reason-table]) {
    min-width: 880px;
  }

  /* A one-word button that wraps reads as two broken buttons. */
  .set-modal .cfg-grid-table .btn,
  .set-modal .cfg-grid-table button {
    white-space: nowrap;
  }
}

/* ===========================================================================
 * 15. The two-pane preset editor, and the overrides table
 *
 * 🔴 BOTH REPLACE A WALL OF ACCORDIONS. A preset carries 56 dropdowns, so an
 * expanded card measured 2,556px and the tab stacked seventeen; the overrides
 * tab was 57 collapsibles over 233 inputs. Neither let you compare two things
 * or see what you had changed.
 * ======================================================================== */

/* ⚠ NO `.cfg-split` RULES HERE. Section 10 already defines that primitive for
   the email templates editor, including the mobile list/detail swap this needs, and
   a second definition in the same file simply fought it. What follows is only what
   the primitive does not already cover: the section headings inside its nav, and
   the preset detail pane's own furniture. */

.cfg-split__section + .cfg-split__section {
  margin-top: var(--cfg-4);
}

.cfg-split__section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cfg-2);
  padding: 0 var(--cfg-3);
}

.cfg-split__section-head .cfg-split__label {
  margin: 0;
}

.cfg-split__section-hint {
  margin: 2px 0 var(--cfg-2);
  padding: 0 var(--cfg-3);
  font-size: 12px;
  line-height: 1.4;
  color: var(--cfg-ink-3);
}

.cfg-split__item-main {
  display: block;
  min-width: 0;
}

.cfg-split__item-title {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--cfg-ink);
}

.cfg-split__item-meta {
  display: block;
  margin-top: 1px;
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--cfg-ink-3);
}

.cfg-detail {
  display: flex;
  flex-direction: column;
  gap: var(--cfg-3);
  padding: var(--cfg-4);
  border: 1px solid var(--cfg-rule);
  border-radius: var(--cfg-r-panel, 10px);
  background: var(--cfg-surface);
}

.cfg-detail--placeholder {
  align-items: flex-start;
  gap: var(--cfg-1);
}

.cfg-detail__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--cfg-3);
}

.cfg-detail__eyebrow {
  margin: 0;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cfg-ink-3);
}

.cfg-detail__title {
  margin: 2px 0 0;
  font-size: 16px;
  font-weight: 650;
  color: var(--cfg-ink);
}

.cfg-detail__hint {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--cfg-ink-3);
  max-width: 58ch;
}

/* The three option tiers, exactly as the door builder groups them. */
.cfg-tier {
  border: 1px solid var(--cfg-rule);
  border-radius: var(--cfg-r-control);
  background: var(--cfg-bg, transparent);
}

.cfg-tier + .cfg-tier {
  margin-top: var(--cfg-2);
}

.cfg-tier__summary {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  padding: 10px 12px;
  cursor: pointer;
  list-style: none;
}

.cfg-tier__summary::-webkit-details-marker { display: none; }
.cfg-tier__summary::marker { content: ''; }

.cfg-tier__chev {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  color: var(--cfg-ink-3);
  transition: transform 0.12s ease;
}

.cfg-tier[open] > .cfg-tier__summary .cfg-tier__chev {
  transform: rotate(90deg);
}

.cfg-tier__main {
  flex: 1 1 auto;
  min-width: 0;
}

.cfg-tier__title {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--cfg-ink);
}

.cfg-tier__hint {
  display: block;
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--cfg-ink-3);
}

.cfg-tier > .cfg-grid {
  padding: 0 12px 12px;
}

/* ---- the overrides table ------------------------------------------------ */

.cfg-grid-table--overrides {
  min-width: 720px;
}

.cfg-row__static {
  display: block;
  padding: var(--cfg-2) 0;
  font-size: 13px;
  color: var(--cfg-ink-2, var(--cfg-ink-3));
}

.cfg-cell__title {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--cfg-ink);
}

.cfg-cell__sub {
  display: block;
  margin-top: 1px;
  font-size: 11px;
  color: var(--cfg-ink-3);
}

.cfg-grid-table__effect { width: 130px; }

/* The option group, as a heading ROW inside its section's table.
   ⚠ `position: sticky` on a `<th>` in a scrolling table needs the offset on the
   cell, not the row — a sticky <tr> does nothing in Chrome. */
.cfg-grid-table__grouprow > th {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: var(--cfg-2) var(--cfg-3);
  text-align: left;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--cfg-ink-3);
  background: var(--cfg-bg-sunken, #f3f0ea);
  border-top: 1px solid var(--cfg-rule);
}

.cfg-grid-table__grouprow:first-child > th {
  border-top: 0;
}

.cfg-effect {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--cfg-ink);
  font-variant-numeric: tabular-nums;
}

/* "Not priced" and "Included" are STATES, not amounts — they must not read as
   a number the workspace chose. */
.cfg-effect__none { color: var(--cfg-ink-3); font-weight: 500; }
/* ⚠ `--cfg-good`, not an invented `--cfg-ok` with a literal fallback. The token
   that exists is defined at the top of this file and tracks the five theme
   palettes; a hardcoded hex would not, which rule 2 of this file's own header
   bans outright. */
.cfg-effect__free { color: var(--cfg-good); }

.cfg-effect__unit {
  font-size: 10.5px;
  font-weight: 500;
  color: var(--cfg-ink-3);
}

.cfg-effect__src {
  display: block;
  margin-top: 1px;
  font-size: 10.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--cfg-ink-3);
}

/* A row the workspace has priced. The marker is the left edge, so it reads
   down the column without colouring the text. */
[data-overrides-row][data-overrides-state="mine"] td:first-child {
  box-shadow: inset 2px 0 0 var(--cfg-mark);
}

@media (max-width: 900px) {
  /* Phones/tablets: the overrides table is editor rows (table-cards.css, data-rc-editor="override"),
     so the 640px sideways-scroll floor that stood here is gone. A row the workspace has priced
     keeps its marker, on the row, because the first cell is no longer the row's left edge. */
  .cfg-grid-table--overrides[data-rc="editor"] > tbody > tr[data-overrides-state="mine"] {
    box-shadow: inset 3px 0 0 var(--cfg-mark);
  }

  /* The failure-reasons editor sits inside a component's white, bordered list box. Its own card
     inside that box, inset by the panel's 16px, was a card around a card and left the Risk select
     too narrow for "Medium" at 390px. The rows run to the box's edges instead (all three hosts:
     Settings, the setup wizard, the client Pricing tab). */
  /* !important + this specificity: table-cards.css caps a [data-rc-wrap] at max-width 100%
     !important, which kept the width and only shifted the rows left. */
  .cfg-list__panel > .cfg-scroll-x[data-rc-wrap]:has(> table[data-rc="editor"]) {
    width: auto;
    max-width: none !important;
    margin-inline: calc(var(--cfg-4) * -1);
  }

  .cfg-list__panel > .cfg-scroll-x > table[data-rc="editor"][data-reason-table] {
    border-radius: 0;
    box-shadow: none;
    border-bottom: 1px solid var(--cfg-rule);
  }

  /* "Add reason" joins the list as its last row (table-cards [data-rc-add]), full-bleed like the
     rows above it, so the list's own bottom rule moves onto it. The toolbar's inline margin-top
     would otherwise open a gap between the last reason and its Add row. */
  .cfg-list__panel:has([data-rc-add]) > .cfg-scroll-x > table[data-rc="editor"][data-reason-table] {
    border-bottom: 0;
  }

  .cfg-list__panel > .cfg-toolbar:has([data-rc-add]) {
    margin-top: 0 !important;
  }

  .cfg-list__panel > .cfg-toolbar > .cfg-toolbar__actions:has(> [data-rc-add]) {
    flex: 1 1 100%;
    margin-inline: calc(var(--cfg-4) * -1);
  }

  .cfg-list__panel .cfg-toolbar__actions > .btn[data-rc-add] {
    width: 100%;
    min-height: 48px;
    padding: 0 14px;
    border-bottom: 1px solid var(--cfg-rule);
    border-radius: 0;
    background: transparent;
    box-shadow: none;
    clip-path: none;
    font-size: 14px;
  }

  /* The overrides editor sits in its tier's bordered box (details.cfg-tier), which is the one
     surface: the editor draws no card of its own inside it. */
  .cfg-tier > .cfg-scroll-x > table.cfg-grid-table--overrides[data-rc="editor"] {
    border-radius: 0;
    box-shadow: none;
  }

  /* "App price" / "Yours" beside the figure in effect: plain small grey text on a card, as the
     design's .ed-src (desktop keeps its small caps). */
  .cfg-grid-table--overrides[data-rc="editor"] .cfg-effect__src {
    display: inline;
    margin: 0;
    font-size: 11.5px;
    font-weight: 400;
    letter-spacing: normal;
    text-transform: none;
    color: var(--ink-4, #9a9188);
  }
}

/* ===========================================================================
 * 12. etpl — Settings → Email templates
 *
 * A three-pane editor: the list of emails, the fields, and a live preview that
 * is always on screen. It does NOT reuse `cfg-split`, and that is deliberate:
 * cfg-split is a two-pane master/detail with a 300px nav, and this pane needs a
 * third column plus a much denser list.
 *
 * WHAT THE OLD LAYOUT GOT WRONG, measured in the browser on 2026-08-30 before
 * the rebuild: 20 rows averaging 161px tall inside a 551px scroll box — 3.4 rows
 * visible out of 3,619px of content — in a 300px column beside a 1,005px editor,
 * with `overflow-x: auto` producing a horizontal scrollbar at 308px of content
 * in a 298px track. The row is now two lines and ~52px, and the column no longer
 * scrolls sideways at all.
 *
 * ⛔ NO DARK-MODE RULES. Out of scope by standing project rule.
 * ========================================================================= */

.etpl__body {
  padding-bottom: 0;
}

.etpl__pane {
  display: grid;
  grid-template-columns: 288px minmax(0, 1fr) 420px;
  gap: 0;
  border-top: 1px solid var(--cfg-rule);
  margin: 0 calc(var(--cfg-5) * -1);
}

/* ---- the list ----------------------------------------------------------- */

.etpl__list {
  border-right: 1px solid var(--cfg-rule);
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.etpl__search {
  padding: var(--cfg-3);
  display: flex;
  flex-direction: column;
  gap: var(--cfg-2);
  border-bottom: 1px solid var(--cfg-rule);
}

.etpl__searchfield {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  background: var(--cfg-inset);
  border: 1px solid var(--cfg-rule);
  border-radius: var(--cfg-r-control);
  padding: 0 var(--cfg-3);
  height: var(--cfg-control-h);
}

.etpl__searchfield svg {
  width: 15px;
  height: 15px;
  flex: none;
  color: var(--cfg-ink-3);
}

.etpl__searchfield input {
  border: 0;
  background: none;
  font: inherit;
  font-size: 13px;
  color: var(--cfg-ink);
  width: 100%;
  min-width: 0;
  height: 100%;
  outline: none;
  padding: 0;
}

.etpl__segs {
  display: flex;
  gap: var(--cfg-1);
}

.etpl__seg {
  flex: 1;
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1.2;
  padding: 6px 4px;
  border-radius: 6px;
  border: 1px solid var(--cfg-rule);
  background: var(--cfg-surface);
  color: var(--cfg-ink-3);
  cursor: pointer;
  white-space: nowrap;
}

.etpl__seg:hover {
  border-color: var(--cfg-rule-strong);
  color: var(--cfg-ink-2);
}

.etpl__seg.is-active {
  background: var(--cfg-ink);
  border-color: var(--cfg-ink);
  color: var(--cfg-surface);
}

/* ⛔ `overflow-x: hidden`, never `auto`. The old pane scrolled sideways because
   a row's aside could exceed the track by 10px; a horizontal scrollbar inside a
   300px column is never the right answer — the row must fit instead. */
.etpl__scroll {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  max-height: 620px;
  padding-bottom: var(--cfg-4);
}

.etpl__grouplabel {
  margin: 0;
  padding: var(--cfg-4) var(--cfg-4) var(--cfg-1);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--cfg-ink-3);
}

.etpl__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--cfg-2);
  padding: 7px var(--cfg-3) 7px var(--cfg-4);
  border-left: 3px solid transparent;
}

.etpl__row:hover {
  background: var(--cfg-inset);
}

.etpl__row[aria-current="true"] {
  background: var(--cfg-mark-bg);
  border-left-color: var(--cfg-mark);
}

/* ⚠ The row's title is a <button> and the switch is its SIBLING, never a child:
   a control nested inside a button is invalid and swallows its own click. */
.etpl__rowmain {
  display: block;
  width: 100%;
  min-width: 0;
  text-align: left;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

.etpl__rowtitle {
  display: block;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--cfg-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.etpl__rowmeta {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 1px;
  font-size: 11.5px;
  line-height: 1.3;
  color: var(--cfg-ink-3);
}

.etpl__dot {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.etpl__rowaside {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  flex: none;
}

.etpl__edited {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--cfg-mark);
  background: var(--cfg-mark-bg);
  padding: 2px 5px;
  border-radius: 4px;
  white-space: nowrap;
}

.etpl__switch {
  position: relative;
  display: inline-flex;
  flex: none;
  cursor: pointer;
}

.etpl__switch input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}

.etpl__track {
  display: block;
  width: 32px;
  height: 18px;
  border-radius: 999px;
  background: var(--cfg-rule-strong);
  position: relative;
  transition: background 0.15s ease;
}

.etpl__track::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(26, 24, 21, 0.3);
  transition: transform 0.15s ease;
}

.etpl__switch input:checked + .etpl__track {
  background: var(--cfg-good);
}

.etpl__switch input:checked + .etpl__track::after {
  transform: translateX(14px);
}

.etpl__switch input:focus-visible + .etpl__track {
  outline: 2px solid var(--cfg-mark);
  outline-offset: 2px;
}

.etpl__empty {
  margin: 0;
  padding: var(--cfg-5) var(--cfg-4);
  font-size: 13px;
  color: var(--cfg-ink-3);
}

/* The four templates nothing sends. Visible, read-only, out of the way. */
.etpl__archive {
  margin: var(--cfg-4) var(--cfg-3) 0;
  border-top: 1px solid var(--cfg-rule);
  padding-top: var(--cfg-3);
}

.etpl__archive summary {
  font-size: 12px;
  font-weight: 600;
  color: var(--cfg-ink-3);
  cursor: pointer;
  list-style: none;
  padding: var(--cfg-1);
}

.etpl__archive summary::-webkit-details-marker {
  display: none;
}

.etpl__archive summary::before {
  content: "\25B8\00a0";
  color: var(--cfg-ink-3);
}

.etpl__archive[open] summary::before {
  content: "\25BE\00a0";
}

.etpl__archivenote {
  margin: 0;
  padding: var(--cfg-2) var(--cfg-1);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--cfg-ink-3);
}

.etpl__row--archived .etpl__rowtitle {
  color: var(--cfg-ink-2);
  font-weight: 500;
}

/* ---- the editor --------------------------------------------------------- */

.etpl__editor {
  padding: var(--cfg-5);
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--cfg-4);
}

/* Shown only below 900px, where the list and the editor swap places. */
.etpl__back {
  display: none;
  align-self: flex-start;
}

.etpl__placeholder {
  margin: 0;
  padding: var(--cfg-6) 0;
  font-size: 14px;
  color: var(--cfg-ink-3);
  text-align: center;
}

.etpl__head {
  display: flex;
  flex-direction: column;
  gap: var(--cfg-3);
  padding-bottom: var(--cfg-4);
  border-bottom: 1px solid var(--cfg-rule);
}

.etpl__headrow {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--cfg-3);
}

.etpl__headcopy {
  min-width: 0;
}

.etpl__title {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--cfg-ink);
}

.etpl__when {
  margin: var(--cfg-1) 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--cfg-ink-3);
  max-width: 62ch;
}

.etpl__chip {
  flex: none;
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: 0.03em;
  padding: 4px 9px;
  border-radius: 999px;
  background: var(--cfg-inset);
  color: var(--cfg-ink-3);
  white-space: nowrap;
}

.etpl__chip.is-active {
  background: var(--cfg-mark-bg);
  color: var(--cfg-mark);
}

.etpl__variant {
  display: flex;
  align-items: center;
  gap: var(--cfg-3);
  flex-wrap: wrap;
  background: var(--cfg-inset);
  border: 1px solid var(--cfg-rule);
  border-radius: var(--cfg-r-control);
  padding: var(--cfg-2) var(--cfg-3);
}

.etpl__variantlabel {
  font-size: 12.5px;
  color: var(--cfg-ink-2);
  font-weight: 500;
}

.etpl__variantbtns {
  display: flex;
  gap: var(--cfg-1);
  margin-left: auto;
}

.etpl__vbtn {
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  padding: 5px 11px;
  border-radius: 6px;
  border: 1px solid var(--cfg-rule);
  background: var(--cfg-surface);
  color: var(--cfg-ink-3);
  cursor: pointer;
  white-space: nowrap;
}

.etpl__vbtn.is-active {
  background: var(--cfg-ink);
  border-color: var(--cfg-ink);
  color: var(--cfg-surface);
}

.etpl__notice {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.55;
  border-radius: var(--cfg-r-control);
  padding: var(--cfg-2) var(--cfg-3);
}

.etpl__notice--muted {
  background: var(--cfg-inset);
  color: var(--cfg-ink-2);
}

.etpl__notice--warn {
  background: var(--cfg-mark-bg);
  color: var(--cfg-ink-2);
  border: 1px solid var(--cfg-mark-bg);
}

.etpl__fields {
  display: flex;
  flex-direction: column;
  gap: var(--cfg-4);
}

.etpl__field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 620px;
}

.etpl__flabel {
  font-size: 12px;
  font-weight: 650;
  color: var(--cfg-ink-2);
}

.etpl__opt {
  font-weight: 500;
  color: var(--cfg-ink-3);
}

.etpl__field input[type="text"],
.etpl__field textarea {
  width: 100%;
  font: inherit;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--cfg-ink);
  background: var(--cfg-surface);
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-control);
  padding: 9px var(--cfg-3);
  resize: vertical;
}

.etpl__field input[type="text"] {
  height: var(--cfg-control-h);
  padding-top: 0;
  padding-bottom: 0;
}

.etpl__field input:focus-visible,
.etpl__field textarea:focus-visible {
  outline: none;
  border-color: var(--cfg-mark);
  box-shadow: 0 0 0 3px var(--cfg-mark-bg);
}

.etpl__field input:disabled,
.etpl__field textarea:disabled {
  background: var(--cfg-inset);
  color: var(--cfg-ink-3);
  cursor: not-allowed;
}

.etpl__fhint {
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--cfg-ink-3);
}

/* Field chips. Four by default, the rest behind a disclosure — the old editor
   offered all 21 raw tokens at once, plumbing included. */
.etpl__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: -8px;
  max-width: 620px;
}

.etpl__chipbtn {
  font: inherit;
  font-size: 11.5px;
  font-weight: 550;
  line-height: 1.3;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--cfg-rule-strong);
  background: var(--cfg-surface);
  color: var(--cfg-ink-2);
  cursor: pointer;
  white-space: nowrap;
}

.etpl__chipbtn:hover {
  border-color: var(--cfg-mark);
  background: var(--cfg-mark-bg);
  color: var(--cfg-ink);
}

.etpl__chipbtn--more {
  border-style: dashed;
  color: var(--cfg-ink-3);
}

.etpl__chipdrawer {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  width: 100%;
}

.etpl__auto {
  background: var(--cfg-inset);
  border: 1px dashed var(--cfg-rule-strong);
  border-radius: var(--cfg-r-control);
  padding: var(--cfg-3);
  max-width: 620px;
}

.etpl__autotitle {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  font-size: 11.5px;
  font-weight: 650;
  color: var(--cfg-ink-2);
}

.etpl__autotitle svg {
  width: 13px;
  height: 13px;
  flex: none;
}

.etpl__autolist {
  margin: var(--cfg-1) 0 0;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--cfg-ink-3);
}

.etpl__raw {
  border-top: 1px solid var(--cfg-rule);
  padding-top: var(--cfg-4);
}

.etpl__raw summary {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--cfg-ink-3);
  cursor: pointer;
  list-style: none;
}

.etpl__raw summary::-webkit-details-marker {
  display: none;
}

.etpl__raw summary::before {
  content: "\25B8\00a0";
}

.etpl__raw[open] summary::before {
  content: "\25BE\00a0";
}

.etpl__rawbody {
  display: flex;
  flex-direction: column;
  gap: var(--cfg-4);
  padding-top: var(--cfg-3);
}

.etpl__rawbody textarea {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 12px;
}

.etpl__rawactions {
  display: flex;
  justify-content: flex-start;
}

/* ---- the preview rail --------------------------------------------------- */

.etpl__preview {
  border-left: 1px solid var(--cfg-rule);
  background: var(--cfg-inset);
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.etpl__previewbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cfg-2);
  padding: var(--cfg-3);
  border-bottom: 1px solid var(--cfg-rule);
}

.etpl__previewtitle {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--cfg-ink-3);
}

.etpl__previewtoggle {
  display: flex;
  gap: 3px;
}

.etpl__pt {
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  padding: 4px 9px;
  border-radius: 6px;
  border: 1px solid var(--cfg-rule);
  background: var(--cfg-surface);
  color: var(--cfg-ink-3);
  cursor: pointer;
}

.etpl__pt.is-active {
  background: var(--cfg-ink);
  border-color: var(--cfg-ink);
  color: var(--cfg-surface);
}

.etpl__previewdata {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  padding: var(--cfg-3) var(--cfg-3) 0;
}

.etpl__previewdatalabel {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--cfg-ink-3);
  flex: none;
}

.etpl__previewdata select {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 12px;
  height: 32px;
  padding: 0 var(--cfg-2);
  border: 1px solid var(--cfg-rule-strong);
  border-radius: 6px;
  background: var(--cfg-surface);
  color: var(--cfg-ink);
}

.etpl__previewsubject {
  margin: 0;
  padding: var(--cfg-3) var(--cfg-3) 0;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.45;
  color: var(--cfg-ink);
}

.etpl__warnings {
  margin: var(--cfg-2) var(--cfg-3) 0;
  padding: var(--cfg-2);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--cfg-ink-2);
  background: var(--cfg-mark-bg);
  border-radius: 6px;
}

.etpl__previewscroll {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--cfg-3);
  max-height: 620px;
}

/* The frame is rendered at desktop width and scaled to fit the rail, so the
   preview shows the layout a client on a laptop actually receives rather than
   the responsive narrow form the rail's own width would force.

   ⛔ THE WRAPPER OWNS THE HEIGHT. A `transform` does not affect layout, so a
      scaled iframe still reserves its unscaled 900px and leaves ~440px of empty
      rail below it. The module writes the wrapper's height alongside the scale.

   It is tall on purpose either way: the old preview was 260px with its own
   scrollbar, which is what kept the duplication bug off screen for as long as
   it existed. */
.etpl__framewrap {
  --etpl-preview-scale: 0.5;
  --etpl-preview-width: 600px;
  position: relative;
  width: 100%;
  height: 450px;
  overflow: hidden;
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-box);
  background: #ffffff;
}

.etpl__frame {
  display: block;
  width: var(--etpl-preview-width, 600px);
  height: 880px;
  border: 0;
  background: #ffffff;
  transform: scale(var(--etpl-preview-scale));
  transform-origin: top left;
}

.etpl__text {
  margin: 0;
  padding: var(--cfg-3);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 11.5px;
  line-height: 1.7;
  color: var(--cfg-ink-2);
  background: var(--cfg-surface);
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-box);
  white-space: pre-wrap;
  word-break: break-word;
}

/* ---- the action bar ------------------------------------------------------ */

.etpl__actions {
  margin-top: 0;
}

/* Destructive, and therefore quiet. All four actions used to be the same grey
   button, so "Restore default" carried the same weight as "Update preview". */
.etpl__quiet {
  background: none;
  border: 0;
  padding: 8px var(--cfg-1);
  font-size: 12.5px;
  font-weight: 500;
  color: var(--cfg-ink-3);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.etpl__quiet:hover:not(:disabled) {
  color: var(--cfg-danger);
}

.etpl__quiet:disabled {
  opacity: 0.5;
  text-decoration: none;
}

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

/* 🔴 THE PREVIEW MOVES, IT DOES NOT DISAPPEAR.
   It was `display: none` below 1200px on the reasoning that a rail too narrow to
   render an email is worse than none — which took the preview away on every
   phone and tablet, i.e. exactly the devices where nobody has a second screen to
   check the result on. Owner caught it, 2026-08-30.
   Below the threshold it becomes a full-width row UNDER both columns instead,
   where it has more room than the rail ever had.

   ⛔ THE THRESHOLD IS 1700px, NOT 1200px, AND THAT IS MEASURED NOT GUESSED. The
      pane is roughly the viewport minus 543px of app chrome (sidebar + settings
      rail + padding), so at a 1280px viewport a three-column split left the
      EDITOR — the column you actually type in — just 273px wide, narrower than
      the list beside it. At 1700px it gets ~449px, at 1920px ~669px. Three
      columns are a wide-screen luxury; below that the editor keeps the width. */
@media (max-width: 1699px) {
  .etpl__pane {
    grid-template-columns: 288px minmax(0, 1fr);
  }

  .etpl__preview {
    grid-column: 1 / -1;
    border-left: 0;
    border-top: 1px solid var(--cfg-rule);
  }

  .etpl__previewscroll {
    max-height: none;
  }

  /* Do not let the frame stretch to the full pane — an email is not 1,100px
     wide, and scaling ABOVE 1 would only blur it. fitPreviewFrame() caps the
     scale at 1; this caps the box. */
  .etpl__framewrap {
    max-width: 600px;
  }
}

@media (max-width: 900px) {
  .etpl__pane {
    grid-template-columns: 1fr;
    margin: 0 calc(var(--cfg-4) * -1);
  }

  .etpl__scroll,
  .etpl__previewscroll {
    max-height: none;
  }

  .etpl__editor {
    padding: var(--cfg-4);
  }

  .etpl__list {
    border-right: 0;
    border-bottom: 1px solid var(--cfg-rule);
  }

  /* One pane at a time, driven by `data-email-mobile-view` on the section.
     ⛔ THE PREVIEW FOLLOWS THE EDITOR, NOT THE LIST. It is the same screen as the
        fields on a phone — you scroll from what you typed straight to what it
        looks like — so it must hide with the list view and show with the editor
        view. Leaving it visible in the list view put a rendered email under the
        list of emails, which reads as "this is the one you are looking at". */
  [data-email-mobile-view="list"] .etpl__editor,
  [data-email-mobile-view="list"] .etpl__preview,
  [data-email-mobile-view="list"] .etpl__actions {
    display: none;
  }

  [data-email-mobile-view="editor"] .etpl__list {
    display: none;
  }

  [data-email-mobile-view="editor"] .etpl__back {
    display: inline-flex;
  }

  .etpl__preview {
    border-top: 1px solid var(--cfg-rule);
  }

  /* 1:1 on a phone — see fitPreviewFrame(). The frame fills the wrapper, so
     the email renders at its real narrow width instead of as a thumbnail. */
  .etpl__framewrap {
    max-width: none;
  }

  .etpl__frame {
    width: 100%;
    height: 100%;
  }
}

/* The transient status line ("Saved.", "Test sent…"). It sits between the head
   and the first field, so it needs its own breathing room — without this it
   butted straight up against the "Subject line" label and read as part of it. */
.etpl .cfg-status:not(:empty) {
  margin: 0 0 var(--cfg-2);
  padding: var(--cfg-2) var(--cfg-3);
  background: var(--cfg-good-bg);
  color: var(--cfg-ink-2);
  border-radius: var(--cfg-r-control);
  font-size: 12.5px;
}

/* ===========================================================================
 * cfg-choices — a set of radio cards, for a setting whose options each need a
 * sentence rather than a word.
 *
 * Added 2026-08-31 with the capture-layout setting. Three layouts, each needing
 * a summary and a "who it suits" line: a native <select> can carry the label
 * and nothing else, which is the same reasoning that kept the section rail off
 * a <select> for its items (seventeen then, nineteen since Capture layout
 * landed on 2026-08-31 — derive the count, do not write one here).
 *
 * ⛔ THE RADIO IS VISUALLY HIDDEN, NOT `display: none`. A `display:none` input
 * is removed from the tab order and from the accessibility tree, so the group
 * stops being keyboard-operable and a screen reader hears three unlabelled
 * blocks. Clipping it keeps arrow-key selection, `:focus-visible` and the
 * radiogroup semantics intact — the label element is what grows the ring.
 *
 * Pattern lifted from `.security-method-tabs__option`, which proved the
 * `:has(:checked)` inset ring on this codebase. The difference is layout:
 * Security tabs are a horizontal strip of three words, these are stacked cards.
 * ======================================================================== */
.cfg-choices {
  display: grid;
  gap: var(--cfg-3);
  max-width: 72ch;
}

.cfg-choice {
  position: relative;
  display: block;
  padding: var(--cfg-4);
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-box);
  background: var(--cfg-surface);
  cursor: pointer;
  transition: border-color 120ms ease, background 120ms ease, box-shadow 120ms ease;
}

.cfg-choice:hover {
  border-color: var(--cfg-mark);
}

/* ⛔ THE ATTRIBUTE SELECTOR IS LOAD-BEARING, NOT DECORATION.
   `style.css` carries `input[type="checkbox"], input[type="radio"] { width: auto }`,
   which is (0,1,1) and beats a bare `.cfg-choice__radio` at (0,1,0). Written as a
   class alone the radio kept its native 13px width — clipped, so invisible, but
   still a 13px box absolutely positioned over the card's top-left corner.
   Measured in a browser; no test in this suite computes a style. */
input[type="radio"].cfg-choice__radio {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.cfg-choice:has(.cfg-choice__radio:checked) {
  background: var(--cfg-mark-bg);
  border-color: transparent;
  box-shadow: inset 0 0 0 2px var(--cfg-mark);
}

/* The ring is on the LABEL, because the input it belongs to is clipped to a
   single pixel and its own focus ring would be invisible. */
.cfg-choice:has(.cfg-choice__radio:focus-visible) {
  outline: 2px solid var(--cfg-mark);
  outline-offset: 2px;
}

.cfg-choice__body {
  display: grid;
  gap: var(--cfg-1);
  min-width: 0;
}

.cfg-choice__title {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
  flex-wrap: wrap;
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--cfg-ink);
}

.cfg-choice__tag {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--cfg-inset);
  color: var(--cfg-ink-3);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.cfg-choice:has(.cfg-choice__radio:checked) .cfg-choice__tag {
  background: var(--cfg-surface);
}

/* ⛔ font-weight IS EXPLICIT ON BOTH BODY LINES. `.cfg-choice` is a <label>, and
   the app's label rule sets 600 — which every span inside it inherits, so the
   summary and the "who it suits" line both rendered as bold as the layout name
   above them and the card read as three headings. Caught by measuring computed
   style in a browser; a markup assertion cannot see it. */
.cfg-choice__summary {
  font-size: 13px;
  font-weight: 400;
  line-height: 1.55;
  color: var(--cfg-ink-2);
}

.cfg-choice__meta {
  font-size: 12.5px;
  font-weight: 400;
  line-height: 1.5;
  color: var(--cfg-ink-3);
}

@media (prefers-reduced-motion: reduce) {
  .cfg-choice {
    transition: none;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
 * cfg-brand — Settings → PDF appearance → Brand colour.
 *
 * Swatches, a custom picker, the colour code, a plain-English verdict and a
 * small sketch of the surfaces the colour fills. `--swatch` / `--brand` are set
 * per element in the markup: they are the workspace's data, not theme tokens.
 * The preview's white text is the point of it: New prints white on the brand.
 * ======================================================================== */
.cfg-brand {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 260px);
  gap: var(--cfg-5);
  align-items: start;
}

.cfg-brand__controls {
  display: grid;
  gap: var(--cfg-4);
  min-width: 0;
}

.cfg-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: var(--cfg-2);
}

.cfg-swatch {
  position: relative;
  width: 36px;
  height: 36px;
  margin: 0;
  padding: 0;
  border: 1px solid var(--cfg-rule-strong);
  border-radius: 50%;
  background: var(--swatch);
  cursor: pointer;
  transition: box-shadow 120ms ease;
}

.cfg-swatch[aria-pressed="true"] {
  box-shadow: 0 0 0 2px var(--cfg-surface), 0 0 0 4px var(--cfg-mark);
}

.cfg-swatch:focus-visible,
.cfg-swatch--custom:has(input:focus-visible) {
  outline: 2px solid var(--cfg-mark);
  outline-offset: 3px;
}

/* The custom swatch is a pill: a rainbow dot and the word. The native colour
   input covers it at zero opacity, so a click anywhere opens the picker and the
   input stays focusable. */
.cfg-swatch--custom {
  display: inline-flex;
  align-items: center;
  gap: var(--cfg-2);
  width: auto;
  padding: 0 var(--cfg-3) 0 6px;
  border-radius: 999px;
  background: var(--cfg-surface);
}

.cfg-swatch--custom::before {
  content: "";
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: conic-gradient(#d33, #d93, #3a3, #39c, #63c, #d33);
}

.cfg-swatch--custom input[type="color"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  cursor: pointer;
}

.cfg-swatch__text {
  font-size: 13px;
  font-weight: 600;
  color: var(--cfg-ink);
}

.cfg-brand__code-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cfg-2);
}

.cfg-brand__code-row input[type="text"] {
  width: 9.5em;
  font-variant-numeric: tabular-nums;
}

.cfg-brand__verdict {
  margin: 0;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.5;
}

.cfg-brand__verdict.is-ok {
  color: var(--cfg-good);
}

.cfg-brand__verdict.is-bad {
  color: var(--cfg-danger);
}

.cfg-brand-preview {
  overflow: hidden;
  border: 1px solid var(--cfg-rule-strong);
  border-radius: var(--cfg-r-box);
  background: var(--cfg-surface);
  font-size: 12px;
}

.cfg-brand-preview__band {
  display: grid;
  gap: 2px;
  padding: var(--cfg-4);
  background: var(--brand);
  color: #fff;
}

.cfg-brand-preview__eyebrow {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.85;
}

.cfg-brand-preview__title {
  font-size: 15px;
  font-weight: 600;
}

.cfg-brand-preview__table {
  padding: var(--cfg-3);
}

.cfg-brand-preview__row {
  display: flex;
  justify-content: space-between;
  gap: var(--cfg-2);
  padding: 6px var(--cfg-2);
  border-bottom: 1px solid var(--cfg-rule);
  color: var(--cfg-ink-2);
}

.cfg-brand-preview__row:last-child {
  border-bottom: 0;
}

.cfg-brand-preview__row--head {
  border-bottom: 0;
  border-radius: 4px;
  background: var(--brand);
  color: #fff;
  font-weight: 600;
}

/* Pass and fail never follow the brand — the same rule the PDFs keep. */
.cfg-brand-preview__pass {
  color: var(--cfg-good);
  font-weight: 600;
}

.cfg-brand-preview__fail {
  color: var(--cfg-danger);
  font-weight: 600;
}

@media (max-width: 900px) {
  .cfg-brand {
    grid-template-columns: 1fr;
  }

  .cfg-swatch {
    width: 44px;
    height: 44px;
  }

  .cfg-swatch--custom::before {
    width: 30px;
    height: 30px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .cfg-swatch {
    transition: none;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
 * FRA — Settings → Assessments → Guidance & standards (item 1, 2026-09-09)
 *
 * ⚠ ONE REPEATABLE ROW, BUILT FROM THE EXISTING `cfg-*` PRIMITIVES rather than a
 * new field system. Every control inside is a plain `.cfg-row`, so the label
 * treatment, the 16px iOS focus floor and the dark-mode-free palette all come
 * from the rules above without being restated — which is the CSS ownership rule
 * this project keeps: standardise a pattern, never fork it.
 *
 * ⛔ NO HARDCODED COLOURS. Every value below is a `--cfg-*` token, which resolves
 * to an `--ipi-*` one. The FRA shell (`fra-shell.css`) declares that family on
 * `.fra-page, .inspections-page, …` — Settings carries `.inspections-page`, so
 * the tokens resolve in FRA mode exactly as in the other two.
 * ⛔ Dark mode is out of scope — see .claude/rules/css-standards.md.
 * ═════════════════════════════════════════════════════════════════════════ */
.fra-cfg-rows {
  display: flex;
  flex-direction: column;
  gap: var(--cfg-4);
}

/* ⚠ `align-items: end`, so the Remove button sits on the control baseline rather
   than being stretched to the height of a two-line label beside it. */
.fra-cfg-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 14rem) auto;
  align-items: end;
  gap: var(--cfg-2) var(--cfg-5);
  min-width: 0;
  padding-bottom: var(--cfg-4);
  border-bottom: 1px solid var(--cfg-rule);
}

.fra-cfg-row:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

/* The edition column. Narrower than the title because an edition is "2017+A2:2023"
   and a title is a full standard name; equal columns wasted half the row. */
.fra-cfg-row .cfg-row--narrow {
  min-width: 0;
}

.fra-cfg-row__remove {
  white-space: nowrap;
}

.fra-cfg-add {
  margin-top: var(--cfg-4);
}

/* ⚠ 900px, MATCHING THE QUERY THE REST OF THIS FILE USES. The three-column row is
   unusable on a phone: the edition field collapses to a few characters and the
   Remove button pushes off the gutter. Stacking keeps every control full width and
   puts Remove at the end of its own row, where a thumb reaches it. */
@media (max-width: 900px) {
  .fra-cfg-row {
    grid-template-columns: 1fr;
    gap: var(--cfg-2);
  }

  .fra-cfg-row__remove {
    justify-self: start;
  }
}

/* ── FRA settings lists (memberships, standard limitations) — 2026-09-27 ────────────────────
   One row per item with its own Remove button, and an Add button with a running count.
   Markup: app/Views/partials/_fra_list_editor.php. ⚠ Twinned in settings-tabs.css and fra.css,
   because the Settings page and the FRA pages each load only their own sheet. */
.fra-list__rows {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* `align-items: start` keeps Remove level with the top of a two-line textarea row. */
.fra-list__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: 8px 12px;
  min-width: 0;
}

.fra-list__field {
  min-width: 0;
  margin: 0;
}

.fra-list__field > input,
.fra-list__field > textarea {
  width: 100%;
}

/* A limitation shows whole: the box grows with its text (where the browser supports it). */
.fra-list__field > textarea {
  field-sizing: content;
  max-height: 14em;
}

/* Two lines by default in a list, not the settings textarea's full block height. */
.cfg-row.fra-list__field > textarea {
  min-height: 3.6em;
}

.fra-list__remove {
  white-space: nowrap;
}

.fra-list__foot {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 10px;
}

.fra-list__count {
  font-size: 12px;
  color: var(--cfg-ink-3);
}

/* ── The optional Advisory band (R20; owner review 2026-09-27) ─────────────────────────────
   Its on/off switch sits in the band's own head, and its fields fade while it is off, so the
   switch reads as belonging to the band it controls. */
.fra-band--optional .cfg-group__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--cfg-4);
}

.fra-band__optional {
  margin-left: 6px;
  font-size: 12px;
  font-weight: 500;
  color: var(--cfg-ink-3);
}

.fra-band__switch {
  flex: 0 0 auto;
}

.fra-band--optional:has(.fra-band__switch input[type="checkbox"]:not(:checked)) .cfg-grid {
  opacity: 0.55;
}

/* "Use the standard set" sits under the Standard limitations save bar, apart from it. */
.fra-list__reset {
  margin-top: var(--cfg-3);
}

/* ── Report glossary (rebuilt after the owner's review, 2026-09-27) ─────────────────────────
   Your own terms: term + definition + Remove, one row each. The standard terms fold away and
   each shows the wording that prints, with "Reword" opening its box. */
.fra-gloss__own {
  display: grid;
  grid-template-columns: minmax(0, 14rem) minmax(0, 1fr) auto;
  align-items: end;
  gap: var(--cfg-2) var(--cfg-3);
  padding-bottom: var(--cfg-3);
  border-bottom: 1px solid var(--cfg-rule);
}

.fra-gloss__own:last-child {
  border-bottom: 0;
}

.fra-gloss__own textarea {
  field-sizing: content;
  min-height: 2.6em;
  max-height: 12em;
}

.fra-gloss__summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--cfg-2) var(--cfg-3);
  cursor: pointer;
}

.fra-gloss__meta {
  font-size: 12.5px;
  color: var(--cfg-ink-3);
}

.fra-gloss__terms {
  list-style: none;
  margin: var(--cfg-3) 0 0;
  padding: 0;
}

.fra-gloss__term {
  padding: var(--cfg-3) 0;
  border-top: 1px solid var(--cfg-rule);
}

.fra-gloss__term-head {
  display: flex;
  align-items: center;
  gap: var(--cfg-2);
}

.fra-gloss__badge {
  font-size: 11px;
  font-weight: 600;
  padding: 1px 6px;
  border: 1px solid var(--cfg-rule);
  border-radius: 999px;
  color: var(--cfg-ink-3);
}

.fra-gloss__def {
  margin: 4px 0 6px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--cfg-ink-3);
  max-width: 80ch;
}

.fra-gloss__reword > summary {
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.fra-gloss__reword .cfg-row {
  margin-top: var(--cfg-2);
}

@media (max-width: 900px) {
  .fra-gloss__own {
    grid-template-columns: 1fr;
  }

  .fra-gloss__own .fra-list__remove {
    justify-self: start;
  }
}

/* ── Report wording: the FRA topic lines, folded by report section (2026-09-27) ───────────── */
.dt-topic-group {
  grid-column: 1 / -1;
  border-top: 1px solid var(--cfg-rule);
  padding: var(--cfg-2) 0;
}

.dt-topic-group:first-child {
  border-top: 0;
}

.dt-topic-group > summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--cfg-2) var(--cfg-3);
  cursor: pointer;
  padding: var(--cfg-2) 0;
}

.dt-topic-group__name {
  font-weight: 600;
}

.dt-topic-group__meta {
  font-size: 12.5px;
  color: var(--cfg-ink-3);
}

.dt-topic-group > .cfg-grid {
  margin-top: var(--cfg-2);
}
