/* ==========================================================================
   Flash messages (M3 banner-style)
   ========================================================================== */

.flashes {
  list-style: none;
  padding: 0;
  margin: 0 0 1rem;
  display: grid;
  gap: 0.5rem;
}

.flashes li {
  padding: 0.75rem 1rem;
  border-radius: var(--md-sys-shape-corner-medium);
  font-size: var(--meta-size);
  line-height: var(--meta-line-height);
  /* Default/uncategorized flashes (e.g. Flask-Login's own
     login-required redirect, category "message") get a neutral tonal
     background — only .flash-error/.flash-success override this. */
  background: var(--md-sys-color-surface-container-high);
  color: var(--md-sys-color-on-surface-variant);
}

.flashes li.flash-error {
  background: var(--md-sys-color-error-container);
  color: var(--md-sys-color-on-error-container);
  border-inline-start: 0.25rem solid var(--md-sys-color-error);
}

.flashes li.flash-success {
  background: var(--md-extended-color-success-container);
  color: var(--md-extended-color-on-success-container);
}

span.flash-error {
  display: block;
  background: none;
  padding: 0;
  color: var(--md-sys-color-error);
  font-size: var(--meta-size);
}

/* ==========================================================================
   Toasts (M3 snackbar-style) — "success" flashes only; see the split in
   base.html and docs/work/0008-toast-confirmations.md. Fixed near the
   bottom of the viewport rather than at the top with .flashes, since a
   toast should surface near wherever the action just happened, not
   require scrolling back up to be seen.
   ========================================================================== */

.toasts {
  position: fixed;
  left: 50%;
  bottom: 1rem;
  transform: translateX(-50%);
  z-index: 20;
  display: grid;
  gap: 0.5rem;
  width: min(28rem, calc(100vw - 2rem));
}

.toast {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  border-radius: var(--md-sys-shape-corner-medium);
  font-size: var(--meta-size);
  line-height: var(--meta-line-height);
  background: var(--md-extended-color-success-container);
  color: var(--md-extended-color-on-success-container);
  box-shadow: var(--md-sys-elevation-2);
}

/* The one-time open-help-request toast (#16 2.4) isn't a success
   confirmation — same warning tone as the nav badge it accompanies. */
.toast-info {
  background: var(--md-extended-color-warning-container);
  color: var(--md-extended-color-on-warning-container);
}

.toast-info a {
  color: inherit;
  font-weight: var(--meta-weight);
}

.toast-close {
  flex-shrink: 0;
  min-height: auto;
  min-width: auto;
  padding: 0.15rem 0.5rem;
  background: none;
  border: none;
  border-radius: var(--md-sys-shape-corner-small);
  color: inherit;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.toast-close:hover {
  background: color-mix(in oklch, var(--md-extended-color-on-success-container) 12%, transparent);
  box-shadow: none;
}

/* Monospace utility — see --font-mono. Timestamps, IDs, domains: values
   read character by character, where a proportional font makes columns of
   digits fail to line up. .mono-label adds the uppercase tracking. */
.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.mono-label {
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: var(--mono-tracking);
}

/* ==========================================================================
   Notice banner (used by several admin pages for a persistent, non-dismissable
   status notice — e.g. kb_chat crawl/ask/answers. Not used for open help
   requests any more; that moved to a nav badge + one-time toast, #16 2.4.)
   ========================================================================== */

.notice-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1rem;
  margin: 0 0 1rem;
  border-radius: var(--md-sys-shape-corner-medium);
  background: var(--md-extended-color-warning-container);
  color: var(--md-extended-color-on-warning-container);
  font-size: var(--meta-size);
}

.notice-banner p {
  margin: 0;
}

.notice-banner a {
  color: inherit;
  font-weight: var(--meta-weight);
}

/* Site banner (platform-wide active dismissable banner) */
.site-banner {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1rem;
  margin: 0 0 1rem;
  border-radius: var(--md-sys-shape-corner-medium);
  background: var(--md-extended-color-warning-container);
  color: var(--md-extended-color-on-warning-container);
  font-size: var(--meta-size);
}

.site-banner p {
  margin: 0;
}

.site-banner form {
  margin: 0;
  flex-shrink: 0;
}

.site-banner .banner-dismiss {
  background: none;
  border: none;
  color: inherit;
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
  padding: 0.15rem 0.35rem;
  border-radius: var(--md-sys-shape-corner-full);
}

