/* ============================================================================
   Nhập Lưu - component styles

   NO RAW HEX. NO RAW FONT SIZE. Tokens only.
   NhapLuu.ArchitectureTests fails the build if that slips.

   Elder-first is not a style here, it is a set of hard floors:
     body text        >= 18px      (--font-size-body)
     control text     >= 16px      (--font-size-control is 18px, deliberately above the floor)
     touch targets    >= 44x44px   (--tap-min)
     font weight      <= 600
     italics          none, anywhere
     colour           never the only carrier of meaning
   ========================================================================= */

/* ---- Reset and page ----------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--surface-page);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  font-weight: var(--font-weight-regular);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

h1, h2, h3 {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-medium);
  line-height: var(--line-height-tight);
  margin: 0;
}

h1 { font-size: var(--font-size-h1); }
h2 { font-size: var(--font-size-h2); }
h3 { font-size: var(--font-size-h3); }

p { margin: 0; }

/* 600 is the ceiling (brief 12). The BROWSER's default for <strong> and <b> is 700, which is above
   it, and no stylesheet rule declares that - so the CSS architecture test cannot see it. It was
   caught by measuring the rendered DOM in a browser instead. This is the one line that holds the
   ceiling for every <strong> in the application. */
strong,
b {
  font-weight: var(--font-weight-semibold);
}

/* Nhập Lưu is a proper name. It is never translated and never italicised. */
.brand {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-medium);
}

/* ---- Focus -------------------------------------------------------------
   One rule, everything focusable, no exceptions. The offset is what makes the
   ring visible on a primary button, whose fill is the same colour as the ring
   (see tokens.css). Never remove the outline without replacing it. */

:where(a, button, input, select, textarea, summary, [tabindex]:not([tabindex="-1"])):focus-visible {
  outline: var(--focus-width) solid var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* FocusOnNavigate puts focus on the <h1> after every navigation - that is how a keyboard user lands
   inside the content without tabbing through the chrome, and it is why there is no skip link.
   Chromium honours :focus-visible on that programmatic focus and draws its OWN default ring, so the
   heading gets boxed for a mouse user who never touched the keyboard. It reads as an error outline.
   Excluding tabindex="-1" from our rule above is not enough; the browser's default has to be
   turned off too. A focus ring belongs on something you can operate. */
[tabindex="-1"]:focus,
[tabindex="-1"]:focus-visible {
  outline: none;
}

/* ---- Layout -------------------------------------------------------------
   RESPONSIVE RULES ARE CONTAINER QUERIES, AND THERE IS ONLY ONE SET OF THEM.

   <main> is a container, so `@container app` drives every ordinary page. The preview's device frame
   is a NESTED container with the same name, so the same rules drive it too - a nested container wins
   for its own descendants.

   The alternative was writing every narrow-screen rule twice, once as @media for real phones and
   once as @container for the frame. That duplication is exactly the thing that rots: someone fixes
   the phone and forgets the frame, and the reviewer's device dropdown quietly stops telling the
   truth. One rule, two consumers. */

main {
  container-type: inline-size;
  container-name: app;
}

.page {
  max-width: 56rem;
  margin: 0 auto;
  padding: var(--space-7) var(--space-4) var(--space-10);
}

.stack        { display: flex; flex-direction: column; gap: var(--space-4); }
.stack-tight  { display: flex; flex-direction: column; gap: var(--space-2); }
.stack-loose  { display: flex; flex-direction: column; gap: var(--space-7); }

.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
}

/* Two short, related fields side by side: dietary and accessibility, relationship and mobile, the
   doctor's telephone and address. Stacked, they cost a whole extra screen across three participants,
   and they are pairs anyway - a reader takes them in together. Collapses to one column when narrow. */
.grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}

/* ---- Bilingual text ----------------------------------------------------
   English above, Vietnamese below, same block, both authored. The Vietnamese
   line is distinguished by SIZE and COLOUR, never by italics: italicised
   diacritics are materially harder to read, and italics would quietly mark
   Vietnamese as the second-class language on the page.

   min-height reserves space for the Vietnamese line even before it renders,
   so the layout does not jump. Vietnamese runs 15-30% longer than English. */

.bilingual {
  display: block;
}

.bilingual > .en {
  display: block;
  color: inherit;
}

.bilingual > .vi {
  display: block;
  color: var(--text-secondary);
  font-size: var(--font-size-secondary);
  font-weight: var(--font-weight-regular);
  min-height: 1.5em;
}

/* INLINE: "Full name · Họ và tên" on one line, for SHORT labels only.

   Stacked pairs cost two lines each. On a form that is a label, a Vietnamese label, a "Required" and
   a Vietnamese "Required" - four lines of chrome per input - and the person has to scroll past all
   of it to reach the next question. Prose stays stacked; a sentence run together with its
   translation is unreadable in both languages. */
.bilingual-inline {
  display: inline;
}

.bilingual-inline > .en,
.bilingual-inline > .vi {
  display: inline;
  min-height: 0;
}

/* The separator lives in CSS, not in the text, so it never reaches a translator's copy sheet and
   never gets read aloud as a word. */
.bilingual-inline > .vi::before {
  content: " · ";
  color: var(--text-muted);
}

/* On a filled control (a primary button) the Vietnamese line must inherit the
   button's text colour. Left to --text-secondary it would be dark-green-on-
   dark-green: we shipped exactly that bug in the prototype and caught it in a
   browser, not in review. */
.btn .bilingual > .vi,
.chip .bilingual > .vi {
  color: inherit;
}

/* MONOLINGUAL MODE (Public Language Mode slice). When the selected language is English or Vietnamese,
   NlBilingual renders only that one line - it is never hidden with CSS, it is simply not in the DOM. A
   single line is the CONTENT, not the quiet secondary half of a pair, so Vietnamese on its own takes the
   primary size and colour its English counterpart would have had, and the inline separator dot - which
   only means anything between a pair - does not appear. English on its own already uses .en, which is
   primary, so it needs nothing here. In Both mode neither rule matches (there are two children). */
.bilingual > .vi:only-child {
  color: inherit;
  font-size: inherit;
  font-weight: inherit;
}

.bilingual-inline > .vi:only-child::before {
  content: none;
}

/* ---- Buttons ------------------------------------------------------------ */

.btn {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  padding: var(--space-3) var(--space-6);
  border: var(--border-emphasis) solid transparent;
  border-radius: var(--radius-control);
  font-family: var(--font-body);
  font-size: var(--font-size-control);
  font-weight: var(--font-weight-semibold);
  line-height: var(--line-height-tight);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 120ms ease-in-out, border-color 120ms ease-in-out;
}

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-on-accent);
}

.btn-primary:hover:not(:disabled) {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

.btn-secondary {
  background: var(--surface-card);
  border-color: var(--border-control);
  color: var(--text-primary);
}

.btn-secondary:hover:not(:disabled) {
  background: var(--surface-subtle);
}

/* A destructive action: cancel an event people have paid for, delete a draft for good.
   ADMIN CHROME ONLY - no public control destroys anything.

   The colour is a SECOND signal, never the only one. Every button carrying this class is also
   worded ("Cancel event", "Delete draft") and every one of them opens a dialog that states the
   consequences first, so the meaning survives for someone who cannot see the difference between
   this and the accent. */
.btn-danger {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--text-on-accent);
}

/* Darkened rather than swapped for a --danger-hover token, because no such token exists and
   inventing one would mean choosing a colour for each of the three themes and justifying its
   contrast against the table in tokens.css. A filter needs no new token, keeps the single
   --danger as the source of truth, and cannot drift away from it. */
.btn-danger:hover:not(:disabled) {
  filter: brightness(0.9);
}

/* A quiet, text-like action (Back, Change email). Still a full 44px target:
   an older person's tap does not become more accurate because the control is
   visually lighter. */
.btn-quiet {
  background: transparent;
  border-color: transparent;
  color: var(--accent);
  font-weight: var(--font-weight-medium);
  text-decoration: underline;
  padding-left: var(--space-3);
  padding-right: var(--space-3);
}

.btn-quiet:hover:not(:disabled) {
  color: var(--accent-hover);
  background: var(--surface-subtle);
}

.btn:disabled {
  background: var(--disabled-fill);
  border-color: var(--disabled-fill);
  color: var(--disabled-text);
  cursor: not-allowed;
}

.btn-secondary:disabled,
.btn-quiet:disabled {
  background: transparent;
  border-color: var(--border-default);
  color: var(--text-muted);
}

/* The button says it is working, in words. A spinner alone tells a first-time
   user nothing, and colour alone is never a status (locked rule). */
.btn[aria-busy="true"] {
  cursor: progress;
}

.btn-block {
  display: flex;
  width: 100%;
}

/* ---- Fields ------------------------------------------------------------- */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.field-label {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-4);
  color: var(--text-primary);
  font-weight: var(--font-weight-medium);
}

/* "Required" and "Optional" are stated in WORDS, in both languages. An asterisk
   is a convention this audience has no reason to know, and a screen reader
   reads it as "star". */
.field-requirement {
  color: var(--text-muted);
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-regular);
}

.field-help {
  color: var(--text-muted);
  font-size: var(--font-size-meta);
}

.input,
.select,
.textarea {
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-card);
  color: var(--text-primary);
  border: var(--border-hairline) solid var(--border-control);
  border-radius: var(--radius-control);
  font-family: var(--font-body);
  font-size: var(--font-size-control);
  line-height: var(--line-height-body);
}

.textarea {
  min-height: calc(var(--tap-min) * 2);
  resize: vertical;
}

.select {
  appearance: none;
  padding-right: var(--space-9);
  /* The chevron is drawn in the markup, not as a background image, so it stays
     crisp and inherits the text colour rather than needing a hard-coded hex. */
}

.select-wrap {
  position: relative;
  display: block;
}

.select-chevron {
  position: absolute;
  right: var(--space-4);
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  color: var(--text-secondary);
}

.input:disabled,
.select:disabled,
.textarea:disabled {
  background: var(--surface-subtle);
  color: var(--text-secondary);
  cursor: not-allowed;
}

/* A read-only field is shown, not editable - the verified email a person signs in with. Unlike
   :disabled it stays selectable so they can copy it, but it reads as settled rather than editable. */
.input:read-only {
  background: var(--surface-subtle);
  color: var(--text-secondary);
}

/* The error state carries a THICKER border and a message, not merely a red
   line. Colour is never the only indicator. */
