/* ==========================================================================
   PSMSchools — EdTech theme
   Loaded AFTER site.css. Everything here is an override layer; site.css is
   left untouched so the diff stays reviewable and reversible (delete the two
   <link>/<script> tags in _Layout.cshtml to fall back to the old skin).

   RTL RULES FOR ANYONE EDITING THIS FILE
   --------------------------------------
   bootstrap.rtl.css is pre-flipped by RTLCSS at build time. Our stylesheet is
   NOT processed by RTLCSS, so a physical `left`/`right`/`margin-left` written
   here would stay pinned to that physical side and break in the LTR print
   views. Use logical properties only:
       inset-inline-start / -end      not  left / right
       margin-inline-start / -end     not  margin-left / -right
       padding-inline-start / -end    not  padding-left / -right
       border-inline-start / -end     not  border-left / -right
       text-align: start / end        not  left / right
   The few physical values below are genuinely direction-agnostic (top/bottom).
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Design tokens
   -------------------------------------------------------------------------- */

:root {
    /* Brand — Indigo */
    --psm-brand-50:  #eef2ff;
    --psm-brand-100: #e0e7ff;
    --psm-brand-200: #c7d2fe;
    --psm-brand-300: #a5b4fc;
    --psm-brand-400: #818cf8;
    --psm-brand-500: #6366f1;
    --psm-brand-600: #4f46e5;
    --psm-brand-700: #4338ca;
    --psm-brand-rgb: 79, 70, 229;

    /* Success — Sage green (muted, not neon) */
    --psm-sage-50:  #eff5f1;
    --psm-sage-100: #d9e8de;
    --psm-sage-500: #6b9b7c;
    --psm-sage-600: #55856a;
    --psm-sage-700: #3f6a52;
    --psm-sage-rgb: 85, 133, 106;

    /* Danger — Soft coral */
    --psm-coral-50:  #fdf1ef;
    --psm-coral-100: #fadcd7;
    --psm-coral-500: #e8776a;
    --psm-coral-600: #d95f52;
    --psm-coral-700: #b94a3e;
    --psm-coral-rgb: 217, 95, 82;

    /* Warning — Warm amber */
    --psm-amber-50:  #fdf5e7;
    --psm-amber-500: #d9a13e;
    --psm-amber-600: #c08a2c;
    --psm-amber-rgb: 192, 138, 44;

    /* Info — Teal */
    --psm-teal-50:  #ecf7f6;
    --psm-teal-500: #3aa8a0;
    --psm-teal-600: #2c8d86;
    --psm-teal-rgb: 44, 141, 134;

    /* Neutrals */
    --psm-ink:          #1f2430; /* headings, table body text */
    --psm-ink-soft:     #444c5c; /* default body copy */
    --psm-muted:        #6b7280; /* secondary / meta text */
    --psm-muted-2:      #98a1af; /* icons at rest, placeholders */
    --psm-border:       #e8ebf0; /* every hairline in the UI */
    --psm-border-strong:#dde1e8;
    --psm-surface:      #ffffff; /* cards */
    --psm-surface-sunk: #f5f7fa; /* table head, hover rows, wells */
    --psm-bg:           #f8f9fa; /* app background — makes cards "pop" */

    /* Elevation — one soft shadow, used everywhere. */
    --psm-shadow-sm: 0 .125rem .25rem rgba(31, 36, 48, .06);
    --psm-shadow:    0 .125rem .25rem rgba(31, 36, 48, .075);
    --psm-shadow-lg: 0 .75rem 2rem rgba(31, 36, 48, .12);
    --psm-shadow-ring: 0 0 0 .2rem rgba(var(--psm-brand-rgb), .18);

    --psm-radius-sm: .5rem;
    --psm-radius:    .75rem;  /* 12px — cards, tables, modals */
    --psm-radius-lg: 1rem;

    --psm-sidebar-w: 250px;
    --psm-topbar-h: 4.25rem;

    /* ---- Bootstrap 5.3 global variable overrides ---- */
    --bs-font-sans-serif: 'IBM Plex Sans Arabic', 'Tajawal', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --bs-body-font-family: var(--bs-font-sans-serif);
    --bs-body-font-size: .9375rem;
    --bs-body-color: var(--psm-ink-soft);
    --bs-body-color-rgb: 68, 76, 92;
    --bs-body-bg: var(--psm-bg);
    --bs-emphasis-color: var(--psm-ink);
    --bs-secondary-color: var(--psm-muted);

    --bs-primary: var(--psm-brand-600);
    --bs-primary-rgb: var(--psm-brand-rgb);
    --bs-success: var(--psm-sage-600);
    --bs-success-rgb: var(--psm-sage-rgb);
    --bs-danger: var(--psm-coral-600);
    --bs-danger-rgb: var(--psm-coral-rgb);
    --bs-warning: var(--psm-amber-600);
    --bs-warning-rgb: var(--psm-amber-rgb);
    --bs-info: var(--psm-teal-600);
    --bs-info-rgb: var(--psm-teal-rgb);
    --bs-light: var(--psm-bg);
    --bs-light-rgb: 248, 249, 250;
    --bs-dark: var(--psm-ink);
    --bs-dark-rgb: 31, 36, 48;

    --bs-border-color: var(--psm-border);
    --bs-border-color-translucent: var(--psm-border);
    --bs-border-radius: var(--psm-radius-sm);
    --bs-border-radius-sm: .375rem;
    --bs-border-radius-lg: var(--psm-radius);
    --bs-border-radius-xl: var(--psm-radius-lg);

    --bs-link-color: var(--psm-brand-600);
    --bs-link-color-rgb: var(--psm-brand-rgb);
    --bs-link-hover-color: var(--psm-brand-700);

    /* Legacy aliases — the old purple tokens still referenced by site.css and
       by inline styles in a handful of views. Repointing them here recolours
       those call sites for free instead of hunting each one down. */
    --color1: var(--psm-brand-600);
    --color2: var(--psm-ink);
    --Color0: #ffffff;
}

/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */

body {
    font-family: var(--bs-font-sans-serif);
    background-color: var(--psm-bg);
    color: var(--psm-body-color, var(--psm-ink-soft));
    font-size: .9375rem;
    line-height: 1.65;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Arabic numerals inside otherwise-Arabic UI read better tabular. */
.text-num, td, th, .dataTables_info, .badge {
    font-feature-settings: 'tnum' 1;
}

/* No letter-spacing anywhere in this file, positive or negative: Arabic is a
   cursive script and tracking pulls joined letterforms apart. Hierarchy is
   carried by size, weight and colour instead. */
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
    color: var(--psm-ink);
    font-weight: 600;
}

a { text-decoration: none; }

hr { border-color: var(--psm-border); opacity: 1; }

/* site.css hard-codes #f5f5f5 with no !important; later + equal specificity wins. */
.bg-light { background-color: var(--psm-bg) !important; }

.text-muted { color: var(--psm-muted) !important; }

/* Utility: the one shadow the spec asks for, as a class. */
.shadow-soft { box-shadow: var(--psm-shadow) !important; }

/* site.css sets a much heavier .shadow — soften it globally. */
.shadow { box-shadow: var(--psm-shadow-lg) !important; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #d5dae2; border-radius: 99px; }
::-webkit-scrollbar-thumb:hover { background: #c2c9d4; }

/* --------------------------------------------------------------------------
   3. App shell — topbar, sidebar, content column
   -------------------------------------------------------------------------- */

.app-topbar {
    background-color: var(--psm-surface);
    border-bottom: 1px solid var(--psm-border) !important;
    box-shadow: 0 1px 2px rgba(31, 36, 48, .03);
    min-height: var(--psm-topbar-h);
}

.app-brand {
    color: var(--psm-ink);
    font-weight: 600;
}

.app-brand-mark {
    width: 32px;
    height: 32px;
    border-radius: .625rem;
    background: linear-gradient(135deg, var(--psm-brand-500), var(--psm-brand-700));
    color: #fff;
    font-size: 1rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
}

.app-avatar {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--psm-brand-500), var(--psm-brand-700));
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    object-fit: contain;
}

