﻿/*
 * Component: Application base styles
 * Description: App-level layout and typography for the BCS Bulk Renewal Utility.
 *              Consumes semantic --bc-* tokens from tokens.css; never raw hex.
 * Author: Matt Horton <BeniComp>
 * Organization: BeniComp
 * Date: 2026-07-30
 *
 * History:
 * 2026-07-30 Matt Horton Initial base layout, typography, and skip link.
 * 2026-08-05 Matt Horton Component layer for the authenticated shell: header and
 *            navigation, environment identity, forms, tables, badges, callouts,
 *            dialogs, and one-time secret display.
 * 2026-08-05 Matt Horton Screens S14 and S2: metric cards, a page toolbar, the filter
 *            grid, month and cohort layout, the two cycle standings, and the native
 *            disclosure used for a cohort's group table.
 * 2026-08-05 Matt Horton Screens S3 through S8: the staging progress bar. Everything
 *            else those screens need was already here.
 * 2026-08-07 Matt Horton Added the pager layout used by the S16 active groups listing.
 * 2026-08-07 Matt Horton Migrated gap/padding/margin literals onto the new
 *            --bc-space-* scale added to tokens.css.
 * 2026-08-07 Matt Horton Remapped undefined var(--bc-border)/--bc-surface/--bc-text
 *            in the template composer to real tokens, replaced a raw #fff, and
 *            added .form/.form--inline/.form-actions/.stat-list/.template-preview
 *            component styles for campaign.html and templates.html markup that
 *            had no matching rules.
 * 2026-08-07 Matt Horton UX-polish audit: added the missing :active state for
 *            every button variant, the app-nav link, and the disclosure
 *            summary (:hover and :focus-visible already existed for all of
 *            them, but no pressed state). Added a defensive @media print rule
 *            that hides every other direct child of <body> while a native
 *            <dialog> is open, so openSecretReveal()'s print button can never
 *            leak the surrounding screen onto the same page as a one-time
 *            secret if a browser doesn't scope printing to the open dialog
 *            on its own (REQ-AUTH-007). Added a scoped body.login-page
 *            background accent using --bc-gradient-illustration-light,
 *            proposed as a reviewable design choice for login.html only.
 * 2026-08-08 Matt Horton Added the wide active-groups table viewport with sticky
 *            context and full-width filter control.
 * 2026-08-08 Matt Horton Added standard/wide desktop workspace variants and
 *            scoped Bootstrap 5 Tabulator theme overrides for Active groups.
 * 2026-08-08 Matt Horton Extended shared Tabulator styling to batch review grids.
 * 2026-08-08 Matt Horton Restored a visible focus ring on Tabulator's internal
 *            keyboard scroll holder after browser no-mouse validation.
 * 2026-08-08 Matt Horton Raised pending-navigation note contrast on the Forest
 *            desktop sidebar after Lighthouse verification.
 * 2026-08-09 Matt Horton Replaced viewport-growing type with a fixed, compact
 *            desktop scale so 1080p workspaces gain area instead of larger text.
 * 2026-08-09 Matt Horton Added the accessible Active groups extras-column toggle.
 * 2026-08-09 Matt Horton Exposed operational-grid column resize handles.
 * 2026-08-09 Matt Horton Moved Active groups extras into an external toggle slider.
 * 2026-08-09 Matt Horton Preserved Tabulator's measured resize-handle width so
 *            header and row column boundaries remain aligned.
 * 2026-08-09 Matt Horton Top-aligned Active groups filters with typeahead hint text.
 * 2026-08-07 Matt Horton Rebuilt authenticated chrome as a persistent desktop
 *            sidebar with a native-dialog mobile navigation menu.
 * 2026-08-07 Matt Horton Unified major-page and card-internal rhythm under
 *            parent-owned grid gaps so tiles and sections cannot drift.
 * 2026-08-07 Matt Horton Reorganized the email template composer around
 *            progressive authoring sections and a responsive preview workspace.
 * 2026-08-10 Matt Horton Restored the fluid root font-size the project standardizes
 *            on, reversing the fixed 100% root of 2026-08-09. Verified in Chromium at
 *            320, 375, 768, 1280, and 1920 CSS pixels and at 200% zoom for overflow,
 *            clipped controls, and table layout. Fixed two narrow-window overflows the
 *            larger root exposed and that predated it: table scrollers now contain
 *            their inline size, and fieldsets shed the UA min-content minimum. Note
 *            that REQ-UI-020, REQ-UI-021, SD-FE, and
 *            .cursor/rules/frontend-brand.mdc still describe the fixed root and need
 *            reconciling to this.
 * 2026-08-11 Matt Horton Added the S13 settings panel as .settings-panel /
 *            .settings-section subclasses over the existing .card, .fieldset, and
 *            .field components rather than a new component layer (ADR 012).
 * 2026-08-13 Matt Horton Added transient notifications as a .toast subclass of the
 *            existing .callout, plus the fixed .toast-region that holds them, and a
 *            .toggle-switch--path variant for the WF-1 cohort work-path switch.
 * 2026-08-13 Matt Horton Extended .callout with persistent tone-aware notices,
 *            one-action toasts, and compound dialog form feedback.
 * 2026-08-13 Matt Horton Made the existing .template-actions bar sticky and added a
 *            dirty-state indicator for the template composer (Wave 2D).
 * 2026-08-15 Matt Horton Added responsive immutable-logo source, thumbnail, and
 *            metadata presentation to the existing template composer system.
 * 2026-08-19 Matt Horton Extended the template composer with narrow-safe Visual
 *            and HTML Source fragment modes.
 * 2026-08-13 Matt Horton Added batch-namespaced wayfinding: the batch-list
 *            breadcrumb, the lifecycle stepper, and the submit-for-review
 *            blocker for the long batch detail page (Wave 2B).
 * 2026-08-14 Matt Horton Extended existing calendar callouts/standings and the
 *            campaign preview dialog for completed Wave 3 surfaces.
 * 2026-08-15 Matt Horton Added narrow campaign preparation and tertiary-grid action
 *            subclasses for the shared Wave 3 integration.
 * 2026-08-15 Matt Horton Let the application grid track shrink after desktop-to-zoom
 *            viewport changes so wide campaign content remains locally scrollable.
 * 2026-08-15 Matt Horton Unified campaign/composer controls and styled the native
 *            file-selector button without replacing its keyboard behavior.
 * 2026-08-17 Matt Horton Let campaign-step summaries wrap exact prerequisite copy
 *            without widening the page at 320px or 200%-equivalent viewports.
 * 2026-08-17 Matt Horton Extended template disclosure, field, list, and callout
 *            primitives for the responsive merge-field reference.
 * 2026-08-18 Matt Horton Added shared Active/History work-queue controls.
 * 2026-08-18 Matt Horton Added task-oriented workspace hierarchy and phase disclosure.
 * 2026-08-18 Matt Horton Corrected current-workspace text and action contrast.
 * 2026-08-18 Matt Horton Pinned the authenticated root to the browser preferred size
 *            and added the data-surface scale operational grids read at (ADR 015),
 *            superseding the fluid root of 2026-08-10 for this stylesheet only. The
 *            separately served confirmation site keeps the fluid rule.
 * 2026-08-18 Matt Horton Extended the existing .badge with four tones, a grid-cell
 *            size, and a glyph slot, and added the .icon / .btn--icon / .stat-chip /
 *            .wave-dots / .cell-fact primitives that let grid cells carry a label and
 *            a glyph instead of a sentence (REQ-UI-040).
 * 2026-08-19 Matt Horton Raised unavailable campaign-action reason contrast on the
 *            quiet wayfinding surface after the unfiltered axe regression gate.
 * 2026-08-19 Matt Horton Extended the existing Tabulator theme with readable
 *            empty-state copy after the integrated Phase 2 axe gate.
 * 2026-08-19 Matt Horton Restored the header account link's WCAG 2.2 target size.
 * 2026-08-19 Matt Horton Contained moved recipient grids and selection controls
 *            at narrow campaign widths.
 * 2026-08-19 Matt Horton Stacked template conflict recovery inside narrow viewports.
 * 2026-09-07 Matt Horton Extended the existing narrow campaign-host containment to
 *            the state-aware Address corrections host slots.
 *
 * Type sizing decision (REQ-UI-013 versus tokens.css): tokens.css authors the
 * brand type scale in points (--bc-body-size: 16pt), which REQ-UI-020 prohibits
 * in application CSS. This stylesheet therefore consumes the token file for
 * families, weights, and colors only, and restates the scale in rem at the
 * semantic roles below, where it rides the user's browser preference rather than
 * a fixed pica measure. The .bc-headline / .bc-subhead / .bc-body helper classes
 * in tokens.css are consequently not used here.
 */

/*
 * The root is the user's own preferred size and nothing else (ADR 015).
 *
 * This is a data workstation, not a reading surface. Its screens are answered by how
 * many rows an operator can compare at once, so window width has to buy records. The
 * fluid root of 2026-08-10 spent it on glyphs instead: calc(1rem + 0.25vw) took the
 * root from about 16.8px at 320 pixels to about 20.8px at 1920, which meant maximizing
 * the browser enlarged every control, row, and label by roughly a third and put fewer
 * rows on screen than the smaller window had. Operators read that as an interface built
 * for the visually impaired, and it is the single largest reason the grids felt unusable.
 *
 * Accessibility is not what the vw term was carrying. 100% is the browser's configured
 * size, so a reader who has raised their default still gets it, and every size below is
 * rem-derived, so the whole scale moves with that preference. What is given up is
 * proportional growth on a large display, which is the thing this application
 * specifically does not want.
 */
:root {
  font-size: 100%;

  /* Type scale in relative units (REQ-UI-013, REQ-UI-020) */
  --bc-type-body: 1rem;
  --bc-type-body-line: 1.4;
  --bc-type-subhead: 1.25rem;
  --bc-type-subhead-line: 1.25;
  --bc-type-headline: 2rem;
  --bc-type-headline-line: 1.15;
  --bc-type-heading-sm: 1.125rem;
  --bc-type-lockup: 1rem;
  --bc-type-small: 0.8125rem;

  /*
   * Data-surface roles. Grids, dense tables, and the badges inside them read one
   * step below body text, and their supporting metadata one step below that. They
   * are separate roles rather than reuses of --bc-type-small so that changing grid
   * density is a two-token edit instead of a sweep through every grid rule, and so
   * that prose set in --bc-type-small does not move when density is retuned.
   */
  --bc-type-data: 0.875rem;
  --bc-type-micro: 0.75rem;

  /*
   * Semantic roles tokens.css does not define. Each references a brand
   * primitive, so a rebrand still only changes the brand layer.
   *
   * Snow on base Mint (#FFFFFF on #44BB8D) measures about 2.4:1, below the 4.5:1
   * that REQ-UI-023 requires and that REQ-UI-011 asks to be checked at
   * implementation. Mint -800 measures about 5.4:1 against Snow, so the CTA fill
   * and the Mint text treatment both use that step of the brand scale.
   */
  --bc-cta-fill-accessible: var(--bc-brand-mint-800);
  --bc-cta-fill-accessible-hover: var(--bc-brand-mint-900);
  --bc-cta-text-accessible: var(--bc-brand-mint-800);

  /* Focus rings: one for light surfaces, one for the Forest header. */
  --bc-focus-ring: var(--bc-brand-mint-800);
  --bc-focus-ring-on-dark: var(--bc-brand-mint-300);

  --bc-text-muted: var(--bc-brand-forest-500);
  --bc-border-subtle: var(--bc-brand-forest-200);
  --bc-surface-notice: var(--bc-brand-mint-100);
  --bc-surface-notice-strong: var(--bc-brand-mint-200);
  --bc-surface-quiet: var(--bc-brand-forest-100);

  /* Header logo (REQ-UI-015): also the header's own padding-block, so the
     mark's clearspace is always exactly one logo-height, on every side. */
  --bc-logo-header-height: var(--bc-space-6);

  /*
   * The palette allows no red (REQ-UI-011), so error and warning states are
   * Forest text carrying a glyph and heavier weight, on a Mint tint. Color is
   * never the only signal (REQ-UI-004).
   */
  --bc-text-error: var(--bc-brand-forest);
}

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  min-block-size: 100vh;
  display: flex;
  flex-direction: column;
  font-family: var(--bc-brand-font-body);
  font-size: var(--bc-type-body);
  line-height: var(--bc-type-body-line);
  color: var(--bc-text-body);
  background: var(--bc-surface-page);
}

h1,
h2,
h3,
h4 {
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-heavy);
  color: var(--bc-text-heading);
}

h1 {
  font-size: var(--bc-type-headline);
  line-height: var(--bc-type-headline-line);
}

h2 {
  font-size: var(--bc-type-subhead);
  line-height: var(--bc-type-subhead-line);
}

