/* =====================================================================
   DESIGN SYSTEM — the single source of truth for how this app looks.

   WHY THIS FILE EXISTS
   The UI grew page by page: ~110 per-page stylesheets, 43 inline <style>
   blocks, three different typefaces and four different ideas of what a
   "card" is. This file defines the system once and applies it globally so
   individual pages stop inventing their own.

   HOW IT IS LOADED
   Last in every layout — after Bootstrap, after style.css/app.css, and
   after each page's @yield('styles'). That ordering is what lets it
   normalise legacy pages without editing all 110 of them. Because it wins
   on source order, most rules here need no !important; the ones that do
   carry it are marked, and they exist only where a legacy page-level rule
   uses !important first.

   HOW TO USE IT
   Reach for a token (--ds-*) instead of a literal. If you find yourself
   writing a new hex code, a new radius or a new font-size in a page
   stylesheet, the value belongs here instead. Adding a page-specific
   one-off is how the drift this file cleans up got started.

   ORDER OF SECTIONS
     1  Tokens                    9  Tables
     2  Dark theme               10  Badges & status pills
     3  Base & typography        11  Alerts & toasts
     4  Layout rhythm            12  Modals
     5  Icons                    13  Dropdowns & menus
     6  Buttons                  14  Navigation (sidebar/topbar/tabs)
     7  Forms                    15  Pagination
     8  Cards & panels           16  Empty & loading states
                                 17  Accessibility & responsive
   ===================================================================== */


/* =====================================================================
   1  TOKENS
   ===================================================================== */

:root {
    /* ── Typeface ──────────────────────────────────────────────────────
       Inter is the app's only typeface. It was designed for user
       interfaces at small sizes, which is most of what this app is. The
       fallbacks are system faces with near-identical metrics, so a failed
       webfont load barely shifts the layout. */
    --ds-font: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
    --ds-font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

    /* ── Type scale ────────────────────────────────────────────────────
       Inter has a tall x-height and a narrow set width, so it stays
       legible at the small sizes this scale leans on while fitting more
       into a table cell than a geometric face would.
       --ds-text-md (14px) is the workhorse for body copy, table cells,
       inputs and buttons. */
    --ds-text-2xs: 0.6875rem;   /* 11px — table headers, kbd, timestamps  */
    --ds-text-xs:  0.75rem;     /* 12px — helper text, badges, footer     */
    --ds-text-sm:  0.8125rem;   /* 13px — secondary/meta text, small btns */
    --ds-text-md:  0.875rem;    /* 14px — BODY DEFAULT                    */
    --ds-text-lg:  1rem;        /* 16px — card titles, modal titles, h5   */
    --ds-text-xl:  1.125rem;    /* 18px — section headings, h4            */
    --ds-text-2xl: 1.375rem;    /* 22px — page titles, h3/h2              */
    --ds-text-3xl: 1.75rem;     /* 28px — h1, dashboard statistics        */
    --ds-text-4xl: 2.25rem;     /* 36px — display / hero only             */

    /* ── Weights ───────────────────────────────────────────────────────
       Only these five cuts are loaded. Asking for anything else makes the
       browser synthesise it, which looks wrong next to a real cut. */
    --ds-weight-light:    300;
    --ds-weight-normal:   400;
    --ds-weight-medium:   500;  /* labels, nav items, table cells         */
    --ds-weight-semibold: 600;  /* headings, buttons, card titles         */
    --ds-weight-bold:     700;  /* page titles, statistics                */

    /* ── Line heights ──────────────────────────────────────────────── */
    --ds-leading-tight:   1.25; /* headings, statistics                   */
    --ds-leading-snug:    1.4;  /* card titles, table cells               */
    --ds-leading-normal:  1.55; /* body copy                              */
    --ds-leading-relaxed: 1.7;  /* long-form paragraphs                   */

    /* Inter is drawn with slightly open spacing for small text, which
       reads loose once a heading gets large; a touch of negative tracking
       restores the intended colour. (This is what Inter's own optical
       sizing does, and the CDN request enables opsz for the same reason.) */
    --ds-tracking-tight:  -0.015em;
    --ds-tracking-normal: 0;
    --ds-tracking-wide:   0.04em;  /* uppercase eyebrows / table headers  */

    /* ── Spacing scale ─────────────────────────────────────────────────
       A 4px base. Every gap, pad and margin in the system is one of
       these; that is what makes rhythm consistent across screens. */
    --ds-space-1:  0.25rem;   /*  4px */
    --ds-space-2:  0.5rem;    /*  8px */
    --ds-space-3:  0.75rem;   /* 12px */
    --ds-space-4:  1rem;      /* 16px */
    --ds-space-5:  1.25rem;   /* 20px */
    --ds-space-6:  1.5rem;    /* 24px */
    --ds-space-8:  2rem;      /* 32px */
    --ds-space-10: 2.5rem;    /* 40px */
    --ds-space-12: 3rem;      /* 48px */

    /* ── Radii ─────────────────────────────────────────────────────────
       Four steps, each with one job. The app previously used 14 distinct
       radius values; these replace all of them. */
    --ds-radius-xs:   6px;    /* chips, tiny inline controls              */
    --ds-radius-sm:   8px;    /* buttons, inputs, selects, alerts         */
    --ds-radius-md:  10px;    /* icon tiles, inner panels                 */
    --ds-radius-lg:  12px;    /* dropdown menus, popovers                 */
    --ds-radius-xl:  16px;    /* cards, modals — the app's signature      */
    --ds-radius-pill: 999px;  /* badges, avatars, toggles                 */

    /* ── Component heights ─────────────────────────────────────────────
       Buttons, inputs and selects share these so a filter row lines up
       without per-page nudging. */
    --ds-control-h-sm: 34px;
    --ds-control-h:    40px;
    --ds-control-h-lg: 46px;
    --ds-topbar-h:     64px;

    /* ── Icons ─────────────────────────────────────────────────────── */
    --ds-icon-sm:   0.8125rem;
    --ds-icon:      0.875rem;
    --ds-icon-lg:   1.0625rem;
    --ds-icon-slot: 1.25rem;   /* fixed width so stacked icons align     */
    --ds-icon-gap:  0.5rem;    /* icon-to-text distance, everywhere      */

    /* ══ THE PALETTE ═══════════════════════════════════════════════════
       Three base colours, and nothing else. Every other colour in the
       system is derived from one of them — same hue, same 100%
       saturation, only the lightness moves.

         #00a76f  green   hsl(160, 100%, 33%)  brand · primary · success
         #00b8d9  blue    hsl(189, 100%, 43%)  info
         #d02600  red     hsl( 11, 100%, 41%)  danger

       Warning has no base of its own, so it is the red's hue rotated to
       amber (11° → 38°) at the same saturation: unmistakably part of the
       warm family, never confusable with an error.

       Every bg/fg pair below was checked against WCAG AA (4.5:1) — the
       measured ratios are in the comments. If you change a value, re-check
       it; do not add a fourth hue. */

    --ds-brand:     #00a76f;
    --ds-brand-600: #007867;   /* hover — white on this is 5.41:1        */
    --ds-brand-700: #005c4b;   /* active                                  */
    --ds-brand-50:  #e5fbf3;   /* tint, shared with success               */
    --ds-brand-100: #c2f2e0;
    --ds-brand-ring: rgba(0, 167, 111, 0.16);

    /* ── Semantic roles ────────────────────────────────────────────────
       Each role has a solid (button fills), a subtle tint (badge/alert
       backgrounds) and a foreground that is readable on that tint. */

    /* success IS the brand green — the app used to carry a second,
       unrelated green (#22c55e) for exactly the same meaning. */
    --ds-success:    #00a76f;
    --ds-success-bg: #e5fbf3;
    --ds-success-fg: #007a51;   /* 4.98:1 on its tint  AA */

    --ds-warning:    #e69100;   /* red's hue rotated to amber            */
    --ds-warning-bg: #fbf3e5;
    --ds-warning-fg: #7a4e00;   /* 6.53:1 on its tint  AA */

    --ds-danger:     #d02600;
    --ds-danger-bg:  #fbe9e5;
    --ds-danger-fg:  #7a1600;   /* 9.21:1 on its tint  AA */

    /* Blue is a tint/accent colour only. White on #00b8d9 is 2.37:1, so
       it must never be a solid button fill — use the tint + fg pair. */
    --ds-info:       #00b8d9;
    --ds-info-bg:    #e5f7fb;
    --ds-info-fg:    #00687a;   /* 5.84:1 on its tint  AA */

    --ds-neutral-bg: #f2f4f7;
    --ds-neutral-fg: #475467;

    /* ── Text colours ──────────────────────────────────────────────── */
    --ds-ink:   #1c252e;  /* headings, primary values                    */
    --ds-body:  #454f5b;  /* body copy, table cells                      */
    --ds-muted: #637381;  /* secondary text, helper copy                 */
    --ds-faint: #919eab;  /* placeholders, disabled, table headers       */

    /* ── Surfaces & borders ────────────────────────────────────────── */
    --ds-surface:        #ffffff;  /* cards, modals, topbar              */
    --ds-surface-sunken: #f9fafb;  /* page background                    */
    --ds-surface-raised: #f4f6f8;  /* tiles, hover rows, inset panels    */
    --ds-border:         #e4e7ec;
    --ds-border-strong:  #d0d5dd;  /* input borders — needs more weight  */

    /* ── Elevation ─────────────────────────────────────────────────────
       Restrained on purpose. Cards get -sm; only things that float above
       the page (dropdowns, modals) earn -lg and -xl. */
    --ds-shadow-xs: 0 1px 2px 0 rgba(16, 24, 40, 0.05);
    --ds-shadow-sm: 0 1px 3px 0 rgba(16, 24, 40, 0.07), 0 1px 2px 0 rgba(16, 24, 40, 0.04);
    --ds-shadow-md: 0 4px 12px -2px rgba(16, 24, 40, 0.08), 0 2px 6px -2px rgba(16, 24, 40, 0.04);
    --ds-shadow-lg: 0 12px 32px -8px rgba(16, 24, 40, 0.14);
    --ds-shadow-xl: 0 24px 48px -12px rgba(16, 24, 40, 0.18);
    --ds-focus-ring: 0 0 0 3px var(--ds-brand-ring);

    /* ── Motion ────────────────────────────────────────────────────────
       Two durations. Legacy pages used everything from 0.15s to 0.3s on
       the same interaction, which is why hovers felt uneven. */
    --ds-transition-fast: 0.15s ease;
    --ds-transition:      0.22s ease;

    /* ═══ BRIDGE ═══════════════════════════════════════════════════════
       The ~110 legacy stylesheets read these older variable names.
       Re-pointing them here means every existing page picks up the new
       system without being edited. Do not add new rules that consume
       these names — use the --ds-* tokens directly. */
    --primary-color:  var(--ds-brand);
    --primary-dark:   var(--ds-brand-600);
    --success-color:  var(--ds-success);
    --warning-color:  var(--ds-warning);
    --danger-color:   var(--ds-danger);
    --info-color:     var(--ds-info);
    --blue-color:     var(--ds-info);
    --dark-color:     var(--ds-ink);
    --light-color:    var(--ds-surface-sunken);

    --gray-50:  var(--ds-surface-sunken);
    --gray-100: var(--ds-surface-sunken);
    --gray-200: var(--ds-surface-raised);
    --gray-300: var(--ds-border);
    --gray-400: var(--ds-faint);
    --gray-600: var(--ds-muted);
    --gray-700: var(--ds-body);
    --gray-800: var(--ds-ink);
    --gray-1000: var(--ds-muted);

    --surface-card:   var(--ds-surface);
    --surface-border: var(--ds-border);
    --shadow-card:       var(--ds-shadow-sm);
    --shadow-card-hover: var(--ds-shadow-md);

    /* Bootstrap reads these for every component it ships. Setting them
       here re-skins buttons, forms, modals, dropdowns, tables, alerts and
       pagination in one place rather than one override rule each. */
    --bs-font-sans-serif: var(--ds-font);
    --bs-body-font-family: var(--ds-font);
    --bs-body-font-size: var(--ds-text-md);
    --bs-body-color: var(--ds-body);
    --bs-body-bg: var(--ds-surface-sunken);
    --bs-border-color: var(--ds-border);
    --bs-border-radius: var(--ds-radius-sm);
    --bs-border-radius-sm: var(--ds-radius-xs);
    --bs-border-radius-lg: var(--ds-radius-lg);
    --bs-border-radius-xl: var(--ds-radius-xl);
    --bs-primary: var(--ds-brand);
    --bs-primary-rgb: 0, 167, 111;
    --bs-success: var(--ds-success);
    --bs-success-rgb: 0, 167, 111;
    --bs-info: var(--ds-info);
    --bs-info-rgb: 0, 184, 217;
    --bs-warning: var(--ds-warning);
    --bs-warning-rgb: 230, 145, 0;
    --bs-danger: var(--ds-danger);
    --bs-danger-rgb: 208, 38, 0;
    --bs-link-color: var(--ds-brand);
    --bs-link-color-rgb: 0, 167, 111;
    --bs-link-hover-color: var(--ds-brand-600);
}