.site-banner .banner-dismiss:hover,
.site-banner .banner-dismiss:focus-visible {
  background: color-mix(in oklch, var(--md-extended-color-on-warning-container) 12%, transparent);
}

/* ==========================================================================
   Buttons
   ========================================================================== */

button,
input[type="submit"],
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 2.5rem;
  background: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
  border: none;
  border-radius: var(--md-sys-shape-corner-full);
  padding: 0.5rem 1.5rem;
  font-size: var(--meta-size);
  font-weight: var(--meta-strong-weight);
  letter-spacing: var(--meta-tracking);
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease, box-shadow 0.15s ease;
}

button:hover,
input[type="submit"]:hover,
.button:hover {
  background: color-mix(in oklch, var(--md-sys-color-primary) 92%, var(--md-sys-color-on-primary) 8%);
  box-shadow: var(--md-sys-elevation-1);
}

button:focus-visible,
input[type="submit"]:focus-visible,
.button:focus-visible {
  outline: 2px solid var(--md-sys-color-primary);
  outline-offset: 2px;
}

.submit-spinner {
  display: inline-block;
  width: 1em;
  height: 1em;
  border: 0.15em solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: submit-spin 0.6s linear infinite;
  flex-shrink: 0;
}

@keyframes submit-spin {
  to {
    transform: rotate(360deg);
  }
}

.submit-feedback-busy {
  cursor: wait;
}

@media (prefers-reduced-motion: reduce) {
  .submit-spinner {
    animation: none;
  }
}

.button-secondary {
  background: transparent;
  color: var(--md-sys-color-primary);
  border: 1px solid var(--md-sys-color-outline);
}

.button-secondary:hover {
  background: color-mix(in oklch, var(--md-sys-color-primary) 8%, transparent);
  box-shadow: none;
}

td[data-label="Actions"] .actions-flex {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.4rem 0.5rem;
  align-items: center;
}

.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.75rem;
  min-height: 2.75rem;
  background: none;
  color: var(--md-sys-color-error);
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-full);
  padding: 0.4rem;
}

.icon-button:hover {
  background: color-mix(in oklch, var(--md-sys-color-error) 8%, transparent);
  border-color: var(--md-sys-color-error);
  box-shadow: none;
}

.icon-button-neutral {
  color: var(--md-sys-color-primary);
}

.icon-button-neutral:hover {
  background: color-mix(in oklch, var(--md-sys-color-primary) 8%, transparent);
  border-color: var(--md-sys-color-primary);
  box-shadow: none;
}

.row-actions {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

/* A pane's h2 heading row with an inline trigger next to it (#128) — the
   Venues pane's filter icon button sits here. */
.pane-heading-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.pane-heading-row h2 {
  margin: 0;
}

/* ==========================================================================
   Cards & panels
   ========================================================================== */

.profile-card,
.empty-state,
.profile-form fieldset,
.consent-box,
.config-panel {
  background: var(--box-surface);
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-medium);
  padding: 1rem;
}

.config-panel {
  margin-bottom: 1rem;
}

.auth-card {
  max-width: 420px;
  margin: var(--space-7) auto 0;
}

@media (max-width: 480px) {
  .auth-card {
    margin-top: var(--space-5);
  }
}

.auth-card-alt {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--md-sys-color-outline-variant);
}

.auth-card-alt-label {
  margin: 0 0 var(--space-3);
  color: var(--md-sys-color-on-surface-variant);
  font-size: var(--meta-size);
  font-weight: var(--meta-strong-weight);
}

.auth-card-frame {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
}

.auth-card-frame .auth-card {
  margin: 0;
}

.auth-card-back-link {
  margin-top: var(--space-4);
  text-align: center;
  font-size: var(--meta-size);
}

/* A box nested inside another box (e.g. .empty-state inside a
   .config-panel) switches to the primary colour for its own stripe, so
   the two don't just double up on an identical secondary line, and sits
   a shade between the page background and a first-level panel — reading
   as recessed into its parent rather than another card stacked on top of
   it, and clearly distinct from the parent's own background either way.
   .table-scroll is excluded from the *inner* side of this: since it has
   no background of its own when nested (see the borderless rule below),
   its --box-surface must fall through unchanged from whichever ancestor
   box actually paints a background, or its sticky first/last columns
   (below) would paint a shade that no longer matches what shows through
   the now-transparent middle columns. */