.input.is-invalid,
.select.is-invalid,
.textarea.is-invalid {
  border-width: var(--border-emphasis);
  border-color: var(--danger);
}

/* ---- Choice controls (radio, checkbox) ---------------------------------
   The whole row is the target, not just the 24px box. The handoff drew a bare
   28px checkbox with a 1.36:1 outline, on the one screen that decides who is
   registered and what is paid. This is the fix. */

.choice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  min-height: var(--tap-min);
  padding: var(--space-3);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-control);
  background: var(--surface-card);
  cursor: pointer;
}

.choice:hover {
  background: var(--surface-subtle);
}

.choice-input {
  flex: none;
  width: var(--space-6);
  height: var(--space-6);
  margin: 0;
  accent-color: var(--accent);
  /* 3:1 boundary (WCAG 1.4.11). --border-default would be 1.4:1 and invisible
     to an older eye. */
  outline: var(--border-hairline) solid var(--border-control);
  outline-offset: 0;
  cursor: pointer;
}

.choice-text {
  flex: 1;
  min-width: 0;
}

/* Selection is carried by the fill AND the tick AND the border. Never by
   colour alone. */
.choice:has(.choice-input:checked) {
  border-color: var(--accent);
  border-width: var(--border-emphasis);
  padding: calc(var(--space-3) - var(--border-hairline));
  background: var(--surface-subtle);
}

.choice-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  border: 0;
  margin: 0;
  padding: 0;
}

.choice-group > legend {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-4);
  padding: 0;
  margin-bottom: var(--space-3);
  font-weight: var(--font-weight-medium);
}

/* ---- Validation --------------------------------------------------------- */

.validation {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  color: var(--danger);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-medium);
}

.validation-icon {
  flex: none;
  margin-top: var(--space-1);
}

/* The error summary at the top of a step. Focus moves here on a failed submit,
   so a screen-reader user and a magnifier user both land on the problem rather
   than hunting for it. */
.validation-summary {
  padding: var(--space-4);
  border: var(--border-emphasis) solid var(--danger);
  border-radius: var(--radius-panel);
  background: var(--danger-bg);
  color: var(--text-primary);
}

.validation-summary ul {
  margin: var(--space-3) 0 0;
  padding-left: var(--space-6);
}

.validation-summary li + li {
  margin-top: var(--space-2);
}

/* ---- Notices ------------------------------------------------------------
   Every notice carries an icon AND a heading in words. There is no --info
   colour on purpose: an informational notice is a neutral panel. */

.notice {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-4);
  border: var(--border-hairline) solid var(--border-strong);
  border-left-width: var(--space-1);
  border-radius: var(--radius-panel);
  background: var(--surface-subtle);
  color: var(--text-primary);
}

.notice-success { border-color: var(--success); background: var(--success-bg); }
.notice-warning { border-color: var(--warning); background: var(--warning-bg); }
.notice-danger  { border-color: var(--danger);  background: var(--danger-bg); }

.notice-icon { flex: none; }

.notice-success .notice-icon { color: var(--success); }
.notice-warning .notice-icon { color: var(--warning); }
.notice-danger  .notice-icon { color: var(--danger); }

/* ---- Accessible disclosure (Mobile Registration Feedback R1) ------------
   A native <details>/<summary>: keyboard-operable, closes on Escape, needs no JavaScript and no
   circuit. It keeps a panel compact while its fuller text stays one keystroke away, never removed -
   the health privacy explanation uses it. The summary is a REAL control: at least a 44px target, the
   shared :focus-visible ring already covers <summary>, and it is never icon-only or colour-only - it
   carries its bilingual label, in the accent colour AND underlined, so the affordance survives for a
   reader who cannot see the colour or is listening. */
.disclosure {
  margin: 0;
}

.disclosure-summary {
  min-height: var(--tap-min);
  padding: var(--space-2) 0;
  color: var(--accent);
  font-weight: var(--font-weight-medium);
  text-decoration: underline;
  cursor: pointer;
}

.disclosure-summary:hover {
  color: var(--accent-hover);
}

.disclosure[open] .disclosure-summary {
  margin-bottom: var(--space-2);
}

.disclosure-body {
  margin: 0;
  color: var(--text-secondary);
}

/* The OQ-19 draft-wording explanation, inside the warning notice (ConsentForm). DESKTOP (base): the
   explanation is shown in full and the "What this means" toggle is hidden - there is room, so it reads
   exactly as before. The <details> has no `open` attribute, so the body is force-shown here rather than
   defaulted open, which lets the SAME element collapse on a phone with no JavaScript. */
.draft-explainer > .draft-explainer-summary {
  display: none;
}

.draft-explainer > .disclosure-body {
  display: block;
}

/* PHONE: reveal the "What this means" toggle and collapse the explanation by default, so the draft
   banner shrinks to its title plus a one-tap disclosure and the consent choices are not pushed off
   the screen. The title of the warning is unaffected - it always shows. */
@container app (max-width: 40rem) {
  .draft-explainer > .draft-explainer-summary {
    display: block;
  }

  .draft-explainer:not([open]) > .disclosure-body {
    display: none;
  }
}

/* ---- Card and section header -------------------------------------------- */

.card {
  padding: var(--space-6);
  background: var(--surface-card);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-panel);
  box-shadow: var(--elevation-card);
}

.section-header {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-5);
  border-bottom: var(--border-hairline) solid var(--border-default);
}

.section-header-lead {
  color: var(--text-secondary);
  font-size: var(--font-size-lead);
}

/* ---- Chips and tier pills ----------------------------------------------
   A pill always carries TEXT ("Adult · A$370"), never a bare colour. */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--surface-subtle);
  color: var(--text-primary);
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-semibold);
}

.chip-tier-adult { background: var(--tier-adult-bg); color: var(--tier-adult-text); }
.chip-tier-child { background: var(--tier-child-bg); color: var(--tier-child-text); }
.chip-tier-free  { background: var(--tier-free-bg);  color: var(--tier-free-text); }

/* ---- Shells -------------------------------------------------------------
   The public shell has NO navigation menu and NO event picker: the public has exactly one path through
   this application, and every extra control is a way for an elderly first-time user to fall out of the
   flow (brief 8, 12). Its ONE deliberate control is the language switch (Public Language Mode slice) -
   which changes how the flow reads, never where it goes. */

.public-header {
  padding: var(--space-5) var(--space-4);
  background: var(--surface-card);
  border-bottom: var(--border-hairline) solid var(--border-default);
}

.public-header-inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  max-width: 56rem;
  margin: 0 auto;
}

.public-header .brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-h3);
  color: var(--text-primary);
}

/* The monastery's round emblem beside the name. Fixed size, never shrinks - the brand NAME shrinks
   first if the row is tight (see .public-header-bar .brand). Clipped to a circle so the source artwork's
   square corners never show on the white header; the circular emblem fills the crop, so the clip lands
   exactly on its yellow ring. */
.public-header-logo {
  flex: 0 0 auto;
  border-radius: 50%;
  object-fit: cover;
}

/* The account link is its OWN row beneath the brand/switch line, right-aligned within the same content
   width, so it never crowds that line at any width. A real 44px target, underlined as a link. */
.public-header-account {
  max-width: 56rem;
  margin: var(--space-2) auto 0;
  text-align: right;
}

.public-header-account a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  color: var(--accent);
  font-size: var(--font-size-meta);
  text-decoration: underline;
  text-underline-offset: var(--space-1);
}

.public-header-subtitle {
  color: var(--text-secondary);
  font-size: var(--font-size-meta);
}

.public-footer {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 56rem;
  margin: 0 auto;
  padding: var(--space-7) var(--space-4) var(--space-9);
  border-top: var(--border-hairline) solid var(--border-default);
  color: var(--text-secondary);
}

/* The monastery's identity and postal address, moved here from the header in R1.2. Meta size: it is a
   contact detail at the foot of the page, not a heading. The bilingual NAME is on its own line; the
   street ADDRESS - identical in both languages - is stated ONCE beneath it, never duplicated. */
.public-footer-identity {
  font-size: var(--font-size-meta);
}

.public-footer-name,
.public-footer-address {
  display: block;
}

.public-footer-address {
  margin-top: var(--space-1);
}

/* ---- Public language switch (Public Language Mode slice) -----------------
   English | Tiếng Việt | Both, in the shared public header. A COMPACT segmented three-option control that
   stays in the top-right corner, on the same line as the "Nhập Lưu" brand, at EVERY width including a
   phone (owner request 2026-07-25). Every colour, size and radius is a token so it re-themes with D-27's
   three themes; the active option is carried by a filled pill AND a heavier weight, never colour alone
   (elder-first rule 11), and aria-pressed carries the state to assistive tech.

   It is deliberately tight, NOT tiny: the 44px minimum touch target (--tap-min) and the 16px control-text
   floor (--font-size-meta) are locked elder-first rules (brief §12) and are held here - "small" is the
   horizontal footprint, not a shrink below those floors. The three options never wrap; the brand may
   shrink before the control does, so the switch keeps its shape in the corner. */

.public-header-bar {
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  /* Its own query container so the switch can tighten on a narrow header (below) without a width @media
     query (banned) and without depending on <main>'s `app` container, which the header sits outside. */
  container-type: inline-size;
  container-name: headerbar;
}

/* The proper name "Nhập Lưu" never wraps to two lines - "Nhập / Lưu" reads as broken. On a tight phone
   header the SWITCH tightens (below) rather than the name breaking. */
.public-header .brand-name {
  white-space: nowrap;
}

.public-header-bar .language-switch {
  margin-left: auto;
  flex: 0 0 auto;
}

.language-switch {
  display: inline-flex;
  align-items: stretch;
  gap: var(--space-1);
  margin: 0;
  padding: var(--space-1);
  background: var(--surface-subtle);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-pill);
}

.language-switch-option {
  -webkit-appearance: none;
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-min);
  padding: var(--space-1) var(--space-2);
  border: var(--border-hairline) solid transparent;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-secondary);
  font-family: var(--font-body);
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-regular);
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
}

.language-switch-option:hover {
  color: var(--text-primary);
}

/* Active: a filled pill in the accent, its label in the on-accent colour, at the semibold weight. The
   fill is a SHAPE change and the weight a hierarchy change, so the selected state never rests on colour
   alone; aria-pressed announces it to a screen reader. */
