/* ============================================================
 * member-management.css — app component styles
 * ============================================================
 * Colours, spacing, radii, and type all come from the design system's --ds-*
 * tokens (shipped by the design-system RCL; the "takt" brand is activated
 * by <html data-brand="takt">). Components use the design system's
 * class-marker convention, ds-c-{name}, styled from the matching
 * --ds-{name}-* component tokens.
 *
 * The only literals left are values the token set does not model: unitless
 * line-height ratios, 50%/full-round shapes, the 1px hairline border the
 * card/table tokens already carry as widths, optical letter-spacing, and the
 * ch-based reading measure. Everything dimensional resolves from a token.
 * ============================================================ */

/* The brand-scoped Takt compositions' layout (slice 5 Task 3). Imported here rather than
   linked from App.razor: App.razor is Task 2's file for the whole slice, and this stylesheet
   is already downstream of the theme and the DS baseline through this file's own <link>. */
@import url("takt-compositions.css");

/* The admin screens' page-level styling (slice 5 Task 8 — roster/workshops-admin/
   locations/payments). Same import rationale as takt-compositions.css above. */
@import url("takt-admin.css");

/* ── Utilities ── */
/* Tabular lining numerals — aligns columns of figures (amounts, counts, prices)
   so digits line up vertically and don't jitter as values change. Backed by the
   design-system token; the DS emits --ds-typography-numeric-tabular but ships the
   matching .ds-u-tabular-nums class only in the RCL, so a tokens-first consumer
   defines it locally (still no hardcoded values). */
.ds-u-tabular-nums { font-feature-settings: var(--ds-typography-numeric-tabular); }

/* ── DS-003: adjustments for the design-system RCL components we consume ──
   Layout/usage fixes for our app (not token overrides):
   - DsButton's icon slot expects a DS icon-font glyph we don't ship (we use our
     own SVG <Icon> in the button content instead), so collapse the empty slot to
     keep the label optically centered.
   - DsPageHeader centers its title row; we bottom-align the title with its
     subtitle so the heading and lead sit on the same baseline. */
/* DS-004 — the only ds-c-* selectors we still write, and deliberately so: these ADJUST the
   package's own components for our layout, they do not re-implement them. The guard test carries
   them as documented exceptions rather than pretending the count is zero.

   The card rule below is the one that matters. DsCard's body is a flex column, so an inline-level
   child — a badge, a button — is blockified and STRETCHES to the card's full width. That turned the
   membership badge and its pay button into full-width bars the moment cards migrated, because our
   old hand-written card rule was a plain block. Cross-axis only and scoped to direct children, so tables, form
   grids and prose still fill the card as they should. (Two more adjustments used to sit here —
   hiding DsButton's empty icon slot and baseline-aligning DsPageHeader's title group — and both
   became the component's own behaviour in DS 1.6.0, so they are gone.) */
.ds-c-card__body > .ds-c-badge,
.ds-c-card__body > .ds-c-btn,
.ds-c-card__body > .md-badge { align-self: flex-start; }
/* Table name-cell rendered as a <button> (interactive open) — look like the link it replaces. */
button.md-name { appearance: none; background: none; border: 0; padding: 0; font: inherit; text-align: left; cursor: pointer; }

/* ── Document baseline (semantic + typography tokens) ── */
html { box-sizing: border-box; }
*, *::before, *::after { box-sizing: inherit; }

body {
  margin: 0;
  font-family: var(--ds-typography-body-font-family);
  font-size: var(--ds-typography-body-font-size);
  line-height: var(--ds-typography-body-line-height);
  color: var(--ds-color-onSurface);
  background-color: var(--ds-color-pageBackground);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  transition: background-color 150ms ease, color 150ms ease;
}

/* Headings wear the HEADING family — under takt that is the display face (Bricolage
   Grotesque), distinct from the body face for the first time; binding them to the body
   token (as this rule did pre-takt, when the two faces were one) silently erased the
   brand's display typography on every h1-h3 in the app. */
h1, h2, h3 {
  font-family: var(--ds-typography-heading1-font-family);
  color: var(--ds-color-onSurface);
  margin: 0 0 var(--ds-space-sm);
}
h1 {
  font-size: var(--ds-typography-heading4-font-size);
  font-weight: var(--ds-typography-heading4-font-weight);
  line-height: var(--ds-typography-heading4-line-height);
  letter-spacing: -0.01em;
}
h2 {
  font-size: var(--ds-typography-heading5-font-size);
  font-weight: var(--ds-typography-heading5-font-weight);
  line-height: var(--ds-typography-heading5-line-height);
}

/* Links wear the LINK role, not the brand fill. --ds-color-primary is contrast-guaranteed
   only against --ds-color-onPrimary (it is a FILL); painting it as text put takt links at
   4.2:1 on the page ground. --ds-color-link is the brand's own AA-safe ink step, so the
   role token is both the honest name and the readable value. */
a { color: var(--ds-color-link); text-decoration: none; }
a:hover { color: var(--ds-color-linkHover); text-decoration: underline; }

code {
  /* primaryInk (#470): the brand's quiet INK — the role for primary-family TEXT on a
     surface the brand does not fill. Same call-site class as the `a` rule above. */
  color: var(--ds-color-primaryInk);
  background: var(--ds-color-primarySubtle);
  font-size: var(--ds-typography-code-font-size);
  padding: var(--ds-space-hairline) var(--ds-space-xs);
  border-radius: var(--ds-radius-sm);
}