:is(.profile-card, .empty-state, .config-panel, .table-scroll, .info-dialog)
  :is(.profile-card, .empty-state, .config-panel, .info-dialog) {
  --box-accent: var(--md-sys-color-primary);
  --box-surface: color-mix(
    in oklch,
    var(--md-sys-color-surface) 50%,
    var(--md-sys-color-surface-container-low) 50%
  );
}

:is(.profile-card, .empty-state, .config-panel, .info-dialog) .table-scroll {
  border: none;
  border-radius: 0;
  background: none;
}

.config-panel h2 {
  color: var(--md-sys-color-on-surface);
  margin-top: 0;
}

.consent-box {
  background: var(--md-sys-color-surface-container);
}

.page-heading,
.profile-card-heading,
.form-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

:where(form:not(.profile-form):not(.inline-form):not(.emails-filters) > .form-field + .form-actions),
:where(form:not(.profile-form):not(.inline-form):not(.emails-filters) > p + .form-actions),
:where(.table-scroll + form:not(.profile-form):not(.inline-form):not(.emails-filters) .form-actions),
:where(.table-scroll + .form-actions) {
  margin-top: var(--space-4);
}

/* Breathing room before whatever boxed content (.config-panel,
   .profile-card, .table-scroll, ...) follows the page's unboxed intro
   heading/text — without this, that first box sits right up against the
   intro paragraph with no visual separation. */
.page-heading {
  margin-bottom: 1.5rem;
}

.page-heading h1,
.page-heading h2,
.profile-card h2 {
  margin-bottom: 0;
}

.page-heading p,
.profile-card-heading p {
  margin: 0.25rem 0 0;
  color: var(--md-sys-color-on-surface-variant);
}

.season-details {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.75rem;
  margin: 0;
}

.season-details > div {
  padding: 0.875rem 1rem;
  background: color-mix(in oklch, var(--md-sys-color-surface) 60%, var(--md-sys-color-surface-container-high) 40%);
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-small);
}

.season-details dt {
  color: var(--md-sys-color-on-surface-variant);
  font-size: var(--meta-size);
  font-weight: var(--meta-weight);
  line-height: var(--meta-line-height);
}

.season-details dd {
  margin: 0.35rem 0 0;
  color: var(--md-sys-color-on-surface);
  font-size: var(--md-sys-typescale-title-large-size);
  font-weight: var(--md-sys-typescale-title-large-weight);
  line-height: var(--md-sys-typescale-title-large-line-height);
}

.detail-note {
  display: inline-block;
  margin-left: 0.45rem;
  color: var(--md-sys-color-on-surface-variant);
  font-size: var(--meta-size);
  font-weight: var(--meta-weight);
  vertical-align: middle;
}

.profile-list {
  display: grid;
  gap: 1rem;
  margin: 0 0 1rem;
}

.profile-details {
  display: grid;
  gap: 0.6rem;
  margin: 0;
}

/* Label/value on one line at every width (#18) — previously stacked
   below 600px, roughly doubling card height on phones for no benefit. */
.profile-details div {
  display: grid;
  grid-template-columns: minmax(6rem, 40%) 1fr;
  gap: 0.1rem 0.75rem;
  align-items: baseline;
}

/* The key-figure treatment (below) needs its own line regardless of
   width — a 32px/700 number reads badly squeezed next to its label. */
.profile-details div:has(.key-figure) {
  grid-template-columns: 1fr;
}

.profile-details dt {
  font-weight: var(--meta-strong-weight);
  color: var(--md-sys-color-on-surface-variant);
}

.profile-details dd {
  margin: 0;
}

/* The single most important number on a request/match screen (e.g. "0 / 2
   found") — called out at 32px/700 per the design review, distinct from
   every other .profile-details dd, which stays plain body text. */
.profile-details dd.key-figure {
  font-size: var(--display-size);
  font-weight: var(--display-weight);
  line-height: var(--display-line-height);
}

.form-intro {
  color: var(--md-sys-color-on-surface-variant);
}

/* ==========================================================================
   Forms (M3 outlined text field, simplified for server-rendered markup —
   labels stay static above the field rather than the JS-free floating-label
   trick, since WTForms doesn't reliably emit placeholders to hook it on)
   ========================================================================== */

.role-choice {
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-medium);
  padding: 0.75rem 1rem;
  margin: 1rem 0;
}

.role-choice legend {
  color: var(--md-sys-color-on-surface-variant);
  font-weight: var(--meta-strong-weight);
  padding: 0 0.25rem;
}

