/* Enabled set
 *
 * The Settings screen where an Owner chooses which currencies and which
 * timezones the Organisation offers. STORY-041.
 *
 * The product SUPPORTS every ISO 4217 currency and every IANA zone, read from
 * the platform at runtime. It would be hostile to put ~180 or ~420 of them in
 * an everyday picker, so each Organisation ENABLES the handful it works in and
 * the everyday control is a plain <select> over those. This component is where
 * that short list is maintained — and it is the ONLY place the full set is
 * searched. i18n.md and ADR-0005 own the rule.
 *
 *   <section class="enabled-set">
 *     <div class="enabled-set__search"> … field + live count … </div>
 *     <h3 class="enabled-set__group" id="g-on">Enabled</h3>
 *     <ul class="row-list row-list--tight" aria-labelledby="g-on">
 *       <li class="row">
 *         <span class="enabled-set__code">EUR</span>
 *         <span class="row__main"> title + meta </span>
 *         <span class="row__actions"> switch or Add button </span>
 *       </li>
 *     </ul>
 *   </section>
 *
 * ---------------------------------------------------------------------------
 * THIS IS NOT A COMBOBOX, and that is the whole design.
 *
 * `person-picker` is an ARIA combobox because it SETS A FORM VALUE. This sets
 * nothing — every row carries a COMMAND (enable this, disable that). So it is a
 * plain <input type="search"> filtering an ordinary list of real <button> and
 * checkbox controls, with the match count in a live region.
 *
 * No role="listbox", no role="option", no aria-activedescendant. Rows here hold
 * interactive controls, and interactive controls nested inside role="option"
 * are unreachable for most screen reader users — which is precisely how
 * transfer-list widgets end up inaccessible. Being the boring pattern is the
 * feature; this component is far simpler than person-picker, not a variant.
 *
 * Two rules the markup has to keep, because CSS cannot:
 *
 *  1. Every control's accessible name carries its SUBJECT — "Enable Japanese
 *     Yen", never a bare "Enable". Twelve identical "Add" buttons in a row is
 *     the most common failure in a list of commands.
 *  2. The match count goes in an aria-live="polite" region. Filtering a list
 *     silently is invisible to anyone not looking at it.
 */

.enabled-set {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* ---- search head --------------------------------------------------------- */

/* The label is a real <label>, never a placeholder standing in for one: a
 * placeholder disappears on the first keystroke, which is exactly when someone
 * checking what this box does needs it. */
.enabled-set__search {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* aria-live="polite". Reads "12 currencies match" as the filter narrows, so the
 * result of typing is announced rather than only rendered. */
.enabled-set__count {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  color: var(--color-text-muted);
}

/* ---- group headings ------------------------------------------------------ */

/* "Enabled" / "Not enabled". Real headings, referenced by aria-labelledby on
 * each list, so the two groups are distinguishable when tabbing rather than
 * being one undifferentiated run of rows.
 *
 * With nothing typed only the Enabled group renders — the point of the feature
 * is that the everyday list is short, and repeating "Not enabled: 174 more"
 * would restate the thing this screen exists to avoid. */
.enabled-set__group {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* ---- the code ------------------------------------------------------------ */

/* EUR, JPY, Europe/Istanbul. Monospaced and tabular so a column of codes lines
 * up and is scanned rather than read. It is not a badge: a badge carries a
 * status, and this is an identifier. */
.enabled-set__code {
  flex: none;
  min-inline-size: 3.5rem;

  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  font-weight: var(--weight-medium);
  color: var(--color-text);
  overflow-wrap: anywhere;
}

/* ---- one shared column across every row ----------------------------------
 * Sizing each code to its own content puts every NAME at a different position:
 * `Europe/Amsterdam` and `Europe/Istanbul` happen to match, then `Asia/Tokyo`
 * pulls Tokyo left and the list stops reading as a column. Found by looking at
 * it — the currency set never shows this, because three-letter codes are all
 * the same width.
 *
 * So the list becomes a grid and each row inherits its tracks with `subgrid`.
 * The column sizes itself to the widest identifier ON SCREEN, which is what
 * keeps a set of short zones from reserving space for a long one. A fixed width
 * would have to be guessed, and would be wrong for both sets.
 *
 * The cap matters: `America/Argentina/Rio_Gallegos` is a real zone, and an
 * uncapped `max-content` track cannot wrap, so one such row would push the list
 * sideways. Capped, it wraps inside its own column and nothing else moves. */
/* Narrow: the identifier takes its own line above the name. Every row then
 * starts its text at the same x for free, with none of the width spent on a
 * column. Squeezing `Europe/Amsterdam` into a side column at 375px was measured
 * and it wraps mid-word — aligned and unreadable is not the trade. */
@media (max-width: 29.999rem) {
  .enabled-set__code { flex-basis: 100%; }
}

/* Wide: one shared column, sized to the widest identifier on screen. */
@supports (grid-template-columns: subgrid) {
  @media (min-width: 30rem) {
    .enabled-set .row-list {
      display: grid;
      /* `fit-content(cap)` is the one track function that means what is wanted:
       * size to the content, but never past the cap, and wrap beyond it.
       * `minmax(3.5rem, 12rem)` looks equivalent and is not — a definite max
       * makes the track take that max, so three-letter currency codes got a
       * 171px column. Measured, not assumed. */
      grid-template-columns: fit-content(12rem) minmax(0, 1fr) max-content;
    }

    .enabled-set .row-list > .row {
      display: grid;
      grid-column: 1 / -1;
      grid-template-columns: subgrid;
      align-items: center;
    }

    /* Spans every track so the message sits under the control it belongs to
     * rather than being squeezed into the third column. */
    .enabled-set .row-list > .row > .enabled-set__error { grid-column: 1 / -1; }
  }
}

/* Without subgrid the wide rows stay flex and each code sizes to itself —
 * ragged, but never broken. Chrome 117, Firefox 71 and Safari 16 all have it. */

/* The matched run of characters, while searching. Weight AND colour: a
 * highlight that is only a colour disappears in greyscale and says nothing to
 * a screen reader. Same treatment as person-picker, deliberately. */
.enabled-set__match {
  font-weight: var(--weight-bold);
  color: var(--color-accent);
}

/* ---- the switch ---------------------------------------------------------- */

/* A native checkbox with role="switch", not a re-skinned div.
 *
 * `accent-color` tints the OS-drawn glyph, which means it follows the theme via
 * `color-scheme` for free and never desynchronises from the platform's own
 * focus and high-contrast handling. Building a custom switch here would buy a
 * nicer shape and cost all of that.
 *
 * The visible text label is not decoration. Colour and position alone would
 * carry the meaning otherwise, which design.md forbids — and "on" is not
 * legible in greyscale from a tinted rectangle. */
.enabled-set__toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);

  min-block-size: var(--target-min);
  font-size: var(--text-sm);
  white-space: nowrap;
  cursor: pointer;
}

.enabled-set__toggle input {
  accent-color: var(--color-accent);
  inline-size: 1.15rem;
  block-size: 1.15rem;
  cursor: inherit;
}

.enabled-set__toggle input:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: var(--focus-ring-offset);
}