.app-avatar-lg {
    width: 72px;
    height: 72px;
    font-size: 1.75rem;
}

/* Small pill that carries a piece of metadata in the topbar (school name,
   credit balance). Reads as information, not as a button. */
.app-chip {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .3rem .75rem;
    border-radius: 99px;
    background: var(--psm-surface-sunk);
    border: 1px solid var(--psm-border);
    color: var(--psm-muted);
    font-size: .8125rem;
    line-height: 1.3;
    white-space: nowrap;
}

.app-content {
    background-color: var(--psm-bg);
    padding: 1.75rem !important;
}

@media (max-width: 767.98px) {
    .app-content { padding: 1rem !important; }
}

/* Page header: title + optional subtitle on one side, actions on the other. */
.page-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    margin-bottom: 1.25rem;
}

.page-title {
    font-size: 1.375rem;
    font-weight: 600;
    color: var(--psm-ink);
    margin: 0;
}

.page-subtitle {
    color: var(--psm-muted);
    font-size: .875rem;
    margin: .125rem 0 0;
}

/* ---- Sidebar ---- */

.app-sidebar {
    background-color: var(--psm-surface);
    border-inline-end: 1px solid var(--psm-border);
}

.app-nav {
    --psm-nav-fg: var(--psm-ink-soft);
    --psm-nav-fg-active: var(--psm-brand-700);
    --psm-nav-icon: var(--psm-muted-2);
    --psm-nav-hover-bg: var(--psm-surface-sunk);
    --psm-nav-active-bg: var(--psm-brand-50);
    padding: .75rem .625rem;
}

.app-nav .nav-link,
.app-nav .items {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    padding: .5625rem .75rem !important;
    margin-bottom: .125rem;
    border-radius: var(--psm-radius-sm) !important;
    color: var(--psm-nav-fg);
    font-size: .9063rem;
    font-weight: 500;
    line-height: 1.5;
    transition: background-color .15s ease, color .15s ease;
}

.app-nav .nav-link i,
.app-nav .items i {
    color: var(--psm-nav-icon);
    font-size: 1.0625rem;
    transition: color .15s ease;
    /* site.css rotates every <i> inside .nav-link by -90deg to animate the
       collapse chevron; that also tipped over each item's leading icon.
       Rotation is re-scoped to .nav-chevron below. */
    transform: none !important;
}

.app-nav .nav-link:hover,
.app-nav .items:hover {
    background-color: var(--psm-nav-hover-bg) !important;
    color: var(--psm-ink) !important;
}

.app-nav .nav-link:hover i,
.app-nav .items:hover i { color: var(--psm-ink-soft); }

.app-nav .nav-link:focus-visible,
.app-nav .items:focus-visible {
    outline: none;
    box-shadow: var(--psm-shadow-ring);
}

/* site.css: `.nav-link.active { background:#ddd !important }` — needs !important
   at higher specificity to unseat. */
.app-nav .nav-link.active,
.app-nav .items.active {
    background-color: var(--psm-nav-active-bg) !important;
    color: var(--psm-nav-fg-active) !important;
    font-weight: 600;
}

.app-nav .nav-link.active i,
.app-nav .items.active i { color: var(--psm-brand-600) !important; }

/* The thin vertical indicator. inset-inline-start keeps it on the correct
   edge in both directions. */
.app-nav .nav-link.active::before,
.app-nav .items.active::before {
    content: "";
    position: absolute;
    inset-inline-start: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 1.375rem;
    border-radius: 99px;
    background-color: var(--psm-brand-600);
}

/* Collapse chevron — the only icon that should rotate.
   Expanded: points down. Collapsed: points toward the inline-start edge, which
   is physically left in RTL and right in LTR. */
.app-nav .nav-chevron {
    flex: 0 0 auto;
    font-size: .75rem !important;
    color: var(--psm-muted-2);
    transition: transform .2s ease;
    transform: rotate(0deg) !important;
}

.app-nav .nav-link.collapsed .nav-chevron { transform: rotate(90deg) !important; }

[dir="ltr"] .app-nav .nav-link.collapsed .nav-chevron { transform: rotate(-90deg) !important; }

/* Sub-items: quieter, indented, hairline rail down the group. */
.app-nav .nav-sub {
    position: relative;
    margin: .125rem 0 .375rem;
    padding-inline-start: 1.375rem;
    list-style: none;
}

.app-nav .nav-sub::before {
    content: "";
    position: absolute;
    inset-inline-start: 1.375rem;
    top: .125rem;
    bottom: .125rem;
    width: 1px;
    background: var(--psm-border);
}

.app-nav .nav-sub .items {
    padding-inline-start: 1rem !important;
    font-size: .875rem;
    font-weight: 500;
    color: var(--psm-muted);
}

.app-nav .nav-sub .items i { font-size: .875rem; }

.app-nav .nav-sub .items.active::before {
    inset-inline-start: -.0625rem;
    height: 1.125rem;
}

.app-nav .nav-group-label {
    padding: 1rem .75rem .375rem;
    font-size: .6875rem;
    font-weight: 600;
    color: var(--psm-muted-2);
}

.app-nav .badge { font-weight: 500; }

/* Optional "Deep Charcoal" sidebar. Add `app-sidebar--dark` to .app-sidebar
   (and to the offcanvas) to switch — no other markup changes needed. */
.app-sidebar--dark {
    background-color: #171b26;
    border-inline-end-color: rgba(255, 255, 255, .06);
}

.app-sidebar--dark .app-nav {
    --psm-nav-fg: #b6bdcb;
    --psm-nav-fg-active: #ffffff;
    --psm-nav-icon: #7d8699;
    --psm-nav-hover-bg: rgba(255, 255, 255, .05);
    --psm-nav-active-bg: rgba(var(--psm-brand-rgb), .22);
}

