/* =============================================================================
   list-page.css — SINGLE SOURCE OF TRUTH for the Standardized List Page Pattern
   =============================================================================
   The pattern was documented in CLAUDE.md but had no implementation, so every list
   page hand-copied the same layout CSS into its own scoped stylesheet. Measured
   2026-07-20: .table-container in 65 stylesheets, .navigation-bar in 50 (25 near-
   identical bodies — the differences were drift, e.g. gap 0.75rem vs 1rem — not
   design), .content-area in 58, tabs in 29.

   These rules are GLOBAL and unscoped, so they apply to every page that uses the
   pattern's class names. A page-scoped rule (.navigation-bar[b-xyz]) still wins on
   specificity, so pages can be migrated off their local copies incrementally and
   anything genuinely bespoke can still override.

   Canonical values taken from the dominant variant across the codebase.
   Colours are tokens only — never literals (see the token rubric in CLAUDE.md).

   SCOPE — this file defines exactly:
     .navigation-bar .tabs-inline .tab-compact .tab-badge .search-inline .nav-search-box
     .refresh-btn .content-area .table-container .filter-toggle-btn .filter-active-dot
     .advanced-filters-bar .advanced-filter-item .advanced-filter-label .advanced-filter-actions
     .adv-clear-btn
     .sort-inline .sort-select .sort-dir-btn
   NOT covered (still page-local): .inline-alert(s), .view-toggle-inline,
   .pagination-wrapper — add them here only after checking for collisions.

   DELIBERATELY EXCLUDED — generic names already used with a DIFFERENT meaning elsewhere. An
   unscoped rule here would silently restyle unrelated markup:
     .spinner-sm  — BusyOverlay/PageLoader use it as a SIZE MODIFIER on a container
                    (`.spinner-sm .spinner-border`, `.page-loader-spinner.spinner-sm`), not as a
                    spinner itself. A global 14px ring + animation would break both components.
     .toggle-btn  — too generic; used outside list pages.
   Before adding any class here, grep the codebase for other meanings of that name first.
   ============================================================================= */

/* ============ Navigation Bar ============ */
/* Compacted with the KPI strip: this toolbar is on every list page, so its height is paid app-wide.
   Measured on /accounting/ledgers, the KPI strip + this bar + the expand/collapse row put the first
   row of data at y=405 — most of the first screen before any content. */
.navigation-bar {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.375rem 0.75rem;
    background: var(--bg-surface);
    border-radius: 12px;
    border: 1px solid var(--border-color);
    margin-bottom: 1.25rem;
    overflow: visible;
    position: relative;
    /* intentional: local stacking context, not a global layer */
    z-index: 10;
}

/* ============ Tabs ============ */
.tabs-inline {
    display: flex;
    background: var(--bg-surface-tertiary);
    border-radius: 8px;
    padding: 0.25rem;
    gap: 0.125rem;
}

.tab-compact {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.5rem 0.75rem;
    background: transparent;
    border: none;
    border-radius: 6px;
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--text-secondary);
    cursor: pointer;
    transition: var(--sidenav-transition-fast);
    white-space: nowrap;
}

.tab-compact:hover {
    color: var(--text-primary);
    background: var(--bg-hover);
}

.tab-compact.active {
    background: var(--bg-surface);
    color: var(--text-primary);
    box-shadow: var(--shadow-sm);
}

.tab-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    background: var(--border-color);
    border-radius: 9px;
    font-size: 0.6875rem;
    font-weight: 600;
    color: var(--text-secondary);
}

.tab-compact.active .tab-badge {
    background: var(--theme-primary);
    color: var(--text-inverse);
}

.tab-badge.success {
    background: var(--theme-success-subtle);
    color: var(--theme-success);
}

.tab-badge.warning {
    background: var(--theme-warning-subtle);
    color: var(--theme-warning);
}