::selection { background: var(--ds-color-primarySubtle); color: var(--ds-color-onPrimarySubtle); }
:focus-visible { outline: var(--ds-border-focusRing-width) solid var(--ds-color-focusRing); outline-offset: var(--ds-border-focusRing-offset); }

.app-lead { color: var(--ds-color-onSurfaceVariant); max-width: 60ch; }

/* ── Season timetable (#112) — weekly grids, venue columns, clash flags; tokens only. ──
 *
 * TAKT-330 (#374) — the colours, re-pointed to the CALENDAR EVENT family and measured.
 *
 * What was wrong, in numbers (takt light / takt dark, WCAG 2.x ratios):
 *   · The block was `primaryContainer` (#FFD8E0 pale pink) and the empty slot was
 *     `outlineVariant` (#E8E0D5 beige) — an OUTLINE role used as a FILL. Block against empty
 *     slot measured **1.01:1 light / 1.03:1 dark**. A block's extent IS the information this
 *     view exists to show (which hour, which room, how long), so that boundary is a non-text
 *     contrast case: 1.4.11 wants ≥3:1 and this was indistinguishable. It is also exactly the
 *     "cream and pink" the owner reported — two washes of the same luminance side by side.
 *   · `primaryContainer` for a class night contradicts a decision this app had already taken
 *     one screen over: ClubCalendar's CategoryFor keeps `primary` deliberately UNUSED because
 *     under Takt it is the signature red and "would read as an alarm on an ordinary Tuesday"
 *     (TAKT-236). So the Ugekalender tab drew a class night in the calendar's `info` tone and
 *     the Skoleskema tab beside it drew the same evening in brand pink — one schedule, two
 *     vocabularies.
 *   · Nothing here failed on TEXT: the block ink measured 14.92:1 / 6.82:1. The failures were
 *     all boundaries, which is why the fix is a ring rather than a darker wash.
 *
 * What it is now: the class block is a CALENDAR EVENT — the same `--ds-calendar-event*Container`
 * / `--ds-calendar-onEvent*` pairs DsCalendar draws the Ugekalender chips from and the app's own
 * calendar legend binds (those were contrast-fixed upstream in DS 1.12.0), in the `info` category
 * the calendar already assigns an ordinary class night. A pale container fill can never reach
 * 3:1 against any quiet surface — that is what a container fill IS — so the boundary is carried
 * by an outline-strength RING, the same answer the DS shipped for its own legend dots.
 *
 * Measured after (light / dark):
 *   block ink on block fill            #172554 on #BFDBFE  10.34:1 / 10.34:1   (text ≥4.5)
 *   block ring vs empty slot           #514A6E on #F4EEE6   7.13:1 /  5.25:1   (non-text ≥3)
 *   block ring vs block fill           #514A6E on #BFDBFE   5.79:1 /  4.64:1   (non-text ≥3)
 *   clash ink on clash fill            #5C0500 on #FBE3D8  11.57:1 / 10.96:1   (text ≥4.5)
 *   clash ring vs clash fill           #9A3412 on #FBE3D8   5.94:1 /  5.99:1   (non-text ≥3)
 *   clash ring vs empty slot           #9A3412 on #F4EEE6   6.34:1 /  6.73:1   (non-text ≥3)
 *   hour-scale ink on hour column      #514A6E on #FFFFFF   8.22:1 /  7.56:1   (text ≥4.5)
 *   column-head ink on its fill        #514A6E on #EDE5DA   6.58:1 /  6.78:1   (text ≥4.5)
 */
.timetable-day h2 { margin-bottom: var(--ds-space-sm); }
.timetable-scroll { overflow-x: auto; }
.timetable {
  display: grid;
  gap: var(--ds-space-hairline);
  /* The empty slot. It has no element of its own — an hour nobody booked is simply the grid
     showing through — so whatever this background is IS the empty-slot colour, and it has to be
     a SURFACE role that relates to the page rather than the outline role that used to sit here.
     surfaceContainer is the quiet rung the design system's own calendar uses for a drained
     chip (`--ds-calendar-eventCancelledContainer` carries the same value). */
  background: var(--ds-color-surfaceContainer);
  border: var(--ds-border-width-thin) solid var(--ds-color-outlineVariant);
  border-radius: var(--ds-radius-md);
  overflow: hidden;
}
.timetable__colhead {
  grid-row: 1;
  /* One rung ABOVE the field now that the field is surfaceContainer, so the venue strip still
     reads as a header rather than merging into the empty hours below it. */
  background: var(--ds-color-surfaceContainerHigh);
  color: var(--ds-color-onSurfaceVariant);
  font-size: var(--ds-typography-labelSmall-font-size);
  font-weight: var(--ds-typography-labelSmall-font-weight);
  padding: var(--ds-space-xs) var(--ds-space-sm);
}
.timetable__hour {
  background: var(--ds-color-surface);
  color: var(--ds-color-onSurfaceVariant);
  font-size: var(--ds-typography-labelSmall-font-size);
  padding: var(--ds-space-hairline) var(--ds-space-xs);
}
.timetable__block {
  background: var(--ds-calendar-eventInfoContainer);
  color: var(--ds-calendar-onEventInfo);
  /* The ring is what makes the block's extent legible — colour alone cannot, at any container
     fill the brand ships. Same device, same reason, as the calendar legend's dot ring. */
  border: var(--ds-border-width-thin) solid var(--ds-color-outlineStrong);
  border-radius: var(--ds-radius-sm);
  padding: var(--ds-space-xs);
  font-size: var(--ds-typography-labelSmall-font-size);
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-hairline);
  overflow: hidden;
  z-index: 1;
}
.timetable__block.is-clash {
  background: var(--ds-calendar-eventErrorContainer);
  color: var(--ds-calendar-onEventError);
  /* A BORDER, not the `outline` this used to carry: the grid clips to a radius
     (`overflow: hidden`), and an outline is painted outside the box, so an edge-column clash lost
     the flag on the side it was clipped from. Border-box keeps the block on its grid track.
     WIDTH, not just hue: a clash ring against a normal block's ring measures 1.13:1 light /
     1.28:1 dark, so hue alone leaves the flag invisible to anyone who cannot separate those two
     — the heavier ring is the non-colour half of the signal (1.4.1). */
  border-color: var(--ds-color-error);
  border-width: var(--ds-border-width-emphasis);
}