/* =====================================================================
   2  DARK THEME

   Only the tokens flip. Every component below is written against tokens,
   so none of them need a dark-mode counterpart — which is the whole
   point of having tokens. Brand green is intentionally unchanged.
   ===================================================================== */

html[data-theme="dark"] {
    color-scheme: dark;

    --ds-ink:   #f4f6f8;
    --ds-body:  #e4e7ec;
    --ds-muted: #919eab;
    --ds-faint: #8a94a3;

    --ds-surface:        #1c252e;
    --ds-surface-sunken: #141a21;
    --ds-surface-raised: #212b36;
    --ds-border:         rgba(145, 158, 171, 0.18);
    --ds-border-strong:  rgba(145, 158, 171, 0.28);

    /* Subtle fills go translucent in dark mode — the light pastel tints
       glow against a near-black page. Each tint is its own base colour at
       low alpha, and each foreground is that same hue lightened until it
       clears AA on the #1c252e card surface (measured ratios noted). */
    --ds-brand-50:   rgba(0, 167, 111, 0.16);
    --ds-success-bg: rgba(0, 167, 111, 0.16);
    --ds-success-fg: #7bf4cc;   /* 11.57:1 on #1c252e  AA */
    --ds-warning-bg: rgba(230, 145, 0, 0.16);
    --ds-warning-fg: #f4c87b;   /*  9.90:1 on #1c252e  AA */
    --ds-danger-bg:  rgba(208, 38, 0, 0.20);
    --ds-danger-fg:  #f4917b;   /*  6.78:1 on #1c252e  AA */
    --ds-info-bg:    rgba(0, 184, 217, 0.16);
    --ds-info-fg:    #7be2f4;   /* 10.36:1 on #1c252e  AA */
    --ds-neutral-bg: rgba(152, 162, 179, 0.16);
    --ds-neutral-fg: #d0d5dd;

    --ds-shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.30);
    --ds-shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.32);
    --ds-shadow-md: 0 4px 12px -2px rgba(0, 0, 0, 0.40);
    --ds-shadow-lg: 0 12px 32px -8px rgba(0, 0, 0, 0.50);
    --ds-shadow-xl: 0 24px 48px -12px rgba(0, 0, 0, 0.60);

    --bs-body-bg: var(--ds-surface-sunken);
    --bs-body-color: var(--ds-body);
    --bs-emphasis-color: #ffffff;
    --bs-secondary-bg: var(--ds-surface-raised);
    --bs-tertiary-bg: var(--ds-surface);
    --bs-modal-bg: var(--ds-surface);
    --bs-card-bg: var(--ds-surface);
    --bs-dropdown-bg: var(--ds-surface-raised);
    --bs-list-group-bg: var(--ds-surface);
    --bs-offcanvas-bg: var(--ds-surface);
    --bs-accordion-bg: var(--ds-surface);
}


/* =====================================================================
   3  BASE & TYPOGRAPHY

   One typeface, one scale, one hierarchy. The !important on font-family
   is the exception that proves the rule: a dozen legacy stylesheets set
   their own family with !important, and Inter has to win all of them.
   ===================================================================== */

html {
    -webkit-text-size-adjust: 100%;
}

body,
body.super-panel,
.modal,
.dropdown-menu,
.toast,
.tooltip,
.popover,
.offcanvas {
    font-family: var(--ds-font) !important;
}

body {
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-normal);
    line-height: var(--ds-leading-normal);
    color: var(--ds-body);
    background-color: var(--ds-surface-sunken);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Form controls do not inherit font-family in any browser — say it once
   here rather than in every component below. */
button,
input,
optgroup,
select,
textarea {
    font-family: inherit;
}

/* ── Heading hierarchy ─────────────────────────────────────────────────
   h1 → page title, h2 → major section, h3 → sub-section, h4/h5 → card
   and panel titles, h6 → eyebrow. Sizes stop at 28px on purpose: this is
   a dense admin tool, not a marketing site. */
h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6 {
    font-family: var(--ds-font);
    color: var(--ds-ink);
    line-height: var(--ds-leading-tight);
    letter-spacing: var(--ds-tracking-tight);
    margin-top: 0;
}

h1, .h1 { font-size: var(--ds-text-3xl); font-weight: var(--ds-weight-bold); }
h2, .h2 { font-size: var(--ds-text-2xl); font-weight: var(--ds-weight-bold); }
h3, .h3 { font-size: var(--ds-text-xl);  font-weight: var(--ds-weight-semibold); }
h4, .h4 { font-size: var(--ds-text-lg);  font-weight: var(--ds-weight-semibold); }
h5, .h5 { font-size: var(--ds-text-lg);  font-weight: var(--ds-weight-semibold); }
h6, .h6 {
    font-size: var(--ds-text-xs);
    font-weight: var(--ds-weight-semibold);
    letter-spacing: var(--ds-tracking-wide);
    text-transform: uppercase;
    color: var(--ds-muted);
}

/* style.css shrank the dashboard's page title to 17px with a compound
   selector; the system's h2 size is the correct value for it. */
h1.h2.mb-1 {
    font-size: var(--ds-text-2xl);
    font-weight: var(--ds-weight-bold);
}

p { line-height: var(--ds-leading-normal); }
p:last-child { margin-bottom: 0; }

small, .small { font-size: var(--ds-text-xs); }

.text-muted {
    color: var(--ds-muted) !important;  /* beats Bootstrap's own !important */
}

/* Several pages set .text-muted at 3 different sizes; normalise the one
   that style.css hard-coded. */
p.text-muted.mb-0 { font-size: var(--ds-text-sm); }

a {
    color: var(--ds-brand);
    text-decoration: none;
    transition: color var(--ds-transition-fast);
}

a:hover { color: var(--ds-brand-600); }

code, kbd, samp, pre {
    font-family: var(--ds-font-mono);
    font-size: 0.85em;
}

code:not(.hljs) {
    background: var(--ds-surface-raised);
    color: var(--ds-body);
    border-radius: var(--ds-radius-xs);
    padding: 0.125rem 0.375rem;
}

/* Semantic text roles — use these instead of inventing a size per page. */
.ds-page-title    { font-size: var(--ds-text-2xl); font-weight: var(--ds-weight-bold); color: var(--ds-ink); line-height: var(--ds-leading-tight); letter-spacing: var(--ds-tracking-tight); margin: 0; }
.ds-page-subtitle { font-size: var(--ds-text-md);  font-weight: var(--ds-weight-normal); color: var(--ds-muted); margin: var(--ds-space-1) 0 0; }
.ds-section-title { font-size: var(--ds-text-xl);  font-weight: var(--ds-weight-semibold); color: var(--ds-ink); margin: 0; }
.ds-label         { font-size: var(--ds-text-md);  font-weight: var(--ds-weight-medium); color: var(--ds-ink); }
.ds-help          { font-size: var(--ds-text-xs);  color: var(--ds-muted); }
.ds-meta          { font-size: var(--ds-text-sm);  color: var(--ds-muted); }
.ds-eyebrow       { font-size: var(--ds-text-2xs); font-weight: var(--ds-weight-semibold); letter-spacing: var(--ds-tracking-wide); text-transform: uppercase; color: var(--ds-muted); }
.ds-stat          { font-size: var(--ds-text-3xl); font-weight: var(--ds-weight-bold); color: var(--ds-ink); line-height: var(--ds-leading-tight); letter-spacing: var(--ds-tracking-tight); }


/* =====================================================================
   4  LAYOUT RHYTHM

   Consistent distance between the shell and the content, and between one
   block of content and the next. Pages previously ranged from 1rem to
   3rem for the same gap.
   ===================================================================== */

/* Wider gutter than vertical padding — the pre-refactor proportion. */
.main-content {
    padding: var(--ds-space-6) var(--ds-space-8);
}

/* NOTE — do not redefine Bootstrap spacing utilities here.
   style.css used to override .py-4 and .px-3 to fake page padding. The
   fix is to leave them alone entirely, NOT to reset them: this file loads
   after Bootstrap, so any declaration here beats the real utility. An
   earlier attempt set `.py-4 { padding-left: 0 !important }`, which
   cancelled the padding-left that `.px-4` supplies on the standard page
   wrapper (`class="flex-grow-1 px-4 py-4"`) and shoved every page flush
   against the sidebar. Page padding belongs to .main-content and to the
   wrapper's own utilities. */

/* Vertical rhythm between stacked blocks. */
.ds-stack > * + * { margin-top: var(--ds-space-6); }

.ds-page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--ds-space-3);
    margin-bottom: var(--ds-space-6);
}

.ds-page-header-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--ds-space-2);
}

@media (max-width: 575.98px) {
    .main-content { padding: var(--ds-space-4); }

    /* Full-width action buttons read better than three wrapped pills. */
    .ds-page-header-actions { width: 100%; }
    .ds-page-header-actions > .btn { flex: 1 1 auto; }
}


/* =====================================================================
   5  ICONS

   One library (Font Awesome 7), one size ramp, one gap. The app used to
   mix 5 icon sizes and space icons with &nbsp;, me-1, me-2 and margins
   interchangeably.

   The global `img, svg { margin: 0 10px !important }` rule in style.css
   is neutralised here: it pushed every avatar, logo and inline image off
   centre and forced eight separate counter-rules elsewhere. Spacing now
   comes from flex `gap` on the containers that actually need it.
   ===================================================================== */

img, svg {
    margin: 0;
    max-width: 100%;
}

.btn i,
.btn svg,
.nav-link i,
.dropdown-item i,
.alert i,
.badge i {
    font-size: var(--ds-icon);
    line-height: 1;
    vertical-align: -0.05em;
}

/* Icon + label spacing, applied structurally so it cannot drift. Buttons
   and menu items that contain both get a real gap; icon-only ones don't
   inherit stray margin. */
.btn,
.nav-link,
.page-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--ds-icon-gap);
}

.nav-link { justify-content: flex-start; }

/* Menu rows fill their menu — Bootstrap ships .dropdown-item as a
   full-width block, and shrinking it to its content would leave the
   hover highlight hugging the label instead of the row. Same reasoning
   applies to sidebar links; see § 14. */
.dropdown-item {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: var(--ds-icon-gap);
    width: 100%;
}

/* These components space their icon with flex `gap`, so an icon that ALSO
   carries a Bootstrap margin utility (me-1 / me-2 / ms-2 …) ends up with
   two gaps stacked and the label drifts away from its icon. The gap is
   the single source of truth, so margin utilities are neutralised inside
   them.

   This is a rule rather than only stripping the classes out of the
   markup, because it also has to cover buttons that JavaScript builds at
   runtime — those never pass through a template.

   Note what is NOT in this list: .nav-link. The sidebar is the one place
   that still spaces its icon with margin-right rather than gap (see § 14
   — it runs gap:0 so it can keep the app's original rhythm), so
   neutralising margins there would weld every label onto its icon. */
.btn > i[class*="me-"],
.btn > i[class*="ms-"],
.btn > svg[class*="me-"],
.dropdown-item > i[class*="me-"],
.dropdown-item > i[class*="ms-"],
.page-link > i[class*="me-"],
.toast-body > i[class*="me-"],
.alert > i[class*="me-"] {
    margin-right: 0 !important;
    margin-left: 0 !important;
}

/* ms-auto is layout, not icon spacing — it still has to push things right
   (dropdown trailing icons and the sidebar submenu caret rely on it). */
.btn > i.ms-auto,
.dropdown-item > i.ms-auto {
    margin-left: auto !important;
}

/* Fixed-width slot so stacked menu icons form a straight optical line.
   The sidebar's own icons are handled in § 14, which keeps all of that
   panel's spacing in one place. */
.dropdown-item i {
    width: var(--ds-icon-slot);
    text-align: center;
    flex-shrink: 0;
}