/* ---- the locked row ------------------------------------------------------ */

/* The Organisation's own currency and own timezone can never be disabled —
 * something must always be pickable. The switch is genuinely `disabled`, and
 * the reason is rendered as text beside it.
 *
 * A greyed control with no explanation is the failure mode design.md and
 * STORY-006 both name: the user is told "no" without being told why or where
 * to go. The reason belongs on screen, not in a tooltip. */
.enabled-set__toggle input:disabled {
  cursor: not-allowed;
  accent-color: var(--color-disabled-text);
}

.enabled-set__toggle input:disabled + span { color: var(--color-text-muted); }

/* ---- row-level error ----------------------------------------------------- */

/* Persisting a change is the only asynchronous step here, so it is the only
 * thing that can fail. When it does, the control returns to its previous
 * position and the row says so — a list left showing a state that was never
 * saved is worse than an error, because it looks like success.
 *
 * Takes the full row width so the message sits under the control it belongs
 * to rather than being squeezed beside it. */
.enabled-set__error {
  flex-basis: 100%;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;

  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  font-weight: var(--weight-medium);
  color: var(--color-danger);
}

.enabled-set__row--error { border-color: var(--color-danger); }

/* ---- the pending confirmation -------------------------------------------- */

/* Disabling a value that is IN USE asks first. Disabling an unused one, and
 * enabling anything, does not — confirming a harmless reversible action teaches
 * people to dismiss dialogs without reading them.
 *
 * **There is only ever one.** A second attempt at something already awaiting an
 * answer must not open a second dialog: spamming the switch stacked fifteen
 * identical callouts, which is worse than the thing being warned about. The
 * repeat attempt instead SHAKES the one on screen and moves focus to its
 * confirm button — focus, because a shake says nothing to a screen reader.
 *
 * Wrap the callout in this class and toggle `--shake` to replay it. */
.enabled-set__confirm { /* layout comes from .callout; this is the hook */ }

.enabled-set__confirm--shake {
  animation: enabled-set-shake var(--duration-base) var(--ease-out);
}

@keyframes enabled-set-shake {
  0%, 100% { transform: translateX(0); }
  20%      { transform: translateX(calc(var(--space-2) * -1)); }
  40%      { transform: translateX(var(--space-2)); }
  60%      { transform: translateX(calc(var(--space-1) * -1)); }
  80%      { transform: translateX(var(--space-1)); }
}

/* Motion is the entire mechanism, so a reduced-motion user cannot simply lose
 * it. They get a border and surface pulse carrying the same "this one, again"
 * without movement. Removing the animation outright would leave the repeat
 * attempt with no feedback at all. */
@media (prefers-reduced-motion: reduce) {
  .enabled-set__confirm--shake {
    animation: enabled-set-pulse calc(var(--duration-base) * 2) var(--ease-out);
  }
  @keyframes enabled-set-pulse {
    0%, 100% { border-color: var(--color-warning); }
    50%      { border-color: var(--color-danger);
               background-color: var(--color-surface-sunken); }
  }
}

/* ---- empty, and the teaching hint ---------------------------------------- */

/* No search match. Never a blank pane — a list that empties without saying why
 * reads as a broken screen rather than as a query with no results. */
.enabled-set__empty {
  margin: 0;
  padding: var(--space-5) var(--space-4);

  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--color-text-muted);
  text-align: center;

  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-md);
}

/* Shown while exactly one value is enabled — which is how every new
 * Organisation starts, since there is no default shortlist anywhere. It is not
 * an empty state (the list has a row) but it is the state that has to teach:
 * without it, nobody discovers that this screen governs what their team can
 * pick. */
.enabled-set__hint {
  margin: 0;
  max-inline-size: var(--measure);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--color-text-muted);
}

/* ---- long content -------------------------------------------------------- */

/* `America/Argentina/Rio_Gallegos` is a real zone, and currency names run
 * long in several languages. Nothing here gets a fixed size, and the row wraps
 * its controls below the text rather than compressing them — i18n.md, and the
 * reason the body never scrolls horizontally. */
.enabled-set .row__title,
.enabled-set .row__meta { overflow-wrap: anywhere; }