/* ── Privacy page — readable measure + spaced sections ── */
.privacy { max-width: 60ch; }
.privacy section { margin-top: var(--ds-space-lg); }
.privacy h2 { margin-bottom: var(--ds-space-xs); }

/* ── Card surface for elements DsCard cannot be (DS-004) ──
   DsCard renders a <div>, so a card-looking <button> (the stat filters), <section> (the demo
   persona picker) or <article> (a news post) cannot use it. Those keep OUR class and get the same
   look from the same --ds-card-* tokens — the design system still owns the values, we just apply
   them to an element it does not produce. Everything that IS a div uses DsCard. */
.md-cardsurface {
  background: var(--ds-card-container);
  border: var(--ds-card-borderWidth) solid var(--ds-card-border);
  border-radius: var(--ds-card-radius);
  padding: var(--ds-card-padding);
  box-shadow: var(--ds-card-elevation);
}

/* TAKT-261: the hand-rolled .page-header/__bar/__heading/__actions rules that used to style
   PageHeader.razor's own <header>/<h1> markup are gone along with that markup — the component
   now delegates to DsPageHeader (Ds.DesignSystem.Blazor's own ds-c-pageHeader* rules), which
   this repo must never re-implement (see CLAUDE.md's "never re-implement a ds-c-* component").
   .app-lead itself stays: it is a general-purpose lead-text utility used well beyond page
   headers (modal lead paragraphs, empty-state copy, …). */

/* TAKT-321 (#341) — the ONE rung between a page header and the first thing under it. Our own
   class, put on DsPageHeader's root by PageHeader.razor (see that component for the pattern), so
   the whole app's header rhythm is this single declaration rather than a per-page margin that
   three screens had and eleven did not: /members' stat cards started flush against the header,
   /workshops' content box hugged it. lg is the same step the page-section stacks
   (.page-sections, DsStack Gap="lg") already use, so a header and a card read on one grid. */
.app-pageheader { margin-block-end: var(--ds-space-lg); }

/* The lead line's own layout, inside the row the design system gives us (PageHeader.razor's
   MetaPlacement.Below). It WRAPS: the DS's meta row is a plain flex line, so on a phone the lead
   sentence took the width and squeezed the chip beside it into a three-line blob. Wrapping puts
   the chip on its own line there and leaves it beside the lead everywhere it fits. min-width: 0 so
   a long lead wraps inside the row rather than setting the header's minimum width. */
.app-pageheader__leadrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ds-space-sm);
  flex: 1 1 auto;
  min-width: 0;
}

/* The lead text itself: quiet ink and a reading measure — the same two things .app-lead gives a
   paragraph. The measure is a CONTENT measure, not a spacing step; no --ds-* token expresses one
   (#ds-gap: token-content-measure). */
.app-pageheader__lead { color: var(--ds-color-onSurfaceVariant); max-width: 72ch; }

/* TAKT-321 (#341) — a SECTION heading inside a page that already owns an h1: the heading and its
   one action on a row, at h2 scale, visibly subordinate to the page header above it. The payments
   screen's Abonnementer tab used to render a second full page header (a second h1, its own action
   button) directly under the first; this is what a tab's own heading looks like instead. */
.admin-section-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--ds-space-sm);
    margin-block-end: var(--ds-space-md);
}
.admin-section-head__text { display: flex; flex-direction: column; gap: var(--ds-space-xxs); }

/* ── Loading state — quiet shimmer instead of bare text (UX review U6) ── */
.md-loading {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-sm);
  color: var(--ds-color-onSurfaceVariant);
}
.md-loading::before {
  content: "";
  width: var(--ds-space-md);
  height: var(--ds-space-md);
  border-radius: 50%;
  border: var(--ds-border-width-thick) solid var(--ds-color-outlineVariant);
  border-top-color: var(--ds-color-primary);
  animation: ds-spin 0.7s linear infinite;
}
@keyframes ds-spin { to { transform: rotate(360deg); } }

/* ── Shared admin filter/search row (Members filter, Payments reference-code search, task-4
   review finding: the two were near-identical copy-paste) — a flex row of an input plus one or
   two buttons that wraps on narrow widths. Each page adds its own class only for the one thing
   that's genuinely page-specific: which side of the row carries the margin, since .members-filter
   sits below its page's header/bulk-action row (margin-top) while .payments-search sits directly
   above its table (margin-bottom). ── */
.admin-filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ds-space-sm);
}
/* #224 — the two rules that used to size this row's hand-rolled inputs are gone with the inputs
   themselves: every control in a filter row is a Ds* component now, and a DS field fills its column
   by default, so each one is sized by its OWN class on the component root (.payments-scope /
   .payments-searchbox / .takt-members__*) rather than by an element selector that would reach
   through the wrapper and fight it. */

.members-filter { margin-top: var(--ds-space-sm); }
.payments-search { margin-bottom: var(--ds-space-sm); }
/* The season/track scope select sizes to its longest option, not the row. */
.payments-search .payments-scope { flex: 0 1 auto; }