.ds-icon-sm { font-size: var(--ds-icon-sm); }
.ds-icon-lg { font-size: var(--ds-icon-lg); }

/* style.css blew .fa-lg up to 1.65em, well past Font Awesome's own 1.33. */
.fa-lg { font-size: 1.25em !important; }

/* Circular icon tile — the app had five near-identical copies of this
   (.attendance-icon, .status-icon, .progress-icon, .payment-icon,
   .announcement-icon, .report-icon), all 80px gradients. */
.ds-icon-tile {
    width: 48px;
    height: 48px;
    border-radius: var(--ds-radius-md);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--ds-icon-lg);
    background: var(--ds-neutral-bg);
    color: var(--ds-neutral-fg);
    flex-shrink: 0;
}

.ds-icon-tile.tone-brand   { background: var(--ds-brand-50);   color: var(--ds-brand-600); }
.ds-icon-tile.tone-success { background: var(--ds-success-bg); color: var(--ds-success-fg); }
.ds-icon-tile.tone-warning { background: var(--ds-warning-bg); color: var(--ds-warning-fg); }
.ds-icon-tile.tone-danger  { background: var(--ds-danger-bg);  color: var(--ds-danger-fg); }
.ds-icon-tile.tone-info    { background: var(--ds-info-bg);    color: var(--ds-info-fg); }

.ds-icon-tile-lg { width: 64px; height: 64px; font-size: 1.5rem; border-radius: var(--ds-radius-lg); }

/* Avatars — three sizes, one shape. */
.avatar,
.navAvatar {
    border-radius: var(--ds-radius-pill);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: var(--ds-weight-semibold);
    font-size: var(--ds-text-xs);
    overflow: hidden;
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    margin: 0;
}

.avatar-sm { width: 28px; height: 28px; font-size: var(--ds-text-2xs); }
.avatar-lg { width: 44px; height: 44px; font-size: var(--ds-text-md); }


/* =====================================================================
   6  BUTTONS

   One height, one radius, one weight, one icon gap — colour is the only
   thing that varies by intent. Previously .btn-modern, .btn-smaller,
   .btn-pos, .btn-edit-profile and raw .btn each had their own geometry.

   .btn-modern is gone entirely: it was on 160 elements, always alongside
   .btn, and once both resolved to the same geometry it was pure noise.
   The class has been stripped from the markup — do not reintroduce it.
   ===================================================================== */

.btn {
    --bs-btn-font-family: var(--ds-font);
    min-height: var(--ds-control-h);
    padding: 0 var(--ds-space-4);
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-semibold);
    line-height: var(--ds-leading-snug);
    letter-spacing: 0;
    border-radius: var(--ds-radius-sm);
    border-width: 1px;
    white-space: nowrap;
    transition: background-color var(--ds-transition-fast),
                border-color var(--ds-transition-fast),
                color var(--ds-transition-fast),
                box-shadow var(--ds-transition-fast);
}

.btn-sm,
.btn-smaller {
    min-height: var(--ds-control-h-sm);
    padding: 0 var(--ds-space-3);
    font-size: var(--ds-text-sm);
    border-radius: var(--ds-radius-sm);
}

.btn-lg {
    min-height: var(--ds-control-h-lg);
    padding: 0 var(--ds-space-6);
    font-size: var(--ds-text-md);
}

.btn:hover { box-shadow: var(--ds-shadow-sm); }

.btn:focus-visible {
    outline: none;
    box-shadow: var(--ds-focus-ring);
}

.btn:disabled,
.btn.disabled {
    opacity: 0.55;
    box-shadow: none;
    cursor: not-allowed;
}

/* ── Intents ─────────────────────────────────────────────────────────── */

.btn-primary {
    --bs-btn-bg: var(--ds-brand);
    --bs-btn-border-color: var(--ds-brand);
    --bs-btn-hover-bg: var(--ds-brand-600);
    --bs-btn-hover-border-color: var(--ds-brand-600);
    --bs-btn-active-bg: var(--ds-brand-700);
    --bs-btn-active-border-color: var(--ds-brand-700);
    --bs-btn-disabled-bg: var(--ds-brand);
    --bs-btn-disabled-border-color: var(--ds-brand);
}

.btn-success {
    --bs-btn-bg: var(--ds-success);
    --bs-btn-border-color: var(--ds-success);
    --bs-btn-hover-bg: var(--ds-brand-600);
    --bs-btn-hover-border-color: var(--ds-brand-600);
    --bs-btn-color: #fff;
    --bs-btn-hover-color: #fff;
}

.btn-danger {
    --bs-btn-bg: var(--ds-danger);
    --bs-btn-border-color: var(--ds-danger);
    --bs-btn-hover-bg: #ad2000;
    --bs-btn-hover-border-color: #ad2000;
    --bs-btn-color: #fff;
    --bs-btn-hover-color: #fff;
}

.btn-warning {
    --bs-btn-bg: var(--ds-warning);
    --bs-btn-border-color: var(--ds-warning);
    --bs-btn-hover-bg: #c27b00;
    --bs-btn-hover-border-color: #c27b00;
    --bs-btn-color: var(--ds-ink);
    --bs-btn-hover-color: var(--ds-ink);
}

/* Secondary = the neutral companion to primary. Bootstrap's grey fill
   competed with primary for attention, so it is an outline here — which
   is also what most of the app already reached for. */
.btn-secondary,
.btn-outline-secondary,
.btn-light {
    --bs-btn-bg: var(--ds-surface);
    --bs-btn-border-color: var(--ds-border-strong);
    --bs-btn-color: var(--ds-body);
    --bs-btn-hover-bg: var(--ds-surface-raised);
    --bs-btn-hover-border-color: var(--ds-border-strong);
    --bs-btn-hover-color: var(--ds-ink);
    --bs-btn-active-bg: var(--ds-surface-raised);
    --bs-btn-active-border-color: var(--ds-border-strong);
    --bs-btn-active-color: var(--ds-ink);
}

.btn-outline-primary {
    --bs-btn-color: var(--ds-brand);
    --bs-btn-border-color: var(--ds-brand);
    --bs-btn-bg: transparent;
    --bs-btn-hover-bg: var(--ds-brand);
    --bs-btn-hover-border-color: var(--ds-brand);
    --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: var(--ds-brand-600);
    --bs-btn-active-border-color: var(--ds-brand-600);
}

.btn-outline-danger {
    --bs-btn-color: var(--ds-danger-fg);
    --bs-btn-border-color: var(--ds-danger);
    --bs-btn-bg: transparent;
    --bs-btn-hover-bg: var(--ds-danger);
    --bs-btn-hover-border-color: var(--ds-danger);
    --bs-btn-hover-color: #fff;
}

/* style.css had turned .btn-info into a green outline button — visually
   identical to .btn-outline-primary but named for a different intent.
   Kept as an alias so existing markup keeps working and looks the same. */
.btn-info {
    --bs-btn-color: var(--ds-brand);
    --bs-btn-border-color: var(--ds-brand);
    --bs-btn-bg: transparent;
    --bs-btn-hover-bg: var(--ds-brand);
    --bs-btn-hover-border-color: var(--ds-brand);
    --bs-btn-hover-color: #fff;
}

.btn-link {
    --bs-btn-color: var(--ds-brand);
    --bs-btn-hover-color: var(--ds-brand-600);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    font-weight: var(--ds-weight-medium);
    text-decoration: none;
    min-height: auto;
    padding: 0;
}

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

/* NOTE — there is no .ds-icon-btn here any more.
   It was defined during the refactor and adopted by exactly zero
   templates, so it was a fourth competing action treatment that existed
   only in CSS. The live ones are the bare icons above (~52 uses),
   Bootstrap outline buttons (~53) and .ta-icon-btn in the platform panel
   (10). Consolidating those three is a markup migration, not a CSS
   change — do that deliberately, do not add a fifth style here. */

/* ── Loading state ─────────────────────────────────────────────────────
   Add `.is-loading` (or aria-busy) to any button: it keeps its width, so
   the layout does not jump while a form submits. */
.btn.is-loading,
.btn[aria-busy="true"] {
    position: relative;
    color: transparent !important;
    pointer-events: none;
}

.btn.is-loading::after,
.btn[aria-busy="true"]::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 1em;
    height: 1em;
    margin: -0.5em 0 0 -0.5em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: var(--ds-radius-pill);
    color: #fff;
    animation: ds-spin 0.6s linear infinite;
}

.btn-secondary.is-loading::after,
.btn-outline-secondary.is-loading::after,
.btn-outline-primary.is-loading::after { color: var(--ds-brand); }

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

/* Legacy one-offs folded into the system rather than left to drift. */
.btn-pos {
    min-height: var(--ds-control-h-lg);
    padding: 0 var(--ds-space-6);
    border-radius: var(--ds-radius-sm);
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-semibold);
    text-transform: none;
    letter-spacing: 0;
    box-shadow: none;
}

.btn-pos:hover { transform: none; box-shadow: var(--ds-shadow-sm); }

.btn-edit-profile {
    min-height: var(--ds-control-h);
    padding: 0 var(--ds-space-4);
    border-radius: var(--ds-radius-sm);
    background: var(--ds-brand);
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-semibold);
    letter-spacing: 0;
    box-shadow: none;
}

.btn-edit-profile:hover {
    background: var(--ds-brand-600);
    transform: none;
    box-shadow: var(--ds-shadow-sm);
    filter: none;
}

/* ── Bare icon row actions (the edit / delete pair in tables) ──────────
   These are the app's most-used action treatment (~52 places).

   Colour is picked for contrast, not for vibrancy. Icons are non-text UI
   components, so WCAG 1.4.11 asks for 3:1 against the surface behind
   them — and against both white and the #f4f6f8 row-hover, the *bright*
   ends of the palette simply fail:

       #00b8d9 blue   2.37:1  FAIL     #00687a info-fg     6.44:1  PASS
       #e69100 amber  2.50:1  FAIL     #7a4e00 warning-fg  7.20:1  PASS
       #00a76f green  3.11:1  marginal #d02600 danger      5.30:1  PASS

   So edit and refund use the darker member of their hue, and delete uses
   the danger red itself — which is both on-palette and comfortably legible.
   Resting colour is the lighter of the pair and hover goes darker, which
   is the direction every other control in the system moves. */
.action-link,
.edit-link,
.view-link,
.print-link,
.return-link,
.refund-link,
a.remove-link,
button.remove-link {
    font-size: var(--ds-text-sm);
    font-weight: var(--ds-weight-medium);
    transition: color var(--ds-transition-fast);
}

/* Any of these on a <button> is still a bare icon, not a button: strip the
   UA chrome. style.css did this for button.remove-link only, because that
   was the one the gym tables put on a <button> — the rest were <a>. The
   platform tables post their row actions (impersonate, suspend, activate
   are all state changes, so they must be forms), which put view-link and
   return-link on a <button> for the first time and exposed the gap. */
button.action-link,
button.edit-link,
button.view-link,
button.print-link,
button.return-link,
button.refund-link {
    padding: 0;
    border: 0;
    background: none;
    line-height: inherit;
}

/* One icon size for every row action, so a column of them lines up.
   14px — the size these were before the refactor. An earlier pass used
   --ds-icon-lg (17px) here, which made every table action visibly
   larger than the text beside it. */
.action-link i,
.edit-link i,
.view-link i,
.print-link i,
.return-link i,
.refund-link i,
a.remove-link i,
button.remove-link i {
    font-size: var(--ds-icon);
    vertical-align: -0.05em;
}

.action-link { color: var(--ds-brand); }
.action-link:hover { color: var(--ds-brand-600); text-decoration: underline; }