.app-sidebar--dark .app-nav .nav-link:hover,
.app-sidebar--dark .app-nav .items:hover { color: #fff !important; }

.app-sidebar--dark .app-nav .nav-link.active i,
.app-sidebar--dark .app-nav .items.active i { color: var(--psm-brand-300) !important; }

.app-sidebar--dark .app-nav .nav-link.active::before,
.app-sidebar--dark .app-nav .items.active::before { background-color: var(--psm-brand-400); }

.app-sidebar--dark .app-nav .nav-sub::before { background: rgba(255, 255, 255, .08); }

/* ---- Auth screens (_AuthLayout) ---- */

.auth-body {
    min-height: 100vh;
    background-color: var(--psm-bg);
    /* A single soft brand wash behind the card, rather than flat grey. */
    background-image:
        radial-gradient(60rem 30rem at 85% -10%, var(--psm-brand-50), transparent 60%),
        radial-gradient(45rem 25rem at 10% 110%, var(--psm-teal-50), transparent 60%);
    background-repeat: no-repeat;
}

.auth-card {
    background: var(--psm-surface);
    border: 1px solid var(--psm-border);
    border-radius: var(--psm-radius-lg);
    box-shadow: 0 .75rem 2.5rem rgba(31, 36, 48, .1);
    padding: 2.25rem;
}

@media (max-width: 575.98px) {
    .auth-card { padding: 1.5rem; }
}

.auth-brand {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .625rem;
    margin-bottom: 1.75rem;
    color: var(--psm-ink);
    font-size: 1.0625rem;
    font-weight: 600;
}

.auth-title {
    font-size: 1.375rem;
    font-weight: 600;
    color: var(--psm-ink);
    margin: 0;
}

.auth-subtitle {
    color: var(--psm-muted);
    font-size: .875rem;
    margin: .25rem 0 0;
}

/* The Users/* auth screens carry their subtitle in a bare .text-muted div
   rather than .auth-subtitle; match it rather than rewrite four templates. */
.auth-card .text-center .text-muted { font-size: .875rem; }

/* `.auth-card` sits directly on the grid column there, so its padding replaces
   the column gutter — which is fine for a single centred column. */
.auth-card.col-11 { margin-inline: auto; }

/* --------------------------------------------------------------------------
   4. Cards — the floating surface everything lives on
   -------------------------------------------------------------------------- */

.card {
    --bs-card-bg: var(--psm-surface);
    --bs-card-border-color: var(--psm-border);
    --bs-card-border-radius: var(--psm-radius);
    --bs-card-inner-border-radius: calc(var(--psm-radius) - 1px);
    --bs-card-spacer-x: 1.25rem;
    --bs-card-spacer-y: 1.25rem;
    --bs-card-cap-bg: transparent;
    box-shadow: var(--psm-shadow);
}

.card-header {
    border-bottom: 1px solid var(--psm-border);
    padding: 1.125rem 1.25rem;
    font-weight: 600;
    color: var(--psm-ink);
}

.card-footer {
    border-top: 1px solid var(--psm-border);
    background: transparent;
}

/* Borderless variant — shadow alone carries the elevation. */
.card-flush { border: 0 !important; }

/* Compact metric tile for dashboards. */
.stat-card {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 1.125rem 1.25rem;
    background: var(--psm-surface);
    border: 1px solid var(--psm-border);
    border-radius: var(--psm-radius);
    box-shadow: var(--psm-shadow);
    height: 100%;
}

.stat-card .stat-icon {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    border-radius: var(--psm-radius-sm);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.25rem;
    background: var(--psm-brand-50);
    color: var(--psm-brand-600);
}

.stat-card .stat-icon.is-success { background: var(--psm-sage-50);  color: var(--psm-sage-600); }
.stat-card .stat-icon.is-danger  { background: var(--psm-coral-50); color: var(--psm-coral-600); }
.stat-card .stat-icon.is-warning { background: var(--psm-amber-50);  color: var(--psm-amber-600); }
.stat-card .stat-icon.is-info    { background: var(--psm-teal-50);   color: var(--psm-teal-600); }

.stat-card .stat-value {
    font-size: 1.5rem;
    font-weight: 600;
    color: var(--psm-ink);
    line-height: 1.2;
}

.stat-card .stat-label {
    font-size: .8125rem;
    color: var(--psm-muted);
}

/* --------------------------------------------------------------------------
   5. Buttons
   -------------------------------------------------------------------------- */

.btn {
    --bs-btn-border-radius: .625rem;
    --bs-btn-font-weight: 500;
    --bs-btn-padding-x: 1rem;
    --bs-btn-padding-y: .5rem;
    --bs-btn-font-size: .9063rem;
    --bs-btn-focus-box-shadow: var(--psm-shadow-ring);
    transition: background-color .15s ease, border-color .15s ease, color .15s ease, box-shadow .15s ease;
}

.btn-sm {
    --bs-btn-border-radius: .5rem;
    --bs-btn-padding-x: .75rem;
    --bs-btn-padding-y: .3125rem;
    --bs-btn-font-size: .8438rem;
}

.btn-lg {
    --bs-btn-border-radius: .75rem;
    --bs-btn-font-size: 1rem;
    --bs-btn-padding-y: .625rem;
}

/* Bootstrap 5.3 hard-codes each variant's colours into local --bs-btn-* vars,
   so recolouring --bs-primary alone is not enough — each variant is restated. */
.btn-primary {
    --bs-btn-bg: var(--psm-brand-600);
    --bs-btn-border-color: var(--psm-brand-600);
    --bs-btn-hover-bg: var(--psm-brand-700);
    --bs-btn-hover-border-color: var(--psm-brand-700);
    --bs-btn-active-bg: var(--psm-brand-700);
    --bs-btn-active-border-color: var(--psm-brand-700);
    --bs-btn-disabled-bg: var(--psm-brand-300);
    --bs-btn-disabled-border-color: var(--psm-brand-300);
    box-shadow: 0 1px 2px rgba(var(--psm-brand-rgb), .25);
}

.btn-success {
    --bs-btn-bg: var(--psm-sage-600);
    --bs-btn-border-color: var(--psm-sage-600);
    --bs-btn-hover-bg: var(--psm-sage-700);
    --bs-btn-hover-border-color: var(--psm-sage-700);
    --bs-btn-active-bg: var(--psm-sage-700);
    --bs-btn-active-border-color: var(--psm-sage-700);
}

.btn-danger {
    --bs-btn-bg: var(--psm-coral-600);
    --bs-btn-border-color: var(--psm-coral-600);
    --bs-btn-hover-bg: var(--psm-coral-700);
    --bs-btn-hover-border-color: var(--psm-coral-700);
    --bs-btn-active-bg: var(--psm-coral-700);
    --bs-btn-active-border-color: var(--psm-coral-700);
}

.btn-warning {
    --bs-btn-bg: var(--psm-amber-500);
    --bs-btn-border-color: var(--psm-amber-500);
    --bs-btn-color: #fff;
    --bs-btn-hover-bg: var(--psm-amber-600);
    --bs-btn-hover-border-color: var(--psm-amber-600);
    --bs-btn-hover-color: #fff;
    --bs-btn-active-color: #fff;
}

.btn-info {
    --bs-btn-bg: var(--psm-teal-600);
    --bs-btn-border-color: var(--psm-teal-600);
    --bs-btn-color: #fff;
    --bs-btn-hover-bg: #24756f;
    --bs-btn-hover-border-color: #24756f;
    --bs-btn-hover-color: #fff;
    --bs-btn-active-color: #fff;
}

.btn-dark {
    --bs-btn-bg: var(--psm-ink);
    --bs-btn-border-color: var(--psm-ink);
    --bs-btn-hover-bg: #12161f;
    --bs-btn-hover-border-color: #12161f;
}

/* The workhorse secondary button: white, hairline border, quiet hover. */
.btn-outline-secondary,
.btn-soft {
    --bs-btn-color: var(--psm-ink-soft);
    --bs-btn-bg: #fff;
    --bs-btn-border-color: var(--psm-border-strong);
    --bs-btn-hover-color: var(--psm-ink);
    --bs-btn-hover-bg: var(--psm-surface-sunk);
    --bs-btn-hover-border-color: var(--psm-border-strong);
    --bs-btn-active-color: var(--psm-ink);
    --bs-btn-active-bg: var(--psm-surface-sunk);
    --bs-btn-active-border-color: var(--psm-border-strong);
}

.btn-outline-primary {
    --bs-btn-color: var(--psm-brand-600);
    --bs-btn-border-color: var(--psm-brand-200);
    --bs-btn-hover-bg: var(--psm-brand-600);
    --bs-btn-hover-border-color: var(--psm-brand-600);
    --bs-btn-active-bg: var(--psm-brand-700);
    --bs-btn-active-border-color: var(--psm-brand-700);
}

.btn-outline-danger {
    --bs-btn-color: var(--psm-coral-600);
    --bs-btn-border-color: var(--psm-coral-100);
    --bs-btn-hover-bg: var(--psm-coral-600);
    --bs-btn-hover-border-color: var(--psm-coral-600);
}

/* site.css's legacy purple button, repointed at the brand. */
.btn-color1 {
    background-color: var(--psm-brand-600);
    border: 1px solid var(--psm-brand-600);
    color: #fff;
}

.btn-color1:hover {
    background-color: var(--psm-brand-700);
    border-color: var(--psm-brand-700);
    color: #fff;
}

.btn-light {
    --bs-btn-bg: var(--psm-surface-sunk);
    --bs-btn-border-color: var(--psm-surface-sunk);
    --bs-btn-color: var(--psm-ink-soft);
    --bs-btn-hover-bg: #eaeef4;
    --bs-btn-hover-border-color: #eaeef4;
}

/* Ghost button — the row-action primitive. Invisible until hovered. */
.btn-ghost {
    --bs-btn-color: var(--psm-muted-2);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    --bs-btn-hover-color: var(--psm-brand-600);
    --bs-btn-hover-bg: var(--psm-brand-50);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-color: var(--psm-brand-700);
    --bs-btn-active-bg: var(--psm-brand-100);
    --bs-btn-active-border-color: transparent;
    --bs-btn-focus-shadow-rgb: var(--psm-brand-rgb);
}

.btn-ghost.btn-ghost-danger {
    --bs-btn-hover-color: var(--psm-coral-600);
    --bs-btn-hover-bg: var(--psm-coral-50);
    --bs-btn-active-color: var(--psm-coral-700);
    --bs-btn-active-bg: var(--psm-coral-100);
}

/* Square icon button. `.btn-icon` is used across the existing views but was
   never defined — it silently did nothing until now. */
.btn-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
}