.role-option {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 0.5rem;
  align-items: baseline;
  padding: 0.35rem 0;
  cursor: pointer;
}

.role-name {
  font-weight: 600;
}

.role-description {
  grid-column: 2;
  font-size: var(--meta-size);
  color: var(--md-sys-color-on-surface-variant);
}

.profile-form {
  display: grid;
  gap: 1rem;
}

.profile-form fieldset {
  margin: 0;
}

.profile-form legend {
  color: var(--md-sys-color-on-surface-variant);
  font-weight: var(--meta-strong-weight);
  padding: 0 0.25rem;
}

.form-field {
  display: grid;
  gap: 0.35rem;
}

.form-field > label {
  color: var(--md-sys-color-on-surface-variant);
  font-size: var(--meta-size);
  font-weight: var(--meta-strong-weight);
}

/* Checkboxes and radios are excluded: they are not text fields, and the
   full-width box treatment below turned them into oversized padded
   rectangles with their label pushed onto the next line. */
.form-field input:not([type="checkbox"], [type="radio"]),
.form-field select,
.form-field textarea {
  box-sizing: border-box;
  width: 100%;
  min-height: 2.75rem;
  background: var(--md-sys-color-surface-container-lowest);
  color: var(--md-sys-color-on-surface);
  border: 1px solid var(--md-sys-color-outline);
  border-radius: var(--md-sys-shape-corner-extra-small);
  padding: 0.6rem 0.75rem;
  font: inherit;
}

.form-field textarea {
  min-height: 4.5rem;
}

.form-field input:not([type="checkbox"], [type="radio"]):focus-visible,
.form-field select:focus-visible,
.form-field textarea:focus-visible {
  outline: none;
  border: 2px solid var(--md-sys-color-primary);
  padding: calc(0.6rem - 1px) calc(0.75rem - 1px);
}

/* A single number edited in place inside a table cell (the Grade
   eligibility policy on the Season page). Sized to its content: the
   full-width .form-field treatment belongs to stacked forms, not to a
   column of two-digit values. */
.policy-number {
  box-sizing: border-box;
  width: 4.5rem;
  min-height: 2.25rem;
  background: var(--md-sys-color-surface-container-lowest);
  color: var(--md-sys-color-on-surface);
  border: 1px solid var(--md-sys-color-outline);
  border-radius: var(--md-sys-shape-corner-extra-small);
  padding: 0.35rem 0.5rem;
  font: inherit;
}

.policy-number:focus-visible {
  outline: none;
  border: 2px solid var(--md-sys-color-primary);
  padding: calc(0.35rem - 1px) calc(0.5rem - 1px);
}

.choice-list {
  display: grid;
  gap: 0.5rem;
}

.choice-list label,
.consent-box > label,
.form-field > label:has(input[type="checkbox"]),
.form-field > label:has(input[type="radio"]) {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  min-height: 2.5rem;
  cursor: pointer;
}

.choice-list label,
.consent-box > label {
  align-items: flex-start;
}

input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--md-sys-color-primary);
  width: 1.15rem;
  height: 1.15rem;
  flex: none;
}

.field-hint {
  display: block;
  color: var(--md-sys-color-on-surface-variant);
  font-size: var(--meta-size);
}

.form-submission-status {
  margin: 0.5rem 0 0;
  color: var(--md-sys-color-on-surface-variant);
  font-size: var(--meta-size);
}

.form-actions {
  justify-content: flex-start;
}

.inline-form {
  display: flex;
  align-items: end;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.inline-form .form-field {
  flex: 1 1 14rem;
}

/* Both of these horizontal, end-aligned rows (align-items: end) break
   when one field shows a validation message: .form-field is a grid
   whose height grows by a row whenever its own span.flash-error is
   present, so the erroring field's label/input shift out of line with
   its unaffected siblings (and with the bottom-flush .form-actions).
   While ANY message is showing in the form, every field reserves a
   fixed-height strip below its content and the message is taken out of
   flow into that strip — all fields keep identical heights regardless
   of which one is erroring. With no message showing nothing applies,
   so the rows render exactly as they did before. Order-insensitive by
   design: fields that carry other spans before the error (e.g. the
   .field-hint on the email allowlist) are handled identically. A
   wrapped multi-line message can exceed the reserved strip and will
   overlap what follows rather than push it down — accepted, since
   single-line messages are the norm here. Browsers without :has() fall
   back to the previous grow-on-error behaviour. Stacked single-column
   layouts (.profile-form and friends) keep the simpler behaviour: with
   no horizontal siblings there is nothing to misalign against. */
.emails-filters:has(.flash-error) .form-field,
.inline-form:has(.flash-error) .form-field {
  position: relative;
  padding-bottom: calc(0.35rem + var(--md-sys-typescale-body-large-line-height));
}

.emails-filters:has(.flash-error) .form-field > .flash-error,
.inline-form:has(.flash-error) .form-field > .flash-error {
  position: absolute;
  left: 0;
  bottom: 0;
}

/* ==========================================================================
   Tables
   ========================================================================== */

.table-scroll {
  overflow-x: auto;
  background: var(--box-surface);
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-medium);
}