h3 {
  font-size: var(--bc-type-heading-sm);
  line-height: var(--bc-type-subhead-line);
}

h4 {
  font-size: var(--bc-type-body);
}

/*
 * Visible focus everywhere (REQ-UI-023). outline is never removed; where a
 * component needs a different look, the ring is replaced by a stronger one.
 */
:focus-visible {
  outline: 3px solid var(--bc-focus-ring);
  outline-offset: 2px;
}

/*
 * Buttons and nav links sit flush against card borders, table cells, and each
 * other in toolbars/inline-actions; a 2px offset visually clips the ring into
 * the neighbouring surface at narrow widths. Pulling the offset in keeps the
 * full ring visible without changing its colour or weight.
 */
.btn:focus-visible,
.app-nav__link:focus-visible,
.disclosure__summary:focus-visible,
.template-list__select:focus-visible {
  outline-offset: 1px;
}

/* Accessible skip link (A11Y-021): visually hidden until focused */
.skip-link {
  position: absolute;
  inset-block-start: -100%;
  inset-inline-start: 0;
  z-index: 10;
  padding-block: var(--bc-space-2);
  padding-inline: var(--bc-space-4);
  background: var(--bc-surface-dark);
  color: var(--bc-text-on-dark);
}

.skip-link:focus {
  inset-block-start: 0;
}

/*
 * The skip link paints itself on the dark surface, so it needs the on-dark ring.
 * Mint -800 against Forest measures about 2.8:1, under the 3:1 that WCAG 2.2 SC
 * 1.4.11 asks of a focus indicator; Mint -300 measures about 10:1.
 */
.skip-link:focus-visible {
  outline-color: var(--bc-focus-ring-on-dark);
}

/* ------------------------------------------------------------------ header */
/*
 * padding-block matches --bc-logo-header-height below: brand clearspace is
 * defined as whitespace equal to the mark's own height on every side, and
 * the header background is the only place that whitespace can live now that
 * the mark is an image instead of a line of text (REQ-UI-015).
 */
.app-header {
  padding-block: var(--bc-logo-header-height);
  padding-inline: clamp(var(--bc-space-4), 4vw, var(--bc-space-12));
  background: var(--bc-surface-dark);
}

.app-header :focus-visible {
  outline-color: var(--bc-focus-ring-on-dark);
}

.app-header__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-3) var(--bc-space-6);
  align-items: center;
  justify-content: space-between;
}

.app-header__brand {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bc-space-3);
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-heavy);
  font-size: var(--bc-type-lockup);
  color: var(--bc-text-on-dark);
  text-decoration: none;
}

/*
 * Knockout-white lockup (REQ-UI-015): the header sits on the dark Forest
 * surface, so this is the only variant of the seven brand PNGs that reads
 * correctly here. block-size is the single source of truth for both the
 * rendered logo height and the header's clearspace padding above.
 * inline-size must be explicit `auto` (not merely omitted): the <img>'s
 * HTML width/height attributes are presentational hints that otherwise win
 * the cascade for the unset inline-size, pinning the rendered width to the
 * source PNG's native 1257px and stretching the mark once block-size
 * shrinks it below that. With inline-size: auto, the browser instead
 * derives width from the image's own aspect ratio, scaled to block-size.
 */
.app-header__logo {
  display: block;
  block-size: var(--bc-logo-header-height);
  inline-size: auto;
  max-inline-size: 100%;
}

.app-header__utility {
  display: block;
  font-family: var(--bc-brand-font-body);
  font-size: var(--bc-type-small);
  font-weight: var(--bc-brand-weight-heading-regular);
  color: var(--bc-brand-forest-200);
}

/* Account identity and sign-out sit together at the end of the header row */
.app-header__utility-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bc-space-2) var(--bc-space-4);
}

.app-header__utility-row .btn--outline {
  border-color: var(--bc-brand-forest-300);
  color: var(--bc-text-on-dark);
}

.app-header__utility-row .btn--outline:hover {
  background: var(--bc-brand-forest-800);
}

.app-header__utility-row .btn--outline:active {
  background: var(--bc-brand-forest-900);
}

.app-header__account {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bc-space-2) var(--bc-space-3);
  font-size: var(--bc-type-small);
  color: var(--bc-brand-forest-200);
}

.app-header__account-name {
  font-weight: var(--bc-brand-weight-heading-heavy);
  color: var(--bc-text-on-dark);
}

.app-header__account-link {
  display: inline-flex;
  align-items: center;
  min-block-size: 1.5rem;
  color: var(--bc-brand-forest-100);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.app-header__account-link:hover,
.app-header__account-link:focus-visible {
  color: var(--bc-text-on-dark);
}

/* --------------------------------------------------------------------- nav */
/*
 * The bar is one continuous strip, not a row of floating chips. A bottom rail
 * (Forest-700, one step off the header's dark surface) runs the full width so
 * that when the items wrap onto a second row every row still shares a common
 * baseline instead of reading as detached. Items sit on the rail with their
 * own horizontal spacing; row gap keeps a wrapped second row off the first.
 *
 * Wrapping is the failure mode, not the plan: eight items at --bc-type-small
 * overflow the ~1177px content width at 1280px by a little over a hundred
 * pixels, so between 56rem and the point where the type's vw term has grown
 * enough room, items are allowed to shrink their horizontal padding to stay on
 * one row. Below 56rem there genuinely isn't room and the bar wraps onto the
 * rail, which is the intended mobile layout.
 */
.app-nav {
  display: flex;
  flex-wrap: wrap;
  column-gap: var(--bc-space-1);
  row-gap: var(--bc-space-1);
  margin-block-start: var(--bc-space-5);
  padding-block-end: var(--bc-space-1);
  border-block-end: 1px solid var(--bc-brand-forest-700);
}

@media (min-width: 56rem) {
  .app-nav {
    flex-wrap: nowrap;
  }
}

.app-nav__link {
  display: inline-flex;
  align-items: baseline;
  gap: var(--bc-space-1-5);
  padding-block: var(--bc-space-2);
  padding-inline: var(--bc-space-2-5);
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-medium);
  font-size: var(--bc-type-small);
  text-decoration: none;
  white-space: nowrap;
}

/*
 * On the single-row desktop bar, let items give up horizontal padding before
 * the bar wraps. Labels are kept on one line by white-space:nowrap above.
 */
@media (min-width: 56rem) {
  .app-nav__link {
    padding-inline: var(--bc-space-1-5);
    min-inline-size: 0;
  }
}

.app-nav__link {
  color: var(--bc-brand-forest-200);
}

.app-nav__link:hover {
  color: var(--bc-text-on-dark);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.app-nav__link:active {
  color: var(--bc-brand-mint-300);
  text-decoration: underline;
}

/*
 * Current page: a Mint accent bar inset from the item's bottom edge, plus
 * full-strength Snow text. An inset bar (rather than a filled tab) reads the
 * same whether the item sits on the first row or a wrapped one, so the active
 * marker never appears to detach from the bar the way a top-radii tab did.
 */
.app-nav__link[aria-current='page'] {
  color: var(--bc-text-on-dark);
  font-weight: var(--bc-brand-weight-heading-bold);
  box-shadow: inset 0 calc(-1 * var(--bc-space-1)) 0 0 var(--bc-brand-mint);
}

/* --------------------------------------------- environment identity strip */
/*
 * REQ-ARCH-011 with REQ-UI-004: never color alone. The strip states the
 * environment in words and carries a glyph; production shows nothing at all, so
 * the marker never becomes background noise operators learn to ignore.
 */
.env-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-2) var(--bc-space-3);
  align-items: center;
  justify-content: center;
  padding-block: var(--bc-space-1-5);
  padding-inline: clamp(var(--bc-space-4), 4vw, var(--bc-space-12));
  background: var(--bc-surface-notice-strong);
  border-block-end: 1px solid var(--bc-border-emphasis);
  font-size: var(--bc-type-small);
  color: var(--bc-text-heading);
  text-align: center;
}

.env-strip__name {
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-bold);
}

.env-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--bc-space-1-5);
  padding-block: 0.125rem;
  padding-inline: var(--bc-space-2-5);
  border: 1px solid var(--bc-border-emphasis);
  border-radius: var(--bc-radius-pill);
  background: var(--bc-surface-notice-strong);
  color: var(--bc-text-heading);
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-heavy);
  font-size: var(--bc-type-small);
}

/* --------------------------------------------------------------------- main */
/*
 * Centered content column. A block box is full-width by default, so the only
 * width signal needed is the ceiling: max-inline-size caps the line length and
 * margin-inline:auto centers it. (An earlier inline-size:100% here made the
 * border-box always fill the viewport, so the auto margins computed to 0 and
 * the column never actually centered at common widths.) box-sizing is
 * border-box (from the reset), so the padding is inside the 70rem, keeping the
 * text column aligned with the header/footer's padded content.
 */
.app-main {
  flex: 1;
  /*
   * body is a flex column (for the sticky footer), which makes .app-main a
   * flex item. Two guards keep a wide inner grid or table from pushing the
   * whole page past the viewport: min-inline-size:0 overrides the flex-item
   * default (min-inline-size:auto) so the box may shrink below its content's
   * intrinsic width, and inline-size:100% ties the column's used width to the
   * body rather than to the content. max-inline-size then caps the readable
   * line length and margin-inline:auto centers it.
   */
  min-inline-size: 0;
  inline-size: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: var(--bc-space-6);
  padding-block: clamp(var(--bc-space-8), 6vw, var(--bc-space-16));
  padding-inline: clamp(var(--bc-space-4), 4vw, var(--bc-space-12));
  max-inline-size: 70rem;
  margin-inline: auto;
}

/*
 * Wide is opt-in: narrative and small-table screens keep the readable 70rem
 * ceiling, while high-volume operational grids can consume the desktop workspace.
 * The narrow base remains bounded so 200% zoom falls back to ordinary page flow.
 */
.app-main--wide {
  max-inline-size: 100%;
}

.app-main h1 {
  margin-block-end: 0;
}

/* Page structure is Grid; component alignment is Flexbox (REQ-UI-021) */
.page-grid {
  display: grid;
  gap: var(--bc-space-6);
}

/*
 * A grid item's automatic minimum size is its content's minimum, which lets a
 * wide data table push the whole page wider than the viewport. Zeroing it hands
 * the overflow to .table-wrap, which is what keeps the layout usable at 320px
 * and at 200% zoom (REQ-UI-023).
 */
.page-grid > * {
  min-inline-size: 0;
}

@media (min-width: 56rem) {
  .page-grid--split {
    grid-template-columns: 2fr 1fr;
    align-items: start;
  }
}

.page-head {
  display: grid;
  gap: var(--bc-space-2);
}

.page-head__intro {
  max-inline-size: 46rem;
  margin-block-start: 0;
  color: var(--bc-text-muted);
}

/*
 * The standing explanation of a data screen, closed by default (REQ-UI-040). It is read
 * once and then costs every later visit the rows it pushed below the fold, so it keeps
 * its wording but gives up its permanent place. The border the shared .disclosure adds
 * is dropped here: inside the page head it reads as a rule under the heading.
 */
.page-head__help {
  border: 0;
}

.page-head__help > .disclosure__summary {
  font-family: var(--bc-brand-font-body);
  font-size: var(--bc-type-small);
  font-weight: var(--bc-brand-weight-body);
}

.app-footer {
  padding-block: var(--bc-space-4);
  padding-inline: clamp(var(--bc-space-4), 4vw, var(--bc-space-12));
  background: var(--bc-brand-forest-100);
  text-align: center;
}

/* -------------------------------------------------------------------- cards */
.card {
  padding: clamp(var(--bc-space-4), 3vw, var(--bc-space-6));
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-page);
}

/*
 * A card owns the rhythm between its direct children. Metric cards are the
 * deliberate compact exception and keep their tighter gap below. Card-to-card
 * spacing belongs to the parent page/grid — never to a sibling selector,
 * because the same .card class is also used by tiles inside CSS Grid.
 */
.card:not(.card--metric) {
  display: grid;
  align-content: start;
  gap: var(--bc-space-4);
}

.card__title {
  font-size: var(--bc-type-subhead);
  line-height: var(--bc-type-subhead-line);
  margin-block-end: 0;
}

.card__note {
  margin-block-start: 0;
  color: var(--bc-text-muted);
  font-size: var(--bc-type-small);
}

/* Parent grid gaps now own these components' outside spacing. */
.app-main > .toolbar,
.app-main > .metric-list,
.card > .toolbar,
.card > .cohort__counts,
.card > .cohort__standings,
.card > .disclosure {
  margin-block: 0;
}

/* .card--auth also carries .stack; suppress the stack's flow margin because
   the card grid above is the sole spacing owner. */