/* delete — the danger red, darkening on hover */
.action-link.danger,
a.remove-link,
button.remove-link { color: var(--ds-danger) !important; }
.action-link.danger:hover,
a.remove-link:hover,
button.remove-link:hover { color: #ad2000 !important; }

/* edit / view / extend — the readable member of the blue family */
.edit-link,
.view-link { color: var(--ds-info-fg) !important; }
.edit-link:hover,
.view-link:hover { color: #004a57 !important; }

/* refund — the readable member of the amber family. Reserve this for
   money going back to a customer; it is not a generic "secondary action"
   colour. The rentals table was using it for a Print Receipt link, which
   is why one row showed a brown printer icon. */
.refund-link { color: var(--ds-warning-fg) !important; }
.refund-link:hover { color: #573700 !important; }

/* print / receipt / download — neutral. These are not state changes, so
   they should not borrow a semantic colour at all. */
.print-link { color: var(--ds-muted) !important; }
.print-link:hover { color: var(--ds-ink) !important; }

/* return / restore / re-activate — a positive action, so the brand green.
   This class was used in the rentals table but defined nowhere, so it
   fell through to the default link colour by accident. */
.return-link { color: var(--ds-brand) !important; }
.return-link:hover { color: var(--ds-brand-600) !important; }


/* =====================================================================
   7  FORMS

   Every input, select and textarea in the app is the same height, radius,
   border and focus ring — including the ones inside modals and filter
   bars, which previously had two more competing definitions.
   ===================================================================== */

.form-label,
.col-form-label {
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-medium);
    color: var(--ds-ink);
    line-height: var(--ds-leading-snug);
    margin-bottom: var(--ds-space-2);
    text-transform: none;
    letter-spacing: 0;
}

/* Required marker — consistent everywhere instead of "*", "(required)"
   and a red label depending on the page. */
.form-label.required::after,
.ds-required::after {
    content: '*';
    margin-left: 0.2em;
    color: var(--ds-danger);
    font-weight: var(--ds-weight-semibold);
}

.form-control,
.form-select {
    min-height: var(--ds-control-h);
    padding: 0.5rem var(--ds-space-3) !important;   /* beats style.css */
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-normal);
    line-height: var(--ds-leading-snug);
    color: var(--ds-ink);
    background-color: var(--ds-surface);
    border: 1px solid var(--ds-border-strong);
    border-radius: var(--ds-radius-sm) !important; /* style.css forced 4px */
    box-shadow: none;
    transition: border-color var(--ds-transition-fast), box-shadow var(--ds-transition-fast);
}

textarea.form-control { min-height: 96px; line-height: var(--ds-leading-normal); }

.form-control::placeholder {
    color: var(--ds-faint);
    font-weight: var(--ds-weight-normal);
    opacity: 1;
}

.form-control:focus,
.form-select:focus {
    border-color: var(--ds-brand);
    box-shadow: var(--ds-focus-ring);
    outline: none;
}

.form-control:disabled,
.form-select:disabled,
.form-control[readonly] {
    background-color: var(--ds-surface-raised);
    color: var(--ds-muted);
    opacity: 1;
    cursor: not-allowed;
}

.form-select {
    /* room for the chevron so long option text never sits under it */
    padding-right: var(--ds-space-8) !important;
    background-position: right var(--ds-space-3) center;
}

/* An unchosen select/date should read as a placeholder, not as a value. */
.form-select:has(option[value=""]:checked),
input[type="date"]:not(:valid),
input[type="time"]:not(:valid) {
    color: var(--ds-faint);
}

.form-select option { color: var(--ds-ink); }

.form-text {
    font-size: var(--ds-text-xs);
    color: var(--ds-muted);
    margin-top: var(--ds-space-1);
    line-height: var(--ds-leading-snug);
}

/* ── Validation ────────────────────────────────────────────────────── */

.form-control.is-invalid,
.form-select.is-invalid,
.was-validated .form-control:invalid {
    border-color: var(--ds-danger);
    background-image: none;   /* the stacked icon crowded small inputs */
    padding-right: var(--ds-space-3) !important;
}

.form-control.is-invalid:focus,
.form-select.is-invalid:focus {
    border-color: var(--ds-danger);
    box-shadow: 0 0 0 3px rgba(208, 38, 0, 0.16);
}

.form-control.is-valid,
.form-select.is-valid {
    border-color: var(--ds-success);
    background-image: none;
}

.form-control.is-valid:focus,
.form-select.is-valid:focus {
    border-color: var(--ds-success);
    box-shadow: 0 0 0 3px rgba(0, 167, 111, 0.16);
}

.invalid-feedback,
.valid-feedback {
    font-size: var(--ds-text-xs);
    font-weight: var(--ds-weight-normal);
    margin-top: var(--ds-space-1);
    line-height: var(--ds-leading-snug);
}

.invalid-feedback { color: var(--ds-danger-fg); }
.valid-feedback   { color: var(--ds-success-fg); }

/* ── Checkboxes & radios ───────────────────────────────────────────── */

.form-check { min-height: auto; margin-bottom: var(--ds-space-2); }

.form-check-input {
    width: 1.075em;
    height: 1.075em;
    margin-top: 0.19em;
    border: 1px solid var(--ds-border-strong);
    background-color: var(--ds-surface);
    cursor: pointer;
    transition: background-color var(--ds-transition-fast), border-color var(--ds-transition-fast);
}

.form-check-input:checked {
    background-color: var(--ds-brand);
    border-color: var(--ds-brand);
}

.form-check-input:focus-visible {
    border-color: var(--ds-brand);
    box-shadow: var(--ds-focus-ring);
}

.form-check-label {
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-normal);
    color: var(--ds-body);
    cursor: pointer;
    padding-left: var(--ds-space-1);
}

.form-switch .form-check-input { width: 2.25em; border-radius: var(--ds-radius-pill); }

/* ── Input groups ──────────────────────────────────────────────────────
   style.css gave these a 10px-0-0-10px radius and a 2px border that no
   longer matched the inputs beside them. */
.input-group-text {
    min-height: var(--ds-control-h);
    padding: 0 var(--ds-space-3);
    font-size: var(--ds-text-md);
    color: var(--ds-muted);
    background-color: var(--ds-surface-raised);
    border: 1px solid var(--ds-border-strong);
    border-radius: var(--ds-radius-sm);
}

.input-group > .form-control,
.input-group > .form-select {
    border-radius: var(--ds-radius-sm);
}

.input-group > :not(:last-child) {
    border-top-right-radius: 0 !important;
    border-bottom-right-radius: 0 !important;
}

.input-group > :not(:first-child) {
    border-top-left-radius: 0 !important;
    border-bottom-left-radius: 0 !important;
    margin-left: -1px;
}

.input-group .form-control:focus {
    border-left-width: 1px;   /* style.css thickened it, shifting the text */
    z-index: 3;
}

/* ── Search field ──────────────────────────────────────────────────────
   The magnifying-glass affordance the app already used, rebuilt on
   tokens so it matches every other control's height and radius. */
#search-input,
input[type="search"],
input.form-control[placeholder*="search" i] {
    min-height: var(--ds-control-h);
    border: 1px solid var(--ds-border-strong) !important;
    border-radius: var(--ds-radius-sm) !important;
    background-color: var(--ds-surface);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23919eab' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: left var(--ds-space-3) center;
    background-size: 16px 16px;
    padding: 0.5rem var(--ds-space-8) 0.5rem var(--ds-space-8) !important;
    font-size: var(--ds-text-md);
    color: var(--ds-ink);
    box-shadow: none;
}

#search-input:focus,
input[type="search"]:focus,
input.form-control[placeholder*="search" i]:focus {
    border-color: var(--ds-brand) !important;
    box-shadow: var(--ds-focus-ring) !important;
}

.input-group .clear-btn,
.position-relative > .clear-btn {
    background: transparent !important;
    border: none !important;
    color: var(--ds-muted) !important;
    box-shadow: none !important;
}

.input-group .clear-btn:hover,
.position-relative > .clear-btn:hover {
    color: var(--ds-danger) !important;
    background: transparent !important;
}

.form-group { margin-bottom: var(--ds-space-4); }


/* ── Filter popover ────────────────────────────────────────────────────
   The system's filtering UI, and the successor to the filter bars above.
   Those cards spent a full band of the screen on controls that are read
   once and then ignored, and pushed the table — the thing the page is
   actually for — below the fold. This is a right-aligned trigger that
   opens a panel of grouped controls over the page instead.

   The panel is a Bootstrap dropdown, so it inherits Popper positioning,
   outside-click and Escape for free; only the skin is ours. See
   components/filter-panel.blade.php for the markup contract.

   Nothing here is !important: the panel uses its own .ds-filter*
   namespace, so no legacy page stylesheet has a rule aimed at it. */

/* The row the trigger sits in. It holds only the filter — page-level
   "Add" and "Export" buttons keep their own header — so it collapses to
   a single right-aligned control. */
.ds-filter-bar {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--ds-space-2);
    margin-bottom: var(--ds-space-4);
}

.ds-filter { position: relative; }

.ds-filter__toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--ds-icon-gap);
    background: var(--ds-surface);
    border: 1px solid var(--ds-border-strong);
    color: var(--ds-body);
}

.ds-filter__toggle:hover {
    background: var(--ds-surface-raised);
    border-color: var(--ds-border-strong);
    color: var(--ds-ink);
}

/* Open, or filtering: both mean "this control is doing something", and
   both read as the brand outline. */
.ds-filter__toggle.is-active,
.ds-filter__toggle[aria-expanded="true"] {
    border-color: var(--ds-brand);
    color: var(--ds-brand-600);
    background: var(--ds-surface);
}

.ds-filter__toggle i { color: currentColor; font-size: var(--ds-icon); }

/* How many filters are in effect. Without it, a collapsed panel hides
   the fact that the table below is showing a subset. */
.ds-filter__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.25rem;
    padding: 0 var(--ds-space-1);
    border-radius: var(--ds-radius-pill);
    background: var(--ds-brand);
    color: #fff;
    font-size: var(--ds-text-2xs);
    font-weight: var(--ds-weight-semibold);
    line-height: 1;
}

.ds-filter__panel {
    --ds-filter-w: 380px;
    width: min(var(--ds-filter-w), calc(100vw - 2rem));
    padding: 0;                       /* § 13 pads menus for their items */
    border-radius: var(--ds-radius-lg);
    box-shadow: var(--ds-shadow-xl);
    overflow: hidden;
}

/* § 13 forces `top: auto` on every open menu, to undo the fixed 43/47/50px
   offsets style.css used to pin them with. Popper places this panel with
   `inset: 0 auto auto 0` plus a transform, so `top: auto` drops it to its
   static position — below the trigger in normal flow — and the transform then
   pushes it down by that much again, leaving a visible gap. Give Popper back
   the origin it measured against. */
.ds-filter__panel.dropdown-menu.show {
    top: 0 !important;
}

.ds-filter__title {
    padding: var(--ds-space-3) var(--ds-space-5);
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-semibold);
    color: var(--ds-ink);
    border-bottom: 1px solid var(--ds-border);
}

/* Long panels scroll rather than run off the viewport; the footer stays
   put, so "Apply now" is always one click away. */
.ds-filter__body {
    max-height: min(60vh, 32rem);
    overflow-y: auto;
}

.ds-filter__group {
    padding: var(--ds-space-4) var(--ds-space-5);
    border-bottom: 1px solid var(--ds-border);
}

.ds-filter__group:last-child { border-bottom: 0; }

.ds-filter__group-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--ds-space-3);
    margin-bottom: var(--ds-space-2);
}

.ds-filter__group-label {
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-semibold);
    color: var(--ds-ink);
}

.ds-filter__reset {
    padding: 0;
    background: none;
    border: 0;
    font-size: var(--ds-text-sm);
    font-weight: var(--ds-weight-semibold);
    color: var(--ds-brand);
    transition: color var(--ds-transition-fast);
}

.ds-filter__reset:hover { color: var(--ds-brand-600); text-decoration: underline; }

.ds-filter__reset:focus-visible {
    outline: none;
    border-radius: var(--ds-radius-xs);
    box-shadow: var(--ds-focus-ring);
}

/* Nothing set in this section — the link stays visible so the layout
   does not jump, but stops claiming there is something to clear. */
.ds-filter__reset.is-empty {
    color: var(--ds-faint);
    cursor: default;
    text-decoration: none;
}

/* Paired controls: a date from/to, a min/max amount, a PDF/Excel pair. */
.ds-filter__row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--ds-space-3);
}

/* Buttons in that grid are stretched by it, so they need to centre their
   own contents rather than rely on inline-block text alignment. */
.ds-filter__row > .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.ds-filter__sublabel {
    display: block;
    margin-bottom: var(--ds-space-1);
    font-size: var(--ds-text-sm);
    font-weight: var(--ds-weight-normal);
    color: var(--ds-muted);
}

/* Controls sit edge to edge in a 380px panel, so they get the compact
   height rather than the 40px used in a full-width form. */
.ds-filter__panel .form-control,
.ds-filter__panel .form-select {
    min-height: var(--ds-control-h-sm);
    font-size: var(--ds-text-md);
}

.ds-filter__panel .form-control + .form-control,
.ds-filter__panel .form-select + .form-select,
.ds-filter__panel .form-check + .form-check {
    margin-top: var(--ds-space-2);
}

