/* PROFILE·LANGUAGE (1022) — the language sheet: bottom-sheet chrome, the standfirst,
 * the English / Français pick-one and the Cancel / Save foot.
 * ═══════════════════════════════════════════════════════════════════════════════
 * EXTRACTION, never a redesign (workbench/RULES.md §8). The chosen design is the code
 * that ships today: REPORT-131's spine row 97 (changeLanguage + nested render,
 * web/js/views/profile.js:243 + :267). The rules below carry the shipped declarations
 * of the app.css rules the sheet consumed — `.modal-top` (:2573) · `.modal-title`
 * (:2583) · `.modal-close` (:2590) · `.modal-body` (:2611) · `.modal-body .radio-grid`
 * (:2650) · `.modal-foot` (:2654) · `.modal-foot .btn` (:2664) · `.radio-grid`
 * (:1773) · `.radio-tile` (:1784, + :hover/:on) · `.btn`/`.secondary`/`.full`
 * (:2058–2098) · `.muted` (:217) — plus two inline styles the renderer wrote itself:
 * the title-row spacer (`width: 28px`) and the standfirst (`fontSize: 13px`,
 * `marginBottom: 16px`).
 *
 * ── THE STEM IS RENAMED, NOT REDESIGNED ──
 * `modal-top`/`modal-title`/`modal-close`/`modal-body`/`radio-grid`/`radio-tile`/`btn`/
 * `muted` are shared vocabulary: seven other profile sheets, chat's two modals and the
 * onboarding steps use the same classes, and check 16 gives a class prefix exactly ONE
 * owner (the 238 precedent). So this card owns `plang` and emits
 * `plang-top/plang-title/plang-close/plang-spacer/plang-body/plang-note/plang-grid/
 * plang-tile/plang-foot/plang-cancel/plang-save` with the same anatomy and the same
 * declarations. ⚠️ DO NOT REGISTER A BARE `pl` PREFIX: check 16 resolves ownership by
 * LONGEST matching prefix, and a bare `pl` would claim every `plang-*` rule here.
 *
 * ── THE SHEET SCAFFOLD IS NOT THIS CARD'S ──
 * `.modal-overlay` (the fixed scrim, the --kb-h keyboard lift, the ≥600px centring) and
 * `.modal-sheet` (paper, max-width, max-height, radius, shadow) stay in app.css. They are
 * the hand-built sheet shell REPORT-131 finding 6 names as a consolidation candidate —
 * nine copies, one future row — and this extraction does not become a tenth copy of it.
 * The view mounts the scaffold and hands this card the sheet; the card owns everything
 * INSIDE it. The card root restates only the column its three regions are tuned in and
 * the type the whole sheet inherits, so it reads the same wherever it mounts.
 *
 * ── CARRIED INHERITS (what the sheet received from its host, not from its own rules) ──
 * · font-family: html/body's `var(--font-body)` (app.css:46) — every div in the sheet
 *   inherited Inter; nothing inside declared a font except the title (display) and the
 *   two buttons (`.btn` names --font-body). Carried on the root.
 * · line-height: html/body's `var(--leading-150)` — inherited, now stated on the root.
 * · font-size: html/body's `100%` (Dynamic Type, D-423) — deliberately NOT pinned. The
 *   sheet's type rode the root size everywhere it shipped; a fixed px here would break
 *   the user's text size for the one sheet that reads as a form.
 * · box-sizing: the universal `*` reset (app.css:34) is NOT carried — it is a page-wide
 *   assumption every card in this repo makes. The mirror page loads app.css, so both
 *   sides run the same reset.
 * · h2/p UA defaults — killed by that same reset and now stated explicitly below, so
 *   the card does not depend on the UA's mind.
 * · the `button` reset (app.css:130: font-family inherit · cursor · border 0 ·
 *   background transparent · color inherit) — carried on each of the card's buttons,
 *   including the parts their own rules happen to restate, so no button here rides a
 *   page-wide rule (D-470; the 2026-09-02 dropped-`font:inherit` lesson).
 *
 * ── EXACT-EQUAL TOKEN SWAPS ──
 * `0.15s ease` → `var(--duration-150) var(--ease-standard)` · `0.1s ease` →
 * `var(--duration-100) var(--ease-standard)` (tokens.css:386-392) ·
 * `fontSize: 13px` → `var(--size-13)` (= 0.8125rem = 13px at the default root; rem
 * follows the app's own Dynamic Type rule, which the inline px literal predated) ·
 * `width: 44px` → `var(--dim-44)` (== --control-min, the tap floor) · `width: 28px` →
 * `var(--dim-28)`. Everything else is carried at its shipped value.
 * ═══════════════════════════════════════════════════════════════════════════════ */