/* Wireframe cfoot: the reconciliation table's standing instructions (where the manual money goes,
   what the reference codes look like) — quiet caption lines at the card's foot, not table content. */
.payments-footnote { display: grid; gap: var(--ds-space-hairline); margin-top: var(--ds-space-sm); }
.payments-footnote p { margin: 0; }

/* ── Table for the WASM reference page, which cannot use DsTable (no RCL reference
   in Web.Client). Same --ds-table-* tokens; our own class name. DS-004. ── */
.md-datatable { width: 100%; border-collapse: collapse; }
/* Selection column: just wide enough for the checkbox, centred. */
.member-select { text-align: center; }
/* TAKT-251-03 — the DsCheckbox box itself is the design system's 20px (the adoption spec's
   accepted checkbox exception); the LABEL it lives in is the thumb target, floored at the brand's
   own interactive minimum so a phone can hit it without hitting the row link beside it. Our own
   class on DsCheckbox's root, never a .ds-c-* rule. */
.member-select__box {
    min-inline-size: var(--ds-input-minHeight);
    min-block-size: var(--ds-input-minHeight);
    justify-content: center;
}
.md-datatable thead th {
  text-align: left;
  background: var(--ds-table-headerContainer);
  color: var(--ds-table-headerText);
  font-size: var(--ds-typography-labelSmall-font-size);
  font-weight: var(--ds-typography-label-font-weight);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: var(--ds-table-cellPaddingY) var(--ds-table-cellPaddingX);
  border-bottom: var(--ds-table-borderWidth) solid var(--ds-table-border);
}
.md-datatable tbody td {
  background: var(--ds-table-cellContainer);
  color: var(--ds-table-cellText);
  padding: var(--ds-table-cellPaddingY) var(--ds-table-cellPaddingX);
  border-bottom: var(--ds-table-borderWidth) solid var(--ds-table-border);
}
.md-datatable tbody tr:hover td { background: var(--ds-table-rowHover); }
.md-datatable tbody tr:last-child td { border-bottom: none; }
/* Keep avatars / chips / multi-line cells centred against single-line ones. */
.md-datatable tbody td { vertical-align: middle; }

/* ── Badge semantics (DS-004, corrected TAKT-263). These SEMANTIC colour variants were
   originally written because DsBadge's Status enum carried no semantic (good/bad) vocabulary
   — only workflow status (Active/Undecided/Irrelevant/Complete/Error), which answers "where
   is this in a process?", not "is this good or bad?". That gap has since shipped: DsBadge's
   Status parameter (DsBadgeStatus) now also carries Success/Warning/Info/Neutral/Tertiary.
   TAKT-263 moved the two shared helpers (MemberBadge, EventBadges) onto DsBadgeStatus, but
   most call sites still pass one of these classes directly (Home.razor, TrackRoster.razor,
   RosterColumn.razor, the Signup components, SeasonBuilder.razor, Workshops.razor, …) — this
   block stays live until those are migrated too, a follow-up in the same spirit rather than a
   still-open upstream gap. .md-badge itself is only for the WASM page, which cannot use the
   component. ── */
.md-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-xs);
  font-size: var(--ds-typography-labelSmall-font-size);
  font-weight: var(--ds-typography-label-font-weight);
  padding: var(--ds-space-xs) var(--ds-space-sm);
  border-radius: var(--ds-badge-radius);
}
.md-badge::before { content: ""; width: var(--ds-space-sm); height: var(--ds-space-sm); border-radius: 50%; background: currentColor; }
/* Compounded with the DS's own class ON PURPOSE, and this is load-bearing. DsBadge's scoped base
   rule is `.ds-c-badge[b-xxxxx]` — specificity (0,2,0) — and it sets background-color AND color. A
   bare `.md-badge--warning` is (0,1,0), so it LOSES: every badge in the app rendered in the DS's
   single default green, and "Not active", "Signed up", "Confirmed" and "Next intake" became
   visually identical. Nothing failed — white on dark green passes contrast, so even the axe test
   was happy. The class is repeated deliberately: compounding once only TIES the DS at (0,2,0), and
   our stylesheet is linked BEFORE the scoped bundle in App.razor, so a tie loses. Repeating it
   makes (0,3,0), which wins outright regardless of link order.
   The design system's own semantic Status variant has since landed (DsBadgeStatus); remove
   this compounding, and the class it targets, as each remaining call site migrates onto it. */
.ds-c-badge.md-badge--success.md-badge--success,
/* onSuccessSubtle, NOT successStrong. successStrong is #166534 in BOTH themes — it is a strong
   colour meant for text on a neutral surface — so on dark, where successSubtle is #052e16, it gave
   dark-green-on-dark-green at 2.09:1. Every other variant here already used its paired on-colour;
   success was the odd one out. Pre-dates DS-004; found by finally running axe in dark mode. */
.md-badge--success { background: var(--ds-color-successSubtle); color: var(--ds-color-onSuccessSubtle); }
.ds-c-badge.md-badge--warning.md-badge--warning,
.md-badge--warning { background: var(--ds-color-warningSubtle); color: var(--ds-color-onWarningSubtle); }
.ds-c-badge.md-badge--brand.md-badge--brand,
.md-badge--brand { background: var(--ds-color-primarySubtle); color: var(--ds-color-onPrimarySubtle); }
.ds-c-badge.md-badge--neutral.md-badge--neutral,
.md-badge--neutral { background: var(--ds-color-surfaceContainer); color: var(--ds-color-onSurfaceVariant); }
/* A basket line that genuinely failed (person NOT signed up) — distinct from --warning
   (waitlisted, nothing wrong) so the member can tell the two apart at a glance. */