table {
  width: 100%;
  border-collapse: collapse;
}

.table-scroll table {
  white-space: nowrap;
}

/* Every .table-scroll table here follows the same shape: an identifying
   first column (name/label) and an actions/details last column, with
   whatever's in between (status, dates, config values) being the part
   that's fine to scroll past. Pinning those two ends means a narrow
   screen never has to horizontally scroll just to see which row this is
   or reach the button that acts on it — the scrollable middle columns
   are still reachable by swiping, they just aren't load-bearing for
   identifying the row or acting on it. Inert (no visible effect) whenever
   the table already fits without scrolling, e.g. on wider screens. */
.table-scroll th:first-child,
.table-scroll td:first-child,
.table-scroll th:last-child,
.table-scroll td:last-child {
  position: sticky;
  /* Must track --box-surface rather than a fixed token: it resolves to
     whatever background is actually visible behind this table (the
     table's own, when top-level; the ancestor panel's, when nested and
     therefore borderless/transparent per the rule above) — otherwise
     these opaque sticky cells would paint a mismatched shade underneath
     the scrolling columns. */
  background: var(--box-surface);
}

.table-scroll th:first-child,
.table-scroll td:first-child {
  left: 0;
}

.table-scroll th:last-child,
.table-scroll td:last-child {
  right: 0;
}

/* Opt-out for a table whose last column isn't an actions/details column
   (see the comment above) -- e.g. global_admin/emails.html, where the last
   column is just one more tracking-status field, arbitrarily last rather
   than load-bearing the way a Manage/Edit action is elsewhere. Pinning it
   would just be a stray fixed column with no purpose. */
.table-scroll.table-no-sticky-last th:last-child,
.table-scroll.table-no-sticky-last td:last-child {
  position: static;
  background: none;
}

th,
td {
  text-align: left;
  padding: 0.5rem 0.6rem;
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
  font-size: var(--meta-size);
}

th {
  color: var(--md-sys-color-on-surface-variant);
  font-weight: var(--meta-strong-weight);
  font-size: var(--meta-size);
}

/* Queue rows: a record link carries the primary line; the state/action
   slot remains separate so #42 can add reply controls without nesting. */
.queue-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.25rem 1rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
}

.queue-row-primary {
  display: inline-flex;
  align-items: center;
  min-inline-size: 44px;
  min-block-size: 44px;
  color: inherit;
  font-weight: var(--meta-strong-weight);
}

.queue-row-supporting {
  grid-column: 1;
  color: var(--md-sys-color-on-surface-variant);
  font-size: var(--meta-size);
}

.queue-row-state {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  text-align: right;
  white-space: nowrap;
}

@media (max-width: 899.98px) {
  .table-scroll.table-stack {
    overflow-x: hidden;
    max-width: 100%;
  }
  .table-scroll.table-stack table {
    white-space: normal;
    max-width: 100%;
  }
  .table-scroll.table-stack table,
  .table-scroll.table-stack thead,
  .table-scroll.table-stack tbody,
  .table-scroll.table-stack tr,
  .table-scroll.table-stack th,
  .table-scroll.table-stack td {
    display: block;
    max-width: 100%;
    min-width: 0;
    box-sizing: border-box;
  }
  .table-scroll.table-stack thead {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
  }
  .table-scroll.table-stack tr {
    margin: 0.75rem; border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--md-sys-shape-corner-small);
  }
  .table-scroll.table-stack td {
    position: static; padding: 0.5rem 0.6rem; background: transparent; white-space: normal;
    overflow-wrap: anywhere;
    word-break: break-word;
  }
  .table-scroll.table-stack td[data-label] {
    display: grid; grid-template-columns: minmax(5rem, 35%) minmax(0, 1fr); gap: 0.5rem;
    align-items: baseline;
  }
  .table-scroll.table-stack td[data-label]::before {
    content: attr(data-label); color: var(--md-sys-color-on-surface-variant);
    font-weight: var(--meta-strong-weight);
    min-width: 0;
  }
}