.tab-badge.danger {
    background: var(--theme-danger-subtle);
    color: var(--theme-danger);
}

.tab-badge.info {
    background: var(--theme-info-subtle);
    color: var(--theme-info);
}

.tab-compact.active .tab-badge.success,
.tab-compact.active .tab-badge.warning,
.tab-compact.active .tab-badge.danger,
.tab-compact.active .tab-badge.info {
    background: var(--theme-primary);
    color: var(--text-inverse);
}

/* ============ Search ============ */
.search-inline {
    flex: 1;
    min-width: 130px;
    max-width: 300px;
}

.nav-search-box {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0 0.75rem;
    background: var(--bg-surface-secondary);
    border: 1px solid var(--border-color);
    border-radius: 6px;
    height: 34px;
    transition: var(--sidenav-transition-fast);
}

.nav-search-box:focus-within {
    border-color: var(--theme-primary);
    box-shadow: 0 0 0 2px var(--focus-ring-color);
}

.nav-search-box i {
    color: var(--text-muted);
    font-size: 1rem;
    flex-shrink: 0;
}

.nav-search-box input {
    flex: 1;
    border: none;
    background: transparent;
    font-size: 0.8125rem;
    color: var(--text-primary);
    outline: none;
    min-width: 0;
}

.nav-search-box input::placeholder {
    color: var(--text-muted);
}

.nav-search-box .clear-search {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    padding: 0;
    background: var(--bg-surface-tertiary);
    border: none;
    border-radius: 50%;
    color: var(--text-secondary);
    cursor: pointer;
    transition: var(--sidenav-transition-fast);
    flex-shrink: 0;
}

.nav-search-box .clear-search:hover {
    background: var(--border-color);
    color: var(--text-primary);
}

/* ============ Refresh Button ============ */
.refresh-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    background: var(--theme-primary);
    border: none;
    border-radius: 6px;
    color: var(--text-inverse);
    cursor: pointer;
    transition: var(--sidenav-transition-fast);
    flex-shrink: 0;
}

/* A refresh button that carries a LABEL (e.g. Period Close renders
   "Refresh" / "Apply" / "Loading…") must size to its content. The 34px above is
   an icon-only square, and it silently clipped the label — measured on
   /accounting/fees/period-close: rendered 34px against a 51px scrollWidth, so
   "Refresh" displayed as "Refre".
   `:not(:empty)` keeps the square for the icon-only usages, whose only spans are
   the empty .refresh-dot / .spinner-sm markers. Browsers without :has() simply
   keep the old fixed width — no regression. */
.refresh-btn:has(span:not(:empty)) {
    width: auto;
    min-width: 34px;
    padding: 0 0.75rem;
    gap: 0.375rem;
}

.refresh-btn:hover:not(:disabled) {
    background: var(--theme-primary-emphasis);
}

.refresh-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.refresh-btn i {
    font-size: 1rem;
}

/* ============ Content Area ============ */
.content-area {
    min-height: 200px;
}

.table-container {
    background: var(--bg-surface);
    border-radius: 12px;
    border: 1px solid var(--border-color);
    overflow: visible;
}


/* ============ Sort control (select + direction toggle) ============
   Promoted from page-local CSS 2026-08-04. `.sort-inline` / `.sort-select` / `.sort-dir-btn`
   were declared in only 5 scoped stylesheets while 6 pages rendered the markup — so
   SchoolPay Transactions shipped a RAW, unstyled native <select> sitting between two styled
   pill buttons (the reported "date sort near the toggle button" defect). Sizing matches
   .refresh-btn / .filter-toggle-btn (34px) so the whole nav bar lines up on one baseline. */
.sort-inline {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    flex-shrink: 0;
}