.ds-c-badge.md-badge--error.md-badge--error,
.md-badge--error { background: var(--ds-color-errorSubtle); color: var(--ds-color-onErrorSubtle); }

/* ── Language switcher (TAKT-219) — a segmented DA|EN pill, both options always visible, the
   active one filled. Rendered by DsSegmentedControl itself now (LanguageSwitcher.razor); the
   `.lang-switch` class is kept only as the hook takt-shell.css's `.takt-topbar .lang-switch`
   redirects a component token on, not for any styling of its own. ── */

/* ── Form field + text input — token-styled (S5) ── */
.form-field { display: flex; flex-direction: column; gap: var(--ds-space-xs); }
/* Two-column field grid (season builder); collapses to one column when narrow. */
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: var(--ds-space-md); }
.form-field--check { flex-direction: row; align-items: center; gap: var(--ds-space-sm); }
/* #106: a checkbox with its consequence line directly beneath it. */
.form-field--check-group { display: flex; flex-direction: column; gap: var(--ds-space-hairline); }
.form-field--check-group .member-actions__hint { margin: 0; }
.form-field > span {
  font-size: var(--ds-typography-labelSmall-font-size);
  font-weight: var(--ds-typography-label-font-weight);
  color: var(--ds-color-onSurfaceVariant);
}
/* #224 — the hand-rolled input base + focus rules are gone with the last inputs that wore them:
   the final 17 were the date/time fields, now DsDatePicker/DsTimePicker, which bring their own
   --ds-input-* styling. Nothing in this app hand-rolls a text input any more. */

/* Per-field validation message (Blazor ValidationMessage) — small, error-coloured (U5). */
.validation-message {
  margin-top: var(--ds-space-xs);
  color: var(--ds-color-error);
  font-size: var(--ds-typography-labelSmall-font-size);
}

/* ── Club list (home) ── */
.club-list { list-style: none; margin: 0 0 var(--ds-space-md); padding: 0; display: flex; flex-direction: column; gap: var(--ds-space-sm); }
.club-list li { display: flex; align-items: center; gap: var(--ds-space-sm); }

/* The club's front-door hero (feedback 2026-08-24) — same image as the directory card, banner-cropped. */
.club-hero {
  width: 100%; max-height: 14rem; object-fit: cover;
  border-radius: var(--ds-radius-md); margin-bottom: var(--ds-space-md);
}

/* Settings preview of the club image — bounded, never the raw upload size. */
.club-image-preview {
  max-width: 20rem; max-height: 14rem; object-fit: cover;
  border-radius: var(--ds-radius-md); border: var(--ds-space-hairline) solid var(--ds-color-outlineVariant);
  margin-bottom: var(--ds-space-sm);
}

/* ── Member row actions (dashboard) — small inline buttons, one form each ── */
.member-actions { display: flex; flex-wrap: wrap; gap: var(--ds-space-xs); }
.member-actions form { margin: 0; }
.member-actions__hint {
  color: var(--ds-color-onSurfaceSubtle);
  font-size: var(--ds-typography-labelSmall-font-size);
  font-style: italic;
}
.member-detail__actions { display: flex; flex-wrap: wrap; gap: var(--ds-space-sm); align-items: center; }
.member-detail__actions form { margin: 0; }

/* ── Track roster — leader/follower columns + member rows ── */
.roster-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: var(--ds-space-lg); }
.roster-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--ds-space-xs); }
.roster-list li { display: flex; align-items: center; justify-content: space-between; gap: var(--ds-space-sm); }
/* TAKT-206 — the waitlist's queue number, printed because .roster-list turns the <ol>'s own
   markers off. A quiet chip, not a badge: it ranks the row, it does not label a state. */
.roster-queue__pos {
  display: inline-block; min-inline-size: 1.5rem; margin-inline-end: var(--ds-space-xs);
  color: var(--ds-color-onSurfaceVariant); font-size: var(--ds-typography-bodySmall-font-size);
}
.roster-pref { color: var(--ds-color-onSurfaceVariant); font-size: var(--ds-typography-labelSmall-font-size); }

/* ── Member signup — track rows with an inline sign-up form ── */
.signup-tracks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--ds-space-md); }
.signup-track { display: flex; align-items: center; justify-content: space-between; gap: var(--ds-space-md); flex-wrap: wrap; }
.signup-track__info { display: flex; flex-direction: column; gap: var(--ds-space-hairline); }

/* ============================================================
 * App-wide UX pass — shared layout primitives + admin-list vocabulary.
 * These replace the per-page inline `style="margin-top:…; max-width:…"` literals
 * and make the Members-dashboard patterns reusable on every page. See
 * spec/app-ux-pass.md. Tokens-first throughout.
 * ============================================================ */

/* ── Page sections — one vertical rhythm for stacked cards (replaces per-card
      inline margin-top). Wrap a page's card stack in <div class="page-sections">. ── */
.page-sections { display: flex; flex-direction: column; gap: var(--ds-space-lg); }

/* TAKT-332 (#379) — `.md-formcard { max-width: 48rem }` used to live here, and with it the
   `.season-settings .md-formcard { max-width: none }` override that already proved the cap was
   being fought page by page. Both are retired, and the class is deleted from its call sites.
   THE RULE THAT REPLACED THEM: a form or component fills 100% of its container, and WIDTH IS THE
   CONTAINER'S JOB — the page column (`.takt-content`'s Standard/Wide rungs, #342), the card, or
   the modal decides the measure. A cap on the form INSIDE a card is what left the track edit form
   at 768px in a 1120px card, so `.form-grid`'s auto-fit yielded three columns and dead space to
   the right of every one of them. What may still cap: a content measure (prose in `ch`, an
   input's expected content length), a page container, or a data glyph whose meaning changes when
   stretched (see `.md-track__capacity`). */