.language-switch-active,
.language-switch-active:hover {
  background: var(--accent);
  color: var(--text-on-accent);
  font-weight: var(--font-weight-semibold);
}

/* NARROW HEADER: on a phone the brand + logo + switch share one line, so the switch tightens its own
   horizontal padding and gaps to make room - the 44px height and 16px text stay, only the side padding
   gives - rather than forcing the proper name to wrap. Container query on the header bar, not a width
   @media (which would lie inside the reviewer frame). */
@container headerbar (max-width: 24rem) {
  .language-switch {
    gap: 0;
  }

  .language-switch-option {
    padding-left: var(--space-1);
    padding-right: var(--space-1);
  }
}

/* ---- Administration shell (Stage 6) ------------------------------------
   Staff and the public share NO chrome: an administrator who cannot tell at a
   glance which side of the fence they are on is one who will eventually act on
   the wrong one (brief §9). The staff area is a dark left rail beside a white
   workspace, following the admin-shell handoff. It is DESIGNED for desktop
   (>= 1280px, handoff §8) - which does not weaken the public flow's responsive or
   accessibility obligations one bit; those pages are unaffected by anything here.

   It no longer FORCES a 1280px floor with `min-width`. That floor added a
   page-level horizontal scrollbar on any narrower viewport - and a laptop at
   125%/150% display scaling reports an effective CSS width well under 1280, so a
   perfectly ordinary admin screen scrolled the whole page sideways and clipped the
   left rail. The grid's `minmax(0, 1fr)` track and the workspace's `min-width: 0`
   let it shrink gracefully instead; the one wide surface (the registrations table)
   sizes its own columns to fit (see .admin-table-registrations). The desktop layout
   is unchanged at >= 1280px.

   Slice 1 builds the SHELL and its authorisation only. The navigation reflects
   the eventual information architecture, but every section whose screens are not
   built yet is an INERT placeholder, never a link that would 404. Each becomes a
   real route, behind its own authorisation policy AT THE ROUTE, as its stage
   lands (Events: Stage 6 slice 2+; Registrations, Waiting list, Audit: Stage 7). */
.admin-app {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 248px minmax(0, 1fr);
  background: var(--surface-page);
}

.admin-sidebar {
  position: sticky;
  top: 0;
  align-self: start;
  height: 100vh;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-6) var(--space-4) var(--space-5);
  background: var(--admin-nav-surface);
  color: var(--admin-nav-text);
}

.admin-brand {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: 0 var(--space-2) var(--space-4);
}

.admin-brand .brand {
  font-family: var(--font-heading);
  font-size: var(--font-size-lead);
}

.admin-badge {
  font-size: var(--font-size-meta);
  color: var(--admin-nav-text-muted);
  letter-spacing: .04em;
  text-transform: uppercase;
}

.admin-nav {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.admin-nav-group-title {
  margin: var(--space-5) var(--space-2) var(--space-1);
  color: var(--admin-nav-text-muted);
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-semibold);
  letter-spacing: .08em;
  text-transform: uppercase;
}

.admin-nav-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-control);
  color: inherit;
  text-decoration: none;
}

.admin-nav-link:hover {
  background: var(--admin-nav-hover);
}

.admin-nav-link.active {
  background: var(--admin-nav-surface-active);
  font-weight: var(--font-weight-semibold);
}

/* The dot marker from the handoff shell. Decorative; the label carries the meaning. */
.admin-nav-mark {
  width: 8px;
  height: 8px;
  flex: none;
  border: var(--border-hairline) solid currentColor;
  border-radius: var(--radius-pill);
  opacity: .7;
}

.admin-nav-link.active .admin-nav-mark {
  background: var(--admin-nav-text);
  opacity: 1;
}

/* Sections whose screens are not built yet. Inert, and visibly so - not links, and they never
   pretend to be one. Marked aria-disabled so assistive tech announces the state too. */
.admin-nav-link.is-placeholder {
  color: var(--admin-nav-text-muted);
  cursor: default;
}

.admin-sidebar-foot {
  margin-top: auto;
  padding: var(--space-4) var(--space-2) 0;
  border-top: var(--border-hairline) solid var(--admin-nav-divider);
  color: var(--admin-nav-text-muted);
  font-size: var(--font-size-meta);
  line-height: var(--line-height-body);
}

.admin-workspace {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.admin-topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  min-height: 68px;
  padding: var(--space-3) var(--space-7);
  background: var(--surface-card);
  border-bottom: var(--border-hairline) solid var(--border-default);
}

.admin-topbar-title {
  font-family: var(--font-heading);
  font-size: var(--font-size-lead);
}

.admin-user {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.admin-user-identity {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.admin-user-lines {
  text-align: right;
  line-height: var(--line-height-tight);
}

.admin-role {
  color: var(--text-muted);
  font-size: var(--font-size-meta);
}

.admin-avatar {
  width: 44px;
  height: 44px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  background: var(--avatar-bg);
  color: var(--avatar-text);
  font-weight: var(--font-weight-semibold);
}

.admin-main {
  /* FULL WIDTH on purpose. The admin workspace is desktop-only (§ admin-shell handoff), and its dense tables
     (the 9-column registrations list, the amendment ledger) need the whole desktop: a 72rem cap left a wide
     screen mostly empty AND squeezed the table until adjacent column headers collided
     ("SUBMITTED PARTICIPANTS"/"AMOUNT"). The page padding still gives comfortable side margins, and the tables
     scroll inside their own .admin-table-wrap if a viewport is ever narrower than they need - so removing the
     cap never introduces a page-level horizontal scrollbar. */
  width: 100%;
  padding: var(--space-7) var(--space-7) var(--space-10);
}

/* ---- Admin: Events list + editor (Stage 6 slice 2) ---------------------- */

/* Stacked cards in an admin workspace (the editor's form sections) get breathing room. */
.admin-main .card + .card,
.admin-main .admin-error-summary + .card,
.admin-main .admin-table-wrap {
  margin-top: var(--space-5);
}

/* The count line above the email-template library table (D-60, slice E2c). */
.admin-results-summary {
  margin: var(--space-4) 0 var(--space-3);
  color: var(--text-secondary);
}

.admin-page-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}

.admin-page-heading h1 {
  font-family: var(--font-heading);
  font-size: var(--font-size-h1);
}

.admin-eyebrow {
  color: var(--text-muted);
  font-size: var(--font-size-meta);
  letter-spacing: .06em;
  text-transform: uppercase;
}

.admin-subheading {
  color: var(--text-secondary);
  margin-top: var(--space-1);
}

.admin-heading-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.admin-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

.admin-search {
  flex: 1;
  min-width: 20rem;
}

.admin-filter {
  width: 14rem;
}

/* The list table. It scrolls inside its own wrapper so the page body never scrolls sideways. */
.admin-table-wrap {
  overflow-x: auto;
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-panel);
}

.admin-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface-card);
}

.admin-table th,
.admin-table td {
  /* A slightly tighter horizontal rhythm than the original space-4: the admin tables carry six to nine
     columns, and the extra room this returns is what lets the widest of them fit the workspace instead of
     overflowing it (the same cut-off the registrations list showed). */
  padding: var(--space-3) var(--space-3);
  text-align: left;
  vertical-align: top;
}

/* Long unbreakable strings - an email like name+tag@host, an event code - must WRAP rather than force
   their column wide and push the table past the workspace. The secondary cell line is where they live. */
.admin-table .admin-cell-sub {
  overflow-wrap: anywhere;
}

.admin-table thead th {
  background: var(--surface-subtle);
  border-bottom: var(--border-hairline) solid var(--border-strong);
  color: var(--text-secondary);
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-semibold);
  letter-spacing: .04em;
  text-transform: uppercase;
}

.admin-table tbody tr {
  border-top: var(--border-hairline) solid var(--border-default);
}

.admin-table tbody tr:hover {
  background: var(--surface-page);
}

.admin-event-name {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.admin-event-name-vi {
  color: var(--text-secondary);
  font-size: var(--font-size-secondary);
}

.admin-event-code {
  color: var(--text-muted);
  font-size: var(--font-size-meta);
}

.admin-meta {
  color: var(--text-muted);
  font-size: var(--font-size-meta);
}

.admin-row-actions {
  text-align: right;
  white-space: nowrap;
}

/* The REGISTRATIONS list carries nine columns - one more than any other admin table - and its Event and
   contact cells hold long, sometimes unbreakable strings (an event title, an email like name+tag@host).
   Left to size themselves those columns pushed the table past the workspace, so the whole table (and, once
   the shell's old 1280 floor met a scaled display, the whole page) scrolled sideways and hid Status and the
   Open action off the right edge.

   A FIXED layout with explicit column widths makes the columns SHARE the available width and wrap instead of
   overflowing, so the table always fits the workspace and never needs a horizontal scrollbar. Tighter cell
   padding buys the width back that the ninth column needs. The wrapper's overflow-x stays as a last-resort
   fallback for an extreme narrow window; with fixed layout it should never actually engage. */
/* A minimal horizontal-scroll container (no border/frame of its own, unlike .admin-table-wrap) for a wide
   element that already sits inside a card - the pricing matrix, whose accommodation × concession columns can
   exceed the workspace. It keeps that overflow inside the card instead of scrolling the whole page. */
.admin-scroll-x {
  overflow-x: auto;
}

.admin-table-registrations {
  table-layout: fixed;
}

.admin-table-registrations th,
.admin-table-registrations td {
  padding: var(--space-2) var(--space-3);
}

.admin-table-registrations .col-ref { width: 12%; }
.admin-table-registrations .col-contact { width: 16%; }
.admin-table-registrations .col-event { width: 15%; }
.admin-table-registrations .col-date { width: 9%; }
.admin-table-registrations .col-count { width: 8%; }
.admin-table-registrations .col-amount { width: 9%; }
.admin-table-registrations .col-payment { width: 12%; }
.admin-table-registrations .col-status { width: 10%; }
.admin-table-registrations .col-actions { width: 9%; }

.admin-no-results {
  text-align: center;
  color: var(--text-muted);
  padding: var(--space-8) var(--space-4);
}

.admin-empty {
  display: grid;
  justify-items: center;
  gap: var(--space-4);
  text-align: center;
  padding: var(--space-7) var(--space-4);
}

.admin-empty h2 {
  font-family: var(--font-heading);
  font-size: var(--font-size-h3);
}

/* Chips and status badges. */
.admin-type-chip {
  display: inline-flex;
  align-items: center;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--surface-subtle);
  color: var(--text-secondary);
  font-size: var(--font-size-meta);
}