.card.stack > * + * {
  margin-block-start: 0;
}

/* Sign-in and other single-task surfaces sit in a narrow, centred card */
.card--auth {
  max-inline-size: 34rem;
  margin-inline: auto;
}

/*
 * UX-polish design accent, login.html only: a restrained wash of the brand's
 * light illustration gradient behind the sign-in card, scoped to
 * body.login-page so no other screen is affected. This is a dense internal
 * ops tool rather than a marketing site, so the gradient is confined to the
 * page background — .card--auth keeps its normal opaque Snow surface
 * (--bc-surface-page, unchanged) and every other region (header, env-strip,
 * footer) already paints its own opaque background over it. Reviewer: revert
 * this rule alone to drop the accent; nothing else on the page depends on it.
 */
body.login-page {
  background: var(--bc-gradient-illustration-light);
}

/* Printing a recovery code should never spend ink on a decorative background */
@media print {
  body.login-page {
    background: var(--bc-surface-page);
  }
}

/*
 * Metric cards (REQ-UI-016): Snow surface, Forest border, value in the heading
 * face. A subclass of .card rather than a new component, so the border, radius,
 * and padding stay in one place.
 */
/*
 * min(11rem, 100%) is the responsive-grid idiom: on a wide row the 11rem floor
 * sets the column count and 1fr shares the slack, but on a phone a column is
 * allowed to drop below 11rem rather than forcing the whole grid wider than
 * its container. Without the min() the four dashboard metrics overran the
 * viewport at 375px (auto-fit only collapses empty tracks, and all four are
 * filled), which is what produced the page-level horizontal scroll.
 */
#cycle {
  display: grid;
  gap: var(--bc-space-6);
}

.metric-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
  gap: var(--bc-space-4);
  margin-block-end: var(--bc-space-6);
  list-style: none;
}

#cycle > .metric-list {
  margin-block-end: 0;
}

.card--metric {
  display: grid;
  gap: var(--bc-space-1);
  align-content: start;
}

.metric__value {
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-heavy);
  font-size: var(--bc-type-subhead);
  line-height: var(--bc-type-subhead-line);
  color: var(--bc-text-heading);
}

.metric__label {
  font-size: var(--bc-type-small);
  color: var(--bc-text-heading);
}

/* ---------------------------------------------------------------- toolbars */
/*
 * A page-level state reading next to that page's one prominent CTA. Wraps to two
 * rows before the button shrinks, so the CTA keeps its target size at 320px.
 */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-3) var(--bc-space-6);
  align-items: center;
  justify-content: space-between;
  margin-block-end: var(--bc-space-6);
}

.toolbar__state {
  font-size: var(--bc-type-small);
  color: var(--bc-text-muted);
}

/*
 * The selected-recipient send bar (REQ-CEN-163..165). A toolbar rather than a card
 * because it belongs to the list beneath it, but it carries five parts instead of two,
 * so the row axis becomes a grid: space-between would push the count away from the
 * button that acts on it, and at 320px the parts would each land on their own line in an
 * unpredictable order. Start-aligned and column-flowing below the tablet step so the
 * reading order stays count, reasons, choice, action at every width.
 *
 * Painted with the notice surface: it is the one control on this screen that can put a
 * message in front of a client, so it should not read as part of the table furniture.
 */
.toolbar--selection {
  display: grid;
  gap: var(--bc-space-3);
  justify-content: start;
  padding: var(--bc-space-4);
  border: 1px solid var(--bc-border-emphasis);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-notice);
}

.toolbar__title {
  margin: 0;
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-medium);
  font-size: var(--bc-type-body);
}

/* The skip chips report counts only and are never pressable, so they drop the pointer
   affordances the filter chips carry and keep only the tone accent. */
.toolbar__reasons .stat-chip {
  cursor: default;
}

/* Why the action cannot run. Body colour rather than the muted reading a toolbar state
   uses: this is the sentence that explains a control the operator just tried to press. */
.toolbar__reason {
  margin: 0;
  max-inline-size: 60ch;
  font-size: var(--bc-type-small);
  color: var(--bc-text-body);
}

.toolbar__operation {
  margin-block-end: 0;
  background: var(--bc-surface-page);
}

/*
 * The choice is two radios in the shared checkbox row, extended here rather than
 * duplicated: a radio renders around 13px by default, below the 24px minimum target of
 * WCAG 2.2 success criterion 2.5.8, and accent-color tints the checked state with the
 * brand without a custom control.
 */
.checkbox-row input[type='radio'] {
  flex: none;
  inline-size: 1.5rem;
  block-size: 1.5rem;
  accent-color: var(--bc-cta-fill-accessible);
}

.toolbar__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-3);
  margin: 0;
}

/*
 * Active/History is a small two-choice extension of the existing outline buttons.
 * The pressed state uses the existing notice surface and a written aria-pressed
 * value, so color only reinforces the selected view.
 */
.work-view-switch {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-2);
  padding: 0;
  border: 0;
}

.work-view-switch .btn[aria-pressed='true'] {
  border-color: var(--bc-border-emphasis);
  background: var(--bc-surface-notice-strong);
  color: var(--bc-text-heading);
}

.work-view-status {
  max-inline-size: 22rem;
  margin-block-end: 0;
}

/* ------------------------------------------------------ task workspaces */
/*
 * These are layout refinements over existing cards, metrics, notices, and native
 * disclosures. They reduce repeated frames without creating a parallel component set.
 */
.workspace-identity {
  padding-block-end: var(--bc-space-4);
  border-block-end: 1px solid var(--bc-border-subtle);
}

.workspace-identity__state {
  margin: 0;
}

.workspace-identity__facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-1) var(--bc-space-3);
  margin: 0;
  font-size: var(--bc-type-small);
}

.workspace-identity__facts dt {
  color: var(--bc-text-muted);
}

.workspace-identity__facts dt::after {
  content: ':';
}

.workspace-identity__facts dd {
  font-weight: var(--bc-brand-weight-heading-bold);
}

.workspace-next-action {
  display: grid;
  gap: var(--bc-space-3);
  padding-block: var(--bc-space-4);
  padding-inline: var(--bc-space-4);
  border-inline-start: 0.25rem solid var(--bc-border-emphasis);
  background: var(--bc-surface-quiet);
}

.workspace-next-action__title {
  margin: 0;
  font-size: var(--bc-type-subhead);
}

.workspace-next-action .btn--secondary {
  color: var(--bc-text-heading);
}

.workspace-exceptions {
  display: grid;
  gap: var(--bc-space-2);
}

.workspace-exceptions .callout {
  padding-block: var(--bc-space-2);
  padding-inline: 0;
  border: 0;
  background: transparent;
}

.workspace-progress .metric-list {
  margin: 0;
}

.workspace-progress .card--metric {
  padding-block: var(--bc-space-2);
  padding-inline: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

.workspace-details {
  border-block: 1px solid var(--bc-border-subtle);
}

.workspace-details__body {
  display: grid;
  gap: var(--bc-space-4);
  padding-block-end: var(--bc-space-4);
}

.workspace-details__body > .card {
  padding-inline: 0;
  border: 0;
  border-block-start: 1px solid var(--bc-border-subtle);
  border-radius: 0;
  background: transparent;
}

/* ------------------------------------------------------------------ filters */
/*
 * Filter controls: one column on a phone, filling out as there is room. The
 * fields keep their own .field margins, so the grid only sets the columns.
 */
.filter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
  gap: 0 var(--bc-space-4);
  align-items: end;
}

.filter-grid--groups {
  align-items: start;
}

.filter-grid .field {
  margin-block-end: var(--bc-space-4);
}

/* The actions span the whole row rather than sitting in a column of their own */
.filter-grid__actions {
  grid-column: 1 / -1;
  margin-block-end: var(--bc-space-2);
}

.filter-grid__wide {
  grid-column: 1 / -1;
}

/* ------------------------------------------------------- renewal calendar */
.month + .month {
  margin-block-start: var(--bc-space-10);
}

.month__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-1) var(--bc-space-4);
  align-items: baseline;
  padding-block-end: var(--bc-space-2);
  margin-block-end: var(--bc-space-4);
  border-block-end: 2px solid var(--bc-border-emphasis);
}

.cohort + .cohort {
  margin-block-start: var(--bc-space-4);
}

.cohort__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-2) var(--bc-space-4);
  align-items: center;
  justify-content: space-between;
}

.cohort__date {
  font-size: 1.25rem;
}

/* Counts read as label-and-value pairs rather than as a table of two rows */
.cohort__counts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-1) var(--bc-space-6);
  margin-block-start: var(--bc-space-2);
  font-size: var(--bc-type-small);
}

.cohort__counts dt {
  color: var(--bc-text-muted);
}

.cohort__counts dt::after {
  content: ':';
}

.cohort__counts dd {
  font-weight: var(--bc-brand-weight-heading-bold);
}

/*
 * The campaign reading and the batch reading, side by side on a wide screen and
 * stacked on a narrow one. They are two columns of a grid and never a single
 * combined figure: the two aggregates stay separate (section 15.2).
 */
.cohort__standings {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  gap: var(--bc-space-4);
  margin-block: var(--bc-space-4);
}

/* The work-path switch and, when the move is closed, the sentence saying why */
.cohort__path {
  display: grid;
  gap: var(--bc-space-1);
  margin-block: var(--bc-space-4);
  padding-block: var(--bc-space-3);
  border-block: 1px solid var(--bc-border-subtle);
}

.standing {
  padding: var(--bc-space-3);
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-quiet);
}

.standing__label {
  font-size: var(--bc-type-small);
  color: var(--bc-text-muted);
}

.standing__value {
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-medium);
}

.standing__link {
  color: inherit;
  text-underline-offset: 0.15em;
}

.calendar-conflict {
  align-items: flex-start;
}

.calendar-conflict__list {
  margin-block-start: var(--bc-space-1);
}

/* --------------------------------------------------------------- disclosure */
/*
 * Native <details>: the platform supplies the toggle, its keyboard behavior, and
 * its expanded state, so there is no ARIA to keep in sync.
 */
.disclosure {
  margin-block-start: var(--bc-space-4);
  border-block-start: 1px solid var(--bc-border-subtle);
  padding-block-start: var(--bc-space-3);
}

.disclosure__summary {
  /* Comfortably above the 24px minimum target of WCAG 2.2 SC 2.5.8. */
  min-block-size: 2.75rem;
  display: flex;
  align-items: center;
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-medium);
  font-size: var(--bc-type-small);
  color: var(--bc-cta-text-accessible);
  cursor: pointer;
}

.disclosure__summary:hover {
  text-decoration: underline;
}

.disclosure__summary:active {
  color: var(--bc-brand-mint-1000);
  text-decoration: underline;
}

/* ------------------------------------------------------------------ buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--bc-space-2);
  min-block-size: 2.75rem;
  padding-block: var(--bc-space-2-5);
  padding-inline: var(--bc-space-5);
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-bold);
  font-size: var(--bc-type-body);
  line-height: 1.2;
  border: 2px solid transparent;
  border-radius: var(--bc-radius-md);
  background: transparent;
  color: var(--bc-cta-text-accessible);
  text-decoration: none;
  cursor: pointer;
}

/* The one prominent CTA per view: Snow on Mint (REQ-UI-014) */
.btn--primary {
  background: var(--bc-cta-fill-accessible);
  color: var(--bc-cta-on-fill);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.btn--primary:hover {
  background: var(--bc-cta-fill-accessible-hover);
}

/* One step darker than :hover, so a press reads distinctly from a hover linger */
.btn--primary:active {
  background: var(--bc-brand-mint-1000);
}

/* Secondary CTAs: Mint text treatment, sentence case */
.btn--secondary {
  padding-inline: var(--bc-space-2);
}

.btn--secondary:hover {
  text-decoration: underline;
}

.btn--secondary:active {
  color: var(--bc-brand-mint-1000);
  text-decoration: underline;
}

.btn--outline {
  border-color: var(--bc-border-emphasis);
  color: var(--bc-text-body);
}

.btn--outline:hover {
  background: var(--bc-surface-quiet);
}

.btn--outline:active {
  background: var(--bc-brand-forest-200);
}

/* Destructive actions read as weight and wording, since the palette has no red */
.btn--danger {
  background: var(--bc-surface-dark);
  color: var(--bc-text-on-dark);
}

.btn--danger:hover {
  background: var(--bc-brand-forest-800);
}

.btn--danger:active {
  background: var(--bc-brand-forest-900);
}

.btn--sm {
  min-block-size: 2.25rem;
  padding-block: var(--bc-space-1-5);
  padding-inline: var(--bc-space-3);
  font-size: var(--bc-type-small);
}

/* Disabled controls are exempt from the contrast minimum (WCAG 1.4.3) */
.btn[disabled],
.btn[aria-disabled='true'] {
  opacity: 0.6;
  cursor: not-allowed;
}

.inline-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bc-space-3);
}