/* Sortable column headers (e.g. admin/competitions.html's Age Groups
   table): the whole header is a link so its full width/height is
   clickable, not just the text. */
.sortable-header {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  color: inherit;
  text-decoration: none;
}

.sortable-header:hover {
  text-decoration: underline;
}

.sort-indicator {
  font-size: 0.7em;
  color: var(--md-sys-color-outline);
}

.sortable-header-active .sort-indicator {
  color: var(--md-sys-color-on-surface-variant);
}

/* ==========================================================================
   Status pills (M3 "assist chip"-style: outlined, not filled — a filled
   pill reads as the same kind of control as a filled button, which is
   exactly what these need to *not* look like next to actions like
   Deactivate/Reactivate. Transparent/surface background, a currentColor
   border, and coloured text instead — informational, not actionable.)
   ========================================================================== */

.status-pill {
  display: inline-block;
  border-radius: var(--md-sys-shape-corner-full);
  border: 1px solid currentColor;
  padding: 0.15rem 0.65rem;
  background: transparent;
  font-size: var(--meta-size);
  font-weight: var(--meta-strong-weight);
  letter-spacing: var(--meta-tracking);
}

.status-active,
.status-accepted,
.status-open,
.status-closed-filled,
.status-available,
.status-clear,
.status-ready,
.status-sent {
  color: var(--md-extended-color-success);
}

.status-inactive,
.status-expired,
.status-withdrawn,
.status-superseded,
.status-closed-enough,
.status-resolved,
.status-unknown,
.status-suppressed {
  color: var(--md-sys-color-on-surface-variant);
}

.status-pending,
.status-preparing,
.status-discussion-needed {
  color: var(--md-extended-color-warning);
}

.status-cancelled,
.status-not-available,
.status-clash,
.status-revoked,
.status-failed,
.status-blocked {
  color: var(--md-sys-color-error);
}

.status-new {
  color: var(--md-extended-color-success);
}

.status-updated,
.status-mapped {
  color: var(--md-extended-color-warning);
}

.status-unchanged,
.status-ignored {
  color: var(--md-sys-color-on-surface-variant);
}

/* ==========================================================================
   Info dialog (M3-styled native <dialog> — no JS beyond the one-line
   showModal() call in the trigger button's onclick; closing is a plain
   method="dialog" form, handled natively by the browser)
   ========================================================================== */

.info-dialog {
  width: min(28rem, calc(100vw - 2rem));
  padding: 1.25rem;
  border: none;
  border-radius: var(--md-sys-shape-corner-large);
  background: var(--md-sys-color-surface-container-high);
  color: var(--md-sys-color-on-surface);
  box-shadow: var(--md-sys-elevation-3);
  /* Undo the ancestor <table>'s white-space: nowrap (for its own
     horizontal-scroll behavior) — a <dialog> renders in the top layer but
     is still a DOM descendant for inheritance, so without this its content
     would try to render as one unbreakable line too. */
  white-space: normal;
}

.info-dialog::backdrop {
  background: color-mix(in oklch, var(--md-sys-color-scrim) 32%, transparent);
}

.info-dialog h4 {
  margin: 0 0 1rem;
  font-size: var(--md-sys-typescale-body-large-size);
  line-height: var(--md-sys-typescale-body-large-line-height);
  font-weight: var(--meta-strong-weight);
  overflow-wrap: break-word;
}

.info-dialog .form-actions {
  margin-top: 1.25rem;
}

/* A "Cancel"/"Close" .button-secondary inside a dialog gets a solid
   tonal fill (M3 "Filled Tonal Button") instead of its usual
   transparent-with-outline look — next to a solid filled action button
   (e.g. .button-danger's "Deactivate") a transparent button reads as
   barely a button at all. Uses the club's own primary-container/
   on-primary-container pair (brand-derived, see --club-primary in
   base.html) rather than a neutral grey, so it's clearly a branded,
   visible control. */
.info-dialog .button-secondary {
  background: var(--md-sys-color-primary-container);
  color: var(--md-sys-color-on-primary-container);
  border-color: transparent;
}