.badge {
  display: inline-flex;
  align-items: center;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-semibold);
  white-space: nowrap;
}

.badge-open {
  color: var(--success);
  background: var(--success-bg);
}

.badge-draft {
  color: var(--warning);
  background: var(--warning-bg);
}

.badge-closed,
.badge-cancelled {
  color: var(--danger);
  background: var(--danger-bg);
}

.badge-info {
  color: var(--text-secondary);
  background: var(--surface-subtle);
}

/* The editor: section headings, the two-column grids, and the bilingual pair. */
.admin-section-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}

.admin-section-heading h2 {
  font-family: var(--font-heading);
  font-size: var(--font-size-h3);
}

.admin-section-heading p {
  color: var(--text-secondary);
  margin-top: var(--space-1);
}

.admin-grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}

.field-gap {
  margin-top: var(--space-5);
}

.field-optional {
  color: var(--text-muted);
  font-weight: var(--font-weight-regular);
}

.field-error {
  color: var(--danger);
  font-size: var(--font-size-meta);
  margin-top: var(--space-1);
}

.admin-language-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-5);
  border-top: var(--border-hairline) solid var(--border-default);
  padding-top: var(--space-5);
}

.admin-language-column {
  display: flex;
  flex-direction: column;
}

.admin-language-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-2);
  color: var(--text-secondary);
  font-size: var(--font-size-meta);
  letter-spacing: .04em;
  text-transform: uppercase;
}

.admin-language-tag {
  padding: 2px var(--space-2);
  border-radius: var(--radius-control);
  background: var(--surface-subtle);
  color: var(--text-muted);
  font-size: var(--font-size-meta);
}

/* The theme picker: three swatch buttons, one selected. */
.admin-theme-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3);
}

.admin-theme-option {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap-min);
  padding: var(--space-3);
  border: var(--border-emphasis) solid var(--border-default);
  border-radius: var(--radius-panel);
  background: var(--surface-card);
  color: var(--text-primary);
  font-size: var(--font-size-control);
  text-align: left;
}

.admin-theme-option:hover {
  border-color: var(--border-strong);
}

.admin-theme-option.selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 var(--border-hairline) var(--accent);
}

.admin-theme-swatch {
  width: 22px;
  height: 22px;
  flex: none;
  border-radius: var(--radius-pill);
  border: var(--border-hairline) solid var(--border-strong);
}

.admin-swatch-warmmonastic {
  background: var(--admin-swatch-warm-monastic);
}

.admin-swatch-quietpaper {
  background: var(--admin-swatch-quiet-paper);
}

.admin-swatch-structuredcalm {
  background: var(--admin-swatch-structured-calm);
}

/* The validation summary at the top of the editor, and the sticky-free footer actions. */
.admin-error-summary {
  padding: var(--space-4);
  margin-bottom: var(--space-5);
  border: var(--border-hairline) solid var(--danger);
  border-radius: var(--radius-panel);
  background: var(--danger-bg);
  color: var(--text-primary);
}

.admin-error-summary ul {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-6);
}

.admin-editor-foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-6);
}

/* ---- Event editor: pricing matrix, capacity, readiness panel (Stage 6 slice 3) ---- */

/* Two columns on desktop: the editor form, and a sticky publish-readiness panel (handoff §8). */
.admin-editor-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 20rem;
  gap: var(--space-5);
  align-items: start;
}

.admin-editor-main {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  min-width: 0;
}

.admin-editor-side {
  position: sticky;
  top: var(--space-5);
}

.admin-section-heading-title {
  font-family: var(--font-heading);
  font-size: var(--font-size-h3);
  margin-bottom: var(--space-4);
}

.admin-grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4);
}

.admin-grid-narrow {
  max-width: 22rem;
}

/* Pricing mode: a two-option segmented radio group. */
.admin-segmented {
  display: inline-flex;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-2);
  overflow: hidden;
}

.admin-segment {
  /* The 44px target floor, stated rather than left to padding to arrive at. It landed at 43px -
     one pixel under, which is exactly the kind of miss that padding arithmetic produces and nobody
     notices. Found by measuring the real editor in a browser (Stage 6 slice 4). */
  min-height: 44px;
  padding: var(--space-2) var(--space-4);
  background: var(--surface);
  border: none;
  border-right: 1px solid var(--border-strong);
  cursor: pointer;
  font: inherit;
  color: var(--text-secondary);
}

.admin-segment:last-child { border-right: none; }

.admin-segment.selected {
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
}

.admin-toggle {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  cursor: pointer;
}

.admin-toggle input { margin-top: 0.3rem; }

/* The price matrix: age-tier rows × dimension columns; each cell is one price rule. */
.admin-price-matrix th[scope="row"] { font-weight: 600; text-align: left; }
.admin-price-matrix .field-help { display: block; font-weight: 400; }

.admin-money-input {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.admin-money-input input { max-width: 8rem; }

.admin-readiness {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.admin-readiness li {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}

.admin-readiness li > span { font-weight: 600; }
.admin-readiness li.is-ready > span { color: var(--success); }
.admin-readiness li.is-pending > span { color: var(--warning); }

/* A FAILED BLOCKER: the reason the event cannot go live. Distinct from is-pending (an unmet
   advisory, which does not stop anything) in colour, in glyph, AND in the word the row carries
   - "Required" against "Recommended". An administrator staring at a panel of amber, trying to
   work out which row is the one holding up their retreat, is exactly the failure this avoids;
   colour alone would leave a red/amber-blind reader doing precisely that. */
.admin-readiness li.is-blocked > span { color: var(--danger); }

/* The severity word. Small-capsy in tone but NOT in size: it sits on the same 16px floor as
   everything else, because it carries meaning rather than decoration. */
.admin-readiness-tag {
  display: inline-block;
  margin-left: var(--space-2);
  color: var(--text-muted);
}

.admin-readiness-meta {
  display: block;
  color: var(--text-secondary);
}

/* A published event's frozen configuration, read-only. A definition list because that is what it
   is - a set of terms and their values - and because a screen reader then announces the pairing
   instead of two unrelated runs of text. */
.admin-summary {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  margin: 0;
}

.admin-summary > div {
  display: grid;
  grid-template-columns: minmax(12rem, 18rem) 1fr;
  gap: var(--space-4);
  align-items: baseline;
  padding-bottom: var(--space-3);
  border-bottom: var(--border-hairline) solid var(--border-default);
}

.admin-summary > div:last-child { border-bottom: none; padding-bottom: 0; }
.admin-summary dt { color: var(--text-secondary); }
.admin-summary dd { margin: 0; color: var(--text-primary); }

/* ---- Hero photograph: the editor's own view, and the draft preview (Stage 6 slice 5B) ---- */

/* The administrator's view of the draft's photograph. Deliberately smaller than the public .hero: it
   is a "yes, that is the right picture" check, not the picture itself - the preview page is where it
   is seen at its real size, in the event's own theme. */
.admin-hero-preview {
  display: block;
  width: 100%;
  max-width: 30rem;
  height: auto;
  max-height: 14rem;
  object-fit: cover;
  border-radius: var(--radius-panel);
  border: var(--border-hairline) solid var(--border-default);
}

.admin-hero-none {
  margin: 0;
  padding: var(--space-5);
  color: var(--text-secondary);
  background: var(--surface-subtle);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-panel);
}

/* The preview's own bar. It sits OUTSIDE the themed container, so it reads as the application talking
   rather than as part of the page being previewed - a label that took the previewed event's theme
   would be indistinguishable from the content it is labelling. */
.admin-preview-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: flex-start;
  justify-content: space-between;
  padding: var(--space-4) var(--space-5);
  background: var(--surface-subtle);
  border-bottom: var(--border-hairline) solid var(--border-default);
}

.admin-preview-title {
  margin: 0;
  font-weight: var(--font-weight-semibold);
  color: var(--text-primary);
}

.admin-preview-note {
  margin: 0;
  color: var(--text-secondary);
  max-width: 60ch;
}

/* Who a cancellation would affect, inside the confirmation dialog. */
.admin-impact-list {
  margin: var(--space-3) 0;
  padding-left: var(--space-6);
}

.admin-impact-list li { margin-bottom: var(--space-2); }

/* ---- What an event asks (Stage 6 slice 6) -------------------------------
   A tick list of the question bank, not a form builder (ADR-0012). A ticked row is emphasised
   because the set of ticked rows IS the configuration - an administrator scanning this needs to
   see what the event asks, not what it could ask. */

.admin-asks {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* NO BORDER, NO PADDING, NO BACKGROUND HERE. `.choice` already draws the box - it is the same
   tickable row the public forms use - and giving the row its own as well nests one outlined box
   inside another. The row is only a layout track: the label, then its controls.

   THE CONTROLS WRAP ONTO THEIR OWN LINE, and that is measured rather than assumed. The editor's main
   column is ~580px; "Must be answered" plus two 44px reorder buttons (the tap-target floor, which is
   not negotiable) needs ~320px of it and cannot shrink. Side by side, that left the prompt 257px and
   broke "Have you been to Nhập Lưu before?" into a column two words wide - on precisely the rows an
   administrator has chosen, which are the ones they most need to read. */
.admin-asks-row {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--space-2);
}

/* The prompt takes the room; the controls take what they need. Without this the label is a flex item
   at its content width and the prompt wraps into a column two words wide, which is unreadable at
   exactly the moment it matters - a ticked row is one the administrator has chosen. min-width:0 is
   the half that is easy to miss: a flex item will not shrink below its content's intrinsic width
   without it, so the label would push the controls off the row instead of wrapping inside it. */
.admin-asks-row > .choice {
  flex: 1;
  min-width: 0;
}

.admin-asks-row.is-asked > .choice {
  border-color: var(--border-strong);
  background: var(--surface-subtle);
}

.admin-asks-prompt {
  display: block;
  font-weight: 600;
}

.admin-asks-meta {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--font-size-meta);
  color: var(--text-muted);
}

.admin-asks-tag {
  margin-left: var(--space-2);
  padding: 0 var(--space-2);
  border-radius: var(--radius-control);
  background: var(--surface-subtle);
  color: var(--text-muted);
}

.admin-asks-controls {
  display: flex;
  flex-basis: 100%;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3);
  padding-right: var(--space-2);
}