.row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-1-5);
}

/* -------------------------------------------------------------------- forms */
/*
 * Vertical rhythm is owned by exactly one layer per context, never two:
 *
 * - In a plain flow form (login, admin create-user, batch create) the .field's
 *   own margin-block-end is the only thing separating it from the next field.
 * - In a .form grid the container's row gap owns that separation instead, so
 *   the field's margin is lifted to keep the two from stacking into a gap
 *   twice the intended size.
 * - .fieldset keeps its own bottom margin only when it sits in a plain flow
 *   form; inside .form the same gap rule applies.
 *
 * The result: a label/input pair reads at the same spacing whether it appears
 * on the login card, the batch create panel, or the template composer.
 */
.form {
  display: grid;
  gap: var(--bc-space-5);
}

.form .field,
.form .fieldset {
  margin-block-end: 0;
}

/* Row layout for a short form: one label/input plus its buttons (REQ-UI-021) */
.form--inline {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--bc-space-4) var(--bc-space-5);
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-3);
}

.field {
  display: grid;
  gap: var(--bc-space-1-5);
  margin-block-end: var(--bc-space-5);
}

.field__label {
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-medium);
}

.field__control {
  font: inherit;
  font-size: var(--bc-type-body);
  min-inline-size: 0;
  inline-size: 100%;
  min-block-size: 2.75rem;
  padding-block: var(--bc-space-2);
  padding-inline: var(--bc-space-3);
  border: 1px solid var(--bc-border-emphasis);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-page);
  color: var(--bc-text-body);
}

.field__control--code {
  font-family: ui-monospace, monospace;
  letter-spacing: 0.35em;
  max-inline-size: 12rem;
}

.field__control--code-area {
  resize: vertical;
  font-family: ui-monospace, 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
  font-size: 0.875em;
  line-height: 1.5;
}

/*
 * Keep the platform file picker intact: only its native button receives the
 * established outline-button treatment. The input remains focusable, keyboard
 * operable, and able to expose the selected filename to assistive technology.
 */
.field__control::file-selector-button {
  min-block-size: 2.25rem;
  margin-inline-end: var(--bc-space-3);
  padding-block: var(--bc-space-1-5);
  padding-inline: var(--bc-space-3);
  border: 1px solid var(--bc-border-emphasis);
  border-radius: var(--bc-radius-sm);
  background: var(--bc-surface-page);
  color: var(--bc-text-body);
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-bold);
  font-size: var(--bc-type-small);
  cursor: pointer;
}

.field__control::file-selector-button:hover {
  background: var(--bc-surface-quiet);
}

.field__control::file-selector-button:active {
  background: var(--bc-brand-forest-200);
}

.field__hint {
  font-size: var(--bc-type-small);
  color: var(--bc-text-muted);
}

/* Error text, not error color: the glyph and weight carry the meaning too */
.field__error {
  display: flex;
  gap: var(--bc-space-1-5);
  font-size: var(--bc-type-small);
  font-weight: var(--bc-brand-weight-heading-bold);
  color: var(--bc-text-error);
}

.field__error::before {
  content: '\26A0';
  font-weight: var(--bc-brand-weight-heading-bold);
}

.field__error:empty {
  display: none;
}

.field--invalid .field__control {
  border-width: 2px;
  background: var(--bc-surface-notice);
}

/*
 * Every fieldset in the Utility is a layout box, so it needs the shrink the UA
 * stylesheet withholds: browsers give `fieldset` an implicit `min-inline-size:
 * min-content`, which no other element carries, so a fieldset silently refuses to
 * narrow past its widest unbreakable child and pushes the whole page into horizontal
 * overflow at 320 CSS pixels instead. Resetting it here restores normal grid and flex
 * shrinking, matching the `min-inline-size: 0` already applied to .page-grid children
 * and the grid regions.
 */
.fieldset,
fieldset {
  min-inline-size: 0;
}

.fieldset {
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  padding: var(--bc-space-4);
  margin-block-end: var(--bc-space-5);
}

.fieldset__legend {
  padding-inline: var(--bc-space-1-5);
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-medium);
}

.checkbox-list {
  display: grid;
  gap: var(--bc-space-2);
  list-style: none;
}

.checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: var(--bc-space-2);
}

/* A default checkbox renders around 13px, below the 24px minimum target of
   WCAG 2.2 success criterion 2.5.8. The label is associated by "for" and so
   forms part of the same target, but enlarging the box removes the argument.
   accent-color tints the checked state with the brand rather than the
   operating system blue, and needs no custom control to do it. */
.checkbox-row input[type='checkbox'] {
  flex: none;
  inline-size: 1.5rem;
  block-size: 1.5rem;
  accent-color: var(--bc-cta-fill-accessible);
}

.checkbox-row label {
  /* Optically centres the first line of the label against the taller box. */
  padding-block-start: 0.125rem;
}

.checkbox-row__description {
  display: block;
  font-size: var(--bc-type-small);
  color: var(--bc-text-muted);
}

.divider-label {
  margin: 0;
  text-align: center;
  font-size: var(--bc-type-small);
  color: var(--bc-text-muted);
}

/* ----------------------------------------------------------------- callouts */
.callout {
  display: flex;
  gap: var(--bc-space-3);
  padding: var(--bc-space-4);
  border: 1px solid var(--bc-border-emphasis);
  border-inline-start-width: 0.375rem;
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-notice);
}

.callout--quiet {
  background: var(--bc-surface-quiet);
}

.callout__body {
  display: grid;
  gap: var(--bc-space-1);
}

.callout__title {
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-bold);
}

/* Correlation id: quiet, selectable, always available to quote to support */
.callout__reference {
  font-family: ui-monospace, monospace;
  font-size: var(--bc-type-small);
  color: var(--bc-text-muted);
  word-break: break-all;
}

.notice {
  align-items: flex-start;
}

.notice__tone {
  font-family: var(--bc-brand-font-heading);
  font-size: var(--bc-type-small);
  font-weight: var(--bc-brand-weight-heading-bold);
}

/*
 * Every reason a write was refused, under the one-line summary. Markers stay so
 * the count is visible at a glance: an author who sees four bullets knows to fix
 * four fields before resubmitting rather than discovering them one retry apiece.
 */
.notice__items {
  margin-block-start: var(--bc-space-1);
  padding-inline-start: var(--bc-space-5);
  font-size: var(--bc-type-small);
}

/*
 * The palette intentionally carries no red (REQ-UI-011). The written tone and
 * glyph distinguish these states first; the surface and start edge reinforce
 * the message without becoming its only signal.
 */
.notice--info {
  background: var(--bc-surface-page);
}

.notice--success {
  border-inline-start-color: var(--bc-cta-fill-accessible);
  background: var(--bc-surface-notice);
}

.notice--warning {
  background: var(--bc-surface-quiet);
}

.notice--error {
  border-inline-start-width: 0.5rem;
  background: var(--bc-surface-notice-strong);
}

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

/* ------------------------------------------------------------------- toasts */
/*
 * A toast is a callout with a lifetime, so it takes the callout's frame and adds
 * only what being transient requires: a fixed region that stays in view wherever
 * the page has been scrolled to, and a dismiss control.
 *
 * The palette allows no red (REQ-UI-011), so the two severities are told apart by
 * their glyph and by the word in .toast__tone, exactly as the badges are. The
 * start-edge tint is confirmation of a reading the operator has already had in
 * words, never the reading itself (REQ-UI-004).
 */
.toast-region {
  position: fixed;
  inset-block-end: var(--bc-space-4);
  inset-inline-end: var(--bc-space-4);
  z-index: 20;
  display: grid;
  gap: var(--bc-space-2);
  inline-size: min(28rem, calc(100vw - var(--bc-space-8)));
  /* The region spans a corner of the viewport even when empty; without this it
     would swallow clicks aimed at whatever sits underneath it. */
  pointer-events: none;
}

.toast {
  pointer-events: auto;
  align-items: flex-start;
  background: var(--bc-surface-page);
  box-shadow: 0 0.25rem 0.75rem rgb(0 0 0 / 18%);
}

.toast .callout__body {
  flex: 1;
  min-inline-size: 0;
}

.toast__tone {
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-bold);
  font-size: var(--bc-type-small);
}

.toast--success {
  border-inline-start-color: var(--bc-cta-fill-accessible);
  background: var(--bc-surface-notice);
}

.toast--warning {
  border-inline-start-color: var(--bc-border-emphasis);
  background: var(--bc-surface-quiet);
}

.toast--info {
  background: var(--bc-surface-page);
}

.toast__action,
.toast__dismiss {
  flex: none;
}

.toast__dismiss {
  margin-inline-start: auto;
}

@media (max-width: 30rem) {
  .toast-region {
    inset-inline: var(--bc-space-2);
    inline-size: auto;
  }
}

/* ------------------------------------------------------------------- tables */
.table-wrap {
  /*
   * A scroller only scrolls if it is allowed to be narrower than its contents. Without
   * this, the wrapper's automatic minimum equals the table's 48rem floor below, so a
   * narrow window widens the card, the card widens the page, and the whole document
   * scrolls sideways instead of the one table that was meant to. Same reset, and same
   * reason, as .tabulator-grid-region.
   *
   * min-inline-size alone is not sufficient: it lets the box shrink, but the table's
   * 806-pixel floor is still reported upward as an intrinsic contribution through every
   * intervening wrapper, so an ancestor grid track grows to fit it. inline-size
   * containment states the intent directly — this box's width is decided by its
   * container, never by its contents — which stops the contribution at this boundary
   * without needing a min-inline-size reset on every wrapper above it.
   */
  min-inline-size: 0;
  contain: inline-size;
  overflow-x: auto;
}

.data-table {
  inline-size: 100%;
  /*
   * Below this the seven columns crush into unreadable slivers, so the wrapper
   * scrolls instead. Scrolling a table sideways is understood; a column two
   * characters wide is not.
   */
  min-inline-size: 48rem;
  border-collapse: collapse;
}

.data-table caption {
  padding-block-end: var(--bc-space-3);
  color: var(--bc-text-muted);
  font-size: var(--bc-type-small);
  text-align: start;
}

.data-table th,
.data-table td {
  padding-block: var(--bc-space-1-5);
  padding-inline: var(--bc-space-2-5);
  border-block-end: 1px solid var(--bc-border-subtle);
  font-size: var(--bc-type-data);
  text-align: start;
  vertical-align: top;
}

.data-table thead th {
  border-block-end: 2px solid var(--bc-border-emphasis);
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-heavy);
  font-size: var(--bc-type-micro);
  /*
   * Column headings are labels rather than prose. Small caps with tracking keeps
   * them legible at micro size and separates the header band from the data band
   * without another rule or border.
   */
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.data-table tbody th {
  font-weight: var(--bc-brand-weight-heading-heavy);
}

/*
 * Tabulator owns row and cell DOM. This named outer region supplies only the
 * keyboard-reachable overflow boundary and brand framing; supported formatters
 * and callbacks own everything inside the grid.
 */
.tabulator-grid-region {
  min-inline-size: 0;
  contain: inline-size;
  overflow-x: auto;
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-page);
}

/*
 * The official Bootstrap 5 theme is loaded before this file. These scoped
 * overrides map its visual treatment to existing brand tokens without importing
 * Bootstrap's framework CSS or changing Tabulator's structural selectors.
 */
.tabulator-grid-region > .tabulator {
  border: 0;
  background: var(--bc-surface-page);
  color: var(--bc-text-body);
  font-family: var(--bc-brand-font-body);
  font-size: var(--bc-type-data);
  text-align: start;
}

.tabulator-grid-region > .tabulator .tabulator-header,
.tabulator-grid-region > .tabulator .tabulator-header .tabulator-col {
  background: var(--bc-surface-page);
  color: var(--bc-text-heading);
}

.tabulator-grid-region > .tabulator .tabulator-header {
  border-block-end: 2px solid var(--bc-border-emphasis);
}

/* Same label treatment as .data-table thead th, for the same reason. */
.tabulator-grid-region > .tabulator .tabulator-header .tabulator-col-title {
  font-family: var(--bc-brand-font-heading);
  font-size: var(--bc-type-micro);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.grid-controls {
  display: flex;
  justify-content: flex-end;
  margin-block: var(--bc-space-2);
}

.toggle-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--bc-space-2);
  min-block-size: 2.75rem;
  font-size: var(--bc-type-small);
  font-weight: var(--bc-brand-weight-heading-medium);
  cursor: pointer;
}

.toggle-switch__input {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  opacity: 0;
}