.sort-select {
    height: 34px;
    padding: 0 1.75rem 0 0.5rem;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    background: var(--bg-surface-secondary);
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--text-primary);
    cursor: pointer;
    transition: var(--sidenav-transition-fast);
    appearance: none;
    /* Native select chevron replaced so the control matches the app's controls in both themes.
       The hex lives inside an SVG data URI, which the token lint explicitly allows. */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.5rem center;
    background-size: 14px;
}

.sort-select:hover {
    border-color: var(--theme-primary);
}

.sort-select:focus {
    outline: none;
    border-color: var(--theme-primary);
    box-shadow: 0 0 0 2px var(--focus-ring-color);
}

.sort-dir-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    background: var(--bg-surface-secondary);
    border: 1px solid var(--border-color);
    border-radius: 6px;
    color: var(--text-secondary);
    cursor: pointer;
    transition: var(--sidenav-transition-fast);
    flex-shrink: 0;
}

.sort-dir-btn:hover {
    color: var(--theme-primary);
    border-color: var(--theme-primary);
    background: var(--theme-primary-subtle);
}


/* ============ Student name cell / identifier badges ============
   CLAUDE.md mandates the Student Name Cell Standard for every student grid: name on line 1,
   payment code (monospace, muted) on line 2, registration number in its OWN column as a badge.
   The classes were only ever declared in a handful of pages' SCOPED stylesheets, so a page that
   used the markup without its own copy rendered them unstyled — SchoolPay Transactions ran the
   student name and payment code together on one line. Shared, so the standard is real. */
.student-cell {
    display: flex;
    flex-direction: column;
    gap: 0.05rem;
    min-width: 0;
}

.student-cell > strong {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pay-code {
    display: block;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.code-badge {
    display: inline-block;
    max-width: 100%;
    padding: 0.1rem 0.4rem;
    background: var(--bg-surface-tertiary);
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: middle;
}


/* ─── Advanced filters, view toggle, alerts ─── */

/* ============ Filter Toggle ============ */
.filter-toggle-btn {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    /* Fixed height, not padding-derived. At 0.5rem padding this computed to 39px while every
       neighbour (.refresh-btn, .sort-select, .sort-dir-btn) is 34px — so the toolbar sat on two
       baselines and the controls around the toggle looked misaligned. */
    height: 34px;
    padding: 0 0.75rem;
    background: var(--bg-surface-tertiary);
    border: 1px solid var(--border-color);
    border-radius: 6px;
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--text-secondary);
    cursor: pointer;
    transition: var(--sidenav-transition-fast);
    white-space: nowrap;
    position: relative;
}

.filter-toggle-btn:hover {
    color: var(--text-primary);
    background: var(--bg-hover);
    border-color: var(--theme-primary);
}

.filter-toggle-btn.active {
    background: var(--theme-primary-subtle);
    color: var(--theme-primary);
    border-color: var(--theme-primary);
}

.filter-toggle-btn i {
    font-size: 1rem;
}

.filter-active-dot {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 7px;
    height: 7px;
    background: var(--theme-danger);
    border-radius: 50%;
    border: 1.5px solid var(--bg-surface);
}

/* ============ Advanced Filters Bar ============ */
.advanced-filters-bar {
    display: flex;
    align-items: flex-end;
    gap: 0.75rem;
    padding: 0.875rem 1.25rem;
    background: var(--bg-surface-secondary);
    border-radius: 10px;
    border: 1px solid var(--border-color);
    margin-bottom: 1rem;
    flex-wrap: wrap;
}

.advanced-filter-item {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    flex: 1 1 140px;
    min-width: 120px;
    max-width: 200px;
}

.advanced-filter-label {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.6875rem;
    font-weight: 600;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin: 0;
    line-height: 1;
}

.advanced-filter-label i {
    font-size: 0.75rem;
}

.advanced-filter-actions {
    display: flex;
    align-items: flex-end;
    gap: 0.375rem;
}

.adv-clear-btn {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.4375rem 0.75rem;
    background: transparent;
    border: 1px solid var(--theme-danger);
    border-radius: 6px;
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--theme-danger);
    cursor: pointer;
    transition: all 0.2s ease;
    white-space: nowrap;
    height: 30px;
}