/* The "must be answered" tick sits INSIDE the row's controls, beside the reorder buttons, so it must
   not draw the full bordered box `.choice` gives a standalone question. It keeps the tap target. */
.choice-inline {
  border: 0;
  background: none;
  padding: 0 var(--space-2);
  white-space: nowrap;
}

.btn-tiny {
  padding: 0 var(--space-2);
  line-height: 1.6;
}

.admin-asks-foot {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}

/* .btn is a COLUMN flex (it stacks an English label over a Vietnamese one), so a squeezed button
   breaks its own words rather than staying on one line. These are English-only admin chrome, so
   there is no second line to make room for - the button keeps its width and the help text beside it
   takes the slack. */
.admin-asks-foot > .btn { flex-shrink: 0; }
.admin-asks-foot > .field-help { margin: 0; }

/* The admin workspace is desktop-only, so the editor's two-column grids never need to collapse -
   but a narrow container (the reviewer frame) should still not force a sideways scroll of the body. */
@container app (max-width: 60rem) {
  .admin-grid-2,
  .admin-language-pair,
  .admin-theme-grid,
  .admin-grid-3 {
    grid-template-columns: 1fr;
  }

  .admin-editor-layout {
    grid-template-columns: 1fr;
  }
}

/* ---- The entry page -----------------------------------------------------
   The only page a stranger sees. One clear action; no event selector, no language selector, no
   account creation (locked, brief 8). */

.hero {
  display: block;
  width: 100%;
  height: auto;
  max-height: 22rem;
  object-fit: cover;
  border-radius: var(--radius-panel);
}

/* NULL until an administrator uploads a photograph (D-18, D-20; OQ-20 is unanswered). It has to look
   DELIBERATE, not broken: a broken image on the first screen a stranger sees is worse than no image
   at all. A calm block of the monastery's own surface colour reads as design. */
.hero-empty {
  height: 12rem;
  background: var(--surface-subtle);
  border: var(--border-hairline) solid var(--border-default);
}

.kicker {
  color: var(--text-secondary);
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* A narrow column for sign-in. A long line of text is hard to track back to the start of, and the
   people who find that hardest are the people this application is for. */
.page-narrow {
  max-width: 34rem;
}

.signin-event {
  font-family: var(--font-heading);
  font-size: var(--font-size-h3);
}

.signin-email {
  color: var(--text-secondary);
}

/* THE OTP SCREEN (mobile registration handoff). Compact by construction: a "sent to" row, a spaced
   code field, and Resend/Continue side by side. All theme-token based, so it wears the event theme. */

/* The address and its "use a different email" switch on ONE quiet row, instead of a paragraph plus a
   separate button below. overflow-wrap keeps a long address from forcing horizontal scroll at 390. */
.inline-email {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-control);
  background: var(--surface-subtle);
}

.inline-email strong {
  overflow-wrap: anywhere;
}

/* The switch is a plain link (the person is not signed in yet); pushed to the end of the row. */
.inline-email-switch {
  margin-left: auto;
  color: var(--accent);
  font-size: var(--font-size-meta);
}

/* SIX SINGLE-DIGIT BOXES (mobile handoff), evenly across the width. They stay six across even at 390 -
   six boxes plus five small gaps fit a phone comfortably - so there is no theme- or width-specific case. */
.otp-code {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--space-2);
}

/* Each box borrows the field look (surface, border, radius, token colours) but is centred, larger and
   taller than the touch floor, with tabular figures so every digit sits the same. min-width:0 lets the
   six share the row without forcing overflow. */
.otp-box {
  min-width: 0;
  height: 3.25rem;
  padding: 0;
  text-align: center;
  background: var(--surface-card);
  color: var(--text-primary);
  border: var(--border-hairline) solid var(--border-control);
  border-radius: var(--radius-control);
  font-family: var(--font-body);
  font-size: var(--font-size-h3);
  font-variant-numeric: tabular-nums;
}

.otp-box:focus-visible {
  outline: var(--border-emphasis) solid var(--accent);
  outline-offset: 1px;
}

/* The code field and Continue stack with the wizard's ordinary rhythm; Continue is last, as everywhere.
   Resend sits in its own quiet form below (spaced by the card's stack-loose), never competing with it. */
.signin-verify {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* ---- The wizard ---------------------------------------------------------
   "Step 2 of 8" carries the meaning; the bar is decorative and aria-hidden. A bar alone tells a
   first-time user nothing about how much is left, and tells a screen-reader user nothing at all. */

.progress {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.progress-label {
  font-weight: var(--font-weight-semibold);
}

.progress-track {
  height: var(--space-2);
  background: var(--surface-subtle);
  border: var(--border-hairline) solid var(--border-strong);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  background: var(--accent);
}

.progress-note {
  color: var(--text-secondary);
  font-size: var(--font-size-meta);
}

/* ---- The wizard shell and the Structured Calm left rail (D-41) ----------
   The rail is ALWAYS in the DOM. By default it is hidden and the shell is a plain block, so Warm
   Monastic and Quiet Paper - and every theme on mobile - render exactly as before: one column, the
   shared top counter. Only Structured Calm at >= 768px turns the shell into rail + content and hides
   the now-redundant counter. This is the one place the three themes differ in markup, not tokens. */
.rail { display: none; }

.rail-title {
  margin: 0 0 var(--space-4);
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-semibold);
  color: var(--text-muted);
}

.rail-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.rail-step {
  display: grid;
  grid-template-columns: var(--space-8) 1fr;
  align-items: start;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-control);
  border-left: 3px solid transparent;
}

.rail-marker {
  width: var(--space-8);
  height: var(--space-8);
  border-radius: var(--radius-pill);
  display: grid;
  place-items: center;
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-semibold);
  border: var(--border-hairline) solid var(--border-strong);
  color: var(--text-muted);
  background: var(--surface-page);
}

/* Done: filled accent with a tick. Current: accent ring + tinted row + accent edge. Upcoming: plain. */
.rail-step.done .rail-marker {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-on-accent);
}

.rail-step.current {
  background: var(--surface-subtle);
  border-left-color: var(--accent);
}

.rail-step.current .rail-marker {
  border-color: var(--accent);
  border-width: 2px;
  color: var(--accent);
}

.rail-step.current .rail-label .en { font-weight: var(--font-weight-semibold); }
.rail-step.upcoming .rail-label .en { color: var(--text-muted); }

.rail-label .en { font-size: var(--font-size-meta); }

.rail-current-note {
  margin: var(--space-4) 0 0;
  padding-top: var(--space-4);
  border-top: var(--border-hairline) solid var(--border-default);
  font-size: var(--font-size-meta);
  color: var(--text-muted);
}

/* A THEME TOKEN SCOPE that adds no box of its own. WizardStep wraps the shell in one so the event's
   data-theme sits above .wizard-shell (the rail rules need it there), without inserting a layout element
   between the shell and the page container - `display: contents` makes the wrapper vanish from layout
   while the attribute and its inherited custom properties still apply to everything inside. */
.theme-scope { display: contents; }

/* Structured Calm, desktop/tablet only: the rail appears and the shell becomes two columns. A
   CONTAINER query, never a width @media - a width media does not fire inside the preview's device
   frame (see the container-query discipline at the top of this file). 47rem clears a 768px viewport
   even after the container's own padding and any scrollbar shrink its inline-size. */
@container app (min-width: 47rem) {
  [data-theme="structured-calm"] .wizard-shell {
    max-width: 75rem;
    margin: 0 auto;
    display: grid;
    grid-template-columns: 19rem minmax(0, 1fr);
    gap: var(--space-8);
    align-items: start;
  }

  [data-theme="structured-calm"] .wizard-shell .page {
    max-width: none;
    margin: 0;
    /* The content column is now a CONTAINER of its own (R1.1). The rail takes ~19rem, so this column is
       much narrower than the viewport - and the pricing tiers must respond to the space they actually
       have, not to the window. A distinctly named container so it never collides with `app`; nothing
       else queries it. */
    container-type: inline-size;
    container-name: wizardcontent;
  }

  [data-theme="structured-calm"] .rail {
    display: block;
    position: sticky;
    top: var(--space-8);
    background: var(--surface-card);
    border: var(--border-hairline) solid var(--border-default);
    border-radius: var(--radius-panel);
    padding: var(--space-5) var(--space-4);
  }

  /* The rail now carries the step position, so the top counter would only repeat it. */
  [data-theme="structured-calm"] .wizard-shell .progress { display: none; }
}

/* Structured Calm's content column, once the rail has narrowed it, shows COMPACT PRICING ROWS rather
   than a cramped two-plus-one card grid (R1.1). Measured against the CONTENT COLUMN (the wizardcontent
   container), not the viewport, so it reacts to the room the rail actually leaves: at ~768px the column
   is ~25rem and gets rows; at ~1024px and up it is wider and keeps the cards. This is the SAME `.tier`
   markup as the mobile rows - restyled, never duplicated (D5). Warm Monastic and Quiet Paper have no
   wizardcontent container, so they are untouched at every width. */
@container wizardcontent (max-width: 30rem) {
  /* Theme-scoped (the wizardcontent container only exists under Structured Calm anyway) so these win the
     cascade over the base `.tier` card rules, which sit later in the file at equal specificity. */
  [data-theme="structured-calm"] .tiers { grid-template-columns: 1fr; }

  [data-theme="structured-calm"] .tier {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1) var(--space-3);
    text-align: left;
    padding: var(--space-3) var(--space-4);
  }

  [data-theme="structured-calm"] .tier-price {
    margin-top: 0;
    margin-left: auto;
    font-size: var(--font-size-body);
  }
}

/* Continue is the primary action and comes LAST in the reading and tab order, after Back. On a
   phone the buttons stack full width, and Continue sits at the bottom where the thumb is. */
.wizard-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding-top: var(--space-6);
  border-top: var(--border-hairline) solid var(--border-default);
}

.wizard-continue {
  margin-left: auto;
}

/* ---- The three pricing-tier cards (approved prototype, adopted without change) ---- */

.tiers {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4);
}

/* Centred, plain-bordered, exactly as the approved prototype draws them. No coloured left rule: the
   prototype has none, the card already names its tier in words, and nothing depended on the colour. */
.tier {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-5);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-panel);
  background: var(--surface-card);
  text-align: center;
}

.tier-name {
  font-weight: var(--font-weight-semibold);
}