.toggle-switch__track {
  position: relative;
  flex: none;
  inline-size: 2.75rem;
  block-size: 1.5rem;
  border: 1px solid var(--bc-border-emphasis);
  border-radius: var(--bc-radius-pill);
  background: var(--bc-surface-quiet);
}

.toggle-switch__track::after {
  position: absolute;
  inset-block-start: 0.1875rem;
  inset-inline-start: 0.1875rem;
  inline-size: 1rem;
  block-size: 1rem;
  border-radius: 50%;
  background: var(--bc-surface-dark);
  content: '';
  transition: inset-inline-start 120ms ease;
}

.toggle-switch__input:checked + .toggle-switch__track {
  background: var(--bc-surface-notice-strong);
}

.toggle-switch__input:checked + .toggle-switch__track::after {
  inset-inline-start: calc(100% - 1.1875rem);
}

.toggle-switch__input:focus-visible + .toggle-switch__track {
  outline: 3px solid var(--bc-focus-ring);
  outline-offset: 2px;
}

.toggle-switch:has(.toggle-switch__input:disabled) {
  opacity: 0.6;
  cursor: not-allowed;
}

/*
 * The cohort work-path switch (WF-1). Same control as the grid toggle above, laid
 * out for a card rather than a toolbar: the question wraps onto its own line above
 * the switch, and the resolved path is written beside the track so the reading never
 * depends on the track's position or tint alone (REQ-UI-004).
 */
.toggle-switch--path {
  display: grid;
  /* The track sizes to itself; the written state takes the rest of the row. */
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: var(--bc-space-2);
  inline-size: 100%;
  font-weight: var(--bc-brand-weight-heading-medium);
}

.toggle-switch--path > .toggle-switch__question {
  grid-column: 1 / -1;
}

.toggle-switch--path .toggle-switch__state {
  font-family: var(--bc-brand-font-body);
  font-weight: normal;
}

.tabulator-grid-region > .tabulator .tabulator-header .tabulator-col-resize-handle {
  cursor: col-resize;
  border-inline-end: 2px solid var(--bc-border-subtle);
}

.tabulator-grid-region > .tabulator .tabulator-header .tabulator-col,
.tabulator-grid-region > .tabulator .tabulator-row .tabulator-cell {
  border-inline-end-color: var(--bc-border-subtle);
}

/*
 * Denser than Bootstrap's table-sm, applied only to operational grids. The vertical
 * padding is what decides rows-per-screen: at a 16px root each 0.0625rem removed from
 * padding-block returns about one row per 600 pixels of grid height.
 */
.tabulator-grid-region > .tabulator .tabulator-header .tabulator-col .tabulator-col-content,
.tabulator-grid-region > .tabulator .tabulator-row .tabulator-cell {
  padding-block: 0.1875rem;
  padding-inline: 0.4375rem;
}

.tabulator-grid-region > .tabulator .tabulator-row {
  min-block-size: 1.875rem;
  border-block-end: 1px solid var(--bc-border-subtle);
  background: var(--bc-surface-page);
  color: var(--bc-text-body);
}

.tabulator-grid-region > .tabulator .tabulator-row.tabulator-row-even {
  background: var(--bc-surface-quiet);
}

.tabulator-grid-region > .tabulator .tabulator-row:hover {
  background: var(--bc-surface-image-frame);
}

.tabulator-grid-region > .tabulator .tabulator-frozen {
  background: inherit;
}

.tabulator-grid-region > .tabulator .tabulator-cell:focus-visible,
.tabulator-grid-region > .tabulator .tabulator-col:focus-visible {
  outline: 3px solid var(--bc-focus-ring);
  outline-offset: -3px;
}

.tabulator-grid-region > .tabulator .tabulator-tableholder:focus-visible {
  outline: 3px solid var(--bc-focus-ring);
  outline-offset: -3px;
}

.tabulator-grid-region > .tabulator .tabulator-loader {
  background: var(--bc-surface-page);
  color: var(--bc-text-heading);
}

.tabulator-grid-region
  > .tabulator
  .tabulator-tableholder
  .tabulator-placeholder
  .tabulator-placeholder-contents {
  color: var(--bc-text-muted);
}

/* ------------------------------------------------------------------- badges */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--bc-space-1-5);
  padding-block: 0.125rem;
  padding-inline: var(--bc-space-2);
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-pill);
  font-size: var(--bc-type-small);
  white-space: nowrap;
}

/*
 * A badge whose label hasn't been populated yet (e.g. #batch-state on
 * batch.html before a batch loads, or in the not-found state) would render as
 * a thin empty bordered pill. Same rule as .status-region:empty — an element
 * with nothing to say should take up no space and paint no border.
 */
.badge:empty {
  display: none;
}

.badge--on {
  background: var(--bc-surface-notice);
  border-color: var(--bc-border-emphasis);
}

.badge--off {
  background: var(--bc-surface-quiet);
}

.badge--warn {
  background: var(--bc-surface-notice);
  border-color: var(--bc-border-emphasis);
}

.badge__glyph {
  font-weight: var(--bc-brand-weight-heading-bold);
}

/*
 * ---- Tones, and the shared glyph slot -------------------------------------
 *
 * A status badge replaces a sentence, so the reading has to survive without one.
 * Four tones cover every state these grids report, and each is distinguished by
 * three signals at once — fill, border weight, and the glyph the caller puts in the
 * icon slot — because the brand palette has no red and colour may never be the only
 * signal (REQ-UI-004, REQ-UI-011). Every combination below is Forest text on a Snow
 * or Mint tint, which measures well above 4.5:1.
 *
 * Tone meanings, fixed so one status does not read two ways on two screens:
 *   positive  — settled and good; nothing is owed
 *   attention — a person must do something
 *   neutral   — in progress, on the expected path
 *   muted     — deliberately out of scope; no longer expected to change
 */
.badge--positive {
  border-color: var(--bc-brand-mint-700);
  background: var(--bc-surface-notice);
  color: var(--bc-brand-mint-900);
}

.badge--attention {
  border-color: var(--bc-border-emphasis);
  border-width: 2px;
  background: var(--bc-surface-notice-strong);
  color: var(--bc-text-heading);
  font-weight: var(--bc-brand-weight-heading-medium);
}

.badge--neutral {
  border-color: var(--bc-border-subtle);
  background: var(--bc-surface-page);
  color: var(--bc-text-body);
}

.badge--muted {
  border-color: var(--bc-border-subtle);
  background: var(--bc-surface-quiet);
  color: var(--bc-text-muted);
}

/*
 * Grid-cell size. A row is 1.875rem, so a badge inside one cannot carry the standard
 * pill's padding without setting the row height itself.
 */
.badge--grid {
  gap: var(--bc-space-1);
  padding-block: 0;
  padding-inline: var(--bc-space-1-5);
  font-size: var(--bc-type-micro);
  line-height: 1.5;
}

/* ------------------------------------------------------------------- glyphs */
/*
 * The shared inline-SVG glyph (icons.mjs). It sizes from the text around it and
 * inherits its colour, so one glyph works in a badge, a button, and a legend without
 * a per-context rule. flex: none keeps it square when a long label wraps.
 */
.icon {
  flex: none;
  inline-size: 1em;
  block-size: 1em;
  vertical-align: -0.125em;
}

.badge__icon {
  /*
   * Slightly larger than the surrounding micro text: at 0.75rem a 1em stroke glyph
   * reads as a smudge, and the glyph is doing half the work of the badge.
   */
  inline-size: 1.15em;
  block-size: 1.15em;
}

/*
 * An icon-only control. It keeps the full 2.75rem pointer target of every other
 * button on non-fine pointers, and tightens to the row height only where a precise
 * pointer is present — a grid row cannot be 2.75rem tall and still be dense.
 * The accessible name lives on the button (icons.mjs sets aria-label and title).
 */
.btn--icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  padding: var(--bc-space-1-5);
  text-decoration: none;
  font-size: var(--bc-type-data);
}

.btn--icon > .icon {
  inline-size: 1.25em;
  block-size: 1.25em;
}

@media (pointer: fine) {
  .tabulator-grid-region .btn--icon,
  .data-table .btn--icon {
    min-block-size: 1.5rem;
    padding: var(--bc-space-1);
  }
}

/*
 * A count that is also a filter. Reading "7 need attention" and acting on it should
 * be one gesture rather than a sentence followed by a hunt for the control that
 * applies it (REQ-UI-040).
 */
.stat-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.stat-chip {
  display: inline-flex;
  align-items: baseline;
  gap: var(--bc-space-1-5);
  padding-block: var(--bc-space-1);
  padding-inline: var(--bc-space-3);
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-pill);
  background: var(--bc-surface-page);
  color: var(--bc-text-body);
  font-family: var(--bc-brand-font-body);
  font-size: var(--bc-type-data);
  line-height: 1.4;
  white-space: nowrap;
  cursor: pointer;
}

.stat-chip__count {
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-heavy);
  font-size: var(--bc-type-subhead);
  line-height: 1;
}

.stat-chip:hover {
  border-color: var(--bc-border-emphasis);
  background: var(--bc-surface-quiet);
}

.stat-chip:focus-visible {
  outline: 3px solid var(--bc-focus-ring);
  outline-offset: 2px;
}

/*
 * The applied filter is marked by weight, fill, and a border step as well as
 * aria-pressed, so the active chip is identifiable without colour vision and
 * without a screen reader.
 */
.stat-chip[aria-pressed='true'] {
  border-color: var(--bc-border-emphasis);
  border-width: 2px;
  background: var(--bc-surface-notice);
  font-weight: var(--bc-brand-weight-heading-medium);
}

.stat-chip:disabled {
  opacity: 0.55;
  cursor: default;
}

/*
 * Tone accents tie a chip to the badge tone it filters for, so the eye can move from
 * "7 Delivery" to the amber rows without re-reading either. The accent is a leading
 * edge rather than a fill: the pressed state already owns the fill, and a chip whose
 * whole background carried its tone would compete with the badges in the grid below.
 * Tone is never the only signal here — every chip states its count and its word.
 */
.stat-chip--positive {
  border-inline-start: 4px solid var(--bc-brand-mint-700);
}

.stat-chip--attention {
  border-inline-start: 4px solid var(--bc-border-emphasis);
}

.stat-chip--muted {
  border-inline-start: 4px solid var(--bc-border-subtle);
}

/*
 * The badge glossary. Closed by default (REQ-UI-040): the vocabulary is needed once,
 * while the grid it explains is needed continuously, so the explanation does not get
 * to hold the top of the workspace open.
 */
/*
 * A current lifecycle step paints its panel with the quiet surface, which is darker
 * than the page. The shared CTA text colour is authored against the page surface and
 * reaches only 4.39:1 there, so a disclosure inside a step darkens one step to clear
 * 4.5:1 (WCAG 2.2 AA, 1.4.3) rather than the whole palette moving for one context.
 */
.campaign-step__body .disclosure__summary {
  color: var(--bc-brand-mint-900);
}

.legend__list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--bc-space-2) var(--bc-space-3);
  align-items: baseline;
  margin: 0;
  font-size: var(--bc-type-data);
}

.legend__list dt {
  margin: 0;
}

.legend__list dd {
  margin: 0;
  color: var(--bc-text-muted);
}

/*
 * Wave dots: which reminders a group has been sent, as a row of filled and hollow
 * marks. The adjacent "2/3" text carries the same fact, so the dots are decorative
 * and marked aria-hidden by the formatter that builds them.
 */
.wave-dots {
  display: inline-flex;
  gap: 0.1875rem;
  align-items: center;
}

.wave-dot {
  inline-size: 0.4375rem;
  block-size: 0.4375rem;
  border: 1px solid var(--bc-brand-mint-800);
  border-radius: 50%;
}

.wave-dot--sent {
  background: var(--bc-brand-mint-800);
}

/* A glyph-plus-word cell: the smallest unit that is still not a sentence. */
.cell-fact {
  display: inline-flex;
  align-items: center;
  gap: var(--bc-space-1-5);
  white-space: nowrap;
}

.cell-fact--muted {
  color: var(--bc-text-muted);
}

.role-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-1);
  list-style: none;
}

/*
 * Campaign engagement rollups (e.g. "SendSucceeded: 12"): each status/count
 * pair reads as its own chip, following the .standing/.badge visual language
 * rather than a table, since the set is small and unordered.
 */
.stat-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-2);
  list-style: none;
}

.stat-list li {
  padding-block: var(--bc-space-1);
  padding-inline: var(--bc-space-3);
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-quiet);
  font-size: var(--bc-type-small);
  font-weight: var(--bc-brand-weight-heading-medium);
}

/* ----------------------------------------------------------------- dialogs */
/*
 * Native <dialog> with showModal(): the focus trap, the backdrop, and Escape
 * handling come from the platform rather than from hand-rolled key handling
 * that would be one bug away from trapping a keyboard user.
 */