.btn-icon:not(.btn-has-label) {
    width: calc(1.5em + var(--bs-btn-padding-y) * 2 + 2px);
    padding-inline: 0;
}

.btn-icon.btn-sm:not(.btn-has-label) { width: 2rem; height: 2rem; }

.btn i { vertical-align: middle; }

/* --------------------------------------------------------------------------
   6. Toolbar + row actions
   -------------------------------------------------------------------------- */

/* Replaces the "wall of buttons". Primary action first, one or two supporting
   outline buttons, everything else behind "المزيد". */
.toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin-bottom: 1rem;
}

.toolbar .toolbar-sep {
    width: 1px;
    height: 1.5rem;
    background: var(--psm-border);
    margin-inline: .25rem;
}

.toolbar-spacer { flex: 1 1 auto; }

/* Grouped ghost buttons in a table row: a single quiet cluster, not a line of
   coloured squares. */
.row-actions {
    display: inline-flex;
    align-items: center;
    gap: .125rem;
    white-space: nowrap;
}

.row-actions .btn {
    --bs-btn-padding-x: .5rem;
    --bs-btn-padding-y: .3125rem;
    --bs-btn-font-size: .875rem;
    --bs-btn-border-radius: .5rem;
}

.row-actions .dropdown-toggle::after { display: none; }

/* A row action that is drawn but has nothing behind it — greyed, not clickable, and still
   hoverable and focusable, because the tooltip saying *why* is the only reason to draw it at
   all rather than leave a gap in the row.

   ⚠ Deliberately **not** Bootstrap's own `.btn.disabled`, and this is the trap: that class sets
     `pointer-events: none`, which suppresses the tooltip and the not-allowed cursor along with
     the click. A button using it ends up explaining nothing, which is worse than no button —
     the operator is left guessing whether the page is broken. Carry the semantics with
     `aria-disabled="true"` on the element instead, and keep it in the tab order so a keyboard
     user can reach the explanation too.

   ⚠ Do not swap the colour for `.text-muted`. That token is `--psm-muted` (#6b7280), which is
     *darker* than `.btn-ghost`'s resting `--psm-muted-2` (#98a1af) — an unavailable action would
     render more prominent than the live ones beside it. */
.row-actions .btn.is-unavailable {
    --bs-btn-hover-color: var(--psm-muted-2);
    --bs-btn-hover-bg: transparent;
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-color: var(--psm-muted-2);
    --bs-btn-active-bg: transparent;
    --bs-btn-active-border-color: transparent;
    --bs-btn-focus-shadow-rgb: var(--psm-brand-rgb);
    color: var(--psm-muted-2);
    cursor: not-allowed;
}

/* Both opacities are scoped under `.table` on purpose: the ghost-row rules further down are
   (0,3,2) at rest and (0,4,2) on hover, so a bare `.row-actions .btn.is-unavailable` would lose
   to them and the greying would only appear while the row was hovered. */
.table tbody tr .row-actions .btn.is-unavailable { opacity: .35; }
.table tbody tr:hover .row-actions .btn.is-unavailable,
.table tbody tr:focus-within .row-actions .btn.is-unavailable { opacity: .5; }

/* Contextual bar that appears once rows are ticked. */
.selection-bar {
    display: none;
    align-items: center;
    flex-wrap: wrap;
    gap: .5rem;
    padding: .625rem .875rem;
    margin-bottom: 1rem;
    border-radius: var(--psm-radius-sm);
    background: var(--psm-brand-50);
    border: 1px solid var(--psm-brand-100);
    color: var(--psm-brand-700);
    font-size: .875rem;
}

.selection-bar.is-active { display: flex; }

/* --------------------------------------------------------------------------
   7. Tables
   -------------------------------------------------------------------------- */

/* Rounded, borderless container that clips the table's own corners. */
.table-card {
    background: var(--psm-surface);
    border: 1px solid var(--psm-border);
    border-radius: var(--psm-radius);
    box-shadow: var(--psm-shadow);
    overflow: hidden;
}

.table {
    --bs-table-color: var(--psm-ink);
    --bs-table-bg: transparent;
    --bs-table-border-color: var(--psm-border);
    --bs-table-hover-bg: var(--psm-surface-sunk);
    --bs-table-hover-color: var(--psm-ink);
    margin-bottom: 0;
    border-color: var(--psm-border) !important;
    vertical-align: middle;
}

.table > :not(caption) > * > * {
    padding: .875rem 1rem;                          /* ≈ py-3 */
    border-bottom-width: 1px;
    box-shadow: none;
}

.table > thead > tr > th,
table.dataTable > thead > tr > th,
table th {
    background-color: var(--psm-surface-sunk) !important;
    color: var(--psm-muted) !important;
    font-size: .75rem !important;
    font-weight: 600;
    text-align: start;
    white-space: nowrap;
    border-bottom: 1px solid var(--psm-border) !important;
    border-top: 0 !important;
}

/* Kill the vertical grid — horizontal hairlines only. */
.table > :not(caption) > * > td,
.table > :not(caption) > * > th {
    border-inline: 0 !important;
}

.table > tbody > tr:last-child > td { border-bottom: 0; }

.table tbody tr { transition: background-color .12s ease; }

.table tbody tr:hover > * {
    background-color: var(--psm-surface-sunk);
    --bs-table-accent-bg: var(--psm-surface-sunk);
}

/* A row plus its explanation line are ONE record, not two.

   `.has-note` is the row, `.row-note` the line under it (أجهزة البصمة uses the pair for the reason a
   device is offline). Two things make them read as one: the hairline moves to *after* the note
   instead of being drawn between them, and hovering either lights both — otherwise a device is two
   separate stripes that highlight independently, which is exactly what makes the note look like a
   stray row.

   The note is a plain <tr> because the message is a sentence and the status column is one word wide;
   putting it in the cell would stretch the column instead. */
.table tbody tr.has-note > * { border-bottom-width: 0; }

.table tbody tr.has-note:hover > *,
.table tbody tr.row-note:hover > *,
.table tbody tr.has-note:hover + tr.row-note > * {
    background-color: var(--psm-surface-sunk);
    --bs-table-accent-bg: var(--psm-surface-sunk);
}

/* ⚠ `:has()` stays in rules of its OWN, never appended to the selector lists above. One
   unparseable selector invalidates the whole list, so folding it in would take the plain
   `:hover` rules down with it on any engine that lacks `:has()` — the note row would stop
   highlighting at all rather than merely stop highlighting its partner. This is the only
   direction that needs it: CSS has no previous-sibling combinator. */
.table tbody tr.has-note:has(+ tr.row-note:hover) > * {
    background-color: var(--psm-surface-sunk);
    --bs-table-accent-bg: var(--psm-surface-sunk);
}

/* Ghost row buttons stay invisible until the row is hovered or focused —
   removes the visual noise of six icons on every row at rest. */
.table tbody tr .row-actions .btn { opacity: .55; }
.table tbody tr:hover .row-actions .btn,
.table tbody tr:focus-within .row-actions .btn,
.table tbody tr .row-actions .btn.show { opacity: 1; }

/* Hovering the note keeps its device's buttons up. Its own rule for the reason above —
   folded into the list, a missing `:has()` would hide every row's buttons on hover. */
.table tbody tr.has-note:has(+ tr.row-note:hover) .row-actions .btn { opacity: 1; }

@media (hover: none) {
    .table tbody tr .row-actions .btn { opacity: 1; }
}

.table .cell-primary {
    color: var(--psm-ink);
    font-weight: 500;
}

.table .cell-meta {
    color: var(--psm-muted);
    font-size: .8438rem;
}

/* Status pills for table cells. */
.pill {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .1875rem .625rem;
    border-radius: 99px;
    font-size: .75rem;
    font-weight: 500;
    line-height: 1.5;
    white-space: nowrap;
}