.plang {
  font-family: var(--font-body);
  line-height: var(--leading-150);
  /* The sheet chrome is .modal-sheet's (see header); the column the card's three
   * regions are tuned in is restated here so the card's own anatomy is explicit. */
  display: flex;
  flex-direction: column;
}

/* ── the title row (was .modal-top) ─────────────────────────────────────────── */
.plang-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-12) var(--space-16);
  border-bottom: 1px solid var(--rule);
  background: var(--paper);
  flex-shrink: 0;
}

.plang-title {
  /* the h-element rule the shipped title rode on is NOT carried — Fraunces at weight
   * 500 is stated here because .modal-title named it, not because a heading element did */
  font-family: var(--font-display);
  font-size: var(--size-17);      /* token-exception */ /* 17px shipped (.modal-title). No role is 17 — the ladder is 16 body / 18 subtitle (D-463). Snapping moves a shipped sheet title. Ruling owed, not taken (the 1021 17px precedent) */
  font-weight: 500;
  color: var(--ink);
}

.plang-close {
  /* the app's `button` reset, carried — this tap target declares its own text,
   * background, border and cursor instead of riding a page-wide rule (D-470) */
  font-family: inherit;
  color: var(--ink-600);
  background: transparent;
  border: 0;
  cursor: pointer;
  width: var(--dim-44);
  height: var(--dim-44);
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-title);   /* --size-22's exact-equal role name — the shipped rule's own size, one name change, no value change */
  line-height: var(--leading-100);
  transition: background var(--duration-150) var(--ease-standard),
              color var(--duration-150) var(--ease-standard);
}

.plang-close:hover {
  background: var(--paper-card);
  color: var(--ink);
}

/* press + focus feedback: the shipped control declared neither (its press feedback was
 * the hover wash alone). :active re-speaks the wash it already uses — the D-422 floor,
 * no new motion invented. :focus-visible is the app's global ring (app.css:44), restated
 * so the card owns its own feedback wherever it mounts. */
.plang-close:active {
  background: var(--paper-card);
}

.plang-close:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* was an inline `style: { width: '28px' }` on a bare <span> — the counterweight that
 * centres the title between two equal columns under space-between */
.plang-spacer {
  width: var(--dim-28);
}

/* ── the body (was .modal-body) ─────────────────────────────────────────────── */
.plang-body {
  padding: 14px var(--space-20) var(--space-16);   /* token-exception */ /* 14px top shipped (.modal-body); the ladder is 4/8/12/16 — no 14. Snapping tightens a shipped sheet's first breath. Ruling owed, not taken */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  flex: 1 1 auto;
  min-height: 0;
}

/* was `p.muted` + inline `fontSize: '13px'` / `marginBottom: '16px'`. The p rule's own
 * `margin: 0 0 var(--space-12)` (app.css:187) is restated as the 0 top here, and the
 * inline 16px bottom wins over it exactly as it shipped. */
.plang-note {
  color: var(--text-secondary);
  font-size: var(--text-caption);  /* the inline `fontSize: 13px` — --size-13's exact-equal role name, so the standfirst scales with the user's text size (D-423) */
  margin: 0 0 var(--space-16);
}

/* ── the pick-one (was .radio-grid / .radio-tile) ───────────────────────────── */
.plang-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;                        /* token-exception */ /* 10px shipped — the space ladder is 4/8/12/16…; no --space-10. Snapping to 12 moves a shipped tile rhythm: a design ruling owed, not taken (the 238 precedent) */
  /* the bare .radio-grid's 14px stand was overridden to 4px inside a sheet body
   * (`.modal-body .radio-grid`, app.css:2650) — the value the sheet actually computed */
  margin: 0 0 var(--space-4);
}

.plang-tile {
  font-family: inherit;
  cursor: pointer;
  background: var(--paper-card);
  border: 2px solid var(--rule);                    /* token-exception */ /* 2px hairline border — shipped (.radio-tile); the grid governs layout, not hairlines (238 carried its 2px the same way) */
  border-radius: var(--r-md);
  padding: var(--space-16);
  text-align: center;
  transition: border-color var(--duration-150) var(--ease-standard),
              background var(--duration-150) var(--ease-standard);
  /* ⚠️ THE SHIPPED CONTROL NEVER DECLARED A LABEL SIZE, AND NEITHER DOES THIS CARD —
   * the shipped .radio-tile sets no font-size, so the label rode the root size
   * (16px at the default root, Dynamic Type with it). Pinned by omission on purpose:
   * declaring a px here would freeze the label against the user's text size (D-423). */
}