dialog {
  max-inline-size: min(36rem, 92vw);
  padding: clamp(var(--bc-space-4), 3vw, var(--bc-space-7));
  border: 1px solid var(--bc-border-emphasis);
  border-radius: var(--bc-radius-lg);
  background: var(--bc-surface-page);
  color: var(--bc-text-body);
}

.campaign-preview-dialog {
  inline-size: min(64rem, 96vw);
  max-inline-size: min(64rem, 96vw);
}

/*
 * ::backdrop resolves custom properties in current browsers; where it does not,
 * the backdrop simply stays transparent and the dialog remains usable.
 */
dialog::backdrop {
  background-color: var(--bc-surface-dark);
  opacity: 0.65;
}

.dialog__title {
  font-size: var(--bc-type-subhead);
  line-height: var(--bc-type-subhead-line);
  margin-block-end: var(--bc-space-3);
}

.dialog__body {
  display: grid;
  gap: var(--bc-space-3);
}

.dialog__body > .notice {
  inline-size: 100%;
}

.dialog__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-3);
  justify-content: flex-end;
  margin-block-start: var(--bc-space-6);
}

/* --------------------------------------------------- one-time secrets, QR */
.secret {
  display: block;
  padding: var(--bc-space-3);
  border: 1px dashed var(--bc-border-emphasis);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-image-frame);
  font-family: ui-monospace, monospace;
  font-size: var(--bc-type-lockup);
  letter-spacing: 0.05em;
  word-break: break-all;
}

.code-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(9rem, 100%), 1fr));
  gap: var(--bc-space-2);
  list-style: none;
}

.code-grid li {
  padding-block: var(--bc-space-2);
  padding-inline: var(--bc-space-1);
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-sm);
  font-family: ui-monospace, monospace;
  text-align: center;
}

.qr-frame {
  inline-size: fit-content;
  padding: var(--bc-space-3);
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-page);
}

.qr-frame svg {
  display: block;
  inline-size: min(14rem, 60vw);
  block-size: auto;
}

/* --------------------------------------------------------------- utilities */
.visually-hidden {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.muted {
  color: var(--bc-text-muted);
}

/* Async placeholders should not create two parent-grid gaps while empty. */
.muted:empty {
  display: none;
}

.stack > * + * {
  margin-block-start: var(--bc-space-4);
}

/*
 * Staging progress. The native element carries the value, so a screen reader reads
 * the percentage without a role and the bar is only the visual half of it; the live
 * region beside it carries the words. Styled for both engine conventions because
 * neither honors the other's pseudo-element.
 */
.progress {
  inline-size: 100%;
  max-inline-size: 24rem;
  block-size: 0.75rem;
  appearance: none;
  border: 1px solid var(--bc-border-emphasis);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-image-frame);
}

.progress::-webkit-progress-bar {
  background: var(--bc-surface-image-frame);
  border-radius: var(--bc-radius-md);
}

.progress::-webkit-progress-value {
  background: var(--bc-cta-fill-accessible);
  border-radius: var(--bc-radius-md);
}

.progress::-moz-progress-bar {
  background: var(--bc-cta-fill-accessible);
  border-radius: var(--bc-radius-md);
}

.placeholder {
  border-style: dashed;
}

/* ------------------------------------------- S13 settings and preferences */
/*
 * A subclass of .card, not a new component: the border, radius, padding, and
 * internal gap all stay with .card, and everything below is the small amount
 * this panel genuinely adds. Every field inside reuses .field, .fieldset,
 * .field__label, .field__hint, .field__error, and .badge unchanged.
 */
.settings-panel__revision {
  justify-self: start;
}

.settings-panel__sections {
  display: grid;
  gap: var(--bc-space-6);
}

/*
 * One <fieldset class="fieldset settings-section"> per catalog section. The
 * fieldset's own bottom margin is lifted because the grid gap above already owns
 * the separation, which is the same single-owner rule the .form grid follows.
 */
.settings-section {
  display: grid;
  gap: var(--bc-space-4);
  margin-block-end: 0;
}

.settings-section__intro {
  margin-block: 0;
  max-inline-size: 46rem;
  color: var(--bc-text-muted);
  font-size: var(--bc-type-small);
}

/*
 * Two columns once there is room, because a settings field is a short control
 * with a long explanation and a single column wastes most of a 1080p row. The
 * min() floor lets a column drop below 22rem on a phone rather than forcing the
 * grid wider than its container, matching the .metric-list idiom above.
 */
.settings-section__fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
  gap: var(--bc-space-5);
}

/*
 * The .field inside this grid gets its separation from the grid's row gap, the
 * same trade the .form grid makes above, so the two spacing owners cannot stack.
 */
.settings-section__fields .field {
  margin-block-end: 0;
}

/* The state line under a control: where the value came from, and its mask. */
.settings-field__state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bc-space-2);
  font-size: var(--bc-type-small);
  color: var(--bc-text-muted);
}

/*
 * The masked stand-in for a stored secret. It shares the dashed monospace
 * treatment of .secret so the two read as the same kind of thing, at body size
 * because this one is a placeholder rather than a value to be transcribed.
 */
.settings-field__mask {
  padding-block: 0.125rem;
  padding-inline: var(--bc-space-2);
  border: 1px dashed var(--bc-border-emphasis);
  border-radius: var(--bc-radius-sm);
  font-family: ui-monospace, monospace;
  letter-spacing: 0.1em;
}

.settings-section__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bc-space-3);
}

/* ------------------------------------------------------------------- pager */
.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--bc-space-sm);
  margin-block-start: var(--bc-space-md);
}

.pager__status {
  margin: 0;
  color: var(--bc-text-muted, inherit);
  font-size: var(--bc-type-small);
}

[hidden] {
  display: none !important;
}

/* -------------------------------------------------------- template composer */
.page-grid--templates {
  gap: var(--bc-space-6);
}

.template-conflict-recovery {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

.template-conflict-recovery > *,
.template-conflict-recovery .btn {
  min-inline-size: 0;
  max-inline-size: 100%;
  overflow-wrap: anywhere;
}

.template-history {
  grid-template-columns: minmax(0, 1fr);
}

.template-history > * {
  min-inline-size: 0;
}

.template-legacy-review {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

.template-legacy-review > * {
  min-inline-size: 0;
  overflow-wrap: anywhere;
}

.template-legacy-delete {
  max-inline-size: 100%;
  overflow-wrap: anywhere;
}

.template-history__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--bc-space-3);
}

.template-threshold {
  align-items: end;
  padding: var(--bc-space-4);
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-quiet);
}

.template-threshold .field {
  flex: 1 1 20rem;
  margin-block-end: 0;
}

.template-threshold input {
  max-inline-size: 10rem;
}

.template-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  gap: var(--bc-space-3);
  list-style: none;
}

.template-workspace {
  display: grid;
  gap: var(--bc-space-6);
}

.template-list__select {
  inline-size: 100%;
  block-size: 100%;
  text-align: start;
  padding: var(--bc-space-3);
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-image-frame);
  color: var(--bc-text-body);
  cursor: pointer;
  font: inherit;
}

.template-list__select:hover {
  border-color: var(--bc-border-emphasis);
  background: var(--bc-surface-quiet);
}

.template-list__item.is-selected .template-list__select {
  border-color: var(--bc-border-emphasis);
  background: var(--bc-surface-notice-strong);
}

.template-token-panel {
  display: grid;
  gap: var(--bc-space-2);
  padding: var(--bc-space-4);
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-quiet);
}

.placeholder-picker {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-2);
}

.placeholder-picker__btn {
  min-block-size: 2.25rem;
  padding-block: var(--bc-space-1);
  padding-inline: var(--bc-space-2);
  border: 1px solid var(--bc-border-emphasis);
  border-radius: var(--bc-radius-sm);
  background: var(--bc-surface-page);
  color: var(--bc-text-body);
  cursor: pointer;
  font-family: ui-monospace, 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
  font-size: 0.875em;
  /*
   * A placeholder label is one unbroken run of characters, so the longest of them
   * ({{ParticipantInstructionsUrl}}) is wider than a 320-pixel column can hold. Allow a
   * break anywhere rather than let one token decide the panel's minimum width; the break
   * only ever happens when the alternative is clipping.
   */
  overflow-wrap: anywhere;
}

.placeholder-picker__btn:hover {
  background: var(--bc-surface-notice);
}

.merge-field-reference {
  margin-block-start: 0;
  padding-block-start: 0;
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
}

.merge-field-reference > .disclosure__summary {
  padding-inline: var(--bc-space-4);
}

.merge-field-reference__body {
  display: grid;
  gap: var(--bc-space-4);
  padding: var(--bc-space-4);
  border-block-start: 1px solid var(--bc-border-subtle);
}

.merge-field-reference__controls,
.merge-field-reference__groups {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--bc-space-4);
}

.merge-field-reference__controls .field {
  margin-block-end: 0;
}

.merge-field-reference__group {
  min-inline-size: 0;
}

.merge-field-reference__group-title {
  margin-block-end: var(--bc-space-2);
}

.merge-field-reference__list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--bc-space-3);
  list-style: none;
}

.merge-field-reference__entry {
  min-inline-size: 0;
  display: grid;
  gap: var(--bc-space-3);
  padding: var(--bc-space-3);
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-page);
}

.merge-field-reference__token {
  display: block;
  min-inline-size: 0;
  padding-block: var(--bc-space-1);
  padding-inline: var(--bc-space-2);
  overflow-wrap: anywhere;
  border-radius: var(--bc-radius-sm);
  background: var(--bc-surface-image-frame);
  font-family: ui-monospace, 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
}

.merge-field-reference__definitions {
  display: grid;
  gap: var(--bc-space-2);
  margin: 0;
}

.merge-field-reference__definitions > div {
  min-inline-size: 0;
  display: grid;
  gap: var(--bc-space-1);
}

.merge-field-reference__definitions dt {
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-bold);
  color: var(--bc-text-heading);
}

.merge-field-reference__definitions dd {
  min-inline-size: 0;
  margin: 0;
  overflow-wrap: anywhere;
}

.merge-field-reference__actions {
  align-items: center;
}

.merge-field-reference__actions .btn {
  max-inline-size: 100%;
  overflow-wrap: anywhere;
}

.merge-field-guidance {
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
}

.merge-field-guidance p {
  min-inline-size: 0;
  flex: 1 1 16rem;
  overflow-wrap: anywhere;
}

.template-section {
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-page);
  overflow: clip;
}

.template-section__summary {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--bc-space-1) var(--bc-space-3);
  align-items: center;
  padding: var(--bc-space-4);
  color: var(--bc-text-heading);
  cursor: pointer;
  list-style: none;
}

.template-section__summary::-webkit-details-marker {
  display: none;
}

.template-section__summary::before {
  content: '+';
  grid-row: 1 / span 2;
  font-family: var(--bc-brand-font-heading);
  font-size: var(--bc-type-subhead);
  font-weight: var(--bc-brand-weight-heading-medium);
  color: var(--bc-cta-text-accessible);
}

.template-section[open] > .template-section__summary::before {
  content: '\2212';
}

.template-section__summary:hover {
  background: var(--bc-surface-quiet);
}

.template-section__title {
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-heavy);
}

.template-section__help {
  grid-column: 2;
  color: var(--bc-text-muted);
  font-size: var(--bc-type-small);
}

.template-section__body {
  display: grid;
  gap: var(--bc-space-4);
  padding: var(--bc-space-4);
  border-block-start: 1px solid var(--bc-border-subtle);
}

.template-composer .form-grid,
.template-section__grid {
  display: grid;
  gap: var(--bc-space-4);
}

#template-logo-source {
  display: grid;
  gap: var(--bc-space-4);
  min-inline-size: 0;
  padding: 0;
  border: 0;
}

#tmpl-logo-asset-panel,
#tmpl-logo-url-panel,
#tmpl-logo-asset-panel > section {
  display: grid;
  gap: var(--bc-space-4);
}

#tmpl-logo-preview {
  display: grid;
  gap: var(--bc-space-3);
  margin: 0;
  padding: var(--bc-space-4);
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-image-frame);
}

#tmpl-logo-thumbnail {
  display: block;
  inline-size: auto;
  block-size: auto;
  max-inline-size: min(240px, 100%);
  max-block-size: 80px;
  object-fit: contain;
}

#tmpl-logo-metadata {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(12rem, 100%), 1fr));
  gap: var(--bc-space-3);
  margin: 0;
}

#tmpl-logo-metadata > div {
  min-inline-size: 0;
}

#tmpl-logo-metadata dt {
  color: var(--bc-text-heading);
  font-weight: var(--bc-brand-weight-heading-bold);
}

#tmpl-logo-metadata dd {
  margin: 0;
  overflow-wrap: anywhere;
  color: var(--bc-text-body);
}