.info-dialog .button-secondary:hover {
  background: color-mix(in oklch, var(--md-sys-color-primary-container) 85%, var(--md-sys-color-on-primary-container) 15%);
}

/* Modify user dialog on Global Admin club users register (#125) */
.modify-user-dialog {
  width: min(34rem, calc(100vw - 2rem));
}

.modify-action-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin: var(--space-4) 0;
}

.modify-action-card {
  padding: var(--space-4);
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-medium);
  background: var(--md-sys-color-surface-container);
}

.modify-action-card h5 {
  margin: 0 0 var(--space-1);
  font-size: var(--md-sys-typescale-body-large-size);
  font-weight: var(--meta-strong-weight);
  color: var(--md-sys-color-on-surface);
}

.modify-action-card p {
  margin: 0 0 var(--space-3);
  font-size: var(--meta-size);
  color: var(--md-sys-color-on-surface-variant);
  line-height: var(--meta-line-height);
}

.modify-action-card .form-field {
  margin-bottom: var(--space-3);
}

.playhq-cache-action-card label {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  font-weight: var(--meta-strong-weight);
  cursor: pointer;
  margin-bottom: var(--space-2);
}

.playhq-cache-action-card p:last-child {
  margin-bottom: 0;
}

/* ==========================================================================
   Published/Unpublished switch component (#204)
   Native submit button styled as track and thumb, no JS required.
   ========================================================================== */

.visibility-switch {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  border: none;
  background: none;
  padding: 0.25rem 0.5rem 0.25rem 0.25rem;
  border-radius: var(--md-sys-shape-corner-full);
  cursor: pointer;
  font: inherit;
}

.visibility-switch .track {
  position: relative;
  width: 2.6rem;
  height: 1.5rem;
  border-radius: var(--md-sys-shape-corner-full);
  background: var(--md-sys-color-outline-variant);
  transition: background 120ms;
}

.visibility-switch .thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 999px;
  background: var(--md-sys-color-surface);
  box-shadow: var(--md-sys-elevation-1);
  transition: transform 120ms;
}

.visibility-switch[aria-pressed="true"] .track {
  background: var(--md-sys-color-primary);
}

.visibility-switch[aria-pressed="true"] .thumb {
  transform: translateX(1.1rem);
}

.visibility-switch .switch-label {
  font-size: var(--meta-size);
  color: var(--md-sys-color-on-surface-variant);
}

/* ==========================================================================
   Misc
   ========================================================================== */

.graphic-preview {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.graphic-preview .club-logo {
  height: 128px;
}

.graphic-preview form {
  margin: 0;
}

/* ==========================================================================
   KB Chat: admin pages
   ========================================================================== */

/* Filled destructive action — see --button-danger-bg / --button-danger-ink
   above. Geometry matches .button (border: none, padding: 0.5rem 1.5rem);
   hover uses the filled-button treatment (color-mix toward on-colour,
   elevation-1). */
.button-danger {
  background: var(--button-danger-bg);
  color: var(--button-danger-ink);
  border: none;
  padding: 0.5rem 1.5rem;
}

.button-danger:hover {
  background: color-mix(in oklch, var(--button-danger-bg) 92%, var(--button-danger-ink) 8%);
  box-shadow: var(--md-sys-elevation-1);
}

.table-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 0.75rem;
}

.table-toolbar-actions,
.table-toolbar-options {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.selection-summary {
  color: var(--md-sys-color-on-surface-variant);
  font-size: var(--meta-size);
}

.url-view-expanded {
  overflow-wrap: anywhere;
}

.url-label {
  color: var(--md-sys-color-on-surface-variant);
  font-weight: var(--meta-strong-weight);
}

.truncate {
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pagination {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  margin-top: 1rem;
}

.pagination-status {
  font-size: var(--meta-size);
  color: var(--md-sys-color-on-surface-variant);
}

.emails-filters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  align-items: end;
  gap: 0.75rem;
  margin: 1.25rem 0;
  padding: 1rem;
  background: var(--md-sys-color-surface-container-low);
  border-radius: var(--md-sys-shape-corner-medium);
  min-width: 0;
  max-width: 100%;
}

/* The Clubs filters sit inside an already padded config panel. */
#club-filter-form {
  margin: 0.75rem 0 0;
  padding: 0;
  background: transparent;
}

.emails-filters .form-field {
  min-width: 0;
  max-width: 100%;
}