.ds-filter__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--ds-space-3);
    padding: var(--ds-space-3) var(--ds-space-5);
    background: var(--ds-surface-raised);
    border-top: 1px solid var(--ds-border);
}

.ds-filter__foot .btn { min-width: 6.5rem; }

/* Below the sm breakpoint a 380px panel anchored to a button is wider
   than the space beside it, so it becomes a bottom sheet instead. */
@media (max-width: 575.98px) {
    /* Matches the `.show` selector above so this wins on specificity as well
       as source order — otherwise the desktop `top: 0` would fight the sheet. */
    .ds-filter__panel.dropdown-menu.show {
        position: fixed !important;
        inset: auto 0 0 0 !important;
        transform: none !important;
        width: 100%;
        max-height: 85vh;
        border-radius: var(--ds-radius-xl) var(--ds-radius-xl) 0 0;
        border-bottom: 0;
    }

    .ds-filter__body { max-height: calc(85vh - 8.5rem); }
    .ds-filter__foot .btn { flex: 1; min-width: 0; }
}


/* =====================================================================
   8  CARDS & PANELS

   One card: white surface, 1px border, 16px radius, soft shadow. The app
   had .card, .stat-card, .form-card, .chart-card, .table-card,
   .report-card, .search-card, .expenses-card-modern and
   .attendance-card-modern with radii from 5px to 20px and four different
   shadows. They are all the same object.
   ===================================================================== */

.card,
.stat-card,
.form-card,
.chart-card,
.table-card,
.report-card,
.result-card,
.message-card,
.announcement-card,
.recent-attendance,
.expenses-card-modern,
.attendance-card-modern,
.quick-stats-card {
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-xl);
    box-shadow: var(--ds-shadow-xs);
    transition: box-shadow var(--ds-transition);
}

/* Hover elevation is for cards you can click, not every card on screen. */
.card:hover,
.expenses-card-modern:hover,
.attendance-card-modern:hover {
    box-shadow: var(--ds-shadow-sm);
}

.stat-card:hover,
.quick-stats-card:hover {
    transform: none;   /* the -5px lift knocked stat rows out of alignment */
    box-shadow: var(--ds-shadow-md);
}

/* Card padding matches what the app had before the design system landed
   (1.25rem/1.5rem header, 1.5rem body). Tighter values read as cramped on
   the dashboard's stat cards, which is the density this app was built at. */
.card-header,
.form-header,
.table-header,
.chart-header,
.card-header-modern {
    background: transparent;
    border-bottom: 1px solid var(--ds-border) !important;
    border-radius: var(--ds-radius-xl) var(--ds-radius-xl) 0 0 !important;
    padding: var(--ds-space-5) var(--ds-space-6) !important;
    margin-bottom: 0;
}

.card-body,
.form-body {
    padding: var(--ds-space-6);
}

.card-footer,
.card-footer-modern {
    background: transparent;
    border-top: 1px solid var(--ds-border);
    border-radius: 0 0 var(--ds-radius-xl) var(--ds-radius-xl);
    padding: var(--ds-space-5) var(--ds-space-6);
}

.card-title {
    font-size: var(--ds-text-lg) !important;   /* style.css forced 0.9rem */
    font-weight: var(--ds-weight-semibold);
    color: var(--ds-ink);
    line-height: var(--ds-leading-snug);
    margin-bottom: 0;
}

.card-subtitle {
    font-size: var(--ds-text-sm);
    color: var(--ds-muted);
    margin-top: var(--ds-space-1);
}

.card-text { font-size: var(--ds-text-md); }

/* A labelled group of fields inside one card — a hairline rule and a
   heading, not a nested card. Long forms (a subscription plan's pricing
   vs its limits) read better broken up than as one 20-field wall, but
   putting each group in its own .card stacks borders and shadows two
   deep. Pair with .ds-section-title from § 3 for the heading. */
.form-section {
    margin-top: var(--ds-space-2);
    padding-top: var(--ds-space-5);
    border-top: 1px solid var(--ds-border);
}

/* Statistic tiles read as data, so the number carries the weight. */
.stat-card .card-text,
.ds-stat-value {
    font-size: var(--ds-text-3xl);
    font-weight: var(--ds-weight-bold);
    color: var(--ds-ink);
    line-height: var(--ds-leading-tight);
    letter-spacing: var(--ds-tracking-tight);
}

.stat-icon,
.attendance-icon-dashboard,
.expenses-icon,
.metric-icon {
    width: 44px;
    height: 44px;
    border-radius: var(--ds-radius-md);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--ds-icon-lg);
    flex-shrink: 0;
}

/* The tinted gradient stat cards fought the neutral cards beside them
   for attention. A tinted icon tile on a white card gives the same
   colour-coding without the visual shouting. */
.stat-card.primary,
.stat-card.success,
.stat-card.warning,
.stat-card.danger,
.stat-card.info {
    background: var(--ds-surface) !important;
    color: var(--ds-body);
}

.list-group {
    /* Bootstrap's own .list-group rule falls the row background back to
       --bs-body-bg, the sunken page grey. Inside a white card that painted a
       grey stripe behind every row (member profile, receipts), so the rows
       take the surface they sit on instead. Overriding it here rather than at
       :root is deliberate: the :root value loses to Bootstrap's .list-group. */
    --bs-list-group-bg: var(--ds-surface);
    --bs-list-group-border-color: var(--ds-border) !important;
    --bs-list-group-item-padding-y: var(--ds-space-3);
    --bs-list-group-item-padding-x: var(--ds-space-4);
}

.list-group-item { font-size: var(--ds-text-md); }

hr { border-color: var(--ds-border); opacity: 1; }

/* Key/value rows used by profiles, receipts and reports. */
.info-row,
.detail-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--ds-space-4);
    padding: var(--ds-space-3) 0;
    border-bottom: 1px solid var(--ds-border);
}

.info-row:last-child,
.detail-row:last-child { border-bottom: none; }

.info-label,
.detail-label {
    font-size: var(--ds-text-sm);
    font-weight: var(--ds-weight-medium);
    color: var(--ds-muted) !important;
}

.info-value,
.detail-value {
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-medium);
    color: var(--ds-ink);
    text-align: right;
}


/* =====================================================================
   9  TABLES

   Replaces partials/table-styles.blade.php. Uppercase micro-headers,
   14px cells, generous row height, hairline rules, no zebra striping —
   the pattern the Online Transactions screen already used and the one
   the rest of the app was inconsistently imitating.
   ===================================================================== */

.table {
    --bs-table-bg: transparent;
    --bs-table-color: var(--ds-body);
    --bs-table-hover-bg: var(--ds-surface-raised);
    --bs-table-hover-color: var(--ds-ink);
    --bs-table-border-color: var(--ds-border);
    margin-bottom: 0;
    font-size: var(--ds-text-md);
    color: var(--ds-body);
}

.table > thead > tr > th,
.table thead th {
    background: transparent;
    padding: var(--ds-space-3) var(--ds-space-4);
    font-size: var(--ds-text-2xs);
    font-weight: var(--ds-weight-semibold);
    letter-spacing: var(--ds-tracking-wide);
    text-transform: uppercase;
    color: var(--ds-muted);
    white-space: nowrap;
    vertical-align: middle;
    border-top: none;
    border-bottom: 1px solid var(--ds-border);
}

.table > tbody > tr > td,
.table > tbody > tr > th,
.table tbody td {
    padding: var(--ds-space-3) var(--ds-space-4);
    font-size: var(--ds-text-md);
    line-height: var(--ds-leading-snug);
    vertical-align: middle;
    color: var(--ds-body);
    border-bottom: 1px solid var(--ds-border);
}

.table > tbody > tr:last-child > td { border-bottom: none; }

.table-hover > tbody > tr:hover > * {
    background-color: var(--ds-surface-raised);
    color: var(--ds-ink);
}

/* Numeric columns belong right-aligned and tabular so digits line up. */
.table .ds-num,
.table td.text-end,
.table th.text-end {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* Action cells: never wrap, never stretch the row. */
.table .ds-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--ds-space-2);
    white-space: nowrap;
}

/* A .btn-group in an action cell stays INLINE flex — block-level `flex`
   would make it claim the cell's full width and break onto its own line
   whenever the cell also holds a sibling button (access cards: Receipt +
   Status). Bootstrap's own geometry is inline-flex; this only adds the
   gap and baseline alignment the action cells want. */
.table td:last-child .btn-group {
    display: inline-flex;
    align-items: center;
    vertical-align: middle;
    gap: var(--ds-space-2);
    white-space: nowrap;
}

/* Avatar-plus-name cell — the app's most repeated table pattern. */
.table .ds-cell-user {
    display: flex;
    align-items: center;
    gap: var(--ds-space-3);
}

.table img { vertical-align: middle; }

.member-name,
.staffname {
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-medium);
    color: var(--ds-ink);
}

/* Table wrapper. Horizontal scroll is confined here so the page body
   itself never scrolls sideways on a phone. */
.table-responsive {
    padding: 0;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.table-cardBody {
    padding: 0 !important;
}

.sortable-th { white-space: nowrap; }

.sort-link {
    display: inline-flex;
    align-items: center;
    gap: var(--ds-space-1);
    color: inherit;
    cursor: pointer;
    user-select: none;
}

.sort-link:hover, .sort-link.is-active { color: var(--ds-brand); }
.sort-link .sort-icon { font-size: 0.6rem; opacity: 0.45; }
.sort-link:hover .sort-icon, .sort-link.is-active .sort-icon { opacity: 1; }


/* =====================================================================
   10  BADGES & STATUS PILLS

   One pill: subtle tinted background, readable same-hue text. Solid
   saturated badges were being used for the same states elsewhere, which
   made status columns look like a paint chart.
   ===================================================================== */

.badge {
    display: inline-flex;
    align-items: center;
    gap: var(--ds-space-1);
    padding: 0.25rem 0.625rem;
    border-radius: var(--ds-radius-pill);
    font-size: var(--ds-text-xs);
    font-weight: var(--ds-weight-medium);
    line-height: 1.5;
    letter-spacing: 0;
    white-space: nowrap;
    vertical-align: middle;
}

.badge.bg-success, .badge-success, .status-active,
.badge.bg-primary {
    background-color: var(--ds-success-bg) !important;
    color: var(--ds-success-fg) !important;
}

.badge.bg-danger, .badge-danger, .status-expired {
    background-color: var(--ds-danger-bg) !important;
    color: var(--ds-danger-fg) !important;
}

.badge.bg-warning, .badge-warning {
    background-color: var(--ds-warning-bg) !important;
    color: var(--ds-warning-fg) !important;
}

.badge.bg-info {
    background-color: var(--ds-info-bg) !important;
    color: var(--ds-info-fg) !important;
}

.badge.bg-secondary, .badge-secondary, .status-inactive,
.badge.bg-light, .badge.bg-dark {
    background-color: var(--ds-neutral-bg) !important;
    color: var(--ds-neutral-fg) !important;
}

.badge-modern,
.status-badge {
    padding: 0.25rem 0.625rem;
    border-radius: var(--ds-radius-pill);
    font-size: var(--ds-text-xs);
    font-weight: var(--ds-weight-medium);
}

/* The notification count is the one badge that must stay loud. */
.notification-badge,
.badge.bg-danger.notification-badge {
    background-color: var(--ds-danger) !important;
    color: #fff !important;
    font-size: var(--ds-text-2xs) !important;
    font-weight: var(--ds-weight-semibold) !important;
    padding: 0.125rem 0.35rem;
    min-width: 1.15rem;
    justify-content: center;
}

.time-badge,
.expenses-date {
    font-size: var(--ds-text-xs);
    font-weight: var(--ds-weight-medium);
    padding: 0.2rem 0.55rem;
    border-radius: var(--ds-radius-pill);
    display: inline-flex;
    align-items: center;
    gap: var(--ds-space-1);
}

.time-in-dashboard { background: var(--ds-success-bg); color: var(--ds-success-fg); }
.time-out-dashboard { background: var(--ds-danger-bg); color: var(--ds-danger-fg); }
.still-inside { background: var(--ds-info-bg); color: var(--ds-info-fg); }
.expenses-date { background: var(--ds-neutral-bg); color: var(--ds-neutral-fg); }


/* =====================================================================
   11  ALERTS & TOASTS

   Tinted background, matching border and text, an icon slot on the left.
   Alerts previously appeared as solid Bootstrap fills on some pages and
   pastel ones on others.
   ===================================================================== */

.alert {
    display: flex;
    align-items: flex-start;
    gap: var(--ds-space-3);
    padding: var(--ds-space-3) var(--ds-space-4);
    border: 1px solid transparent;
    border-radius: var(--ds-radius-sm);
    font-size: var(--ds-text-md);
    line-height: var(--ds-leading-snug);
    margin-bottom: var(--ds-space-4);
}

.alert > i:first-child { margin-top: 0.15em; font-size: var(--ds-icon-lg); flex-shrink: 0; }
.alert ul { margin-bottom: 0; padding-left: var(--ds-space-4); }
.alert ul li + li { margin-top: var(--ds-space-1); }
.alert .alert-heading { font-size: var(--ds-text-md); font-weight: var(--ds-weight-semibold); margin-bottom: var(--ds-space-1); }

.alert-success { background: var(--ds-success-bg); border-color: rgba(0, 167, 111, 0.24); color: var(--ds-success-fg); }
.alert-danger  { background: var(--ds-danger-bg);  border-color: rgba(208, 38, 0, 0.24); color: var(--ds-danger-fg); }
.alert-warning { background: var(--ds-warning-bg); border-color: rgba(230, 145, 0, 0.24); color: var(--ds-warning-fg); }
.alert-info    { background: var(--ds-info-bg);    border-color: rgba(0, 184, 217, 0.24); color: var(--ds-info-fg); }
.alert-primary { background: var(--ds-brand-50);   border-color: rgba(0, 167, 111, 0.24); color: var(--ds-brand-600); }
.alert-secondary, .alert-light, .alert-dark { background: var(--ds-neutral-bg); border-color: var(--ds-border); color: var(--ds-neutral-fg); }

.alert .btn-close { margin-left: auto; padding: 0.4rem; }

/* ── Toasts ────────────────────────────────────────────────────────────
   Clear of the topbar, same radius and elevation as a dropdown. */
.toast-container {
    top: calc(var(--ds-topbar-h) + var(--ds-space-3)) !important;
    z-index: 1090;
}

.toast {
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-lg);
    box-shadow: var(--ds-shadow-lg);
    font-size: var(--ds-text-md);
}