.email-editor-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-1);
  padding: var(--bc-space-2);
  border: 1px solid var(--bc-border-emphasis);
  border-block-end: 0;
  border-start-start-radius: var(--bc-radius-md);
  border-start-end-radius: var(--bc-radius-md);
  background: var(--bc-surface-quiet);
}

.body-mode-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-2);
  margin-block-end: var(--bc-space-3);
  padding-block-end: var(--bc-space-3);
  border-block-end: 1px solid var(--bc-border-subtle);
}

.body-mode-tabs .btn[aria-selected='true'] {
  border-color: var(--bc-border-emphasis);
  background: var(--bc-surface-notice-strong);
  color: var(--bc-text-heading);
}

.body-mode-panel {
  min-inline-size: 0;
}

.body-source {
  display: grid;
  gap: var(--bc-space-3);
}

.body-source__textarea {
  min-block-size: 18rem;
  resize: vertical;
  white-space: pre;
  overflow: auto;
  tab-size: 2;
  font-family: ui-monospace, 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
}

.body-source__actions {
  flex-wrap: wrap;
}

.body-source__report {
  min-inline-size: 0;
  padding: var(--bc-space-3);
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-quiet);
}

.body-source__report-title {
  margin-block-end: var(--bc-space-2);
  font-size: var(--bc-type-body);
}

.body-source__report-list {
  display: grid;
  gap: var(--bc-space-1);
  margin: 0;
  padding-inline-start: var(--bc-space-5);
  overflow-wrap: anywhere;
}

.body-source__metadata {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
  gap: var(--bc-space-3);
  margin: 0;
}

.body-source__metadata > div {
  min-inline-size: 0;
}

.body-source__metadata dt {
  color: var(--bc-text-heading);
  font-weight: var(--bc-brand-weight-heading-bold);
}

.body-source__metadata dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.body-visual-readonly {
  display: grid;
  gap: var(--bc-space-3);
}

.body-visual-readonly__frame {
  inline-size: 100%;
  min-block-size: 18rem;
  border: 1px solid var(--bc-border-emphasis);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-page);
}

.email-editor-toolbar .btn {
  min-block-size: 2.25rem;
  padding-inline: var(--bc-space-2);
  background: var(--bc-surface-page);
}

.email-editor-toolbar .btn[aria-pressed='true'] {
  border-color: var(--bc-border-emphasis);
  background: var(--bc-surface-notice-strong);
  color: var(--bc-text-heading);
}

.email-editor {
  min-block-size: 16rem;
  padding: var(--bc-space-4);
  overflow-wrap: anywhere;
  border: 1px solid var(--bc-border-emphasis);
  border-end-start-radius: var(--bc-radius-md);
  border-end-end-radius: var(--bc-radius-md);
  background: var(--bc-surface-page);
  color: var(--bc-text-body);
  line-height: var(--bc-type-body-line);
}

.email-editor:focus-visible {
  position: relative;
  z-index: 1;
}

.field--invalid .email-editor {
  border-width: 2px;
  background: var(--bc-surface-notice);
}

.email-editor ul,
.email-editor ol {
  padding-inline-start: var(--bc-space-6);
}

.email-editor blockquote {
  margin-block: var(--bc-space-3);
  padding-inline-start: var(--bc-space-4);
  border-inline-start: 0.25rem solid var(--bc-border-subtle);
  color: var(--bc-text-muted);
}

.email-editor div + div,
.email-editor p + p,
.email-editor ul + div,
.email-editor ol + div,
.email-editor blockquote + div {
  margin-block-start: var(--bc-space-2);
}

.template-plain-text__actions {
  justify-content: space-between;
}

/*
 * Sticky so Save/Preview/Publish stay reachable on the long composer form, with
 * unsaved state stated in words beside them (REQ-UI-031/036). The separator is a
 * box-shadow rather than a border so pinning the bar does not change its height.
 */
.template-actions {
  position: sticky;
  inset-block-end: 0;
  z-index: 2;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--bc-space-3);
  margin-block-start: var(--bc-space-4);
  padding-block: var(--bc-space-3);
  padding-inline: var(--bc-space-2);
  background: var(--bc-surface-page);
  box-shadow: 0 -1px 0 var(--bc-border-subtle);
}

.template-preview {
  display: grid;
  gap: var(--bc-space-4);
  min-block-size: 20rem;
}

.template-preview__empty {
  display: grid;
  min-block-size: 16rem;
  place-items: center;
  padding: var(--bc-space-6);
  border: 1px dashed var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-image-frame);
  color: var(--bc-text-muted);
  text-align: center;
}

.template-preview__frame {
  inline-size: 100%;
  block-size: 24rem;
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-page);
}

.template-preview__text {
  margin-block-start: 0;
  padding: var(--bc-space-4);
  white-space: pre-wrap;
  overflow: auto;
  border: 1px solid var(--bc-border-subtle);
  border-radius: var(--bc-radius-md);
  background: var(--bc-surface-image-frame);
  font-family: ui-monospace, 'Cascadia Code', 'Segoe UI Mono', Menlo, Consolas, monospace;
  font-size: 0.875em;
}

@media (min-width: 48rem) {
  .merge-field-reference__controls {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }

  .template-composer .form-grid {
    grid-template-columns: 1fr 1fr;
  }

  .template-section__grid {
    grid-template-columns: 1fr 1fr;
  }

  .template-section__full {
    grid-column: 1 / -1;
  }
}

/* Two panes only when both the editor and a useful preview width fit beside
   the persistent sidebar. Below this, stacking is calmer than squeezing. */
@media (min-width: 72rem) {
  .template-workspace {
    grid-template-columns: minmax(0, 1.45fr) minmax(18rem, 0.75fr);
    align-items: start;
  }

  .template-preview-card {
    position: sticky;
    inset-block-start: var(--bc-space-6);
    max-block-size: calc(100dvh - var(--bc-space-12));
    overflow-y: auto;
  }
}

/* ----------------------------------------------------- application shell */
/*
 * Authenticated pages use a real application shell rather than asking one
 * horizontal header to carry brand, account, and eight navigation items.
 * Mobile remains the base layout: a compact header opens the same grouped
 * navigation in a native dialog. At 56rem the header becomes a persistent
 * sidebar and the environment/work/footer form a separate workspace column.
 */
.app-shell .app-header__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--bc-space-4);
  align-items: center;
}

.app-shell .app-header__brand {
  min-inline-size: 0;
}

.app-shell .app-header__logo {
  max-inline-size: min(100%, 13rem);
}

.app-shell .app-header__utility-row {
  grid-column: 1 / -1;
  justify-content: space-between;
}

/* Roles are available in the account section on desktop; mobile keeps the
   identity concise so the header does not become another wall of text. */
@media (max-width: 55.999rem) {
  .app-shell .app-header__account-roles {
    display: none;
  }
}

.app-menu-toggle {
  grid-column: 2;
  grid-row: 1;
  align-self: center;
  border-color: var(--bc-brand-forest-300);
  color: var(--bc-text-on-dark);
}

.app-menu-toggle:hover {
  background: var(--bc-brand-forest-800);
}

.app-menu-toggle:active {
  background: var(--bc-brand-forest-900);
}

/* The nav is moved into this dialog below the desktop breakpoint. Native
   <dialog> supplies Escape handling, a focus trap, and an inert background. */
dialog.app-menu {
  inset-block: 0;
  inset-inline-start: auto;
  inset-inline-end: 0;
  inline-size: min(22rem, 100%);
  max-inline-size: none;
  block-size: 100dvh;
  max-block-size: none;
  margin: 0;
  padding: 0;
  border-block: 0;
  border-inline-end: 0;
  border-inline-start: 1px solid var(--bc-border-emphasis);
  border-radius: 0;
  overflow: hidden;
}

dialog.app-menu[open] {
  display: flex;
  flex-direction: column;
}

.app-menu__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--bc-space-4);
  padding: var(--bc-space-5);
  border-block-end: 1px solid var(--bc-border-subtle);
  background: var(--bc-surface-page);
}

.app-menu__title {
  font-size: var(--bc-type-subhead);
}

.app-menu__body {
  flex: 1;
  min-block-size: 0;
  padding: var(--bc-space-4);
  overflow-y: auto;
  background: var(--bc-surface-dark);
}

/* Grouped vertical navigation shared by the drawer and desktop sidebar. */
.app-shell .app-nav {
  display: grid;
  gap: var(--bc-space-5);
  margin-block-start: 0;
  padding-block-end: 0;
  border-block-end: 0;
}

.app-nav__group {
  display: grid;
  gap: var(--bc-space-2);
}

.app-nav__heading {
  padding-inline: var(--bc-space-3);
  font-size: var(--bc-type-small);
  font-weight: var(--bc-brand-weight-heading-medium);
  color: var(--bc-brand-forest-300);
}

.app-nav__list {
  display: grid;
  gap: var(--bc-space-1);
  list-style: none;
}

.app-nav__item {
  min-inline-size: 0;
}

.app-shell .app-nav__link {
  display: flex;
  inline-size: 100%;
  min-block-size: 2.75rem;
  align-items: center;
  padding-block: var(--bc-space-2);
  padding-inline: var(--bc-space-3);
  border-radius: var(--bc-radius-md);
  white-space: normal;
}

.app-shell .app-nav__link:hover {
  background: var(--bc-brand-forest-800);
  text-decoration: none;
}

.app-shell .app-nav__link[aria-current='page'] {
  background: var(--bc-surface-notice-strong);
  color: var(--bc-text-heading);
  font-weight: var(--bc-brand-weight-heading-bold);
  box-shadow: inset var(--bc-space-1) 0 0 var(--bc-brand-mint);
}

@media (min-width: 56rem) {
  body.app-shell {
    display: grid;
    grid-template-columns: clamp(15rem, 21vw, 17rem) minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto;
  }

  .app-shell .app-header {
    position: sticky;
    inset-block-start: 0;
    grid-column: 1;
    grid-row: 1 / -1;
    align-self: start;
    display: flex;
    flex-direction: column;
    block-size: 100dvh;
    padding: var(--bc-space-3) var(--bc-space-5);
    overflow-y: auto;
  }

  .app-shell .app-header__row {
    display: contents;
  }

  .app-shell .app-header__brand {
    order: 1;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: flex-start;
    gap: var(--bc-space-2);
    margin-block-end: var(--bc-space-5);
  }

  .app-shell .app-header__logo {
    inline-size: 100%;
    max-inline-size: 12.5rem;
    block-size: auto;
  }

  .app-shell .app-nav {
    order: 2;
    flex: 0 0 auto;
    gap: var(--bc-space-3);
  }

  .app-shell .app-nav__group {
    gap: var(--bc-space-1);
  }

  .app-shell .app-nav__list {
    gap: 0;
  }

  .app-shell .app-nav__link {
    min-block-size: 2.25rem;
    padding-block: var(--bc-space-1);
    padding-inline: var(--bc-space-3);
  }

  .app-shell .app-header__utility-row {
    order: 3;
    display: grid;
    gap: var(--bc-space-4);
    align-items: stretch;
    margin-block-start: auto;
    padding-block-start: var(--bc-space-5);
    border-block-start: 1px solid var(--bc-brand-forest-700);
  }

  .app-shell .app-header__account {
    display: grid;
    gap: var(--bc-space-1);
  }

  .app-shell .app-header__account > .app-header__account-roles {
    display: block;
    color: var(--bc-brand-forest-300);
    font-size: var(--bc-type-small);
    line-height: 1.35;
  }

  .app-shell .app-header__utility-row .btn {
    inline-size: 100%;
  }

  .app-menu-toggle {
    display: none;
  }

  .app-shell .env-strip {
    grid-column: 2;
    grid-row: 1;
  }

  .app-shell .app-main {
    grid-column: 2;
    grid-row: 2;
  }

  /*
   * Desktop operational workspaces spend space on the grid instead of a large
   * page gutter and vertical chrome. Standard pages continue using .app-main's
   * 70rem reading measure.
   */
  .app-shell .app-main--wide {
    max-inline-size: none;
    gap: var(--bc-space-4);
    padding-block: var(--bc-space-5);
    padding-inline: clamp(var(--bc-space-4), 2vw, var(--bc-space-8));
  }

  .app-shell .app-footer {
    grid-column: 2;
    grid-row: 3;
  }
}

/* Recovery codes and temporary passwords have to survive a print (REQ-AUTH-007) */
@media print {
  .app-header,
  .app-footer,
  .env-strip,
  .btn {
    display: none !important;
  }

  /*
   * Defensive fallback for openSecretReveal() (dialogs.mjs): its "Print" button
   * prints a temporary password or one-time recovery codes shown in a native
   * <dialog>, which is appended straight to <body>. Modern browsers already
   * scope printing to an open modal dialog's top layer, but that's engine
   * behavior, not something this stylesheet controls. If a browser instead
   * prints the whole page, this explicitly hides every other direct child of
   * <body> so the surrounding screen's content can never leak onto the page
   * alongside the secret.
   */
  body:has(dialog[open]) > :not(dialog) {
    display: none !important;
  }
}