.plang-tile:hover {
  border-color: var(--accent);
  background: var(--surface-hover-wash);
}

/* the selected language — `.on`, set by the renderer, as shipped */
.plang-tile.on {
  border-color: var(--accent);
  background: var(--surface-notice);
}

/* press + focus feedback: the shipped tile declared neither (its press feedback was the
 * hover/selected colour alone, and it is a <div>, which cannot take focus at all —
 * flagged, not fixed: a div→button conversion is an a11y redesign owed a ruling, not an
 * extraction). :active re-speaks the accent border the control already uses; the
 * :focus-visible rule states the ring the app gives every focusable thing, ready for the
 * day the tile can receive it. */
.plang-tile:active {
  border-color: var(--accent);
}

.plang-tile:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── the foot (was .modal-foot + .btn/.secondary/.full) ─────────────────────── */
.plang-foot {
  display: flex;
  flex-direction: row-reverse;
  gap: 10px;                        /* token-exception */ /* 10px shipped (.modal-foot) — no ladder value; snapping narrows a shipped door gap. Ruling owed */
  padding: var(--space-12) var(--space-16) calc(14px + env(safe-area-inset-bottom));  /* token-exception */ /* the 14px inside the calc is shipped (.modal-foot's safe-area stand) — no ladder value; the env() inset is the platform's */
  border-top: 1px solid var(--rule);
  background: var(--paper);
  flex-shrink: 0;
}

/* the two doors were `.btn secondary full` (Cancel) and `.btn full` (Save), with
 * `.modal-foot .btn`'s own flex/padding on top — carried as one geometry rule and two
 * colour-ways, so the foot owns what the app spread across five app.css rules */
.plang-cancel,
.plang-save {
  /* the app's `button` reset, carried (D-470) */
  font-family: inherit;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-8);
  font-size: var(--size-15);       /* token-exception */ /* .btn's own size, pre-dating the D-463 body floor of 16. No role is 15; snapping grows a shipped door label. Ruling owed, not taken */
  font-weight: 500;
  padding: 14px var(--space-20);   /* token-exception */ /* 14px block shipped (.modal-foot .btn's override of .btn's 12) — no ladder value. Ruling owed */
  border-radius: var(--radius-pill);
  flex: 1;
  width: 100%;                     /* .btn.full */
  transition: transform var(--duration-100) var(--ease-standard),
              opacity var(--duration-150) var(--ease-standard);
}

.plang-cancel {
  color: var(--text-primary);
  background: var(--surface-card);
  border: 1px solid var(--border-hairline);         /* token-exception */ /* 1px hairline — shipped (.btn.secondary) */
}

.plang-save {
  color: var(--text-on-dark);
  background: var(--surface-inverse);
  border: 0;
}

.plang-cancel:hover,
.plang-save:hover {
  opacity: 0.92;
}

.plang-cancel:active,
.plang-save:active {
  transform: translateY(1px);      /* .btn:active's shipped 1px press */
}

.plang-cancel:disabled,
.plang-save:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.plang-cancel:focus-visible,
.plang-save:focus-visible {
  outline: 2px solid var(--accent);                 /* the app's global focus ring, restated for this card's own elements */
  outline-offset: 2px;
}

/* the ≥600px foot: the doors stop stretching and take a floor (app.css:2669-2673) —
 * carried so the card keeps the shipped wide shape wherever it mounts */
@media (min-width: 600px) {        /* token-exception */ /* the shipped breakpoint, carried verbatim — no breakpoint token exists */
  .plang-cancel,
  .plang-save {
    flex: 0 0 auto;
    min-width: 120px;              /* token-exception */ /* 120px shipped — no --dim value at this size */
  }
}

/* the ≤480px single-column stack (app.css:2505) — on a phone the language grid renders
 * ONE column; the two-column rule above is the wide-viewport shape. Carried so the card
 * keeps the shipped breakpoint wherever it mounts. */
@media (max-width: 480px) {        /* token-exception */ /* the shipped breakpoint, carried verbatim — no breakpoint token exists */
  .plang-grid {
    grid-template-columns: 1fr;
  }
}