.pill-neutral { background: var(--psm-surface-sunk); color: var(--psm-muted); }
.pill-success { background: var(--psm-sage-50);  color: var(--psm-sage-700); }
.pill-danger  { background: var(--psm-coral-50); color: var(--psm-coral-700); }
.pill-warning { background: var(--psm-amber-50); color: #8a6318; }
.pill-info    { background: var(--psm-teal-50);  color: var(--psm-teal-600); }
.pill-brand   { background: var(--psm-brand-50); color: var(--psm-brand-700); }

.badge {
    --bs-badge-font-weight: 500;
    --bs-badge-border-radius: 99px;
    --bs-badge-padding-x: .625rem;
    --bs-badge-padding-y: .3em;
}

/* Empty state */
.table-empty {
    padding: 3rem 1rem;
    text-align: center;
    color: var(--psm-muted);
}

.table-empty i {
    font-size: 2rem;
    color: var(--psm-border-strong);
    display: block;
    margin-bottom: .5rem;
}

/* --------------------------------------------------------------------------
   8. DataTables 1.13 — make its chrome look like native Bootstrap 5
   -------------------------------------------------------------------------- */

.dataTables_wrapper {
    padding: 0;
    color: var(--psm-ink-soft);
}

/* Search box → .form-control. DataTables renders `<label>text<input></label>`,
   so the label is turned into a flex row and the input given real styling. */
.dataTables_wrapper .dataTables_filter { margin-bottom: 1rem; }

.dataTables_wrapper .dataTables_filter label {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin: 0;
    font-size: .875rem;
    color: var(--psm-muted);
    width: 100%;
    max-width: 22rem;
}

.dataTables_wrapper .dataTables_filter input {
    flex: 1 1 auto;
    margin: 0 !important;
    padding: .5rem .875rem;
    font-size: .9063rem;
    color: var(--psm-ink);
    background-color: #fff;
    border: 1px solid var(--psm-border-strong);
    border-radius: .625rem;
    transition: border-color .15s ease, box-shadow .15s ease;
}

.dataTables_wrapper .dataTables_filter input:focus {
    outline: 0;
    border-color: var(--psm-brand-400);
    box-shadow: var(--psm-shadow-ring);
}

/* Length menu → .form-select (chevron drawn inline so no asset is needed). */
.dataTables_wrapper .dataTables_length {
    font-size: .875rem;
    color: var(--psm-muted);
}

.dataTables_wrapper .dataTables_length label {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin: 0;
}

.dataTables_wrapper .dataTables_length select {
    appearance: none;
    -webkit-appearance: none;
    padding: .3125rem 2rem .3125rem .75rem;
    font-size: .8438rem;
    color: var(--psm-ink-soft);
    background-color: #fff;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
    background-repeat: no-repeat;
    background-position: left .625rem center;
    background-size: 14px 11px;
    border: 1px solid var(--psm-border-strong);
    border-radius: .5rem;
    cursor: pointer;
}

[dir="ltr"] .dataTables_wrapper .dataTables_length select {
    padding: .3125rem .75rem .3125rem 2rem;
    background-position: right .625rem center;
}

.dataTables_wrapper .dataTables_length select:focus {
    outline: 0;
    border-color: var(--psm-brand-400);
    box-shadow: var(--psm-shadow-ring);
}

.dataTables_wrapper .dataTables_info {
    font-size: .8438rem;
    color: var(--psm-muted);
    padding-top: 0 !important;
}

/* Pagination → Bootstrap-ish outline buttons.
   site.js adds `.btn.btn-sm` to `.paginate_button` on initComplete, so these
   rules deliberately out-specify .btn's own variables.

   `.psm-pager` rides on the same rules: a grid painted in JS (Bio/Match) has no
   DataTables wrapper to inherit them, and a second pager that looked different
   from every other pager on the site would be the only tell that the table is
   hand-rendered. Markup is `<div class="psm-pager"><button>…` — see
   docs/conventions.md#ui-and-styling. */
.psm-pager,
.dataTables_wrapper .dataTables_paginate {
    display: inline-flex;
    align-items: center;
    gap: .375rem;
    padding: 0;
}

.psm-pager > button,
.dataTables_wrapper .dataTables_paginate .paginate_button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2.125rem;
    height: 2.125rem;
    padding: 0 .75rem !important;
    margin: 0 !important;
    font-size: .8438rem !important;
    font-weight: 500;
    line-height: 1;
    color: var(--psm-ink-soft) !important;
    background: #fff !important;
    border: 1px solid var(--psm-border-strong) !important;
    border-radius: .5rem !important;
    box-shadow: none !important;
    cursor: pointer;
    transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}

.psm-pager > button:not(:disabled):not(.is-current):hover,
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
    color: var(--psm-brand-700) !important;
    background: var(--psm-brand-50) !important;
    border-color: var(--psm-brand-200) !important;
}

.psm-pager > button.is-current,
.dataTables_wrapper .dataTables_paginate .paginate_button.current,
.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
    color: #fff !important;
    background: var(--psm-brand-600) !important;
    border-color: var(--psm-brand-600) !important;
}

.psm-pager > button:disabled,
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover {
    color: var(--psm-muted-2) !important;
    background: #fff !important;
    border-color: var(--psm-border) !important;
    cursor: default;
    opacity: .65;
}

/* Header sort arrows: DataTables pins them to the physical right. Flip for RTL
   and give the label room. */
table.dataTable thead th {
    position: relative;
    background-position: center left .5rem !important;
}

[dir="ltr"] table.dataTable thead th {
    background-position: center right .5rem !important;
}

table.dataTable thead th.sorting,
table.dataTable thead th.sorting_asc,
table.dataTable thead th.sorting_desc { padding-inline-end: 1.75rem !important; }

table.dataTable thead th:active { outline: none; }

/* Scrolling tables: DataTables splits head/body into two tables and adds its
   own borders — remove them so the container's radius reads cleanly. */
.dataTables_scrollHead,
.dataTables_scrollBody,
.dataTables_scrollFoot { border: 0 !important; }

.dataTables_scrollBody { border-bottom: 0 !important; }

table.dataTable.no-footer { border-bottom: 0 !important; }

.dataTables_wrapper .dataTable td { padding: .875rem 1rem; }
.dataTables_wrapper .dataTable th { padding: .75rem 1rem; }

.dataTables_empty {
    padding: 3rem 1rem !important;
    color: var(--psm-muted);
    font-size: .9063rem;
}

/* Processing overlay — a small floating card instead of a bare white slab. */
div.dataTables_wrapper div.dataTables_processing {
    position: fixed;
    top: 50%;
    inset-inline-start: 50%;
    transform: translate(-50%, -50%);
    margin: 0;
    width: auto;
    min-width: 9rem;
    padding: .875rem 1.5rem;
    font-size: .875rem;
    color: var(--psm-ink-soft);
    background-color: #fff;
    border: 1px solid var(--psm-border);
    border-radius: var(--psm-radius-sm);
    box-shadow: var(--psm-shadow-lg);
    z-index: 1080;
}

/* --------------------------------------------------------------------------
   9. Forms
   -------------------------------------------------------------------------- */

.form-label,
.form-label-modern {
    display: block;
    margin-bottom: .375rem;
    font-size: .8438rem;
    font-weight: 600;
    color: var(--psm-ink);
}

.form-label .req,
.form-label-modern .req { color: var(--psm-coral-600); font-weight: 400; }

.form-control,
.form-select {
    padding: .625rem .875rem;
    font-size: .9375rem;
    color: var(--psm-ink);
    background-color: #fff;
    border: 1px solid var(--psm-border-strong);
    border-radius: .625rem;
    box-shadow: none;
    transition: border-color .15s ease, box-shadow .15s ease;
}

.form-control-lg,
.form-select-lg { padding: .75rem 1rem; font-size: 1rem; border-radius: .75rem; }

.form-control-sm,
.form-select-sm { padding: .375rem .625rem; font-size: .8438rem; border-radius: .5rem; }