.toast-body {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    padding: var(--ds-space-3) var(--ds-space-4);
    line-height: var(--ds-leading-snug);
}


/* =====================================================================
   12  MODALS

   Replaces partials/modal-styles.blade.php. That partial hard-coded 14
   light-mode colours with !important, which is why modals stayed white
   in dark mode until a second set of overrides was bolted on. Same
   design, expressed in tokens, so dark mode needs no special casing.
   ===================================================================== */

.modal-content {
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-xl) !important;
    box-shadow: var(--ds-shadow-xl);
    overflow: hidden;
    color: var(--ds-body);
}

/* Coloured modal headers were used to signal intent; the title and the
   confirm button already do that, and the colour blocks fought the
   content. Every header is a plain surface. */
.modal-header,
.modal-header.bg-primary,
.modal-header.bg-success,
.modal-header.bg-danger,
.modal-header.bg-warning,
.modal-header.bg-info,
.modal-header.bg-dark {
    background: var(--ds-surface) !important;
    color: var(--ds-ink) !important;
    border-bottom: 1px solid var(--ds-border) !important;
    border-radius: 0 !important;
    padding: var(--ds-space-4) var(--ds-space-5) !important;
}

.modal-title {
    font-size: var(--ds-text-lg) !important;
    font-weight: var(--ds-weight-semibold) !important;
    color: var(--ds-ink) !important;
    line-height: var(--ds-leading-snug) !important;
    letter-spacing: 0 !important;
}

.modal-header .btn-close,
.modal-header .btn-close-white {
    filter: none !important;
    opacity: 0.5;
    margin: 0;
    padding: 0.5rem;
    border-radius: var(--ds-radius-sm);
    transition: opacity var(--ds-transition-fast), background-color var(--ds-transition-fast);
}

.modal-header .btn-close:hover,
.modal-header .btn-close-white:hover {
    opacity: 1;
    background-color: var(--ds-surface-raised);
}

html[data-theme="dark"] .modal-header .btn-close,
html[data-theme="dark"] .btn-close { filter: invert(1) grayscale(100%) brightness(200%) !important; }

.modal-body {
    background: var(--ds-surface) !important;
    padding: var(--ds-space-5) !important;
    font-size: var(--ds-text-md) !important;
    color: var(--ds-body) !important;
}

.modal-footer {
    background: var(--ds-surface) !important;
    border-top: 1px solid var(--ds-border) !important;
    padding: var(--ds-space-4) var(--ds-space-5) !important;
    gap: var(--ds-space-2) !important;
}

.modal-footer > * { margin: 0; }

.modal-footer .btn {
    min-width: 92px;
    min-height: var(--ds-control-h);
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-semibold);
    border-radius: var(--ds-radius-sm);
}

/* Inputs inside a modal are the same inputs as everywhere else — the old
   partial redefined them, and drifted. Only the colour is restated here,
   to beat that partial's leftover !important declarations. */
.modal .form-control,
.modal .form-select {
    color: var(--ds-ink) !important;
    background-color: var(--ds-surface) !important;
    border-color: var(--ds-border-strong) !important;
}

.modal .form-control:focus,
.modal .form-select:focus {
    border-color: var(--ds-brand) !important;
    box-shadow: var(--ds-focus-ring) !important;
}

.modal .form-label { color: var(--ds-ink) !important; }
.modal .invalid-feedback { font-size: var(--ds-text-xs); }

/* A card inside a modal is an inset panel, not a second elevated card. */
.modal .card {
    background: var(--ds-surface-raised);
    border: 1px solid var(--ds-border);
    box-shadow: none;
    border-radius: var(--ds-radius-lg);
}

.modal-body::-webkit-scrollbar { width: 8px; }
.modal-body::-webkit-scrollbar-thumb { background: var(--ds-border-strong); border-radius: var(--ds-radius-pill); }

.modal-backdrop.show { opacity: 0.5; }

@media (max-width: 575.98px) {
    /* Stacked full-width footer buttons beat two cramped ones. */
    .modal-footer { flex-direction: column-reverse; align-items: stretch; }
    .modal-footer .btn { width: 100%; }
}


/* =====================================================================
   13  DROPDOWNS & MENUS
   ===================================================================== */

.dropdown-menu {
    --bs-dropdown-link-hover-bg: var(--ds-surface-raised);
    --bs-dropdown-link-hover-color: var(--ds-ink);
    min-width: 200px;
    padding: var(--ds-space-1);
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-lg);
    box-shadow: var(--ds-shadow-lg);
    font-size: var(--ds-text-md);
    margin-top: var(--ds-space-1) !important;
}

.dropdown-item {
    padding: var(--ds-space-2) var(--ds-space-3);
    border-radius: var(--ds-radius-sm);
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-normal);
    color: var(--ds-body);
    transition: background-color var(--ds-transition-fast), color var(--ds-transition-fast);
}

.dropdown-item:hover,
.dropdown-item:focus {
    background-color: var(--ds-surface-raised);
    color: var(--ds-ink);
}

.dropdown-item.active,
.dropdown-item:active {
    background-color: var(--ds-brand-50);
    color: var(--ds-brand-600);
}

.dropdown-item i { color: var(--ds-muted); }
.dropdown-item:hover i { color: inherit; }

.dropdown-divider {
    margin: var(--ds-space-1) 0 !important;
    border-color: var(--ds-border);
}

.dropdown-header {
    padding: var(--ds-space-2) var(--ds-space-3);
    font-size: var(--ds-text-2xs);
    font-weight: var(--ds-weight-semibold);
    letter-spacing: var(--ds-tracking-wide);
    text-transform: uppercase;
    color: var(--ds-muted);
}

/* style.css pinned open menus to fixed `top` values (43px / 47px / 50px),
   which detached them from their trigger. Let Popper position them. */
.dropdown-menu.show,
.user-dropwdown,
.logout {
    top: auto !important;
}

/* The notification panel — a dropdown, styled like every other dropdown. */
.dropdown-menu-modern {
    padding: 0;
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-lg);
    box-shadow: var(--ds-shadow-lg);
    background: var(--ds-surface);
    backdrop-filter: none;
    overflow: hidden;
}

.dropdown-menu-modern .dropdown-item {
    padding: var(--ds-space-3);
    border-radius: 0;
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-normal);
}

.notification-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--ds-space-2);
    margin: 0;
    padding: var(--ds-space-3) var(--ds-space-4);
    border-bottom: 1px solid var(--ds-border);
    font-size: var(--ds-text-md) !important;
    font-weight: var(--ds-weight-semibold);
    color: var(--ds-ink);
}

.notification-item {
    border-bottom: 1px solid var(--ds-border) !important;
    transition: background-color var(--ds-transition-fast);
}

.notification-item:last-of-type { border-bottom: none !important; }
.notification-item.notif-unread { background-color: var(--ds-brand-50); }
.notification-item:hover { background-color: var(--ds-surface-raised); }
.notification-item img { width: 28px; height: 28px; border-radius: var(--ds-radius-pill); margin: 0; }

.notification-list { max-height: 320px; overflow-y: auto; }

/* Empty bell. The icon badge deliberately matches the one a real row draws
   in components/notification-item.blade.php, so the empty state reads as a
   sibling of the rows it replaces rather than a different kind of object. */
.notification-empty { padding: var(--ds-space-8) var(--ds-space-4); text-align: center; }

.notification-empty__icon {
    width: 44px;
    height: 44px;
    margin: 0 auto var(--ds-space-3);
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--ds-radius-pill);
    background: var(--ds-surface-raised);
    color: var(--ds-muted);
}

.notification-empty__title {
    margin: 0 0 var(--ds-space-1);
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-semibold);
    color: var(--ds-ink);
}

.notification-empty__hint { margin: 0; font-size: var(--ds-text-sm); color: var(--ds-muted); }

.mark-all-read-link,
.notification-viewall {
    font-size: var(--ds-text-sm);
    font-weight: var(--ds-weight-semibold);
    color: var(--ds-brand);
}

.notification-viewall {
    display: block;
    padding: var(--ds-space-3);
    text-align: center;
    border-top: 1px solid var(--ds-border);
}

.notification-viewall:hover { background: var(--ds-brand-50); color: var(--ds-brand-600); }

.notification-bell:hover { background-color: transparent; transform: none; }

/* ── Account menu ──────────────────────────────────────────────────────
   Topbar trigger + panel, shared by the gym and member navs. Matches the
   platform panel's ta-user-* component in shape, but written against the
   --ds-* tokens: those rules are scoped to body.super-panel and live in a
   stylesheet these two panels do not load. */

.account-toggle {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    padding: var(--ds-space-1) var(--ds-space-2);
    background: none;
    border: 0;
    border-radius: var(--ds-radius-md);
    transition: background-color var(--ds-transition);
}

.account-toggle:hover { background: var(--ds-surface-sunken); }

.account-toggle .navAvatar {
    background: var(--ds-brand-50);
    color: var(--ds-brand-600);
}

.account-toggle-text { text-align: left; line-height: 1.2; }
.account-toggle-text .staffname { display: block; }

.account-toggle-role {
    display: block;
    font-size: var(--ds-text-xs);
    color: var(--ds-muted);
}

.account-toggle-caret { font-size: var(--ds-text-xs); color: var(--ds-muted); }

.profile-dropdown,
.custom-dropdown {
    width: 250px;
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-lg);
    box-shadow: var(--ds-shadow-lg);
    overflow: hidden;
}

/* Wider than the sibling menus: enough that an ordinary address sits on one
   line instead of breaking mid-word. Genuinely long ones still wrap. */
.profile-dropdown {
    width: 272px;
    animation: dsMenuIn 0.15s ease;
}

/* Identity card. Every line is real data; a role or email the row does not
   carry is omitted, so the card is simply shorter rather than padded out. */
.profile-header {
    display: flex;
    align-items: center;
    gap: var(--ds-space-3);
    padding: var(--ds-space-3);
    background: var(--ds-surface-raised);
}

.avatar-dropdown {
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border-radius: var(--ds-radius-pill);
    background: var(--ds-brand-50);
    color: var(--ds-brand-600);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--ds-text-sm);
    font-weight: var(--ds-weight-semibold);
}

.profile-identity { min-width: 0; line-height: 1.35; }
.profile-identity > * { display: block; }
.profile-identity .role { font-size: var(--ds-text-xs); color: var(--ds-body); }