.adv-clear-btn:hover {
    background: var(--theme-danger);
    color: var(--text-inverse);
}

/* ============================================================================
   RESPONSIVE — owned HERE, once, for every list page.
   ============================================================================
   This file defined all 16 toolbar classes and NOT ONE media query, so each page
   re-implemented the small-screen behaviour itself: 779 duplicated media-query
   rules across 150 page stylesheets, `.navigation-bar` alone re-declared 79 times.
   The base styling was shared; the responsive half never was — which is why
   responsiveness broke page by page, and why 100 of those copies could sit dead
   (written as `::deep`, which cannot match a component's own markup) without
   anyone noticing.

   Page-scoped copies of these rules are now redundant and should be deleted when
   a page is next touched; nothing here needs `::deep` because it is global.

   NOTE ON AUTHORING: the base rules above are the WIDE case, so these are
   max-width queries. That is not mobile-first authoring — inverting the base for
   150 consumers is a separate change — but the OUTCOME is fully responsive and
   measured at 1024 / 768 / 600 / 480 / 375.
   ============================================================================ */

/* Tablet and below: the toolbar becomes a wrapping row rather than a single line. */
@media (max-width: 1200px) {
    .navigation-bar {
        flex-wrap: wrap;
        row-gap: 0.5rem;
    }

    .search-inline {
        flex: 1 1 220px;
        min-width: 0;
    }
}

@media (max-width: 768px) {
    .navigation-bar {
        gap: 0.5rem;
    }

    /* Search takes its own full row — it is the control people reach for first. */
    .search-inline {
        order: 2;
        flex: 1 1 100%;
    }

    .nav-search-box {
        width: 100%;
    }

    /* Tabs stay on one line but scroll, rather than wrapping into a tall block. */
    .tabs-inline {
        order: 1;
        flex: 1 1 100%;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: thin;
    }

    .tab-compact {
        flex: 0 0 auto;
    }

    .filter-item,
    .filter-toggle-btn,
    .refresh-btn {
        order: 3;
    }

    /* Advanced filters stack; each control gets the full width to stay tappable. */
    .advanced-filters-bar {
        flex-direction: column;
        align-items: stretch;
    }

    .advanced-filter-item {
        width: 100%;
    }
}

@media (max-width: 480px) {
    /* Below this the tab labels cost more than they inform — the badge carries the
       count and the icon carries the meaning. */
    .tab-compact span:not(.tab-badge) {
        display: none;
    }

    .filter-toggle-btn span {
        display: none;
    }

    .filter-item {
        flex: 1 1 100%;
    }

    /* Keep every control at a usable touch target even when labels are hidden. */
    .filter-toggle-btn,
    .refresh-btn {
        min-width: 38px;
        min-height: 38px;
    }
}

/* A list page's table always scrolls inside its own pane — never the document. */
.table-container {
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

/* ── Toolbar ordering on a wrapped bar (second wave of centralisation) ────────
   `.view-toggle-inline` / `.sort-inline` / `.filter-inline` were re-declared in page media
   queries purely to set `order:` once the bar wraps. Same behaviour everywhere, so it belongs
   here rather than in 30 page stylesheets. */
@media (max-width: 768px) {
    .filter-inline,
    .filter-item {
        order: 3;
        flex: 1 1 auto;
        min-width: 0;
    }

    .view-toggle-inline {
        order: 4;
        flex: 0 0 auto;
    }

    .sort-inline {
        order: 5;
        flex: 1 1 auto;
        min-width: 0;
    }

    .sort-select {
        flex: 1 1 auto;
        min-width: 0;
    }
}

@media (max-width: 480px) {
    .filter-inline,
    .filter-item,
    .sort-inline {
        flex: 1 1 100%;
    }
}