/* Section heading with breathing room above a new block (replaces inline
   margin-top on h2/h3 used as section dividers within a page). */
.section-title { margin-top: var(--ds-space-xl); }

/* Form actions row — submit-button spacing; --span makes a button fill a form grid
   row (replaces inline margin-top / grid-column: 1 / -1 on submit buttons). */
.form-actions { margin-top: var(--ds-space-md); }

/* Authored prose — preserves the line breaks the author typed (news posts, doc notes;
   replaces inline white-space: pre-wrap). */
.prose-body { white-space: pre-wrap; }

/* Vertical form stack — fieldsets/fields/submit spaced by one gap (replaces a column of
   inline margin-top literals inside a single form card). */
.form-stack { display: flex; flex-direction: column; gap: var(--ds-space-md); }
.form-stack > .form-actions { margin-top: 0; }

/* A wrapping row of buttons (e.g. several pay-method submit forms side by side). */
.button-row { display: flex; flex-wrap: wrap; gap: var(--ds-space-sm); }

/* Table card — full content width (was capped at 60rem) */
.md-tablecard { margin-top: var(--ds-space-md); overflow: visible; }
/* DS-gap bridge: DsTable's scoped cell rules (`ds-c-table td[b-…]` etc.) can never match
   consumer-authored rows — the scope attribute lands on OUR page's <tr>/<td>, not the DS's — so
   every cell rendered with 0 padding and no row rule (dates jammed against names, headers
   touching). Until the DS republishes those rules through ::deep, our own .md-table class carries
   the same look from the same --ds-table-* tokens. Delete when the DS fix lands. */
.md-table thead th {
  padding: var(--ds-table-cellPaddingY) var(--ds-table-cellPaddingX);
  border-bottom: var(--ds-table-headerBorderWidth) solid var(--ds-table-border);
}
.md-table td {
  padding: var(--ds-table-cellPaddingY) var(--ds-table-cellPaddingX);
  border-bottom: var(--ds-table-borderWidth) solid var(--ds-table-border);
}
.md-table tr:last-child td { border-bottom: none; }
/* Click-to-sort column header: the <th>'s label is a <button> (reset native chrome, inherit the
   header type), with a caret showing the active direction. */
/* TAKT-251-04 — re-sorting is the one thing this header does, and on a phone it was a 24px-tall
   word. The button keeps the header's own typography and alignment, but takes the brand's button
   minimum as its box so a thumb has something to land on; the negative inline margin keeps the
   label optically flush with the cell's padding edge rather than indenting every column. */
.md-sortable {
  appearance: none; background: none; border: none; cursor: pointer;
  font: inherit; color: inherit; text-align: inherit;
  display: inline-flex; align-items: center; gap: var(--ds-space-xs);
  min-block-size: var(--ds-button-minHeight);
  /* Both axes: a short header ("Role") measured 41×44 under a coarse pointer — three pixels
     under the AAA floor sideways (2026-09-14 consumer capture, touch-target-below-coarse-floor).
     The same brand minimum the block axis already takes; the label stays start-aligned. */
  min-inline-size: var(--ds-button-minHeight);
  justify-content: flex-start;
  padding-block: 0;
  padding-inline: var(--ds-space-xs);
  margin-inline-start: calc(-1 * var(--ds-space-xs));
  /* App issue #422 — one radius per role: this IS a <button>, and its focus ring is the only
     place the corner shows, so it takes the button role's radius like every other button on
     the page rather than the 0px a reset button starts with. */
  border-radius: var(--ds-button-radius);
}
.md-sortable:hover { color: var(--ds-color-primary); }
.md-sortcaret { font-size: 0.7em; color: var(--ds-color-primary); }
/* Kebab action column — just wide enough for the ⋯ trigger. */
.md-actioncol { text-align: right; }
/* Inline-button action column — hugs its buttons (content width), right-aligned. */
.md-actions { width: 1%; white-space: nowrap; text-align: right; }
/* Owner 2026-09-03: action buttons sit on ONE line, always — the stacked column this used to be
   made row heights vary with the verb count, so the register read as uneven bands. Width is the
   cheaper cost: the table already scrolls horizontally in its own container, so a wide actions
   cell pans while every row keeps a single line's height. */
.md-actionstack { display: inline-flex; align-items: center; gap: var(--ds-space-xs); white-space: nowrap; }
/* The payer's e-mail sub-line is the register's only unbreakable long token; without a break
   opportunity its width is the table's minimum, and the whole register drags a sideways
   scrollbar for those last few pixels. */
[data-payment-member] .md-muted { overflow-wrap: anywhere; }
/* A cell whose content is a STACK (a payer's name over their e-mail): each line truncates on its
   own, so the sub-line can never set the column's width for the line above it. Applied to the
   cell, not to each span, because the spans are DS components we do not add classes inside. */
/* The ellipsis for a LINE INSIDE a flexible cell (a purpose line, an admin note, a refund note
   under the payer): DsTable's data-col="flex" truncates the cell's own inline content, but a
   block child inside it is only clipped, never ellipsised, so each such line says so itself.
   Our own elements, never a DS box (app issue #435). */
