:root{
  --primary:#4f46e5;--primary-hover:#4338ca;--navy:#0f172a;
  --bg:#f6f7fb;--surface:#fff;--muted:#64748b;--border:#e2e8f0;
  --text:#172033;--success:#16a34a;--warning:#d97706;--danger:#dc2626;--info:#2563eb;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:var(--bg);color:var(--text);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
button,input,select{font:inherit}
.card{background:#fff;border:1px solid var(--border);border-radius:14px;box-shadow:0 1px 2px rgba(15,23,42,.03)}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;border-radius:9px;padding:9px 14px;font-size:14px;font-weight:600;border:1px solid transparent;transition:.18s ease}
.btn-primary{background:var(--primary);color:#fff}.btn-primary:hover{background:var(--primary-hover)}
.btn-secondary{background:#fff;color:#334155;border-color:var(--border)}.btn-secondary:hover{background:#f8fafc}
.btn-danger{background:#dc2626;color:#fff}
.field{width:100%;border:1px solid #dbe2ea;border-radius:9px;background:#fff;padding:10px 12px;outline:none;transition:.15s}
.field:focus{border-color:#818cf8;box-shadow:0 0 0 3px rgba(99,102,241,.12)}
.badge{display:inline-flex;align-items:center;border-radius:999px;padding:4px 9px;font-size:12px;font-weight:600}
.badge-success{background:#ecfdf3;color:#15803d}.badge-warning{background:#fff7ed;color:#c2410c}
.badge-danger{background:#fef2f2;color:#b91c1c}.badge-info{background:#eff6ff;color:#1d4ed8}
.badge-neutral{background:#f1f5f9;color:#475569}
.table-wrap{overflow-x:auto}.data-table{width:100%;border-collapse:collapse;min-width:720px}
.data-table th{font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:#64748b;text-align:left;background:#f8fafc;padding:12px 16px;border-bottom:1px solid var(--border)}
/* .data-table th's own text-align:left above (class+element) outranks the
   plain .text-right utility (single class) by specificity, so a header
   like <th class="col-sm text-right"> silently stayed left-aligned while
   its <td class="text-right"> correctly went right — every numeric column
   (Semesters, CH, Marks, GPA, etc.) had its header out of step with its
   own data. This rule only needs to win that one tie. */
.data-table th.text-right{text-align:right}
/* A sortable header's content is the flex row inside .sort-th-link (see
   its own display:flex fix above), not inline text — text-align has no
   effect on a flex item's position, so a right-aligned sortable numeric
   column (e.g. Semester) needs the flex row itself pushed to the end. */
.data-table th.text-right a.sort-th-link{justify-content:flex-end}
/* Same pair of fixes as text-right above, for center-aligned short numeric
   columns (Semester, CH, Marks, GPA, etc. — 2026-09-17, direct request to
   switch these from right- to center-aligned for readability at their
   width). */
.data-table th.text-center{text-align:center}
.data-table th.text-center a.sort-th-link{justify-content:center}
.data-table td{padding:14px 16px;border-bottom:1px solid #edf1f5;font-size:14px}.data-table tbody tr:hover{background:#fafbff}
/* Column widths driven by content, not layout (2026-09-17, direct report:
   "column width should be fixed according to nature of columns... not
   consistence UI design") — every .data-table cell wraps long content by
   default (rather than forcing the table wider than its container) so a
   table can safely go `table-fixed` with per-column widths set via the
   .col-* classes below on each <th> (fixed layout takes every column's
   width from the header row, not from whichever cell happens to hold the
   longest content that render). A column with no .col-* class is the
   table's own "identity" column (name/title/subject/etc.) and is meant to
   receive whatever width the fixed ones don't use.
   Usage: <table class="data-table table-fixed"> ... <th class="col-xs">AG
   Number</th> ... and the matching <td> gets col-wrap if its content can
   run long (a name, a title, a note) so it wraps inside its column
   instead of overflowing it. */
.data-table td,.data-table th{overflow-wrap:break-word}
.data-table .col-2xs{width:56px}
.data-table .col-xs{width:88px}
.data-table .col-sm{width:120px}
.data-table .col-md{width:160px}
.data-table .col-lg{width:220px}
.data-table .col-xl{width:280px}
.data-table .col-actions{width:100px}
.data-table .col-actions-wide{width:160px}
/* The un-classed "identity" column (name/title/subject/etc.) needs an
   explicit width too, not just "whatever's left" — table-layout:fixed only
   shares remaining space with auto-width columns when there IS remaining
   space; once the other columns' declared widths already reach the
   table's own width (a real case here: several .col-* columns on a
   narrower viewport), an unclassed column collapses toward zero instead of
   the table simply growing past 100% into .table-wrap's horizontal
   scroll the way it would with table-layout:auto. Giving it a real
   minimum via width+min-width (not just min-width alone, which
   table-fixed ignores for column sizing) keeps it readable and lets the
   table overflow into the scroll wrapper instead. */
.data-table.table-fixed th:not([class*="col-"]){width:240px;min-width:240px}
.data-table .col-wrap{white-space:normal;overflow-wrap:break-word}
/* Scoped to #rows-table (the scheme-review formset, academics/review_scheme_pdf.html)
   only — its cells are a bare <td><input></td> with no width of its own, so the
   input needs to fill the cell. A blanket .data-table td input rule instead hit
   every table-embedded input, including ones like admin_student_list.html's
   password field that already has a real Tailwind width (w-28) inside its own
   inline-flex form — 100% width inside that shrink-to-fit flex container
   collapsed it to a near-invisible box instead of respecting w-28. */
#rows-table td input:not([type=checkbox]):not([type=radio]),#rows-table td select{width:100%;min-width:0}
@media(max-width:640px){.data-table.table-fixed{table-layout:auto}}
/* Column-header sorting (2026-09-16, direct request: click a header to sort
   ascending, click again for descending, small vertical-arrow indicator
   after the column name) — two mechanisms share this styling: server-side
   sort links (components/_sort_th.html, a <a class="sort-th-link">) on
   tables backed by enrollment.list_utils.paginate_and_sort(), and the
   client-side auto-sort in static/js/app.js's initSortableTables() (a
   <th class="sortable-th">) for every other plain data-table. */
.sort-indicator{display:inline-block;margin-left:4px;color:#a3adba;font-size:11px;line-height:1}
.sort-indicator--active{color:var(--primary)}
.data-table th a.sort-th-link{color:inherit;text-decoration:none;display:flex;align-items:center;min-width:0}
.data-table th a.sort-th-link .sort-th-label{min-width:0;overflow-wrap:break-word}
.data-table th a.sort-th-link:hover{color:var(--text)}
.data-table th.sortable-th{cursor:pointer;user-select:none}
.data-table th.sortable-th:hover{color:var(--text)}
.sidebar{transition:transform .22s ease}.overlay{display:none}
/* Dark sidebar scrollbar (2026-09-05, direct request: "The scroll bar of
   sidebar should follow the sidebar design theme") — the default OS
   scrollbar is a pale grey that clashes with the sidebar's bg-slate-950. */
.sidebar-scroll{scrollbar-width:thin;scrollbar-color:#334155 transparent}
.sidebar-scroll::-webkit-scrollbar{width:8px}
.sidebar-scroll::-webkit-scrollbar-track{background:transparent}
.sidebar-scroll::-webkit-scrollbar-thumb{background:#334155;border-radius:999px}
.sidebar-scroll::-webkit-scrollbar-thumb:hover{background:#475569}
/* Sidebar nav category accordion (2026-09-05, direct request: "add the
   expand/hide functionality to all nav categories, so that only one
   category items should be displayed at a time") — see static/js/app.js's
   [data-nav-cat-toggle] handler, which closes every other category before
   opening the clicked one. Initial open/closed state per category is set
   server-side in components/_staff_sidebar.html (matching the page's own
   nav_active), so a page load never needs JS to guess which category the
   current page belongs to. */
.nav-cat-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 12px 6px;background:transparent;border:0;cursor:pointer;color:#64748b;font-size:10px;text-transform:uppercase;letter-spacing:.06em;font-family:inherit}
.nav-cat-toggle:hover{color:#cbd5e1}
.nav-cat-chevron{width:14px;height:14px;transition:transform .18s ease;flex-shrink:0}
.nav-cat--open .nav-cat-chevron{transform:rotate(90deg)}
/* Sub-group labels inside one nav-cat-body (2026-09-17, direct request: the
   Admin category had 17 links in one flat scrolling list — group related
   ones together). Plain text, not another accordion level: nesting a
   second [data-nav-cat] inside this one would make static/js/app.js's
   "opening one category closes every other" handler treat it as a sibling
   of Admin/Faculty/etc. and collapse the parent category itself, which is
   not what grouping within an already-open category should do. */
.nav-subgroup-label{padding:14px 12px 4px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:#475569;font-weight:600}
.nav-subgroup-label:first-child{padding-top:2px}
@media(max-width:1023px){.sidebar{transform:translateX(-100%);position:fixed!important;z-index:50}.sidebar.open{transform:translateX(0)}.overlay.open{display:block;position:fixed;inset:0;background:rgba(15,23,42,.45);z-index:40}}
@media(max-width:640px){.data-table{min-width:640px}.mobile-hide{display:none!important}}
.skeleton{background:linear-gradient(90deg,#eef2f7,#f8fafc,#eef2f7);background-size:200% 100%;animation:shimmer 1.5s infinite}
@keyframes shimmer{to{background-position:-200% 0}}
.toast{animation:slideIn .22s ease}@keyframes slideIn{from{transform:translateY(10px);opacity:0}to{transform:none;opacity:1}}
.public-nav{display:inline-flex;align-items:center;padding:8px 10px;border-radius:8px;color:#475569;transition:.15s}.public-nav:hover{background:#f8fafc;color:#312e81}.public-hero{background:radial-gradient(circle at 80% 20%,rgba(99,102,241,.18),transparent 35%),linear-gradient(135deg,#0f172a,#172554)}
.public-grid{background-image:linear-gradient(rgba(148,163,184,.08) 1px,transparent 1px),linear-gradient(90deg,rgba(148,163,184,.08) 1px,transparent 1px);background-size:32px 32px}
/* FIX (2026-09-03, ported from ui_mvp): .public-hero and .public-grid are
   used together on the hero (class="public-hero public-grid text-white"),
   and both set background-image at equal specificity — so .public-grid,
   coming last, REPLACED the hero's dark gradient with the faint grid
   lines. The result was white hero text on the page's near-white body
   background: invisible. Confirmed in the browser, not guessed.

   The combined rule below restates all four layers in one declaration —
   grid lines on top, then the indigo glow, then the navy base — so the two
   classes compose the way the markup clearly intends. Both classes are
   left alone so either still works on its own. The same bug is in
   ui_mvp/static/css/app.css; this file is now the corrected copy. */
.public-hero.public-grid{
  background-image:
    linear-gradient(rgba(148,163,184,.08) 1px,transparent 1px),
    linear-gradient(90deg,rgba(148,163,184,.08) 1px,transparent 1px),
    radial-gradient(circle at 80% 20%,rgba(99,102,241,.18),transparent 35%),
    linear-gradient(135deg,#0f172a,#172554);
  background-size:32px 32px,32px 32px,auto,auto;
}
/* Admin list-page filter bar + row-actions dropdown (2026-09-12, ported
   from the sos_mvp UI concept): every admin_*_list.html filter <form> was a
   single flex-wrap row mixing the search box in with the dropdowns, so on
   a merely-medium viewport the search box was the first thing to get
   squeezed. Search now sits alone on its own row, full width; every real
   filter field flows into a second row that ends with the Filter submit
   button pinned to the right via margin-left:auto (auto-margin on a flex
   item also drags every sibling still queued after it — here just the
   optional "Clear" link — along to the same edge, so the two stay
   together instead of Filter landing mid-row and Clear trailing off after
   it). `.admin-filter-row .field` gets an explicit white background since
   a `<select>`'s native rendering can otherwise pick up the platform's own
   control color in some browsers/themes. */
.admin-search-row{margin-bottom:12px}
.admin-filter-row{display:flex;flex-wrap:wrap;gap:10px;align-items:end}
.admin-filter-row .field{background:#fff}
.admin-filter-btn{white-space:nowrap}
/* Apply + Clear All as one flex item (2026-09-12 fix): margin-left:auto
   used to sit on the Apply button alone, which pushed IT flush to the
   row's right edge but left Clear All — a separate item right after it —
   to fend for itself; the moment the row ran out of room for both, only
   Clear All wrapped to a second line while Apply stayed put with empty
   space sitting unused in front of it (confirmed in the browser: the gap
   was real, Clear All just wasn't in it). Wrapping both in one element and
   moving the auto-margin here means the pair now wraps (or doesn't)
   together — Clear All can never separate from Apply. */
.admin-filter-actions{margin-left:auto;display:flex;gap:10px;flex-wrap:nowrap}
/* Row-actions dropdown: collapses a row/card's Edit/Delete/etc. links and
   POST forms into one "Actions" menu instead of a wall of buttons per row.
   Deliberately NOT used for the few pages where a row only ever has one
   action (Advisors/Focal Persons' single Deactivate/Reactivate toggle,
   Notifications' single Retract) — a one-item menu is worse than the
   button it would hide. Reset Password/Invite mini-forms (a text input +
   submit, not just a link) stay outside the menu everywhere, unchanged —
   folding a form with its own input field into a dropdown is more
   confusing than useful.
   Positioned via JS as position:fixed (see static/js/app.js's
   initActionMenus) rather than plain CSS absolute, specifically so the
   menu is never clipped by a `.table-wrap`'s `overflow-x:auto` (which,
   per the CSS overflow-x/overflow-y interdependency rule, computes
   overflow-y to auto too the moment only overflow-x is set non-visible —
   confirmed in the browser, not guessed) — a fixed-position element
   escapes an ancestor's overflow clipping entirely. */
.admin-actions{position:relative;display:inline-flex}
.admin-actions-trigger{display:inline-flex;align-items:center;gap:6px;padding:7px 12px;border:1px solid var(--border);border-radius:8px;background:#fff;color:#475569;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;font-family:inherit}
.admin-actions-trigger:hover{background:#f8fafc}
.admin-actions-trigger .chev{font-size:10px;color:#94a3b8;transition:transform .15s}
.admin-actions-trigger[aria-expanded="true"] .chev{transform:rotate(180deg)}
.admin-actions-menu{position:fixed;min-width:180px;background:#fff;border:1px solid var(--border);border-radius:10px;box-shadow:0 12px 28px rgba(15,23,42,.14);padding:6px;z-index:70;opacity:0;visibility:hidden;pointer-events:none;transform:translateY(-4px);transition:opacity .12s ease,transform .12s ease}
.admin-actions-menu.open{opacity:1;visibility:visible;pointer-events:auto;transform:none}
.admin-actions-menu a,.admin-actions-menu button{display:flex;width:100%;align-items:center;gap:8px;padding:8px 10px;border-radius:7px;font-size:13px;color:#334155;text-align:left;background:none;border:none;cursor:pointer;font-family:inherit}
.admin-actions-menu a:hover,.admin-actions-menu button:hover{background:#f1f5f9}
.admin-actions-menu .danger{color:#dc2626}.admin-actions-menu .danger:hover{background:#fef2f2}
.admin-actions-menu hr{border:none;border-top:1px solid var(--border);margin:6px 2px}
/* Org-scope filter row + checkbox-chip filters + consolidated Sort-by
   (2026-09-12, direct request, reference screenshot supplied): Faculty/
   Department/Program[/Scheme] on one line sharing equal width regardless
   of how many of them a given page includes (2-4) — `flex:1` on every
   direct child does this generically without a per-page column count.
   Semester/Status turned from a <select> into a horizontal group of real
   `<input type=checkbox>` chips (still a normal form field per value, just
   restyled) so multiple values can be picked at once; a vertical divider
   separates each filter group from the next and from the Sort-by dropdown,
   which replaces what used to be separate sort links on each column
   header — one place to change the sort instead of hunting across the
   table. `:has()` is relied on here (broad support since 2023) rather than
   a JS toggle class, since the chip's checked/unchecked look is purely a
   function of its own checkbox's state. */
.admin-orgscope-row{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:10px}
.admin-orgscope-row>div{flex:1;min-width:160px}
.admin-filter-divider{width:1px;align-self:stretch;background:var(--border);margin:0 2px}
.admin-check-group{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
/* Label moved to its own small line above the chips (2026-09-12, direct
   request) rather than sitting inline to the chips' left — matches every
   other filter field's own label-above-field layout (see "Employment
   type"/"Category" in these same forms) and, not incidentally, frees up
   enough row width for Apply/Clear All to land on the same line instead
   of wrapping to a second one. */
.admin-check-group-label{display:block;font-size:12px;font-weight:500;color:#64748b;margin-bottom:4px}
.admin-check-chip{display:inline-flex;align-items:center;gap:6px;padding:7px 11px;border:1px solid var(--border);border-radius:8px;background:#fff;font-size:13px;color:#334155;cursor:pointer;user-select:none;white-space:nowrap}
.admin-check-chip:hover{background:#f8fafc}
.admin-check-chip input{width:14px;height:14px;accent-color:var(--primary);margin:0}
.admin-check-chip:has(input:checked){background:var(--primary);border-color:var(--primary);color:#fff}
/* "Sort by" label moved above the select (2026-09-12), same treatment and
   same reason as the checkbox-group labels just above. */
.admin-sort-group label{display:block;font-size:12px;font-weight:500;color:#64748b;margin-bottom:4px}
.admin-sort-group select{width:auto;min-width:180px}