.tier-range {
  color: var(--text-secondary);
  font-size: var(--font-size-meta);
}

.tier-price {
  margin-top: var(--space-1);
  font-size: var(--font-size-lead);
  font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums;
}

/* ---- Participant rows: checkbox, avatar, name, tier pill, Edit ---- */

.people {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.person {
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-panel);
  background: var(--surface-card);
  overflow: hidden;
}

.person-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
}

/* The 24px box inside a 44px target. The box may be small; the thing you have to hit may not be. */
.check-wrap {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  height: var(--tap-min);
}

.avatar {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-8);
  height: var(--space-8);
  border-radius: var(--radius-pill);
  background: var(--avatar-bg);
  color: var(--avatar-text);
  font-weight: var(--font-weight-semibold);
  font-size: var(--font-size-meta);
}

/* The label IS the row's clickable area: clicking a person's name ticks them. */
.person-id {
  flex: 1 1 12rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  cursor: pointer;
}

.person-name {
  display: block;
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-medium);
}

.person-born {
  display: block;
  color: var(--text-secondary);
  font-size: var(--font-size-meta);
}

.person-born .en,
.person-born .vi {
  display: block;
}

.tag-new {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 0 var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--text-on-accent);
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-semibold);
}

.person-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-left: auto;
}

/* Expands IN PLACE, one at a time. */
.person-editor {
  padding: var(--space-5);
  border-top: var(--border-hairline) solid var(--border-default);
  background: var(--surface-page);
}

.person-confirm {
  padding: var(--space-4);
  border-top: var(--border-hairline) solid var(--border-default);
}

/* R2d: birth month + year as two controls on one line, with a shared help line beneath.
   Month is the wider column (its names are longer than a four-digit year). It stacks to one
   column when the editor is too narrow, so neither control is ever squeezed below a tap target. */
.field-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.month-year {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: var(--space-3);
}

@container (max-width: 22rem) {
  .month-year { grid-template-columns: 1fr; }
}

/* Dashed, because it adds a thing that is not there yet. */
.btn-add {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-4);
  background: transparent;
  border: var(--border-emphasis) dashed var(--border-control);
  border-radius: var(--radius-panel);
  color: var(--accent);
  font-family: var(--font-body);
  font-size: var(--font-size-control);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
}

.btn-add:hover {
  background: var(--surface-subtle);
  border-color: var(--accent);
}

.btn-add-plus {
  font-size: var(--font-size-h3);
  line-height: 1;
}

.btn-add .bilingual > .vi {
  color: inherit;
}

/* A panel inside a participant's card: their emergency contact, their health. Keeping the
   information visually attached to the person it describes is the whole reason Step 3 is shaped
   this way. */
.subpanel {
  padding: var(--space-5);
  background: var(--surface-page);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-panel);
}

.subpanel > h3 {
  margin-bottom: var(--space-4);
}

/* ---- Cost summary ------------------------------------------------------- */

.cost {
  width: 100%;
  border-collapse: collapse;
}

.cost th,
.cost td {
  padding: var(--space-3) 0;
  text-align: left;
  border-bottom: var(--border-hairline) solid var(--border-default);
  font-weight: var(--font-weight-regular);
  vertical-align: top;
}

.cost-name {
  display: block;
}

.cost-tier {
  display: block;
  color: var(--text-secondary);
  font-size: var(--font-size-meta);
}

.cost-amount {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Only the AMOUNT CELLS resist wrapping (so "A$370.00" never breaks); the column HEADING may wrap.
   Before this, the header cell "For the whole retreat · Cho cả khoá tu" was held on one line too, which
   forced the amount column wide and squeezed the tier column until "16 years and above" broke one word
   per line with tall gaps. The heading now wraps and the tier names get their room back. */
td.cost-amount {
  white-space: nowrap;
}

/* Give the "Who" column the room it needs so the tier name + age band read as a block, not a sliver. */
.cost th[scope="row"] {
  width: 62%;
}

.cost-total th,
.cost-total td {
  padding-top: var(--space-4);
  border-bottom: 0;
  font-size: var(--font-size-lead);
  font-weight: var(--font-weight-semibold);
}

/* ---- Review ------------------------------------------------------------- */

.review {
  margin: 0 0 var(--space-5);
}

.review-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-3) 0;
  border-bottom: var(--border-hairline) solid var(--border-default);
}

.review-label {
  flex: 1 1 14rem;
  color: var(--text-secondary);
  margin: 0;
}

.review-value {
  flex: 2 1 16rem;
  margin: 0;
  color: var(--text-primary);
}

/* ---- Payment ------------------------------------------------------------
   Square's SDK mounts its own iframe into this container. Our markup contains no card fields and
   never will (ADR-0004). We style the CONTAINER; Square owns what is inside it. */

.card-element {
  min-height: calc(var(--tap-min) * 2);
  padding: var(--space-5);
  background: var(--surface-page);
  border: var(--border-emphasis) dashed var(--border-strong);
  border-radius: var(--radius-panel);
}

.card-element-placeholder {
  color: var(--text-secondary);
}

.card-element-placeholder .en,
.card-element-placeholder .vi {
  display: block;
}

.card-element-placeholder .vi {
  font-size: var(--font-size-secondary);
}

/* The in-flight processing line (R1, D2). It surfaces the step's existing working state in words; it
   is a status, not an outcome, so it stays quiet - the Pending/Confirmed/Failed pages own the loud
   colours. It sits at body size, never the meta floor: a person on a slow connection must read it. */
.payment-processing {
  color: var(--text-secondary);
  font-weight: var(--font-weight-medium);
}

/* ---- Digital wallets (R5) ----------------------------------------------
   Apple Pay / Google Pay sit ABOVE the card, which is always present as the fallback. Each button is
   revealed only when the SDK confirms that wallet on this device; the group dims and stops accepting
   input while any attempt (card or wallet) is in flight. The brand buttons carry their own required
   look; only the surrounding lead and divider use theme tokens, so all three event themes adapt with
   no per-theme rules. Blazor renders NO children into the button containers, so it leaves Square's and
   Google's injected DOM alone across re-renders - the same contract the card container already relies on. */
.wallet-methods {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* One attempt in flight: the whole group is visibly and functionally disabled (R5 #8). The JS gesture
   guard refuses a click too; this is the half a person can see. */
.wallet-methods--busy {
  opacity: 0.55;
  pointer-events: none;
}

.wallet-lead {
  margin: 0;
}

/* Apple Pay: Apple's own button presentation. Hidden until the SDK reports Apple Pay available - the
   probe (payments.applePay) does not touch this element, so hiding it before then is safe. */
.apple-pay-button {
  display: none;
  width: 100%;
  min-height: var(--tap-min);
  height: calc(var(--tap-min) + var(--space-2));
  border: 0;
  border-radius: var(--radius-panel);
  cursor: pointer;
  -webkit-appearance: -apple-pay-button;
  appearance: -apple-pay-button;
  -apple-pay-button-type: plain;
  -apple-pay-button-style: black;
}

.wallet-methods.has-apple .apple-pay-button {
  display: block;
}

/* Google Pay: Google injects its branded button into this container via attach(). It stays laid out
   until the SDK has answered (attach needs a real element to render into); once probed, it is hidden
   unless Google Pay came back available. Empty (before injection) it has no height, so it shows nothing. */
.google-pay-button {
  width: 100%;
}

.wallet-methods.wallet-probed:not(.has-google) .google-pay-button {
  display: none;
}

/* "or pay by card", a hairline rule either side. Tokens only, so it reads correctly in every theme. */
.wallet-divider {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--font-size-secondary);
}

.wallet-divider::before,
.wallet-divider::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--border-strong);
}

.wallet-divider span {
  white-space: nowrap;
}

/* ---- Preview devbar -----------------------------------------------------
   Review tooling. Development only. None of this is part of the public interface. */

.devbar {
  padding: var(--space-3) var(--space-4);
  background: var(--text-primary);
  color: var(--surface-card);
}

/* The controls themselves. On desktop they are the flat row the reviewer knows; on a phone this row is
   collapsed behind the toggle below (R1.1) so it never pushes the actual page far below the fold or
   scrolls sideways. */
.devbar-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-4);
}

/* The narrow-width disclosure button. Hidden on desktop, where the controls are always shown. Dev-only
   tooling, so it is English-only like the rest of the devbar - it is never part of the public interface. */
.devbar-toggle {
  display: none;
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-3);
  background: transparent;
  border: var(--border-hairline) solid var(--surface-card);
  border-radius: var(--radius-control);
  color: var(--surface-card);
  font-family: var(--font-body);
  font-size: var(--font-size-control);
  font-weight: var(--font-weight-semibold);
  text-align: left;
  cursor: pointer;
}

.devbar-title {
  font-weight: var(--font-weight-semibold);
}

.devbar-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--font-size-meta);
}

.devbar-select {
  width: auto;
  min-height: var(--tap-min);
}

.devbar-reset {
  color: var(--surface-card);
}

.devbar-note {
  margin-left: auto;
  font-size: var(--font-size-meta);
}

/* Narrow width (R1.1): the reviewer toolbar collapses behind a full-width "Preview controls" button and,
   when expanded, stacks vertically so every control stays inside the viewport and nothing scrolls
   sideways. A container query like every responsive rule here, so it fires for a real phone. */
@container app (max-width: 40rem) {
  .devbar-toggle {
    display: block;
    width: 100%;
  }

  .devbar-controls {
    display: none;
  }

  .devbar-controls.is-open {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    margin-top: var(--space-3);
  }

  .devbar-field,
  .devbar-select {
    width: 100%;
  }

  .devbar-note {
    margin-left: 0;
  }
}

/* ---- The device frame (review tool) -------------------------------------
   CONTAINER queries, not media queries. They measure .viewport, so the SAME rules drive a real
   phone and this dropdown: one responsive layout, checked two ways.

   The width is EXPLICIT, never max-width. max-width can only SHRINK, so choosing "Desktop 1180" in
   a 900px window would silently do nothing while the readout claimed otherwise. We shipped exactly
   that bug in the HTML prototype and it took a screenshot from the product owner to find it. The
   shell scrolls horizontally instead, and nothing is ever clipped. */

.viewport-shell {
  width: 100%;
  overflow-x: auto;
}

.viewport {
  container-type: inline-size;
  container-name: app;
  width: 100%;
  margin: 0 auto;
}