/* Long addresses wrap instead of widening or clipping the panel. */
.profile-identity .email { overflow-wrap: anywhere; }

.profile-dropdown .name, .name { font-size: var(--ds-text-md); font-weight: var(--ds-weight-semibold); color: var(--ds-ink); }
.profile-dropdown .email, .email { font-size: var(--ds-text-xs); color: var(--ds-muted); }
.menu-divider, .divider { height: 1px; background: var(--ds-border); margin: 0; }
.profile-links { padding: var(--ds-space-1); }

.profile-dropdown .dropdown-item {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    width: 100%;
    padding: var(--ds-space-2) var(--ds-space-3);
    font-size: var(--ds-text-md);
    border: 0;
    border-radius: var(--ds-radius-md);
    background: none;
    text-align: left;
}

.profile-dropdown .dropdown-item > i {
    width: 1rem;
    text-align: center;
    color: var(--ds-muted);
}

.profile-dropdown .dropdown-item.active {
    background: var(--ds-brand-50);
    color: var(--ds-brand-600);
}

.profile-dropdown .dropdown-item.active > i { color: inherit; }

/* --ds-brand-600 is a light-theme hover shade and is not re-declared for dark,
   where it lands as dark teal on a near-black tint. The brand base reads
   cleanly on both, so the tinted text in this component switches to it. */
html[data-theme="dark"] .account-toggle .navAvatar,
html[data-theme="dark"] .avatar-dropdown,
html[data-theme="dark"] .profile-dropdown .dropdown-item.active { color: var(--ds-brand); }
.custom-dropdown .dropdown-item { font-weight: var(--ds-weight-normal); letter-spacing: 0; color: var(--ds-body); }
.profile-dropdown .logout:hover,
.profile-dropdown .logout:hover > i { color: var(--ds-danger-fg); }
.profile-dropdown .logout:hover { background: var(--ds-danger-bg); }
.btn-logout { padding: var(--ds-space-3) !important; }

/* Panel entrance. Named distinctly because several page stylesheets define
   their own `fadeIn`, and a duplicate @keyframes name wins globally. */
@keyframes dsMenuIn {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
}
.view-all { width: auto; border-radius: var(--ds-radius-sm) !important; }


/* =====================================================================
   14  NAVIGATION — sidebar, topbar, breadcrumbs, tabs
   ===================================================================== */

/* ── Sidebar ───────────────────────────────────────────────────────── */

.sidebar {
    background: var(--ds-sidebar-bg, #27313d);
    min-height: 100vh;
    box-shadow: none;
    border-right: 1px solid rgba(255, 255, 255, 0.06);
}

/* Sidebar spacing is deliberately the app's original set of values, not
   the generic control metrics: 0.8rem/1.1rem rows, a 1rem row for the
   submenu parents, and the icon gap coming from the icon's own margin.
   An earlier pass normalised all of this to 0.625rem rows with a flex
   gap, which read as noticeably cramped. Do not "tidy" it back. */
.sidebar .nav-link {
    /* Full-width row, not a content-width pill: the hover and active
       backgrounds must span the whole rail.

       Icon-to-label distance comes from `gap`, not from margins on the
       icon. That matters for alignment: the app used to space plain rows
       with a 0.45rem icon margin and submenu-parent rows with a 12px
       .linkPadding margin, on top of 1.1rem vs 1rem side padding — so
       labels started at 44.8px on some rows and 48px on others and the
       column visibly staggered. One gap + one padding = one left edge. */
    display: flex;
    width: 100%;
    gap: 0.7rem;
    padding: 0.8rem 1.1rem;
    font-size: var(--ds-text-sm);
    font-weight: var(--ds-weight-semibold);
    color: #919eab;
    border-left: 3px solid transparent;
    border-radius: 0;
    transition: background-color var(--ds-transition-fast), color var(--ds-transition-fast);
}

.sidebar .nav-link:hover {
    background-color: rgba(145, 158, 171, 0.08);
    color: #fff;
}

.sidebar .nav-link.active {
    background-color: rgba(0, 167, 111, 0.12);
    color: var(--ds-brand);
    border-left-color: var(--ds-brand);
    font-weight: var(--ds-weight-semibold);
}

/* A fixed slot is what actually lines the labels up: without it a narrow
   icon (dumbbell) and a wide one (three people) push their labels to
   different x positions. Margin is zeroed here so `gap` is the only
   thing creating space. */
.sidebar .nav-link i {
    margin-right: 0;
    width: var(--ds-icon-slot);   /* 20px */
    text-align: center;
    font-size: 0.85rem;
    flex-shrink: 0;
}

.sidebar .nav-link .badge {
    flex-shrink: 0;
    margin-left: 0.4rem;
    font-size: 0.65rem;
    padding: 0.22rem 0.45rem;
}

.menu-text { font-size: var(--ds-text-sm); }
.submenu .menu-text { font-size: var(--ds-text-sm); }

/* .navPadding and .linkPadding are legacy hooks on the submenu-parent
   rows. They used to give those rows their own padding (1rem) and their
   own icon margin (12px), which is precisely what knocked the labels out
   of alignment with the plain rows. Both now defer to the values above so
   every row in the rail measures the same. */
.navPadding { padding: 0.8rem 1.1rem !important; }
.linkPadding { margin-right: 0 !important; }

/* Submenu children are indented one step and share the parent's gap so
   their labels form their own straight column. */
.submenu {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    margin: 0 0 0 0.6rem;
    min-width: 0;
}

.super-nav-heading,
.sidebar .nav-heading {
    padding: var(--ds-space-4) var(--ds-space-4) var(--ds-space-1);
    font-size: var(--ds-text-2xs);
    font-weight: var(--ds-weight-semibold);
    letter-spacing: var(--ds-tracking-wide);
    text-transform: uppercase;
    color: #6b7684;
}

.sidebar-header {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    height: var(--ds-topbar-h);
    padding: 0 var(--ds-space-3);
    margin-bottom: var(--ds-space-2);
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.navbar-brand {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    font-size: var(--ds-text-lg);
    font-weight: var(--ds-weight-semibold);
    color: #fff !important;
    padding: 0 !important;
}

.sidebar-header .navbar-brand img { width: 32px; height: auto; margin: 0; flex-shrink: 0; }
.sidebar-header .navbar-brand .menu-text { margin: 0; font-weight: var(--ds-weight-semibold); color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Brand lockup, shared by all three panels via partials/sidebar-brand.
   Previously .super-brand-* in super.css, scoped to body.super-panel — that
   scope is why they live here now: the tenant and member sidebars render the
   same lockup and matched none of it. */
.sidebar-brand-mark {
    width: 35px;
    height: 35px;
    border-radius: var(--ds-radius-md);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--ds-brand);
    color: #fff;
    flex: 0 0 auto;
}

/* #919eab, not --ds-muted: the sidebar is dark (#27313d) in BOTH themes, so a
   token that flips with the theme is wrong on it. This is the exact colour
   § 14 gives `.sidebar .nav-link`, so the brand sub-line sits at the same
   weight as the nav labels beneath it. */
.sidebar-brand-sub {
    font-size: var(--ds-text-2xs);
    font-weight: var(--ds-weight-medium);
    color: #919eab;
    letter-spacing: 0.02em;
}

.sidebar-collapse-btn {
    border: none;
    background: transparent;
    color: #919eab;
    padding: var(--ds-space-2);
    border-radius: var(--ds-radius-sm);
    line-height: 1;
}

.sidebar-collapse-btn:hover { background-color: rgba(145, 158, 171, 0.12); color: #fff; }

/* ── Topbar ────────────────────────────────────────────────────────── */

/* No horizontal padding here. The topbar's own `.container-fluid px-4`
   already supplies the gutter, and that is the whole point: it is the same
   `px-4` the page wrapper below uses (`class="flex-grow-1 px-4 py-4"`), so
   the breadcrumb and the profile menu land on the page's own left and right
   edges. Adding padding to the <nav> as well stacked 20px on top of the
   container's 24px and pushed the topbar 20px inside the content. */
.navbar.navbar-expand-lg {
    min-height: var(--ds-topbar-h);
    background: var(--ds-surface) !important;
    border-bottom: 1px solid var(--ds-border);
    padding: 0;
}

.topbar-actions { gap: var(--ds-space-1); }

.topbar-actions .topbar-icon-btn,
.sidebar-toggle-top {
    width: var(--ds-control-h-sm);
    height: var(--ds-control-h-sm);
    padding: 0;
    margin: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: relative;
    border: none;
    background: transparent;
    border-radius: var(--ds-radius-sm);
    color: var(--ds-muted);
    font-size: var(--ds-icon-lg);
    line-height: 1;
    transition: background-color var(--ds-transition-fast), color var(--ds-transition-fast);
}

/* Optical alignment for the leading control. Its 20px glyph is centred in a
   34px hit area, so the glyph would sit 7px inside the topbar gutter while
   the page title below starts right on it. Pull the button out by that 7px
   and the two line up; the hover square simply reaches a little further
   left, which is what it should do at the edge of the bar. */
.sidebar-toggle-top {
    margin-left: calc((20px - var(--ds-control-h-sm)) / 2);
}

.topbar-actions .topbar-icon-btn:hover,
.sidebar-toggle-top:hover {
    background: var(--ds-surface-raised);
    color: var(--ds-ink);
    transform: none;
}

.topbar-actions .topbar-icon-btn i,
.sidebar-toggle-top i {
    font-size: var(--ds-icon-lg);
    color: inherit !important;
    margin: 0;
}

.topbar-actions .frontdesk.btn,
.topbar-actions .branch-switch-toggle {
    min-height: var(--ds-control-h-sm);
    background: transparent !important;
    border: none !important;
    box-shadow: none !important;
    padding: 0 var(--ds-space-2) !important;
    border-radius: var(--ds-radius-sm);
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-medium);
    color: var(--ds-muted);
}

.topbar-actions .frontdesk.btn:hover,
.topbar-actions .branch-switch-toggle:hover {
    background: var(--ds-surface-raised) !important;
    color: var(--ds-ink);
}

.topbar-actions .branch-switch-label { max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.topbar-actions .branch-switch-caret { font-size: var(--ds-text-2xs); opacity: 0.7; }
.branch-switch-menu { min-width: 250px; }
.branch-switch-menu .branch-item {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    padding: var(--ds-space-2) var(--ds-space-3);
    border-radius: var(--ds-radius-sm);
    font-size: var(--ds-text-md);
    color: var(--ds-body);
    white-space: nowrap;
}
.branch-switch-menu .branch-item:hover { background: var(--ds-surface-raised); color: var(--ds-ink); }
.branch-switch-menu .branch-item.active { background: var(--ds-brand-50); color: var(--ds-brand-600); font-weight: var(--ds-weight-medium); }
.branch-switch-menu .branch-item-ico { width: var(--ds-icon-slot); text-align: center; color: var(--ds-muted); }
.branch-switch-menu .branch-item-label { flex: 1; }
.branch-switch-menu .branch-item-label small { display: block; font-size: var(--ds-text-2xs); color: var(--ds-muted); margin-top: 1px; }
.branch-switch-head { padding: var(--ds-space-2) var(--ds-space-3) var(--ds-space-1); font-size: var(--ds-text-2xs); font-weight: var(--ds-weight-semibold); letter-spacing: var(--ds-tracking-wide); text-transform: uppercase; color: var(--ds-muted); }

.user-dropdown-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--ds-space-2);
    padding: var(--ds-space-1) var(--ds-space-2);
    border-radius: var(--ds-radius-pill);
    transition: background-color var(--ds-transition-fast);
}

.user-dropdown-btn:hover { background: var(--ds-surface-raised); }
.user-dropdown-btn:hover span { color: var(--ds-ink); }
.user-header-text { font-size: var(--ds-text-md); font-weight: var(--ds-weight-medium); color: var(--ds-body); }

/* ── Breadcrumbs ───────────────────────────────────────────────────── */

.breadcrumb {
    --bs-breadcrumb-divider: '/';
    display: flex;
    align-items: center;
    gap: var(--ds-space-1);
    background: transparent;
    margin-bottom: 0;
    font-size: var(--ds-text-sm);
}

.breadcrumb-item { font-size: var(--ds-text-sm); }
.breadcrumb-item a { color: var(--ds-muted); font-weight: var(--ds-weight-normal); }
.breadcrumb-item a:hover { color: var(--ds-brand); }
.breadcrumb-item.active { color: var(--ds-ink); font-weight: var(--ds-weight-medium); }
.breadcrumb-item + .breadcrumb-item::before { color: var(--ds-faint); font-size: var(--ds-text-sm); padding-right: var(--ds-space-1); padding-left: 0; }

/* ── Tabs ──────────────────────────────────────────────────────────── */

.app-tabs,
.nav-tabs {
    gap: var(--ds-space-1);
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    border-bottom: 1px solid var(--ds-border);
    margin-bottom: var(--ds-space-5);
}

.app-tabs::-webkit-scrollbar, .nav-tabs::-webkit-scrollbar { display: none; }

.app-tabs .nav-link,
.nav-tabs .nav-link {
    padding: var(--ds-space-3) var(--ds-space-4);
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-medium);
    color: var(--ds-muted);
    background: transparent;
    border: none;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    white-space: nowrap;
    transition: color var(--ds-transition-fast), border-color var(--ds-transition-fast);
}