.template-dirty {
  flex: 1 1 auto;
  margin: 0;
  color: var(--bc-text-muted);
  font-size: 0.875em;
}

.template-dirty:not([hidden]) {
  display: inline-block;
}

.template-list__select[aria-current='true'] {
  font-weight: var(--bc-brand-weight-heading-medium);
  text-decoration: underline;
}

/* ------------------------------------------------------- census campaign page */
/*
 * The campaign screen is one long lifecycle rather than a set of peer panels, so
 * it reads top to bottom: where the campaign is, what happens next, what is in the
 * way, then one card per step. Everything here extends an existing component —
 * .card, .badge, .callout, .disclosure__summary — rather than restating it, so a
 * change to the shared look reaches this page too.
 */

/*
 * A warning-toned callout. The palette carries no red (REQ-UI-011), so the tone is
 * carried by the heading text and a heavier start edge, as .notice--error does.
 */
.callout--warn {
  border-inline-start-width: 0.5rem;
  background: var(--bc-surface-notice-strong);
}

.campaign-header__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-3);
  align-items: baseline;
  justify-content: space-between;
}

.campaign-header {
  padding-inline: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

/*
 * A current step can contain a Tabulator whose measured desktop width remains wider
 * after a 200%-equivalent viewport change. The details card is itself a grid, so its
 * default auto track would adopt that stale intrinsic width and push the page sideways.
 * Keep the wide table inside the existing local scroller instead.
 */
.campaign-step {
  min-inline-size: 0;
  grid-template-columns: minmax(0, 1fr);
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}

.campaign-step[data-step-status='complete'],
.campaign-step[data-step-status='stopped'] {
  padding-block: var(--bc-space-2);
  border-block-start: 1px solid var(--bc-border-subtle);
}

.campaign-step[data-step-status='current'] {
  padding-block: var(--bc-space-4);
  padding-inline: var(--bc-space-4);
  border-inline-start: 0.25rem solid var(--bc-border-emphasis);
  background: var(--bc-surface-quiet);
}

.campaign-step[data-step-status='current'] > .campaign-step__summary {
  color: var(--bc-text-heading);
}

.campaign-step[data-step-status='current'] .btn--tertiary {
  color: var(--bc-text-heading);
}

/*
 * The stepper is a horizontal rail on a wide screen and a plain vertical list on a
 * narrow one, which is the mobile-first base: wrapping a rail into two ragged rows
 * loses the sense of order that the rail exists to convey.
 */
.campaign-stepper {
  display: flex;
  flex-direction: column;
  gap: var(--bc-space-2);
  margin: var(--bc-space-3) 0 0;
  padding: 0;
  list-style: none;
}

.campaign-stepper__step {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-1) var(--bc-space-2);
  align-items: baseline;
  padding-inline-start: var(--bc-space-3);
  border-inline-start: 0.25rem solid var(--bc-border-subtle);
}

.campaign-stepper__step--complete {
  border-inline-start-color: var(--bc-cta-fill-accessible);
}

.campaign-stepper__step--current {
  border-inline-start-color: var(--bc-border-emphasis);
}

.campaign-stepper__step--stopped {
  border-inline-start-style: dashed;
}

.campaign-stepper__name {
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-medium);
}

.campaign-stepper__step--upcoming .campaign-stepper__name {
  color: var(--bc-text-muted);
}

.campaign-stepper__status {
  color: var(--bc-text-muted);
  font-size: var(--bc-type-small);
}

@media (min-width: 56rem) {
  .campaign-stepper {
    flex-direction: row;
    flex-wrap: wrap;
  }

  .campaign-stepper__step {
    flex: 1 1 8rem;
    flex-direction: column;
    gap: var(--bc-space-1);
    padding-block-start: var(--bc-space-2);
    padding-inline-start: 0;
    border-inline-start: 0;
    border-block-start: 0.25rem solid var(--bc-border-subtle);
  }

  .campaign-stepper__step--complete {
    border-block-start-color: var(--bc-cta-fill-accessible);
  }

  .campaign-stepper__step--current {
    border-block-start-color: var(--bc-border-emphasis);
  }

  .campaign-stepper__step--stopped {
    border-block-start-style: dashed;
  }
}

/* The one sentence that says what to do now, so it outweighs the rail above it. */
.campaign-next {
  margin-block-start: var(--bc-space-3);
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-medium);
}

.campaign-current,
.campaign-counts {
  margin-block-start: var(--bc-space-1);
  color: var(--bc-text-muted);
  font-size: var(--bc-type-small);
}

.campaign-blockers {
  flex-direction: column;
  gap: var(--bc-space-2);
  margin-block-start: var(--bc-space-4);
}

.campaign-blockers__title {
  margin: 0;
  font-size: var(--bc-type-small);
}

/* A per-section host for renderNotice(); empty until something needs saying. */
.campaign-notice {
  margin-block-start: var(--bc-space-3);
}

.campaign-notice:empty {
  display: none;
}

/*
 * An action the server allows but has disabled stays in the tab order so its reason
 * can be read (REQ-UI-030), so it cannot rely on :disabled for its appearance.
 */
.campaign-action--unavailable {
  opacity: 0.65;
  cursor: not-allowed;
}

.campaign-action__reason {
  flex-basis: 100%;
  margin: 0;
  color: var(--bc-text-body);
  font-size: var(--bc-type-small);
}

.campaign-step__summary {
  flex-wrap: wrap;
  gap: var(--bc-space-3);
  justify-content: space-between;
  font-size: var(--bc-type-body);
}

.campaign-step__summary > * {
  min-inline-size: 0;
  overflow-wrap: anywhere;
}

.campaign-step__outcome,
.campaign-step__blocker {
  flex-basis: 100%;
}

.campaign-step__body {
  min-inline-size: 0;
  margin-block-start: var(--bc-space-4);
}

/*
 * The recipient card moves through an otherwise empty host between lifecycle steps.
 * Both wrappers are grid items, so their automatic minimum would adopt Tabulator's
 * desktop column width and widen the document instead of the local grid scroller.
 */
[data-recipients-host],
[data-overrides-host],
#campaign-recipients-section {
  min-inline-size: 0;
}

.campaign-step__body > .card {
  padding-inline: 0;
  border: 0;
  border-block-start: 1px solid var(--bc-border-subtle);
  border-radius: 0;
  background: transparent;
}

.campaign-exception,
.batch-exception {
  display: flex;
  gap: var(--bc-space-3);
  padding-block: var(--bc-space-2);
  border-block-start: 1px solid var(--bc-border-subtle);
}

.campaign-step__unavailable:empty {
  display: none;
}

.campaign-setup__actions {
  align-items: center;
}

/* Mirrors .template-dirty: a quiet, inline flag that work is unsaved. */
.campaign-dirty {
  margin: 0;
  color: var(--bc-text-muted);
  font-size: 0.875em;
}

.campaign-dirty:empty {
  display: none;
}

.campaign-worklist__row.is-selected {
  background: var(--bc-surface-quiet);
  font-weight: var(--bc-brand-weight-heading-medium);
}

/*
 * Preparation composes the existing card, badge, notice, stack, and button primitives.
 * These rules provide only the checklist/progress layout those primitives do not own.
 */
.campaign-preparation {
  display: grid;
  gap: var(--bc-space-3);
  margin-block-end: var(--bc-space-4);
}

.campaign-preparation__heading {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-2);
  align-items: baseline;
  justify-content: space-between;
}

.campaign-preparation__progress {
  inline-size: 100%;
  accent-color: var(--bc-cta-fill-accessible);
}

.campaign-preparation__prerequisites {
  display: grid;
  gap: var(--bc-space-1);
  padding-inline-start: var(--bc-space-5);
}

.campaign-preparation__prerequisite {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bc-space-2);
  justify-content: space-between;
}

/*
 * A grid Preview remains a full-size native target, but its quiet underline prevents
 * a row-level inspection action from competing with the current workflow action.
 */
.btn--tertiary {
  border-color: transparent;
  background: transparent;
  color: var(--bc-cta-text-accessible);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-transform: none;
}

.btn--tertiary:hover {
  border-color: var(--bc-border-subtle);
  background: var(--bc-surface-quiet);
}

.btn--tertiary:active {
  background: var(--bc-surface-notice-strong);
}

.campaign-grid__action {
  white-space: nowrap;
}

/* The row and header selection control. Sized and tinted on the same reasoning as
   .checkbox-row input[type='checkbox'] above: a default checkbox renders near 13px,
   under the 24px minimum target of WCAG 2.2 success criterion 2.5.8, and a grid cell
   gives it no associated visible label to borrow target area from. The margin reset
   is what lets the frozen column stay narrow at that size. */
.campaign-grid__select {
  inline-size: 1.5rem;
  block-size: 1.5rem;
  margin: 0;
  accent-color: var(--bc-cta-fill-accessible);
}

/* Motion safety (A11Y-022) */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* -------------------------------------------------------- batch wayfinding */
/*
 * Wave 2B additions for the batch detail page (REQ-UI-032, REQ-UI-033,
 * REQ-UI-036). All classes are namespaced under `batch` so they never collide
 * with the shared component layer above. Severity and step position are never
 * carried by colour alone: the current step and the submit blocker each say so
 * in words, and the tint only reinforces them (REQ-UI-011, WCAG 2.2 AA 1.4.1).
 */
.batch-breadcrumb {
  margin-block-end: var(--bc-space-3);
  font-family: var(--bc-brand-font-body);
  font-size: var(--bc-type-small);
}

.batch-breadcrumb__link {
  color: var(--bc-text-body);
}

.batch-lifecycle {
  margin-block: var(--bc-space-4);
  padding-block: var(--bc-space-3);
  padding-inline: 0;
  border-block: 1px solid var(--bc-border-subtle);
}

.batch-lifecycle__list {
  display: flex;
  flex-direction: column;
  gap: var(--bc-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: batch-step;
}

.batch-lifecycle__item {
  counter-increment: batch-step;
}

/*
 * The numbered marker orients the reader in a sequence without relying on the
 * order of links alone; it is decorative, so it is spoken by neither the count
 * nor the state word that follows it.
 */
.batch-lifecycle__item::before {
  content: counter(batch-step) '. ';
  color: var(--bc-text-muted);
}

.batch-lifecycle__link {
  color: var(--bc-text-body);
}

.batch-lifecycle__toggle {
  padding: 0;
  border: 0;
  background: transparent;
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}

.batch-lifecycle__toggle:focus-visible {
  outline: 3px solid var(--bc-focus-ring);
  outline-offset: 2px;
}

.batch-lifecycle__link--upcoming {
  color: var(--bc-text-muted);
}

.batch-lifecycle__state {
  margin-inline-start: var(--bc-space-2);
  font-family: var(--bc-brand-font-heading);
  font-size: var(--bc-type-small);
  font-weight: var(--bc-brand-weight-heading-bold);
  color: var(--bc-text-muted);
}

/*
 * The current step is the one the reader is meant to act on, so its link is
 * given the heading weight and forest ink the rest of the list lacks. The word
 * in .batch-lifecycle__state carries the same meaning for anyone who cannot see
 * the weight.
 */
.batch-lifecycle__item--current .batch-lifecycle__link {
  font-family: var(--bc-brand-font-heading);
  font-weight: var(--bc-brand-weight-heading-bold);
  color: var(--bc-text-heading);
}

.batch-lifecycle__item--complete .batch-lifecycle__state {
  color: var(--bc-cta-fill-accessible);
}

.batch-workspace__section {
  padding-inline: 0;
  border: 0;
  border-block-start: 1px solid var(--bc-border-subtle);
  border-radius: 0;
  background: transparent;
}

.batch-workspace__section--concealed {
  display: none !important;
}

@media (min-width: 48rem) {
  .batch-lifecycle__list {
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--bc-space-2) var(--bc-space-5);
  }
}

/*
 * The reason a batch cannot be submitted yet, sat directly under the next-step
 * text and pointed at by the disabled control's aria-describedby (REQ-UI-032).
 * Quiet forest surface rather than an alarm, because it is a prerequisite notice
 * and not a failure.
 */
.batch-submit-blocker {
  margin-block: var(--bc-space-2) var(--bc-space-4);
  padding-block: var(--bc-space-2);
  padding-inline: var(--bc-space-3);
  border-inline-start: 0.25rem solid var(--bc-border-subtle);
  background: var(--bc-surface-quiet);
  color: var(--bc-text-body);
}