.emails-filters .form-field select,
.emails-filters .form-field input {
  min-width: 0;
  max-width: 100%;
  width: 100%;
}

.emails-filters .form-actions {
  /* The actions row spans the whole grid rather than occupying a single
     minmax(11rem, 1fr) track: inside one ~11rem cell the Apply/Clear pair
     exceeds the track width and the second control wraps underneath
     instead of sitting beside it. */
  grid-column: 1 / -1;
  align-self: end;
  margin: 0;
  min-width: 0;
}

@media (max-width: 40rem) {
  .emails-filters {
    grid-template-columns: 1fr;
  }
}

.avatar,
.avatar-fallback {
  align-items: center;
  border-radius: 50%;
  display: inline-flex;
  justify-content: center;
  overflow: hidden;
  vertical-align: middle;
}

.avatar {
  object-fit: cover;
}

.avatar-sm {
  width: 24px;
  height: 24px;
}

.nav-avatar-trigger {
  width: 100%;
  height: 100%;
  flex-shrink: 0;
}

.avatar-md {
  width: 40px;
  height: 40px;
}

.avatar-lg {
  width: 64px;
  height: 64px;
}

.avatar-fallback {
  background-color: var(--md-sys-color-primary-container, #e0e2ec);
  color: var(--md-sys-color-on-primary-container, #161b26);
}

.avatar-fallback svg {
  width: 62.5%;
  height: 62.5%;
  fill: currentColor;
}

.avatar-color-0 {
  background-color: var(--avatar-color-0-container);
  color: var(--avatar-color-0-on);
}

.avatar-color-1 {
  background-color: var(--avatar-color-1-container);
  color: var(--avatar-color-1-on);
}

.avatar-color-2 {
  background-color: var(--avatar-color-2-container);
  color: var(--avatar-color-2-on);
}

.avatar-color-3 {
  background-color: var(--avatar-color-3-container);
  color: var(--avatar-color-3-on);
}

.avatar-color-4 {
  background-color: var(--avatar-color-4-container);
  color: var(--avatar-color-4-on);
}

.avatar-color-5 {
  background-color: var(--avatar-color-5-container);
  color: var(--avatar-color-5-on);
}

.avatar-color-6 {
  background-color: var(--avatar-color-6-container);
  color: var(--avatar-color-6-on);
}

.avatar-color-7 {
  background-color: var(--avatar-color-7-container);
  color: var(--avatar-color-7-on);
}

.avatar-color-8 {
  background-color: var(--avatar-color-8-container);
  color: var(--avatar-color-8-on);
}

.avatar-color-9 {
  background-color: var(--avatar-color-9-container);
  color: var(--avatar-color-9-on);
}

.avatar-color-10 {
  background-color: var(--avatar-color-10-container);
  color: var(--avatar-color-10-on);
}

.avatar-color-11 {
  background-color: var(--avatar-color-11-container);
  color: var(--avatar-color-11-on);
}

.settings-avatar-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.settings-avatar-actions form {
  margin: 0;
}
/* #75: the visible "Add photo"/"Change photo" label is gone — the upload
   form now hosts only hidden/absolutely-positioned inputs, so it must not
   consume a flex slot (and the whole actions row collapses when "Remove
   photo" is absent too), matching the reviewed mockup. */
.settings-avatar-actions > form:first-child {
  display: contents;
}
.settings-avatar-actions:not(:has(button)) {
  display: none;
}

/* Side-by-side From/Until (and similar) fields on desktop, wrapping to
   stacked on narrow viewports. */
.form-field-row {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.form-field-row .form-field {
  flex: 1 1 12rem;
  min-width: 0;
}

/* Destructive buttons pushed to the far end of a Save/Cancel action row. */
.form-actions-end {
  margin-left: auto;
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* PlayHQ import background worker job panels */
.job-panel-progress {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0.5rem 0;
}
.job-progress-track {
  flex: 0 1 220px;
  height: 6px;
  border-radius: 999px;
  background: var(--md-sys-color-surface-container-highest);
  overflow: hidden;
}
.job-progress-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--md-sys-color-primary);
}
.job-panel-progress .job-progress-count {
  font-size: var(--meta-size);
  color: var(--md-sys-color-on-surface-variant);
}
.job-poll-hint {
  margin: 0.25rem 0 0;
}
.disabled-form-note {
  margin: 0.35rem 0 0;
}