.viewport.is-framed {
  width: var(--frame-width);
  border-left: var(--border-hairline) solid var(--border-strong);
  border-right: var(--border-hairline) solid var(--border-strong);
}

/* ---- Narrow screens -----------------------------------------------------
   THE ONLY responsive rules in the application. One fluid layout, not a separate mobile design; the
   handoff's 1180 and 390 are reference widths, not breakpoints.

   These are CONTAINER queries against <main>, so a real phone and the reviewer's device frame obey
   exactly the same rules. There is no @media twin to fall out of step with. */

@container app (max-width: 60rem) {
  /* The three tier cards go to two-up before they go to one-up: three narrow columns turn the price
     into a wrapped stack of digits long before the screen is actually a phone. */
  .tiers {
    grid-template-columns: repeat(2, 1fr);
  }
}

@container app (max-width: 40rem) {
  .page {
    padding: var(--space-5) var(--space-4) var(--space-9);
  }

  .btn {
    width: 100%;
  }

  .wizard-continue {
    margin-left: 0;
  }

  .review-row {
    flex-direction: column;
    gap: var(--space-1);
  }

  /* Size the label and value to their CONTENT once the row is a column (R1.2). Their desktop flex
     bases - 14rem and 16rem - are WIDTHS for the side-by-side row; in a column the main axis is
     vertical, so those bases became row HEIGHTS (224px and 256px) and opened a tall blank gap between
     every label and its value. The entry page's Dates/Location/Who-it-is-for list was the worst of it. */
  .review-label,
  .review-value {
    flex: 0 0 auto;
  }

  /* The participant row restacks as a GRID, not by reordering flex items. The person's name must
     follow the checkbox and the avatar; a flex `order` hack put the name BELOW the Edit button, and
     a long tier pill squeezed it into a sliver. Both were caught in a browser at 390px. */
  .person-row {
    display: grid;
    grid-template-columns: auto auto 1fr;
    grid-template-areas:
      "check avatar name"
      "acts  acts   acts";
    align-items: center;
  }

  .check-wrap { grid-area: check; }
  .avatar { grid-area: avatar; }
  .person-id { grid-area: name; }

  .person-actions {
    grid-area: acts;
    margin-left: 0;
    justify-content: space-between;
  }

  .tiers {
    grid-template-columns: 1fr;
  }

  .grid-2 {
    grid-template-columns: 1fr;
  }

  /* ---- Mobile compaction (Mobile Registration Feedback R1) --------------
     Everything below reduces VERTICAL SPACE at phone width only. It lives inside this one container
     query, so desktop and tablet keep their readable spacing untouched (desktop protection, D4/D5).
     Nothing here shrinks a type size, a touch target or a weight - those floors are held by tokens
     and are the same on a phone as on a desktop. */

  /* The single biggest saving: the loose 32px rhythm between major sections becomes 16px (R1.1, tightened
     from 20px). Applied to the shared stack utility, so it also closes the gap between the progress block
     and the page heading, and it compacts the deeply-nested Step-3 cards - one change, every step. */
  .stack-loose { gap: var(--space-4); }

  /* A shorter hero, so the event name and the current action are reachable without a long scroll past
     a full-height photograph. Still a real photograph, still object-fit: cover. */
  .hero { max-height: 12rem; }
  .hero-empty { height: 8rem; }

  /* The repeated monastery identity band shrinks its padding (its one identity line and subtitle stay
     - the brand is shown once, never duplicated). */
  .public-header { padding: var(--space-3) var(--space-4); }

  /* Panels lose inner padding; the border and surface still read as a card (R1.1: participant cards and
     sub-panels down to 12px). The Square container keeps more room - its iframe needs it. */
  .card { padding: var(--space-3); }
  .subpanel,
  .person-editor { padding: var(--space-3); }
  .card-element { padding: var(--space-4); }

  /* Step-3 density (R1.1): tighten the headings and bring "Required · Bắt buộc" up against its label,
     without moving any of the health, stay or emergency-contact rules or shrinking a floor. */
  .card-title { margin-bottom: var(--space-3); }
  .subpanel > h3 { margin-bottom: var(--space-2); }
  .section-header { padding-bottom: var(--space-3); margin-bottom: var(--space-3); }
  .field-label { gap: var(--space-1) var(--space-2); }

  /* Pricing tiers become COMPACT ROWS on mobile (D5): the tier name and age band sit on the left and
     the price is pushed to the right, instead of a tall centred card. One semantic structure, one set
     of markup - the desktop cards and these rows are the same .tier restyled by this container query,
     never duplicated. Warm Monastic and Quiet Paper keep their desktop cards; Structured Calm keeps
     its approved desktop/tablet structure; all three use these rows at phone width. */
  .tier {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1) var(--space-3);
    text-align: left;
    padding: var(--space-3) var(--space-4);
  }

  .tier-price {
    margin-top: 0;
    margin-left: auto;
    font-size: var(--font-size-body);
  }
}

/* ---- The account strip (D-52) ------------------------------------------- */

/* Who is signed in, and how to stop being them. It is DELIBERATELY QUIET: it is never the task on any
   page it appears on, and it answers exactly one question - "is this me?". Tokens only, no new colours,
   no new radius, no new type sizes; it is the entry page's own surface with a rule round it. */

.account-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-5);
  align-items: baseline;
  justify-content: space-between;
  padding: var(--space-3) var(--space-4);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-panel);
  background: var(--surface-subtle);
}

/* The payment page's variant (D-52's payment exception): identity, and nothing to press. There is no
   action to push to the far edge, so the identity does not sit alone against a wide gap. */
.account-strip-readonly {
  justify-content: flex-start;
}

/* ONE LINE (mobile handoff account row): the "signed in as" label and the address sit side by side,
   not stacked, so the strip is a single quiet line at the top rather than a tall block. It still wraps
   gracefully - a long address drops under the label at 390 rather than forcing the page sideways. */
.account-strip .account-who {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
  min-width: 0;
}

.account-strip .account-label {
  color: var(--text-secondary);
}

.account-strip .account-id {
  font-weight: var(--font-weight-medium);
  /* An email address is the one string here that cannot be re-wrapped politely, and a long one at
     390px would otherwise push the whole page sideways. Break it instead. */
  overflow-wrap: anywhere;
}

.account-strip form {
  margin: 0;
}

/* ---- The switch-account confirmation (D-52) ----------------------------- */

/* Native <dialog>, on the same reasoning as the admin one: showModal() does the containment, the
   inerting, Escape and focus restoration correctly on every platform. This is PUBLIC content, so it
   lives here with the rest of the public design system rather than in staff chrome. */

.account-confirm {
  background: var(--surface-card);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  width: min(34rem, calc(100vw - var(--space-7)));
  padding: 0;
  border: var(--border-emphasis) solid var(--border-strong);
  border-radius: var(--radius-panel);
}

.account-confirm::backdrop {
  background: rgb(0 0 0 / 45%);
}

.account-confirm-panel {
  padding: var(--space-6);
}

.account-confirm-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
}

/* ---- Screen-reader-only ------------------------------------------------- */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- Stage 7: admin registration operations ----------------------------- */

/* An attention badge, for a registration that needs a staff decision. */
.badge-review {
  color: var(--warning);
  background: var(--warning-bg);
}

/* The registrations toolbar carries several filters, so it wraps freely. */
.admin-toolbar-wrap .admin-filter {
  width: 12rem;
}

.registration-ref {
  font-variant-numeric: tabular-nums;
  font-weight: var(--font-weight-semibold);
  white-space: nowrap;
}

.admin-cell-strong {
  display: block;
  font-weight: var(--font-weight-semibold);
}

.admin-cell-sub {
  display: block;
  color: var(--text-muted);
  font-size: var(--font-size-meta);
}

.admin-mono {
  font-family: ui-monospace, "Cascadia Code", "Source Code Pro", monospace;
  font-size: var(--font-size-meta);
  word-break: break-all;
}

.admin-muted {
  color: var(--text-secondary);
}

.admin-slice-note {
  margin-top: var(--space-4);
  color: var(--text-muted);
  font-size: var(--font-size-secondary);
}

.admin-back {
  margin-bottom: var(--space-4);
}

/* Pager under the list. */
.admin-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.admin-pager-summary,
.admin-pager-position {
  color: var(--text-secondary);
  font-size: var(--font-size-secondary);
}

.admin-pager-controls {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* The detail workspace: summary strip, tabs, and definition grids. */
.admin-summary-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-5);
  background: var(--surface-subtle);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-panel);
}

.admin-summary-label {
  display: block;
  color: var(--text-muted);
  font-size: var(--font-size-meta);
  letter-spacing: .04em;
  text-transform: uppercase;
}

.admin-summary-value {
  display: block;
  font-weight: var(--font-weight-semibold);
  margin-top: 2px;
}

.admin-tabs {
  display: flex;
  gap: var(--space-2);
  border-bottom: var(--border-hairline) solid var(--border-default);
  margin-bottom: var(--space-5);
}

.admin-tab {
  appearance: none;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  padding: var(--space-3) var(--space-4);
  min-height: 44px;
  color: var(--text-secondary);
  font: inherit;
  cursor: pointer;
}

.admin-tab:hover {
  color: var(--text-primary);
}

.admin-tab.is-active {
  color: var(--text-primary);
  border-bottom-color: var(--accent, var(--text-primary));
  font-weight: var(--font-weight-semibold);
}

.admin-tab:focus-visible {
  outline: 2px solid var(--focus-ring, var(--text-primary));
  outline-offset: 2px;
}

.admin-tabpanel > .card + .card {
  margin-top: var(--space-4);
}

.admin-card-title {
  font-family: var(--font-heading);
  font-size: var(--font-size-h3);
  margin-bottom: var(--space-4);
}

.admin-participant-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.admin-participant-head .admin-card-title {
  margin-bottom: 0;
}

/* A read-only definition grid for the overview and participant cards. */
.admin-detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: var(--space-4);
}

.admin-detail-grid > div {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.admin-detail-grid dt {
  color: var(--text-muted);
  font-size: var(--font-size-meta);
  letter-spacing: .04em;
  text-transform: uppercase;
}

.admin-detail-totals {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: var(--border-hairline) solid var(--border-default);
}

/* The operations dashboard stat cards. */
.admin-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}

.admin-stat {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-5);
  background: var(--surface-card);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-panel);
  text-decoration: none;
  color: inherit;
}

a.admin-stat:hover {
  border-color: var(--border-strong);
  background: var(--surface-page);
}