.app-tabs .nav-link:hover,
.nav-tabs .nav-link:hover { color: var(--ds-ink); border-color: transparent; isolation: isolate; }

.app-tabs .nav-link.active,
.nav-tabs .nav-link.active {
    color: var(--ds-brand);
    background: transparent;
    border: none;
    border-bottom: 2px solid var(--ds-brand);
    font-weight: var(--ds-weight-semibold);
}

/* ── Member bottom navigation (phones) ──────────────────────────────
   The member portal's phone navigation: a floating pill above the page,
   not a bar flush to the edge. Detached on purpose — it reads as a
   control over the content rather than as a frame around it.

   Every value here is a token, so the dark theme in § 2 flips the bar
   without a single rule of its own.

   z-index sits below the off-canvas sidebar's 1045. That sidebar draws
   its backdrop with `box-shadow: 0 0 0 100vw`, so keeping the bar
   underneath means opening "More" dims the bar too, for free. */

.member-bottom-nav {
    position: fixed;
    /* env() lifts the whole bar clear of the iPhone home indicator.
       A floating bar wants to move up, not to grow bottom padding. */
    bottom: calc(16px + env(safe-area-inset-bottom));
    left: 16px;
    right: 16px;
    z-index: 1030;

    display: flex;
    align-items: center;
    gap: var(--ds-space-1);
    height: 64px;              /* fits five 48px targets plus 8px padding */
    padding: var(--ds-space-2);

    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-pill);
    box-shadow: var(--ds-shadow-lg);
}

.member-bottom-nav .mbn-tab {
    flex: 1 1 0;
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;           /* WCAG 2.1 target-size minimum; the freed 16px
                                   across the four inactive tabs is wanted by
                                   the active tab's label */
    height: 48px;
    padding: 0 var(--ds-space-2);

    border: 0;
    background: transparent;
    border-radius: var(--ds-radius-pill);
    color: var(--ds-muted);
    font-size: var(--ds-text-sm);
    font-weight: var(--ds-weight-semibold);
    line-height: 1;
    text-decoration: none;
    white-space: nowrap;
    transition: background-color var(--ds-transition),
                color var(--ds-transition),
                flex-grow var(--ds-transition);
}

.member-bottom-nav .mbn-tab i {
    font-size: 1.15rem;
}

.member-bottom-nav .mbn-tab-active {
    /* Grows to make room for its label; the others give up the space.

       3, not 2. The active tab's width comes from this split, not from its
       content, so the ratio is what decides whether the label fits. At 2 the
       tab got 103px on a 375px phone — a 61px budget for a label needing
       ~70px, so "Attendance" hit the ellipsis on the most common screen
       size there is. At 3 it gets 132px, a 90px budget, and fits outright.
       Below ~354px the inactive tabs hit their min-width floor and the
       active tab is squeezed regardless; that is what the ellipsis is for. */
    flex-grow: 3;
    /* The brand green, matching every other solid brand fill in the app —
       .btn-primary's --bs-btn-bg, the sidebar brand mark, the badge at
       § 13. This pill briefly shipped as --ds-brand-600 for contrast, but
       -600 is the app's HOVER shade, so a resting tab read as permanently
       hovered and sat visibly darker than every other green on screen.

       Known trade: white on --ds-brand is 3.11:1, under the 4.5:1 AA floor
       for text this size. That is true of every primary button in the app,
       so it is an app-wide decision about the brand palette rather than
       something for this one component to solve by going off-palette. If
       the palette is ever revisited, the fix belongs in --ds-brand itself
       and this rule inherits it for free. */
    background: var(--ds-brand);
    color: #fff;
}

/* The label is always in the DOM — collapsed, not display:none — so the
   reveal can animate and so a screen reader always reads a name for each
   tab rather than an unlabelled icon. */
.member-bottom-nav .mbn-label {
    max-width: 0;
    margin-left: 0;
    overflow: hidden;
    /* Safety net: the active tab's width comes from the flex-grow split
       against its siblings, not from its own content, so a narrow phone
       can still leave less room than the label needs. Degrade to an
       ellipsis rather than a hard mid-word chop. */
    text-overflow: ellipsis;
    opacity: 0;
    transition: max-width var(--ds-transition),
                margin-left var(--ds-transition),
                opacity var(--ds-transition);
}

.member-bottom-nav .mbn-tab-active .mbn-label {
    max-width: 7rem;
    margin-left: var(--ds-space-2);
    opacity: 1;
}

.member-bottom-nav .mbn-tab:focus-visible {
    outline: none;
    box-shadow: var(--ds-focus-ring);
}

@media (prefers-reduced-motion: reduce) {
    .member-bottom-nav .mbn-tab,
    .member-bottom-nav .mbn-label {
        transition: none;
    }
}

/* The bar floats over the page, so the footer's last line would sit
   under it. 96px = the bar's 64px height + its 16px offset + 16px of gap.

   Scoped through the sibling selector, not a bare `.footer-bg`: this
   stylesheet is shared with the admin, super and scanner panels, and none
   of them render the bar. The member layout puts the bar immediately
   before the footer, which is what makes the combinator match. */
@media (max-width: 991.98px) {
    .member-bottom-nav ~ .footer-bg {
        padding-bottom: calc(96px + env(safe-area-inset-bottom));
    }
}

/* ── Footer ────────────────────────────────────────────────────────── */

.footer-bg {
    background: var(--ds-sidebar-bg, #27313d);
    border-top: var(--ds-sidebar-bg, #27313d);
    padding: var(--ds-space-3) var(--ds-space-5);
    color: var(--ds-muted);
}

.footer-text, .dev-name { font-size: var(--ds-text-xs); color: var(--ds-neutral-bg); }


/* =====================================================================
   15  PAGINATION
   ===================================================================== */

.pagination {
    --bs-pagination-border-radius: var(--ds-radius-sm);
    gap: var(--ds-space-1);
    margin-bottom: 0;
    flex-wrap: wrap;
}

.pagination .page-link {
    min-width: var(--ds-control-h-sm);
    height: var(--ds-control-h-sm);
    padding: 0 var(--ds-space-2);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-sm);
    font-size: var(--ds-text-md);
    font-weight: var(--ds-weight-medium);
    color: var(--ds-body);
    background: var(--ds-surface);
    transition: background-color var(--ds-transition-fast), color var(--ds-transition-fast);
}

.pagination .page-link:hover { background: var(--ds-surface-raised); color: var(--ds-ink); border-color: var(--ds-border); }
.pagination .page-link:focus-visible { box-shadow: var(--ds-focus-ring); }

.pagination .page-item.active .page-link {
    background: var(--ds-brand);
    border-color: var(--ds-brand);
    color: #fff;
}

.pagination .page-item.disabled .page-link { color: var(--ds-faint); background: var(--ds-surface); opacity: 0.6; }

.pagination-text { font-size: var(--ds-text-sm); color: var(--ds-muted); }


/* =====================================================================
   16  EMPTY & LOADING STATES

   Empty tables used to render as a bare "No records found" row on some
   screens and a full illustration on others.
   ===================================================================== */

.empty-state,
.ta-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: var(--ds-space-12) var(--ds-space-4);
    color: var(--ds-muted);
}

.empty-state i:not(.ds-icon-tile i),
.ta-empty-icon {
    width: 56px;
    height: 56px;
    margin-bottom: var(--ds-space-4);
    border-radius: var(--ds-radius-pill);
    background: var(--ds-surface-raised);
    color: var(--ds-faint);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--ds-text-xl);
    opacity: 1;
}

.empty-state h5, .empty-state h6, .ds-empty-title {
    font-size: var(--ds-text-lg);
    font-weight: var(--ds-weight-semibold);
    color: var(--ds-ink);
    margin-bottom: var(--ds-space-1);
}

.empty-state p, .ds-empty-text {
    font-size: var(--ds-text-md);
    color: var(--ds-muted);
    max-width: 42ch;
    margin-bottom: var(--ds-space-4);
}

.ds-skeleton {
    background: linear-gradient(90deg, var(--ds-surface-raised) 25%, var(--ds-border) 37%, var(--ds-surface-raised) 63%);
    background-size: 400% 100%;
    border-radius: var(--ds-radius-xs);
    animation: ds-shimmer 1.4s ease infinite;
}

@keyframes ds-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

.spinner-border { border-width: 2px; }


/* =====================================================================
   17  ACCESSIBILITY & RESPONSIVE
   ===================================================================== */

/* A visible focus ring on every interactive element, keyboard only —
   pointer users never see it, so nothing looks "outlined" on click. */
a:focus-visible,
button:focus-visible,
.btn:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible,
.form-check-input:focus-visible,
.dropdown-item:focus-visible,
.page-link:focus-visible,
.nav-link:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid transparent;
    box-shadow: var(--ds-focus-ring);
    border-radius: var(--ds-radius-sm);
}

.sidebar .nav-link:focus-visible { box-shadow: inset 0 0 0 2px var(--ds-brand); }

/* Screen-reader-only label, for icon-only controls that carry no text. */
.ds-sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Comfortable tap targets on touch devices without inflating the desktop
   layout — 34px controls are fine with a mouse, not with a thumb. */
@media (hover: none) and (pointer: coarse) {
    .btn-sm, .ta-icon-btn, .pagination .page-link, .topbar-actions .topbar-icon-btn {
        min-height: 40px;
        min-width: 40px;
    }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* Nothing may push the page sideways; wide content scrolls inside its
   own container instead. This is what stops wide tables dragging the
   whole page off-screen on a phone.

   `clip`, not `hidden`, and deliberately so: `overflow-x: hidden` turns
   the element into a scroll container, which silently breaks every
   `position: sticky` descendant — including the platform panel's sticky
   topbar. `clip` trims the overflow without that side effect. */
body { overflow-x: clip; }

.table-responsive,
.dashboard-table-scroll,
.chart-container { max-width: 100%; }

@media (max-width: 991.98px) {
    .sidebar { border-right: none; }
    .footer-text, .dev-name { font-size: var(--ds-text-2xs); }
}

@media (max-width: 767.98px) {
    /* Tables get slightly tighter cells rather than a smaller font: at
       11px the data stops being scannable. */
    .table > thead > tr > th,
    .table > tbody > tr > td { padding: var(--ds-space-3) var(--ds-space-3); }

    .card-body, .form-body { padding: var(--ds-space-4); }
    .card-header, .form-header, .table-header { padding: var(--ds-space-4) !important; }
    .modal-body { padding: var(--ds-space-4) !important; }
    .modal-header, .modal-footer { padding: var(--ds-space-3) var(--ds-space-4) !important; }

    h1, .h1 { font-size: var(--ds-text-2xl); }
    h2, .h2 { font-size: var(--ds-text-xl); }
    .ds-page-title, h1.h2.mb-1 { font-size: var(--ds-text-xl); }
    .ds-stat, .stat-card .card-text, .ds-stat-value { font-size: var(--ds-text-2xl); }

    /* The filter bar's neighbours (exports, report actions) wrap into a
       column at this width; full-width keeps them tappable. */
    .ds-filter-bar > .btn { flex: 1 1 auto; }
}

/* Print: drop the app shell, keep the document. */
@media print {
    .sidebar, .navbar, .footer-bg, .topbar-actions, .breadcrumb,
    .pagination, .member-bottom-nav, .no-print { display: none !important; }

    .card, .table-card, .report-card, .receipt-container {
        box-shadow: none !important;
        border: 1px solid #ddd !important;
    }

    body { background: #fff !important; }
}