.md-truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.md-truncate--stacked > * { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.md-muted { color: var(--ds-color-onSurfaceVariant); }

/* Pager */
.md-pager {
  display: flex;
  /* Owner 2026-09-03 (430px screenshot): the no-wrap pager row collided with itself at phone
     widths — the count text and the page pills now wrap onto their own lines instead of
     overlapping. row-gap keeps the wrapped lines apart at the same rhythm the row uses. */
  flex-wrap: wrap;
  row-gap: var(--ds-space-sm);
  align-items: center;
  gap: var(--ds-space-sm);
  padding: var(--ds-space-md);
  border-top: var(--ds-space-hairline) solid var(--ds-color-outlineVariant);
  font-size: var(--ds-typography-bodySmall-font-size);
}
.md-pager__spacer { flex: 1; }

/* ── Global chat drawer (#17): a floating toggle + a right-side slide-in panel ── */
/* DsFab styles the button but deliberately does NOT place it — where a floating action button
   lives is the page's decision, not the component's. So its position stays ours. This rule is
   the fallback for a FAB rendered outside TaktShell's floating slot; inside it (the only place
   ChatDrawer is used today) takt-shell.css's `.takt-shell__fab .chat-fab { position: static }`
   wins instead, and DsAppShell's own --ds-zIndex-fab layer governs stacking (TAKT-250 finding
   C2) — so this z-index is a token, not the literal `38` it used to be, purely so the fallback
   path never regresses to an un-themed value if it is ever exercised. */
.chat-fab { position: fixed; right: var(--ds-space-lg); bottom: var(--ds-space-lg); z-index: var(--ds-zIndex-fab); }

/* ── Chat drawer (DS-004). DsFab and DsDrawer now own the button and the panel — size,
      size, elevation, the backdrop and the open/closed transition are all theirs. What is left
      here is only the drawer's INTERNAL layout: a fixed head, the channel switcher, and a
      scrolling body. The old rules positioned the panel themselves and waited for an `.is-open`
      class that DsDrawer never sets, which left it parked off-screen at translateX(100%). ── */
.chat-drawer { display: flex; flex-direction: column; }
.chat-drawer__channels {
  flex: none; padding: var(--ds-space-sm) var(--ds-card-padding);
  border-bottom: var(--ds-space-hairline) solid var(--ds-color-outlineVariant);
}
.chat-drawer__body { flex: 1; overflow: auto; padding: var(--ds-card-padding); }

/* ── Chat panel (SMM #157). DsChatLayout + DsChatComposer now own the log region, its
      scroller and the whole composer — the scrolling/overflow half of `.chat-log`, plus
      `.chat-input-row` and `.chat-input`, went with them. DsChatLayout is `height: 100%`,
      which resolves to `auto` inside the auto-height DsCard on /comms, so the one thing
      still ours is the cap that makes the log scroll rather than grow: the same 24rem
      `.chat-log` used to carry. The <ul> stays for the list semantics the log has always
      had, so it keeps its own reset. ── */
.chat-panel__layout { max-height: 24rem; }
.chat-log {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--ds-space-sm);
}

/* ── Track cards (EVENT-001 / task-4-schedule-on-the-track) — one card per level track,
   each carrying its own dates/venue/price. Replaces the old season-wide flattened dates
   table: two tracks can legally share a date now, so a date's Remove button only makes
   sense scoped under its own track. ── */
.md-tracks { display: flex; flex-direction: column; gap: var(--ds-space-md); }

/* TAKT-230 — the "+ Tilføj et hold" slot: a grid stacking the (now FullWidth) DsButton above
   its hint caption. */
.md-addtrack { display: grid; gap: var(--ds-space-xs); margin-top: var(--ds-space-md); }
.md-addtrack__hint { text-align: center; }
/* TAKT-332 (#379): the header is identity only now — the reorder arrows lead it, the name and its
   badges follow, and every verb that acts on the hold lives in ONE row at the card's foot. No
   `justify-content: space-between` any more: with nothing pinned right there is nothing to
   distribute, and space-between is what used to fling a lone Edit into the middle of the card. */
.md-track__header { display: flex; align-items: center; gap: var(--ds-space-sm); flex-wrap: wrap; }
/* The list-order pair, at the card's leading edge — the wireframe's ⠿ handle position. A different
   AXIS from the card's own actions (this moves the hold's place in the list, not the hold), which
   is the whole reason it does not sit in the action row. */
.md-track__order { display: flex; align-items: center; gap: var(--ds-space-xxs); flex: none; }
.md-track__title { display: flex; align-items: center; gap: var(--ds-space-sm); flex-wrap: wrap; min-width: 0; }
.md-track__title h3 { margin: 0; }
.md-track__meta {
  display: flex; flex-wrap: wrap; gap: var(--ds-space-md); margin-top: var(--ds-space-xs);
  color: var(--ds-color-onSurfaceVariant); font-size: var(--ds-typography-bodySmall-font-size);
}
/* TAKT-206 — the role meters on a track card. Bounded, not full-bleed: DsCapacityMeter's bars
   take whatever inline size they are given, and a bar stretched across a 1120px card reads as a
   progress indicator for the card rather than as "3 of 12 leader seats". Everything inside the
   box is the component's; this rule only decides how much room it gets. */
.md-track__capacity { margin-top: var(--ds-space-md); max-inline-size: 28rem; }