.admin-stat-value {
  font-family: var(--font-heading);
  font-size: var(--font-size-h1);
  font-weight: var(--font-weight-semibold);
}

.admin-stat-label {
  color: var(--text-secondary);
  font-size: var(--font-size-secondary);
}

/* Slice 2: staff-review actions on the registration detail. */
.admin-action-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.admin-reject-form {
  margin-top: var(--space-4);
}

.admin-reject-form textarea {
  width: 100%;
  margin-top: var(--space-2);
}

.admin-action-ok {
  color: var(--success);
  font-weight: var(--font-weight-semibold);
}

.admin-action-error {
  color: var(--danger);
  font-weight: var(--font-weight-semibold);
}

/* Slice 3: cancel + refund. */
.admin-refund-form {
  margin-top: var(--space-4);
}

.admin-refund-form textarea {
  width: 100%;
  margin-top: var(--space-2);
}

.admin-subtitle {
  font-family: var(--font-heading);
  font-size: var(--font-size-h3);
  margin-bottom: var(--space-2);
}

.admin-policy-note {
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-panel);
  background: var(--surface-subtle);
}

.admin-policy-note.is-draft {
  border-color: var(--warning);
  background: var(--warning-bg);
}

.admin-policy-badge {
  display: inline-block;
  margin-bottom: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--warning);
  color: var(--surface-card);
  font-size: var(--font-size-meta);
  font-weight: var(--font-weight-semibold);
  letter-spacing: .04em;
}

/* Slice 5: the restricted-health block on the participant card. Rendered only for a HealthViewer; the
   tinted, bordered, top-separated treatment makes the restricted nature explicit at a glance. */
.admin-health-block {
  margin-top: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-panel);
  background: var(--surface-subtle);
}



/* D-60 E2b: the email preview. The two widths are the point of the control - an administrator checks the
   same wording at a desktop reading width and at a phone width, because that is where a long Vietnamese
   line or an unbroken reference wraps badly. These are EMAIL-CLIENT widths, not app breakpoints, which is
   why they are literal rather than token breakpoints (tokens.css owns colour and type; DESIGN_TOKENS 1
   forbids raw colour anywhere else and raw font sizes outside tokens.css - neither applies to a max-width). */
.email-preview-frame {
  margin-top: var(--space-3);
  padding: var(--space-4);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-panel);
  background: var(--surface-default);
  overflow-x: auto;
}

.email-preview-desktop { max-width: 600px; }

.email-preview-narrow { max-width: 390px; }

/* The plain-text alternative is shown as-sent: preserved whitespace, wrapped rather than scrolled sideways. */
.email-preview-text {
  margin-top: var(--space-3);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* A link to be pasted elsewhere: the field takes the room so the whole address is readable without
   scrolling it, and the Copy button keeps its natural width beside it. */
.admin-copy-value {
  flex: 1 1 32ch;
  min-width: 0;
}

/* ---- My Nhập Lưu - the signed-in account area (Stage 7 member workstream, M1) ----------------
   THE ONE PLACE THE PUBLIC SHELL AND THIS ONE DIFFER IS THE NAVIGATION, and it is a deliberate
   exception rather than a drift. PublicLayout has no menu because the public has exactly one path
   through the application and every extra control is a way to fall out of it (brief 8, 12). A person
   who has already registered and signed in is in the opposite situation: they came on purpose, to
   find their own things, and four named destinations is what serves them.

   RESPONSIVE RULES ARE CONTAINER QUERIES, like every other rule in this file. There is no width
   media query here - <main> carries `container-name: app`, so `@container app` drives the account
   area exactly as it drives every other page, and the preview frame gets the same behaviour free.

   EVERY COLOUR AND SIZE IS A TOKEN. The tone classes below reuse the SAME status palette the notices
   and tier pills use; the account area introduces no colour of its own, because a colour written
   here could not be re-themed by D-27's three themes. */

/* The account header is its OWN container, because the nav lives inside it and NOT inside <main> (the
   `app` container). A width media query is banned in this file - responsive layout is container queries,
   so the preview frame and a real phone agree - so the nav's row/grid switch below queries THIS header,
   whose inline size tracks the header's own width. A distinctly named container so it never collides with
   `app` or `wizardcontent`. */
.member-header {
  container-type: inline-size;
  container-name: memberchrome;
}

.member-header .public-header-inner {
  flex-direction: row;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
}

.member-brand {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.member-area-name {
  color: var(--text-secondary);
  font-size: var(--font-size-meta);
}

/* The four destinations, NEVER an overflow menu at any width (essential actions are never hidden,
   brief 12) - so no hamburger, at any size. It is MOBILE-FIRST: a compact two-column grid of bordered
   pills is the DEFAULT, which serves both the phone AND the awkward mid-widths (wider than a phone, too
   narrow for four across) that a wrapping flex row turned into a four-row stack - the stack pushed the
   welcome heading below the fold. Only at >=48rem, where four destinations fit comfortably on one line,
   does it become the underlined row. */
.member-nav {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
  max-width: 56rem;
  margin: var(--space-4) auto 0;
}

.member-nav-item {
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* 44px is the locked floor for a touch target (brief 12). The padding keeps it there even when
     the Vietnamese pairing wraps to a second line. */
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-control);
  color: var(--text-secondary);
  text-decoration: none;
}

.member-nav-item:hover {
  color: var(--text-primary);
}

/* The active destination is marked by COLOUR AND A BORDER AND aria-current - never by colour alone. */
.member-nav-current {
  color: var(--accent);
  border-color: var(--accent);
}

/* Wide enough for one honest row: the destinations spread across it and the pill borders give way to a
   single underline beneath the current one. Queried against `memberchrome` (the header), NOT `app` - the
   nav is in the header, not <main>, so an `app` query would never match it and the row would never
   appear. */
@container memberchrome (min-width: 48rem) {
  .member-nav {
    display: flex;
  }

  .member-nav-item {
    flex: 1;
    border: none;
    border-bottom: 3px solid transparent;
    border-radius: 0;
  }

  .member-nav-current {
    border-bottom-color: var(--accent);
  }
}

/* An unbuilt destination is plain text, not a disabled control: a greyed button invites a second
   press and reads as a fault. It is not focusable, so it costs a keyboard user nothing. */
.member-nav-unbuilt {
  color: var(--text-muted);
  cursor: default;
}

.member-nav-note {
  font-size: var(--font-size-meta);
  color: var(--text-muted);
}

.member-section-title {
  font-size: var(--font-size-h2);
}

/* ---- The attention panel ----------------------------------------------------------------------
   Warm rather than alarming. These are things to do, not failures - a retreat booking is not an
   emergency, and a red panel on somebody's account page reads as one. */
.member-attention {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5);
  border: var(--border-hairline) solid var(--warning);
  border-radius: var(--radius-panel);
  background: var(--warning-bg);
}

.member-attention-event {
  font-size: var(--font-size-h3);
}

/* ---- Event cards and registration rows ------------------------------------------------------- */

.member-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
}

.member-card,
.member-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5);
  background: var(--surface-card);
  border: var(--border-hairline) solid var(--border-default);
  border-radius: var(--radius-panel);
}

.member-card-title {
  font-size: var(--font-size-h3);
}

.member-card-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding-top: var(--space-4);
  border-top: var(--border-hairline) solid var(--border-default);
}

.member-badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.member-meta {
  margin: 0;
  color: var(--text-secondary);
}

/* The human reference is read aloud over the telephone and copied by hand, so it is spaced for
   transcription rather than set as ordinary prose. */
.member-reference {
  letter-spacing: 0.04em;
}

/* ---- Status badges ---------------------------------------------------------------------------
   THE WORDS ARE THE STATUS; the tone only tints them. Every badge carries its bilingual label, so a
   reader who cannot distinguish these tints - or who is listening - loses nothing (brief 12). */
.member-badge {
  display: inline-flex;
  align-items: center;
  /* At the meta floor, never below it: 16px is the smallest text this application shows anyone. */
  min-height: 34px;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-control);
  font-size: var(--font-size-meta);
  background: var(--surface-subtle);
  color: var(--text-secondary);
}

.member-badge-positive { background: var(--success-bg); color: var(--success); }
.member-badge-attention { background: var(--warning-bg); color: var(--warning); }
.member-badge-ended { background: var(--surface-subtle); color: var(--text-secondary); }

/* ---- Loading, empty and contact --------------------------------------------------------------- */

.member-loading {
  padding: var(--space-6);
  color: var(--text-secondary);
}

.member-empty-title {
  font-size: var(--font-size-h3);
}

.member-shared-device {
  color: var(--text-secondary);
  font-size: var(--font-size-meta);
}

/* ---- Profile (M3) -----------------------------------------------------------------------------
   A short form is easier to read and to complete when it does not stretch the full width of a desktop
   page - the eye tracks a whole field label without travelling. It stays full-width on a phone. */
.member-profile-form {
  max-width: 34rem;
}

.member-profile-actions {
  display: flex;
}

/* Full-width primary action on a narrow screen, matching the cards and attention panel below - a
   generous target an older person is not asked to aim for. */
@container app (max-width: 40rem) {
  .member-profile-actions .btn {
    width: 100%;
  }
}

/* ---- Narrow ----------------------------------------------------------------------------------
   One column, full-width actions, and the header stacks. Nothing is hidden and nothing scrolls
   sideways: a bilingual card that forces an older person to scroll horizontally to find out when a
   retreat starts is a failure, not a compromise. */
@container app (max-width: 40rem) {
  .member-header .public-header-inner {
    flex-direction: column;
    align-items: stretch;
  }

  /* The nav itself needs no rule here: its two-column pill grid is now the mobile-first DEFAULT (see
     .member-nav above), so it is already correct at this width and every width below the 48rem row. */

  .member-grid {
    grid-template-columns: 1fr;
  }

  .member-attention,
  .member-card-footer {
    flex-direction: column;
    align-items: stretch;
  }

  .member-card-footer .btn,
  .member-attention .btn {
    width: 100%;
  }
}

/* A quiet text link between account sections - "View waiting-list activity", "Back to my
   registrations". It is a link, not a button: it navigates rather than acting, and the accent colour
   plus underline carry it. It sits at the body text size (never the meta floor), because it is a
   destination an elder reads, not a caption. */
.member-inline-link {
  color: var(--accent);
  font-weight: var(--font-weight-medium);
}