.form-control:hover:not(:disabled):not([readonly]),
.form-select:hover:not(:disabled) { border-color: #cfd5df; }

/* site.css sets `box-shadow:none` on :focus — restore a real focus ring. */
.form-control:focus,
.form-select:focus {
    border-color: var(--psm-brand-400) !important;
    box-shadow: var(--psm-shadow-ring) !important;
}

.form-control::placeholder { color: var(--psm-muted-2); opacity: 1; }

.form-control:disabled,
.form-control[readonly],
.form-select:disabled {
    background-color: var(--psm-surface-sunk);
    color: var(--psm-muted);
    border-color: var(--psm-border);
}

textarea.form-control { min-height: 5.5rem; line-height: 1.6; }

.form-text { font-size: .8125rem; color: var(--psm-muted); }

.form-check-input {
    width: 1.075rem;
    height: 1.075rem;
    margin-top: .2rem;
    border-color: #c8cfda;
    border-radius: .3125rem;
    cursor: pointer;
}

.form-check-input:checked {
    background-color: var(--psm-brand-600);
    border-color: var(--psm-brand-600);
}

.form-check-input:focus {
    border-color: var(--psm-brand-400);
    box-shadow: var(--psm-shadow-ring);
}

.input-group-text {
    background-color: var(--psm-surface-sunk);
    border-color: var(--psm-border-strong);
    color: var(--psm-muted);
    border-radius: .625rem;
}

/* Filter strip above a grid: sunk panel, so it reads as a control surface
   rather than as content.

   `.search-box` is styled identically on purpose. It is the class already on the
   filter row of 33 views, so adopting it applies the panel everywhere without
   restructuring markup — new views should use `.filter-bar`. */
.filter-bar,
.search-box {
    padding: 1rem 1.25rem;
    margin-bottom: 1.25rem;
    background: var(--psm-surface-sunk);
    border: 1px solid var(--psm-border);
    border-radius: var(--psm-radius-sm);
}

/* Most of those rows are `.search-box.row`; Bootstrap's negative row gutters
   would eat the panel's padding. */
.search-box.row {
    --bs-gutter-x: 1rem;
    margin-inline: 0;
}

.search-box.row > * { padding-inline: .5rem; }

/* Belt and braces: if a view wraps its `.search-box` in an explicit
   `.filter-bar`, the inner one must not draw a second panel. */
.filter-bar .search-box {
    padding: 0;
    margin: 0;
    background: transparent;
    border: 0;
    border-radius: 0;
}

.filter-bar .form-control,
.filter-bar .form-select,
.search-box .form-control,
.search-box .form-select { background-color: #fff; }

/* These rows put controls in a bare `.col-*` with no label; the buttons need to
   line up with the inputs beside them. */
.search-box .btn { white-space: nowrap; }

/* --------------------------------------------------------------------------
   10. Modals
   -------------------------------------------------------------------------- */

.modal-content {
    border: 0;
    border-radius: var(--psm-radius-lg);
    box-shadow: 0 1.5rem 3rem rgba(31, 36, 48, .18);
    overflow: hidden;
}

.modal-header {
    padding: 1.25rem 1.5rem;
    background-color: #fff !important;   /* site.css paints it #f0f0f0 */
    border-bottom: 1px solid var(--psm-border);
    align-items: flex-start;
}

.modal-title {
    font-size: 1.0625rem;
    font-weight: 600;
    color: var(--psm-ink);
    line-height: 1.4;
}

.modal-subtitle {
    display: block;
    margin-top: .125rem;
    font-size: .8125rem;
    font-weight: 400;
    color: var(--psm-muted);
}

.modal-body { padding: 1.5rem; }

.modal-footer {
    padding: 1rem 1.5rem;
    background-color: var(--psm-surface-sunk);
    border-top: 1px solid var(--psm-border);
    gap: .5rem;
}

.modal-footer > * { margin: 0; }

.modal-backdrop.show { opacity: .4; }

.btn-close:focus { box-shadow: var(--psm-shadow-ring); }

/* Section header inside a modal — groups related fields without adding boxes. */
.form-section {
    margin-bottom: 1.5rem;
}

.form-section:last-child { margin-bottom: 0; }

.form-section-title {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-bottom: 1rem;
    padding-bottom: .5rem;
    border-bottom: 1px solid var(--psm-border);
    font-size: .8125rem;
    font-weight: 600;
    color: var(--psm-muted);
}

.form-section-title i { color: var(--psm-brand-500); font-size: .875rem; }

/* Read-only summary line, e.g. "12 students selected". */
.form-summary {
    display: flex;
    align-items: center;
    gap: .625rem;
    padding: .75rem 1rem;
    margin-bottom: 1.25rem;
    background: var(--psm-brand-50);
    border: 1px solid var(--psm-brand-100);
    border-radius: var(--psm-radius-sm);
    color: var(--psm-brand-700);
    font-size: .875rem;
}

.form-summary i { font-size: 1.0625rem; }

/* --------------------------------------------------------------------------
   11. Dropdowns, tabs, alerts, tooltips
   -------------------------------------------------------------------------- */

.dropdown-menu {
    --bs-dropdown-border-color: var(--psm-border);
    --bs-dropdown-border-radius: var(--psm-radius-sm);
    --bs-dropdown-link-hover-bg: var(--psm-surface-sunk);
    --bs-dropdown-link-active-bg: var(--psm-brand-50);
    --bs-dropdown-link-active-color: var(--psm-brand-700);
    --bs-dropdown-item-padding-x: .875rem;
    --bs-dropdown-item-padding-y: .5rem;
    --bs-dropdown-font-size: .875rem;
    padding: .375rem;
    box-shadow: 0 .5rem 1.5rem rgba(31, 36, 48, .12);
}

.dropdown-item {
    display: flex;
    align-items: center;
    gap: .625rem;
    border-radius: .375rem !important;
    color: var(--psm-ink-soft);
}

.dropdown-item i { color: var(--psm-muted-2); font-size: .9375rem; width: 1rem; }

.dropdown-item:hover i { color: var(--psm-ink-soft); }

.dropdown-item.text-danger,
.dropdown-item.dropdown-item-danger { color: var(--psm-coral-600) !important; }

.dropdown-item.dropdown-item-danger i { color: var(--psm-coral-500); }

.dropdown-item.dropdown-item-danger:hover {
    background-color: var(--psm-coral-50);
    color: var(--psm-coral-700) !important;
}

.dropdown-item.dropdown-item-danger:hover i { color: var(--psm-coral-600); }

.dropdown-header {
    font-size: .75rem;
    font-weight: 600;
    color: var(--psm-muted-2);
    padding: .5rem .875rem .25rem;
}

.dropdown-divider { border-color: var(--psm-border); margin: .375rem 0; }

/* Menu detached to <body> by theme.js to escape a DataTables scroll container. */
.dropdown-menu.psm-floating { position: fixed; z-index: 1065; margin: 0; }

.nav-tabs {
    --bs-nav-tabs-border-color: var(--psm-border);
    --bs-nav-tabs-link-active-color: var(--psm-brand-700);
    --bs-nav-tabs-link-active-border-color: transparent transparent var(--psm-brand-600);
    --bs-nav-tabs-link-active-bg: transparent;
    gap: .25rem;
}

.nav-tabs .nav-link {
    border-width: 0 0 2px;
    border-radius: 0;
    padding: .625rem 1rem;
    color: var(--psm-muted);
    font-weight: 500;
}

.nav-tabs .nav-link:hover {
    border-color: transparent transparent var(--psm-border-strong);
    color: var(--psm-ink);
    background: transparent !important;
}

.nav-tabs .nav-link.active {
    background: transparent !important;
    color: var(--psm-brand-700) !important;
    font-weight: 600;
}

.alert {
    --bs-alert-border-radius: var(--psm-radius-sm);
    border: 1px solid transparent;
    padding: .875rem 1rem;
    font-size: .9063rem;
}

.alert-primary { background: var(--psm-brand-50);  border-color: var(--psm-brand-100);  color: var(--psm-brand-700); }
.alert-success { background: var(--psm-sage-50);   border-color: var(--psm-sage-100);   color: var(--psm-sage-700); }
.alert-danger  { background: var(--psm-coral-50);  border-color: var(--psm-coral-100);  color: var(--psm-coral-700); }
.alert-warning { background: var(--psm-amber-50);  border-color: #f2e2c2;               color: #8a6318; }
.alert-info    { background: var(--psm-teal-50);   border-color: #cfe9e6;               color: var(--psm-teal-600); }
.alert-secondary { background: var(--psm-surface-sunk); border-color: var(--psm-border); color: var(--psm-muted); }

/* ⚠ Set the Bootstrap variable, not just `.tooltip-inner`'s background. Bootstrap colours each
   of the four arrow directions from `--bs-tooltip-bg`, so styling only the bubble leaves the
   arrow black on any tooltip Popper decides to flip. That went unnoticed while nothing on the
   site used tooltips; the first real user is the `.is-unavailable` row action above, and a grid
   row near the top of the viewport flips to `right` routinely. */
.tooltip { --bs-tooltip-bg: var(--psm-ink); }

.tooltip-inner {
    border-radius: .375rem;
    font-size: .75rem;
    padding: .375rem .625rem;
}

.offcanvas { border: 0; }
.offcanvas-header { border-bottom: 1px solid var(--psm-border); padding: 1.125rem 1.25rem; }

.spinner-border { color: var(--psm-brand-600); }

.loading-spinner .spanner { background: rgba(31, 36, 48, .28); }

/* --------------------------------------------------------------------------
   12. SweetAlert2 — same type, same buttons, no oversized icons
   -------------------------------------------------------------------------- */

/* ⚠ A toast is also a `.swal2-popup` — every dialog rule below that sets a
   size, a padding or a margin has to exclude `.swal2-toast`, or the corner
   toast inflates into a dialog-sized card with a centred 44px icon. Colours
   and fonts are safe to share; geometry is not. */

.swal2-popup {
    border-radius: var(--psm-radius-lg) !important;
    font-family: var(--bs-font-sans-serif) !important;
    box-shadow: 0 1.5rem 3rem rgba(31, 36, 48, .2) !important;
}

.swal2-popup:not(.swal2-toast) { padding: 1.75rem 1.5rem 1.25rem !important; }

/* The dim backdrop belongs to modal dialogs only. SweetAlert2 clears it for
   toasts via `body.swal2-toast-shown .swal2-container`, which an unscoped
   `!important` here would beat — leaving a grey block over the corner. */
body:not(.swal2-toast-shown) .swal2-container.swal2-backdrop-show {
    background: rgba(31, 36, 48, .4) !important;
}

.swal2-title {
    font-weight: 600 !important;
    color: var(--psm-ink) !important;
}

.swal2-popup:not(.swal2-toast) .swal2-title {
    font-size: 1.125rem !important;
    padding: 0 !important;
}

.swal2-html-container {
    color: var(--psm-ink-soft) !important;
}

.swal2-popup:not(.swal2-toast) .swal2-html-container {
    font-size: .9375rem !important;
    margin: .5rem 0 0 !important;
    line-height: 1.65 !important;
}

/* Small, quiet icon instead of the default 5rem animated ring. */
/* ⚠ Resize the icon by scaling its FONT, never by pinning width/height.
   SweetAlert2 draws the success tick and the error X as absolutely positioned
   bars whose offsets and lengths are all in `em` against a 5em box — the tick's
   long bar alone is 2.9375em starting at 2.375em. Pinning the box to 2.75rem
   while the font stayed at 1rem left that geometry sized for an 80px icon
   inside a 44px circle, so the tick hung out of the bottom of the ring and read
   as a crossed-out circle instead of a checkmark.
   5em x .55rem = the same 2.75rem, with the internals scaled to match. */
.swal2-popup:not(.swal2-toast) .swal2-icon {
    font-size: .55rem !important;
    width: 5em !important;
    height: 5em !important;
    margin: 0 auto .875rem !important;
    border-width: 2px !important;
}

.swal2-icon { border-width: 2px !important; }

.swal2-popup:not(.swal2-toast) .swal2-icon .swal2-icon-content { font-size: 1.5rem !important; }
.swal2-icon.swal2-success .swal2-success-ring { border-color: var(--psm-sage-500) !important; }
.swal2-icon.swal2-success [class^='swal2-success-line'] { background-color: var(--psm-sage-500) !important; }
.swal2-icon.swal2-success { border-color: var(--psm-sage-500) !important; color: var(--psm-sage-500) !important; }
.swal2-icon.swal2-error { border-color: var(--psm-coral-500) !important; color: var(--psm-coral-500) !important; }
.swal2-icon.swal2-error [class^='swal2-x-mark-line'] { background-color: var(--psm-coral-500) !important; }
.swal2-icon.swal2-warning { border-color: var(--psm-amber-500) !important; color: var(--psm-amber-500) !important; }
.swal2-icon.swal2-info,
.swal2-icon.swal2-question { border-color: var(--psm-brand-400) !important; color: var(--psm-brand-500) !important; }

.swal2-popup:not(.swal2-toast) .swal2-actions { margin-top: 1.5rem !important; gap: .5rem; }

/* theme.js sets buttonsStyling:false and hands these the .btn classes, so the
   only job left here is spacing. Fallback colours stay in case a call site
   opts back into SweetAlert2's own styling. */
.swal2-styled.swal2-confirm {
    background-color: var(--psm-brand-600) !important;
    border-radius: .625rem !important;
    font-size: .9063rem !important;
    font-weight: 500 !important;
    padding: .5rem 1.25rem !important;
    box-shadow: none !important;
}

.swal2-styled.swal2-cancel {
    background-color: #fff !important;
    color: var(--psm-ink-soft) !important;
    border: 1px solid var(--psm-border-strong) !important;
    border-radius: .625rem !important;
    font-size: .9063rem !important;
    font-weight: 500 !important;
    padding: .5rem 1.25rem !important;
}

.swal2-styled:focus { box-shadow: var(--psm-shadow-ring) !important; }

.swal2-input, .swal2-textarea, .swal2-select {
    border: 1px solid var(--psm-border-strong) !important;
    border-radius: .625rem !important;
    box-shadow: none !important;
    font-size: .9375rem !important;
    font-family: var(--bs-font-sans-serif) !important;
}

.swal2-input:focus, .swal2-textarea:focus, .swal2-select:focus {
    border-color: var(--psm-brand-400) !important;
    box-shadow: var(--psm-shadow-ring) !important;
}

/* Applied by theme.js to confirm dialogs whose action is destructive. */
.swal2-popup.psm-swal-danger .swal2-styled.swal2-confirm {
    background-color: var(--psm-coral-600) !important;
}

/* psmToast() — a compact corner strip, not a dialog. SweetAlert2's own toast
   geometry is left alone; this only sets the card's chrome and type. */
.swal2-popup.swal2-toast {
    padding: .75rem 1rem !important;
    border-radius: var(--psm-radius) !important;
    border: 1px solid var(--psm-border) !important;
    box-shadow: var(--psm-shadow-lg) !important;
    max-width: min(22rem, calc(100vw - 2rem)) !important;
}

/* SweetAlert2 spaces the toast icon with a physical `margin-right`, which in
   RTL puts the gap against the card edge and leaves the title touching the
   icon. Logical margin puts it back between the two. */
.swal2-popup.swal2-toast .swal2-icon {
    margin: 0 !important;
    margin-inline-end: .625rem !important;
}

.swal2-popup.swal2-toast .swal2-title {
    font-size: .9375rem !important;
    margin: 0 !important;
    line-height: 1.5 !important;
}

.swal2-popup.swal2-toast .swal2-timer-progress-bar {
    background: var(--psm-brand-400) !important;
}

/* --------------------------------------------------------------------------
   13. Noty (v2, bootstrapTheme)
   -------------------------------------------------------------------------- */

.noty_bar {
    border-radius: var(--psm-radius-sm) !important;
    box-shadow: var(--psm-shadow-lg) !important;
    font-family: var(--bs-font-sans-serif) !important;
    overflow: hidden;
}

.noty_bar .noty_message,
.noty_bar .alert {
    border: 1px solid var(--psm-border) !important;
    border-radius: var(--psm-radius-sm) !important;
    padding: .875rem 1rem !important;
    font-size: .9063rem !important;
    margin: 0 !important;
    text-align: start;
}

.noty_bar .alert-success { background: var(--psm-sage-50) !important;  border-color: var(--psm-sage-100) !important;  color: var(--psm-sage-700) !important; }
.noty_bar .alert-error,
.noty_bar .alert-danger  { background: var(--psm-coral-50) !important; border-color: var(--psm-coral-100) !important; color: var(--psm-coral-700) !important; }
.noty_bar .alert-warning { background: var(--psm-amber-50) !important; border-color: #f2e2c2 !important;              color: #8a6318 !important; }
.noty_bar .alert-info    { background: var(--psm-teal-50) !important;  border-color: #cfe9e6 !important;              color: var(--psm-teal-600) !important; }

.noty_bar .noty_close { opacity: .5; }
.noty_bar .noty_close:hover { opacity: 1; }

/* --------------------------------------------------------------------------
   14. Hijri datetimepicker (Bootstrap 3 widget shipped in wwwroot)
   -------------------------------------------------------------------------- */

.bootstrap-datetimepicker-widget.dropdown-menu {
    padding: .625rem;
    border: 1px solid var(--psm-border);
    border-radius: var(--psm-radius-sm);
    box-shadow: 0 .5rem 1.5rem rgba(31, 36, 48, .12);
}

.bootstrap-datetimepicker-widget table td,
.bootstrap-datetimepicker-widget table th { border-radius: .375rem; }

.bootstrap-datetimepicker-widget table td.day:hover,
.bootstrap-datetimepicker-widget table td.hour:hover,
.bootstrap-datetimepicker-widget table td.minute:hover { background: var(--psm-brand-50); }

.bootstrap-datetimepicker-widget table td.active,
.bootstrap-datetimepicker-widget table td.active:hover,
.bootstrap-datetimepicker-widget table td span.active {
    background-color: var(--psm-brand-600) !important;
    color: #fff !important;
    text-shadow: none !important;
}

.bootstrap-datetimepicker-widget table td.today::before { border-bottom-color: var(--psm-brand-600) !important; }

.bootstrap-datetimepicker-widget table th { background-color: transparent !important; color: var(--psm-muted) !important; }

/* --------------------------------------------------------------------------
   15. المراسلات — the correspondence transcript (/threads, /admin/threads)

   Bubbles are aligned with `align-self`, never with a float or a physical
   margin, so the same markup reads correctly in the RTL app and in the LTR
   print views. `.is-mine` is whichever side is looking — the same post is
   "mine" in the control panel and "theirs" in the school.
   -------------------------------------------------------------------------- */

.thread-transcript {
    display: flex;
    flex-direction: column;
    gap: .75rem;
}

.thread-post {
    display: flex;
    flex-direction: column;
    max-width: min(46rem, 88%);
    align-self: flex-start;
}

.thread-post.is-mine { align-self: flex-end; }

.thread-bubble {
    background: var(--psm-surface-sunk);
    border: 1px solid var(--psm-border);
    border-radius: .75rem;
    padding: .625rem .875rem;
}

.thread-post.is-mine .thread-bubble {
    background: var(--psm-brand-50);
    border-color: var(--psm-brand-100);
}

.thread-bubble-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .5rem;
    margin-bottom: .25rem;
    font-size: .8125rem;
    color: var(--psm-muted);
}

.thread-bubble-head .thread-sender {
    font-weight: 600;
    color: var(--psm-ink);
}

/* pre-wrap, so the paragraphs a school typed survive. The body is escaped in
   JS and Razor before it gets here — never render it as HTML. */
.thread-bubble-body {
    white-space: pre-wrap;
    word-break: break-word;
    color: var(--psm-ink-soft);
}

/* Read receipt, under your own posts only. It reports whether the OTHER SIDE has
   opened the thread since you wrote — per side, not per user, the same model the
   two unread counters use. Quiet by default and brand-coloured once seen; there
   is no red "unread" state, because not-yet-read is the normal case.

   align-self: flex-end matches .thread-post.is-mine, so the receipt sits against
   the same edge as the bubble it belongs to under both directions. */
.thread-receipt {
    display: flex;
    align-items: center;
    gap: .25rem;
    align-self: flex-end;
    margin-top: .25rem;
    padding-inline: .25rem;
    font-size: .75rem;
    color: var(--psm-muted);
}

.thread-receipt.is-read {
    color: var(--psm-brand-600);
}

/* A generated note ("تم إغلاق المراسلة"). Centred and quiet: it is not a party
   to the conversation and must not look like one. */
.thread-post.is-system {
    align-self: center;
    max-width: 100%;
    align-items: center;
}

.thread-post.is-system .thread-bubble {
    background: transparent;
    border: 0;
    padding: .25rem .5rem;
    font-size: .8125rem;
    color: var(--psm-muted);
    text-align: center;
}

.thread-composer {
    border-top: 1px solid var(--psm-border);
    padding-top: 1rem;
    margin-top: 1rem;
}

/* Unread rows in the two grids. A dot, not a colour on the row — colour alone
   is not a signal. */
.thread-unread-dot {
    display: inline-block;
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
    background: var(--psm-brand-600);
    margin-inline-end: .375rem;
    vertical-align: middle;
}

/* --------------------------------------------------------------------------
   16. Dashboards — KPI tiles and chart cards (/dashboard, /admin)
   -------------------------------------------------------------------------- */

/* These lived inline in Views/Admin/Dashboard.cshtml until the school dashboard
   became a second consumer. Two copies of a card style is how two dashboards end
   up looking like two products — the same reason the admin one's hard-coded warm
   greys were moved onto the tokens in the first place.

   ⚠ .kpi-card overlaps `.stat-card` in §4, which is the design system's tile and
   is what /bio/devices/{id}/match and ui-kit.html use. The difference is real but
   small: this one carries a third `note` line and trails its icon. The duplication
   is recorded in docs/conventions.md rather than fixed here, because merging them
   reworks the tiles on two live dashboards. Do not add a third tile component.

   The palette is NOT here: series colours are per-chart and stay with the chart,
   as inline styles beside the canvas they belong to. */

.kpi-card {
    height: 100%;
    background: var(--psm-surface);
    border: 1px solid var(--psm-border);
    border-radius: var(--psm-radius);
    box-shadow: var(--psm-shadow);
}

.kpi-label {
    font-size: .8125rem;
    color: var(--psm-muted);
}

.kpi-value {
    font-size: 1.7rem;
    font-weight: 600;
    line-height: 1.2;
    color: var(--psm-ink);
}

.kpi-note {
    font-size: .78rem;
    color: var(--psm-muted-2);
}

.kpi-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    flex: 0 0 42px;
    border-radius: var(--psm-radius-sm);
    font-size: 1.2rem;
}

.chart-card {
    background: var(--psm-surface);
    border: 1px solid var(--psm-border);
    border-radius: var(--psm-radius);
    box-shadow: var(--psm-shadow);
}

.chart-title {
    font-size: 1rem;
    font-weight: 600;
    color: var(--psm-ink);
}

.chart-sub {
    font-size: .78rem;
    color: var(--psm-muted);
}

/* Chart.js needs a sized box to draw into — maintainAspectRatio:false plus a
   positioned parent, or the canvas grows on every resize. */
.chart-box {
    position: relative;
    height: 300px;
}

.chart-box-sm {
    position: relative;
    height: 260px;
}

/* Identity in a legend is never colour alone; this sits beside a label, always. */
.legend-swatch {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 2px;
    margin-inline-end: .25rem;
}

/* --------------------------------------------------------------------------
   17. Print — keep reports clean and direction-independent
   -------------------------------------------------------------------------- */

@media print {
    .app-sidebar, .app-topbar, .toolbar, .filter-bar, .selection-bar,
    .row-actions, .dataTables_paginate, .psm-pager, .dataTables_length, .dataTables_filter,
    .thread-composer { display: none !important; }

    /* A printed transcript is a record, so every post gets its border back and
       nothing is aligned off to one side. */
    .thread-post, .thread-post.is-mine { align-self: stretch; max-width: 100%; }
    .thread-bubble, .thread-post.is-mine .thread-bubble { background: #fff !important; border-color: #ccc !important; }

    .card, .table-card { box-shadow: none !important; border-color: #ccc !important; }

    .app-content { padding: 0 !important; }
}