/* ── Season builder: the season's two questions, weighted ── (TAKT-331 / #375)
   This used to be `repeat(auto-fit, minmax(20rem, 1fr))` over THREE cards — three EQUAL columns
   for wildly unequal content (two date fields · one textarea · six controls) — with
   `align-items: stretch` below it, so two mostly-empty tall cards were painted beside one full
   one. Two regions now, weighted 3:2 on purpose: "what IS this season" (its window and the
   sentence a member reads) takes the wide column because the description textarea is the
   widest-wanting control on the page, and the signup rules take the narrow one because a
   checkbox with a help line under it is a reading unit that wants ONE column, not three.
   `start`, never `stretch`: unequal content is allowed to be unequal. */
.season-settings {
  display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  gap: var(--ds-space-lg); align-items: start;
}
/* One column below the shell's own breakpoint — the same 900px rung every other admin composition
   collapses at (adoption §3 / ruling S5-D). */
@media (max-width: 899px) {
  .season-settings { grid-template-columns: 1fr; }
}

/* TAKT-332 (#379) — the card's ONE action surface. Everything an admin can do to this hold, in
   one row, ordered by kind: the door out, the edit, its calendar (an attached pair, because both
   verbs act on the same dates), then the ⋯ holding the rare and the destructive. Six controls in
   two places is what this replaced. */
.md-track__actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-sm);
  margin-top: var(--ds-space-md);
}
/* The overflow trigger takes the row's trailing edge, so the destructive verb is as far from the
   frequent ones as the row allows — and it stays put when the row wraps on a phone. Our own
   wrapper, not DsMoreMenu's Class: that lands on the trigger BUTTON, inside the component's anchor
   element, where an auto margin has nothing to push against. */
.md-track__actions-more { display: flex; margin-inline-start: auto; }
/* Parity audit 2026-09-02 (WCAG 2.5.8 AA): the bare heading anchor measured 19px tall — under
   the 24px floor that applies on every device, mouse included. Same inline-flex +
   min-block-size technique as .roster__navback / the footer links: the click box grows, the
   Heading6 text does not. */
.md-track__namelink {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--ds-size-targetMin);
}

/* FEAT-007b — the structured help under the formula field (owner round 2: "the description
   needs more formatting to deliver any value"): a lead line, labelled definition rows with the
   vocabulary in code face, and the off note — scannable, not a paragraph. */
.md-formula-help {
  display: grid;
  gap: var(--ds-space-2xs);
  font-size: var(--ds-typography-caption-font-size);
  color: var(--ds-color-onSurfaceVariant);
}
.md-formula-help p { margin: 0; }
.md-formula-help dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--ds-space-2xs) var(--ds-space-sm);
  margin: 0;
}
.md-formula-help dt { font-weight: var(--ds-typography-label-font-weight); }
.md-formula-help dd { margin: 0; }
.md-formula-help code {
  font-family: var(--ds-typography-code-font-family);
  font-size: 0.95em;
}

/* The preview: the typeset formula (FormulaDisplay) and its worked examples as one quiet block. */
.md-formula-preview {
  display: grid;
  gap: var(--ds-space-xs);
  padding: var(--ds-space-sm) var(--ds-space-md);
  border-inline-start: var(--ds-space-hairline) solid var(--ds-color-outlineVariant);
}
.md-formula-preview__line {
  margin: 0;
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
}
.md-formula-preview__line--example {
  font-size: var(--ds-typography-caption-font-size);
  color: var(--ds-color-onSurfaceVariant);
}
/* …and below the shell's breakpoint the same link takes the 44px AAA/touch tier, like the
   other quiet-link raises in this pass. */
@media (max-width: 899px) {
  .md-track__namelink { min-block-size: var(--ds-size-touchTarget); }
}
/* …but only while there IS a trailing edge to take. Below the shell's breakpoint the row wraps and
   the auto margin pushed a 32px trigger onto a line of its own, three rows down from the verbs it
   belongs with. It just follows the group there. */
@media (max-width: 899px) {
  .md-track__actions-more { margin-inline-start: 0; }
}

/* ── Season schedule (list + calendar) ──
   The season's dates in one place instead of a table per track. Everything here is token-driven;
   the per-class colour is a hue rotation applied to the brand's own tertiary container, so the
   palette follows the brand rather than a hardcoded set of chip colours. */
/* The liste view is a DsCard now (TAKT-230), so the surrounding stack owns the spacing. What is
   left is the min-width the shell's grid column needs, or the table's intrinsic width becomes the
   column's minimum and the whole page scrolls sideways instead of the table scrolling in its own
   wrapper — the same rule .takt-season__viewcard carries for the other three readings. */
.season-schedule { min-width: 0; }

/* TAKT-230 — the pattern sentence above the list. LAYOUT ONLY (the row it sits on is a DsInline);
   min-width:0 so a long sentence wraps INSIDE the row rather than setting the row's minimum width
   and scrolling the page sideways — the overflow-audit rule. */
.season-schedule__patterntext { flex: 1 1 20rem; min-width: 0; }

/* EVENT-004 — a week the class does not run (holiday, exams, closed venue). Deliberately still
   VISIBLE in both views: a member who sees nothing at all can't tell a planned break from a date
   the club forgot to add, which is the whole reason the reason is stored. Quiet, not alarming —
   this is a normal, planned part of a term, so it reads as muted rather than as an error. */
.schedule-cancelled { color: var(--ds-color-onSurfaceVariant); font-style: italic; }
tr.is-cancelled { color: var(--ds-color-onSurfaceVariant); }

/* TAKT-331 (#375) — `.season-settings { align-items: stretch }` used to close this file. It was
   there because the three cards had once carried a Save each and their footers had to line up;
   the single commit point moved to the page header in TAKT-230, so all stretch did after that was
   inflate the short card to the tall one's height and paint the difference as empty surface. The
   grid above is `start`-aligned instead. */
