/* =========================================================================
   ACAGES UI — global component layer
   Loaded LAST, after AdminLTE/Bootstrap 3 core + acages-tokens.css.
   Restyles the app's existing class vocabulary (.box, .btn, .table,
   .form-control, .dropdown-menu, .main-sidebar, ...) so every one of the
   ~868 CI3 views inherits the new look without per-file edits.
   Structural rebuilds (shell, dashboard, auth) layer additional
   ac-* classes documented inline where introduced.
   ========================================================================= */

/* ---------- 1. base / typography ---------- */

html, body {
  background: var(--ac-bg) !important;
  color: var(--ac-text);
  font-family: var(--ac-font-sans);
  -webkit-font-smoothing: antialiased;
  transition: background-color var(--ac-duration) var(--ac-ease), color var(--ac-duration) var(--ac-ease);
}

body, .content-wrapper, .box, .panel, td, th, .form-control, .btn, p, span, div {
  font-family: var(--ac-font-sans);
}

h1, h2, h3, h4, h5, h6,
.box-title, .page-header, .content-header > h1 {
  font-family: var(--ac-font-display);
  color: var(--ac-text);
  font-weight: 700;
  letter-spacing: -0.01em;
}

a { color: var(--ac-primary); }
a:hover, a:focus { color: var(--ac-primary-700); }

.content-wrapper { background: transparent; }

::selection { background: rgba(var(--ac-primary-rgb), 0.22); }

/* scrollbars */
* { scrollbar-width: thin; scrollbar-color: var(--ac-slate-300) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--ac-slate-300); border-radius: var(--ac-radius-full); border: 3px solid transparent; background-clip: content-box; }
body.dark ::-webkit-scrollbar-thumb { background: var(--ac-slate-700); background-clip: content-box; }

/* ---------- 2. layout shell ---------- */

.wrapper { background: var(--ac-bg); }

.main-header,
.main-header .navbar {
  background: linear-gradient(180deg, rgba(var(--ac-primary-rgb), 0.12) 0%, rgba(var(--ac-primary-rgb), 0.03) 70%), var(--ac-glass-bg) !important;
  backdrop-filter: blur(var(--ac-glass-blur));
  -webkit-backdrop-filter: blur(var(--ac-glass-blur));
  border-bottom: 1px solid var(--ac-border);
  box-shadow: none;
  /* NOT setting `position` here: style-main.css already makes .main-header
     `position: fixed` (the sticky topbar) — redeclaring it, even to
     `relative`, would win the cascade (same specificity, loaded later) and
     silently un-stick the header. `fixed` already establishes a valid
     positioning context for the ::after accent line below, so nothing
     extra is needed. */
}

/* thin brand-color accent line under the topbar, echoing the sidebar's
   active-item accent and the dashboard hero gradient — ties the topbar to
   the school's chosen color instead of leaving it neutral */
.main-header::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  background: linear-gradient(90deg, var(--ac-primary) 0%, var(--ac-accent-500) 60%, transparent 100%);
  opacity: 0.55;
  pointer-events: none;
}

.main-header .logo {
  background: transparent !important;
  border-bottom: none;
}

.main-header .sidebar-toggle {
  color: var(--ac-text) !important;
  border-radius: var(--ac-radius-md);
  transition: background var(--ac-duration-fast) var(--ac-ease), color var(--ac-duration-fast) var(--ac-ease);
}
.main-header .sidebar-toggle:hover {
  background: rgba(var(--ac-primary-rgb), 0.10) !important;
  color: var(--ac-primary) !important;
}

.sidebar-session {
  color: var(--ac-text);
  font-weight: 600;
  font-family: var(--ac-font-display);
}

/* topbar icon buttons */
.headertopmenu > li > a,
.navbar-custom-menu .dropdown-toggle {
  color: var(--ac-text-muted) !important;
  border-radius: var(--ac-radius-md);
  transition: color var(--ac-duration-fast) var(--ac-ease), background var(--ac-duration-fast) var(--ac-ease), transform var(--ac-duration-fast) var(--ac-ease);
}
.headertopmenu > li > a:hover {
  color: var(--ac-primary) !important;
  background: var(--ac-surface-2);
  transform: translateY(-1px);
}
.headertopmenu > li > a i { transition: transform var(--ac-duration-fast) var(--ac-ease); }
.headertopmenu > li > a:hover i { transform: scale(1.12); }

/* ---------- 3. sidebar ---------- */

/* Themeable width -- mirrors AdminLTE.css's own 4 hardcoded 230px
   companion rules (.main-sidebar/.left-side, .content-wrapper/.right-side/
   .main-footer, .main-header > .navbar, .main-header .logo) so the
   sidebar can widen/narrow without breaking that coupling. Deliberately
   NOT touched here for collapsed/mobile states -- those already resolve
   to fixed widths of their own (sidebar-mini's 50px rail, the <768px
   off-canvas breakpoint) that don't reference 230px at all. */
@media (min-width: 768px) {
  .main-sidebar,
  .left-side { width: var(--ac-sidebar-width) !important; }
  .content-wrapper,
  .right-side,
  .main-footer { margin-left: var(--ac-sidebar-width) !important; }
  .main-header > .navbar { margin-left: var(--ac-sidebar-width) !important; }
  .main-header .logo { width: var(--ac-sidebar-width) !important; }
}

.main-sidebar,
.main-sidebar .sidebar {
  /* keep the sidebar dark (per spec, in both light/dark modes) but hue-shift
     it toward the school's chosen brand color instead of a fixed navy, so
     changing the color actually changes what the sidebar looks like */
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ac-primary) 18%, var(--ac-sidebar-bg) 82%) 0%,
    color-mix(in srgb, var(--ac-primary) 10%, var(--ac-sidebar-bg-2) 90%) 100%) !important;
}

/* Sidebar scroll architecture: .main-sidebar is the theme's real scroll
   container (`overflow-y:auto; height:100%` from style-main.css, desktop
   only) and previously EVERYTHING inside it — session bar, quick links,
   search filter, and the nav menu — scrolled together as one region, with
   only `position:sticky` holding the session/quick-links bar in place.
   Explicitly split it instead: `.sidebar` becomes a column flexbox with a
   non-scrolling header (Current Session + Quick Links, fixed size) and a
   second region that scrolls independently, so nav items can never pass
   underneath the header — they're in a completely separate box, not just
   visually covered by it. */
.main-sidebar .sidebar {
  display: flex;
  flex-direction: column;
  height: 100% !important; /* overrides the JS-set inline height:auto from AdminLTE's fixSidebar() (harmless no-op there since slimScroll never activates without body.fixed, but this keeps the flex column's height deterministic regardless) */
  min-height: 0;
  /* deliberately NOT forcing overflow here: the fixed-top + scroll flex
     children sum to exactly this element's height, so nothing overflows it
     in the first place — and collapsed mode needs `.sidebar { overflow:
     visible }` (base theme) so the flyout submenu isn't clipped; fighting
     that with an !important override here would risk breaking it. */
}
.ac-sidebar-fixed-top {
  flex: 0 0 auto;
  position: relative;
  z-index: 30;
}
.ac-sidebar-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
}

.sidebar-menu > li > a {
  color: var(--ac-sidebar-text) !important;
  border-radius: var(--ac-radius-md);
  margin: 2px 10px;
  padding: 10px 14px !important;
  transition: background var(--ac-duration-fast) var(--ac-ease), color var(--ac-duration-fast) var(--ac-ease), transform var(--ac-duration-fast) var(--ac-ease);
  border-left: none !important;
}

.sidebar-menu > li > a:hover {
  background: var(--ac-sidebar-hover-bg) !important;
  color: var(--ac-sidebar-hover-text) !important;
  transform: translateX(2px);
}

.sidebar-menu > li.header {
  color: var(--ac-sidebar-heading) !important;
  background: transparent !important;
  padding: 10px 15px !important;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* In collapsed mode the hovered <a> is exactly where the flyout position
   math runs (footer.php's adjustSubmenuPosition) — any transform value here
   (even a small translate) turns this <a> into the containing block for its
   position:fixed label span, corrupting the coordinates. Keep the hover
   background/color but drop the transform in this state only. */
.sidebar-mini.sidebar-collapse .sidebar-menu > li > a:hover {
  transform: none;
}

.sidebar-menu > li.active > a,
.sidebar-menu > li.treeview.active > a {
  background: var(--ac-sidebar-active-bg) !important;
  color: var(--ac-sidebar-active-text) !important;
  box-shadow: inset 3px 0 0 0 var(--ac-primary-400);
}

.sidebar-menu li > a > i:first-child {
  color: var(--ac-sidebar-text-muted);
  width: 20px;
  text-align: center;
  margin-right: 8px;
  transition: color var(--ac-duration-fast) var(--ac-ease);
}
.sidebar-menu li.active > a > i:first-child { color: var(--ac-sidebar-active-icon); }
.sidebar-menu li > a:hover > i:first-child { color: var(--ac-sidebar-hover-icon); }

.sidebar-menu .treeview-menu {
  background: color-mix(in srgb, var(--ac-primary) 8%, rgba(0, 0, 0, 0.18)) !important;
  border-radius: var(--ac-radius-md);
  margin: 2px 10px 6px 26px;
  padding: 4px 0;
}
.sidebar-menu .treeview-menu > li > a {
  color: var(--ac-sidebar-text) !important;
  border-radius: var(--ac-radius-sm);
  margin: 1px 6px;
  transition: background var(--ac-duration-fast) var(--ac-ease), color var(--ac-duration-fast) var(--ac-ease);
}
.sidebar-menu .treeview-menu > li.active > a,
.sidebar-menu .treeview-menu > li > a:hover {
  background: rgba(255, 255, 255, 0.07) !important;
  color: #fff !important;
}

/* Collapsed sidebar ("sidebar-mini.sidebar-collapse"): the theme's own JS
   (adjustSubmenuPosition in layout/footer.php) rips the item label / submenu
   out to `position: fixed` and flies it out next to the icon rail. Our
   .treeview-menu background above (rgba(0,0,0,.18)) and the label's inherited
   transparent background were designed for sitting INSIDE the dark sidebar —
   floating over the light main content area instead, both become illegible
   (light sidebar text on a near-transparent tile). Give the flown-out state
   its own opaque, properly contrasted surface. */
.sidebar-mini.sidebar-collapse .sidebar-menu > li > a > span,
.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > a > span,
.sidebar-mini.sidebar-collapse .sidebar-menu > li > .treeview-menu,
.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > .treeview-menu {
  background: color-mix(in srgb, var(--ac-primary) 14%, var(--ac-sidebar-bg-2) 86%) !important;
  border: 1px solid var(--ac-sidebar-border);
  box-shadow: var(--ac-shadow-lg);
  color: var(--ac-sidebar-text) !important;
  z-index: 2000;
}
.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > .treeview-menu > li > a {
  color: var(--ac-sidebar-text) !important;
}
.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > .treeview-menu > li > a:hover,
.sidebar-mini.sidebar-collapse .sidebar-menu > li:hover > .treeview-menu > li.active > a {
  background: rgba(255, 255, 255, 0.08) !important;
  color: #fff !important;
}
/* the collapsed icon rail itself: don't let our normal-mode margin/padding
   shrink the 65px column the theme's flyout math assumes */
.sidebar-mini.sidebar-collapse .sidebar-menu > li > a {
  margin: 2px 6px;
  padding: 10px !important;
  text-align: center;
}

/* collapsed logo: the small square mark, sized to sit cleanly in the glass header */
.sidebar-mini.sidebar-collapse .main-header .logo {
  width: 65px;
  text-align: center;
}
.sidebar-mini.sidebar-collapse .main-header .logo .logo-mini {
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  margin: 0 !important;
}
.sidebar-mini.sidebar-collapse .main-header .logo .logo-mini img {
  max-height: 32px;
  width: auto;
  object-fit: contain;
}

.search-form2 .form-control,
.search-form .form-control {
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--ac-sidebar-border);
  color: #fff;
  border-radius: var(--ac-radius-md);
}
.search-form2 .form-control::placeholder { color: var(--ac-sidebar-text-muted); }

/* sidebar menu-item filter (client-side, distinct from the student-name search above it) */
.ac-sidebar-filter {
  position: relative;
  margin: 10px 14px 4px;
}
.ac-sidebar-filter i {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  color: var(--ac-sidebar-text-muted); font-size: 12px; pointer-events: none;
}
.ac-sidebar-filter-input {
  width: 100%;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--ac-sidebar-border);
  color: #fff;
  border-radius: var(--ac-radius-md);
  padding: 8px 12px 8px 30px;
  font-size: 12.5px;
  transition: background var(--ac-duration-fast) var(--ac-ease), border-color var(--ac-duration-fast) var(--ac-ease);
}
.ac-sidebar-filter-input::placeholder { color: var(--ac-sidebar-text-muted); }
.ac-sidebar-filter-input:focus {
  outline: none;
  background: rgba(255, 255, 255, 0.09);
  border-color: rgba(var(--ac-primary-rgb), 0.5);
}

/* current-session quick-switch bar sitting at the top of the (now dark) sidebar.
   It's `position: sticky; top: 0` from the base theme (by design, so it stays
   pinned while .sidebar-menu scrolls underneath) — it MUST have a fully
   opaque background, or the scrolling menu items show/overlap through it.
   Deliberately a solid, light, brand-tinted card (not a dark tint of the
   sidebar) so it reads as a distinct "current session" highlight against the
   dark sidebar, and deliberately a plain hex-backed token (not color-mix())
   so it renders identically on older browsers/webviews instead of silently
   falling back to transparent when color-mix() isn't supported. */
.sessionul.fixedmenu,
.sessionul {
  background: var(--ac-primary-50) !important;
  border-bottom: 1px solid var(--ac-border) !important;
  box-shadow: 0 8px 12px -8px rgba(0, 0, 0, 0.35);
  margin: 0 !important;
}
/* base theme's `.accurrent` adds its own 8px bottom padding on the <li>,
   stacking on top of the <a>'s own padding below and making the row taller
   than it needs to be — trim it down. */
.sessionul .accurrent {
  padding: 0 !important;
}
.sessionul.fixedmenu > li > a,
.sessionul > li > a {
  color: var(--ac-slate-800) !important;
  /* these <a> tags are never given `display:block` by the base theme (only
     .sidebar-menu links get that) — without it, top/bottom padding on an
     inline element paints outside its line box instead of reserving space,
     bleeding into whatever sits above/below (the next li, then the sidebar
     menu itself). */
  display: block !important;
  padding: 6px 14px !important;
}
.sessionul.fixedmenu > li > a:hover,
.sessionul > li > a:hover { color: var(--ac-primary-700) !important; background: rgba(0, 0, 0, 0.05) !important; }
.sessionul .fa-pencil,
.sessionul .fa-th,
.sessionul .icon-chevron-left { color: var(--ac-slate-500) !important; }

/* current-session item: "Current Session: 2025-26" and the edit icon all on
   one line, existing typography kept (no redesign) — just prevented from
   wrapping. Higher specificity than the generic
   `.sessionul > li > a { display:block }` rule above, so this only affects
   this item (Quick Links below is untouched). */
.ac-session-switch {
  display: flex !important;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: nowrap;
  white-space: nowrap;
  gap: 8px;
}
.ac-session-text {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* slightly smaller than the sidebar-menu's own 14px body text so the full
     "Current Session: 2025-26" string fits one line at normal sidebar
     widths instead of wrapping — per "reduce font size instead of wrapping" */
  font-size: 12.5px;
}
.ac-session-edit {
  flex-shrink: 0;
  color: var(--ac-slate-500) !important;
}

/* "quick links" mega-dropdown — dense, readable panel, not a blur-glass surface */
.mega-dropdown .dropdown-menu {
  background: var(--ac-surface) !important;
  padding: var(--ac-space-4) !important;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.side-navbar-vertical h4,
.card-sidebar h4 {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ac-text) !important;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.side-navbar-vertical ul li a,
.card-sidebar ul li a {
  color: var(--ac-text-muted) !important;
  border-radius: var(--ac-radius-sm);
  padding: 4px 8px;
  display: block;
  transition: background var(--ac-duration-fast) var(--ac-ease), color var(--ac-duration-fast) var(--ac-ease);
}
.side-navbar-vertical ul li a:hover,
.card-sidebar ul li a:hover { background: var(--ac-surface-2); color: var(--ac-primary) !important; text-decoration: none; }

/* ---------- 4. cards / boxes (AdminLTE .box == Bootstrap "panel") ---------- */

.box, .panel {
  background: var(--ac-surface);
  border: 1px solid var(--ac-border);
  border-radius: var(--ac-radius-lg);
  box-shadow: var(--ac-shadow-sm);
  transition: box-shadow var(--ac-duration) var(--ac-ease), transform var(--ac-duration) var(--ac-ease), border-color var(--ac-duration) var(--ac-ease);
}
.box:hover, .panel:hover {
  box-shadow: var(--ac-shadow-md);
}
.box.ac-hover-lift:hover, .panel.ac-hover-lift:hover {
  transform: translateY(-3px);
  border-color: var(--ac-border-strong);
}

.box-header, .panel-heading {
  background: transparent;
  border-bottom: 1px solid var(--ac-border);
  border-radius: var(--ac-radius-lg) var(--ac-radius-lg) 0 0 !important;
  padding: var(--ac-space-5) var(--ac-space-6);
}
.box-body, .panel-body { padding: var(--ac-space-6); }
.box-title, .panel-title {
  font-size: 15px;
  font-weight: 700;
}

/* glass surface utility — applied to floating panels: dropdowns, search modal, quick-action widgets */
.ac-glass {
  background: var(--ac-glass-bg) !important;
  backdrop-filter: blur(var(--ac-glass-blur));
  -webkit-backdrop-filter: blur(var(--ac-glass-blur));
  border: 1px solid var(--ac-glass-border) !important;
  box-shadow: var(--ac-shadow-lg);
}

/* ---------- 5. buttons ---------- */

.btn {
  border-radius: var(--ac-radius-md);
  font-weight: 600;
  font-size: 13.5px;
  padding: 8px 18px;
  border: 1px solid transparent;
  box-shadow: var(--ac-shadow-sm);
  transition: transform var(--ac-duration-fast) var(--ac-ease), box-shadow var(--ac-duration-fast) var(--ac-ease), filter var(--ac-duration-fast) var(--ac-ease), opacity var(--ac-duration-fast) var(--ac-ease);
}
.btn:active { transform: translateY(1px) scale(0.98); }
.btn:focus-visible { outline: 2px solid var(--ac-primary-300); outline-offset: 2px; }
.btn:hover { filter: brightness(1.05); box-shadow: var(--ac-shadow-md); }
.btn.disabled, .btn[disabled] { opacity: 0.55; box-shadow: none; filter: none; transform: none; }

.btn-primary, .btn-info {
  background: linear-gradient(135deg, var(--ac-primary-500), var(--ac-accent-600));
  border-color: transparent;
  color: #fff;
}
.btn-success { background: linear-gradient(135deg, #22c55e, var(--ac-success)); border-color: transparent; color: #fff; }
.btn-warning { background: linear-gradient(135deg, #f59e0b, var(--ac-warning)); border-color: transparent; color: #fff; }
.btn-danger  { background: linear-gradient(135deg, #ef4444, var(--ac-danger)); border-color: transparent; color: #fff; }
.btn-default { background: var(--ac-surface); border-color: var(--ac-border-strong); color: var(--ac-text); box-shadow: none; }
.btn-default:hover { background: var(--ac-surface-2); }

.btn-flat { border-radius: var(--ac-radius-md); }

/* loading state: <button class="btn ac-loading"> */
.btn.ac-loading { color: transparent !important; pointer-events: none; position: relative; }
.btn.ac-loading::after {
  content: "";
  position: absolute; left: 50%; top: 50%;
  width: 16px; height: 16px; margin: -8px 0 0 -8px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.45);
  border-top-color: #fff;
  animation: ac-spin 0.7s linear infinite;
}
@keyframes ac-spin { to { transform: rotate(360deg); } }

/* ---------- 6. tables ---------- */

.table {
  border-collapse: separate;
  border-spacing: 0;
  background: var(--ac-surface);
}
.table > thead > tr > th {
  background: var(--ac-surface-2);
  color: var(--ac-text-muted);
  text-transform: uppercase;
  font-size: 11px;
  letter-spacing: 0.04em;
  font-weight: 700;
  border-bottom: 1px solid var(--ac-border);
  padding: 12px 16px;
  position: sticky;
  top: 0;
  z-index: 2;
}
.table > tbody > tr > td {
  padding: 12px 16px;
  border-top: 1px solid var(--ac-border);
  color: var(--ac-text);
  vertical-align: middle;
}
.table > tbody > tr {
  transition: background var(--ac-duration-fast) var(--ac-ease);
}
.table-hover > tbody > tr:hover { background: var(--ac-surface-2) !important; }
.table-bordered, .table-bordered > thead > tr > th, .table-bordered > tbody > tr > td {
  border: 1px solid var(--ac-border);
}

.dataTables_wrapper .dataTables_filter input,
.dataTables_wrapper .dataTables_length select {
  border-radius: var(--ac-radius-md);
  border: 1px solid var(--ac-border-strong);
  background: var(--ac-surface);
  color: var(--ac-text);
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
  border-radius: var(--ac-radius-sm) !important;
  margin: 0 2px;
}
/* grand-total / summary rows across fee collection, balance fees, and finance reports */
tr.total-bg,
.total-bg {
  background: rgba(var(--ac-primary-rgb), 0.06) !important;
}
tr.total-bg td {
  font-weight: 700 !important;
  font-size: 13.5px;
  color: var(--ac-text) !important;
  border-top: 2px solid rgba(var(--ac-primary-rgb), 0.25) !important;
  border-bottom: none !important;
}

.dataTables_wrapper .dataTables_paginate .paginate_button.current {
  background: var(--ac-primary) !important;
  border-color: var(--ac-primary) !important;
  color: #fff !important;
}

/* ---------- 7. forms ---------- */

.form-control {
  background: var(--ac-surface);
  border: 1px solid var(--ac-border-strong);
  border-radius: var(--ac-radius-md);
  color: var(--ac-text);
  box-shadow: none;
  padding: 9px 14px;
  height: auto;
  transition: border-color var(--ac-duration-fast) var(--ac-ease), box-shadow var(--ac-duration-fast) var(--ac-ease);
}
.form-control:focus {
  border-color: var(--ac-primary-400);
  box-shadow: 0 0 0 3px rgba(var(--ac-primary-rgb), 0.14);
}
.form-control::placeholder { color: var(--ac-text-faint); }

/* Bootstrap's own .input-sm { padding:5px 10px; height:30px; font-size:12px }
   is the SAME specificity (one class) as the generic .form-control rule
   above -- and since this file loads last, .form-control was silently
   winning the tie for every <input>/<textarea> with both classes, so
   .input-sm has never actually shrunk a plain input/textarea in this app,
   only selects (which already have their own more-specific
   select.form-control.input-sm override below). Restoring the intended
   compact size here, at higher specificity, for input/textarea too. */
.form-control.input-sm,
textarea.form-control.input-sm {
  padding: 5px 10px;
  height: 30px;
  font-size: 12px;
}
textarea.form-control.input-sm { height: auto; }
label, .control-label { color: var(--ac-text); font-weight: 600; font-size: 13px; }
.form-group { margin-bottom: var(--ac-space-5); }

.has-error .form-control { border-color: var(--ac-danger); }
.has-success .form-control { border-color: var(--ac-success); }

.input-group-addon {
  background: var(--ac-surface-2);
  border: 1px solid var(--ac-border-strong);
  color: var(--ac-text-muted);
}

.bootstrap-select .dropdown-toggle,
select.form-control { border-radius: var(--ac-radius-md); }

/* ROOT CAUSE of the "selected value invisible" bug (hit repeatedly across
   this app -- Live Dashboard, Master/School Timetable filters, and
   whatever's next unless this stays fixed here): every select.form-control
   in the app kept the browser's DEFAULT native appearance
   (appearance: auto). With native appearance, the CLOSED box's value text
   is painted by the OS/UA's own widget renderer, not by the page's normal
   text-rendering path -- on some Windows/Chromium builds that native paint
   path does not reliably pick up CSS `color`, no matter the specificity or
   `!important`, because it isn't going through ordinary CSS text painting
   at all. That's why a plain `color` override (previously tried here, and
   independently as a page-local hack on the Dashboard -- both removed)
   had no visible effect: the property was set correctly, but the paint
   path ignoring it doesn't check.
   The fix: turn OFF native appearance entirely so CSS is unconditionally
   in charge of painting the box, and draw a custom dropdown arrow to
   replace the one `appearance: none` removes. This is the standard,
   cross-browser-reliable way to style a native <select> -- not a
   workaround, the actual correct approach. */
select.form-control {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  /* Windows "Forced Colors" (High Contrast) mode overrides author
     color/background on native form controls (select, button, input)
     with system colors -- and it does this on top of everything above:
     past appearance:none, past !important, no exception. That's the
     actual explanation for reports where the value was "correctly
     selected" but still invisible even after every prior fix here.
     forced-color-adjust:none is the one documented opt-out that lets
     this rule's own colors win instead of being silently replaced. */
  forced-color-adjust: none;
  color: #1e293b !important;
  background-color: #fff !important;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%231e293b' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 10px 6px;
  padding-right: 32px !important;
  /* Same story as the color fix above: under appearance:none, this
     environment does not reliably size a <select> the same way it sizes
     a sibling <input class="form-control"> from inherited height/
     line-height alone, even though both resolve to the same value on
     paper (bootstrap.min.css's own select.input-sm{height:30px;
     line-height:30px} rule is specific enough to win the cascade over
     the theme's .form-control{height:auto} override -- verified by
     specificity, not a guess). The rendered result still comes out
     shorter than the input next to it. Pin height/line-height/padding
     directly and explicitly here instead of trusting inheritance to
     land on the right number -- padding-top/bottom:0 with
     line-height == height is the standard cross-browser way to
     vertically-center a single line of text in a fixed-height <select>. */
  height: 36px !important;
  min-height: 36px;
  line-height: 36px !important;
  padding-top: 0 !important;
  padding-bottom: 0 !important;
}
select.form-control::-ms-expand { display: none; }

/* The 32px right-padding above (reserved for the arrow) is sized for the
   default .form-control box (padding: 9px 14px, ~14px font). Bootstrap's
   .input-sm variant is much more compact (padding: 5px 10px, ~12px font)
   -- reusing the same 32px on it leaves too little room for the actual
   value text in already-narrow filter columns, and the browser hard-clips
   rather than shrinking/wrapping, which is exactly the "all text cut off"
   regression this rule exists to prevent. Scale the reserved space and
   arrow down to match the smaller control instead of sharing one size. */
select.form-control.input-sm {
  padding-right: 22px !important;
  background-position: right 7px center;
  background-size: 8px 5px;
  /* Same explicit-height treatment as the base rule above, scaled down to
     match .input-sm's own 30px (5px+5px padding + 18px line-height + 2px
     border, the exact numbers bootstrap.min.css's own .input-sm rule
     already uses for plain inputs -- selects get the same number pinned
     directly instead of trusting it to fall out of inheritance). */
  height: 30px !important;
  min-height: 30px;
  line-height: 30px !important;
  padding-top: 0 !important;
  padding-bottom: 0 !important;
}

/* The dropdown POPUP's <option> list is still an OS-native popup (that
   part can't be replaced by appearance:none -- only the closed box can)
   -- typically a light/white surface regardless of page CSS, but it still
   inherits the page's `color`. In dark mode --ac-text is light, so
   without this the popup shows light text on a native white background --
   "blank" looking even though the option text is genuinely there. */
select.form-control option {
  color: #1e293b;
  background: #fff;
}

/* ---------- 8. dropdowns / menus (glass) ---------- */

.dropdown-menu {
  border: 1px solid var(--ac-glass-border);
  border-radius: var(--ac-radius-lg);
  background: var(--ac-glass-bg);
  backdrop-filter: blur(var(--ac-glass-blur));
  -webkit-backdrop-filter: blur(var(--ac-glass-blur));
  box-shadow: var(--ac-shadow-lg);
  padding: 8px;
  animation: ac-pop var(--ac-duration) var(--ac-ease);
}
.dropdown-menu > li > a {
  border-radius: var(--ac-radius-sm);
  color: var(--ac-text);
  padding: 8px 12px;
  transition: background var(--ac-duration-fast) var(--ac-ease);
}
.dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus {
  background: var(--ac-surface-2);
  color: var(--ac-text);
}
.dropdown-menu .divider { background: var(--ac-border); }

@keyframes ac-pop {
  from { opacity: 0; transform: translateY(-6px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

/* ---------- 9. alerts / toasts / badges ---------- */

.alert {
  border: 1px solid transparent;
  border-radius: var(--ac-radius-md);
  padding: var(--ac-space-4) var(--ac-space-5);
}
.alert-success { background: var(--ac-success-bg); color: var(--ac-success); border-color: rgba(var(--ac-success-rgb), 0.25); }
.alert-warning { background: var(--ac-warning-bg); color: var(--ac-warning); border-color: rgba(var(--ac-warning-rgb), 0.25); }
.alert-danger  { background: var(--ac-danger-bg);  color: var(--ac-danger);  border-color: rgba(var(--ac-danger-rgb), 0.25); }
.alert-info    { background: var(--ac-info-bg);    color: var(--ac-info);    border-color: rgba(var(--ac-info-rgb), 0.25); }

.label, .badge {
  border-radius: var(--ac-radius-full);
  font-weight: 600;
  padding: 3px 10px;
}

/* toast container driven by sstoast.js — restyle the existing markup, no JS changes */
.toast, .ac-toast {
  border-radius: var(--ac-radius-lg) !important;
  box-shadow: var(--ac-shadow-lg) !important;
  border: 1px solid var(--ac-border) !important;
  background: var(--ac-surface) !important;
  color: var(--ac-text) !important;
}

/* ---------- 10. modals ---------- */

.modal-content {
  border: 1px solid var(--ac-border);
  border-radius: var(--ac-radius-xl);
  box-shadow: var(--ac-shadow-lg);
  overflow: hidden;
}
.modal-header { border-bottom: 1px solid var(--ac-border); padding: var(--ac-space-5) var(--ac-space-6); }
.modal-body { padding: var(--ac-space-6); }
.modal-footer { border-top: 1px solid var(--ac-border); padding: var(--ac-space-4) var(--ac-space-6); }
.modal-backdrop { background: rgba(8, 12, 22, 0.55); }
.modal-backdrop.in { opacity: 1; backdrop-filter: blur(2px); }

/* ---------- 11. progress bars ---------- */

.progress {
  height: 8px;
  border-radius: var(--ac-radius-full);
  background: var(--ac-surface-2);
  box-shadow: none;
  overflow: hidden;
}
.progress-bar {
  background: linear-gradient(90deg, var(--ac-primary-500), var(--ac-accent-600));
  transition: width var(--ac-duration-slow) var(--ac-ease);
}

/* ---------- 11b. AdminLTE stat/progress widgets used across the dashboard
   (.info-box, .topprograssstart, .flex-card) — restyled as glass-ish cards
   so every existing RBAC-gated widget block inherits the new look without
   any PHP/markup changes. ---------- */

.info-box {
  background: var(--ac-surface);
  border: 1px solid var(--ac-border);
  border-radius: var(--ac-radius-lg);
  box-shadow: var(--ac-shadow-sm);
  overflow: hidden;
  transition: transform var(--ac-duration) var(--ac-ease), box-shadow var(--ac-duration) var(--ac-ease);
}
.info-box:hover { transform: translateY(-3px); box-shadow: var(--ac-shadow-md); }
.info-box a { display: flex; align-items: center; gap: var(--ac-space-4); padding: var(--ac-space-5); color: var(--ac-text); text-decoration: none; }
.info-box-icon {
  width: 46px; height: 46px; min-width: 46px;
  border-radius: var(--ac-radius-md);
  display: flex; align-items: center; justify-content: center;
  background: rgba(var(--ac-primary-rgb), 0.12);
  color: var(--ac-primary);
  font-size: 18px;
}
.info-box-text { display: block; color: var(--ac-text-muted); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.02em; }
.info-box-number { display: block; font-family: var(--ac-font-display); font-size: 20px; font-weight: 800; color: var(--ac-text); }

.topprograssstart {
  background: var(--ac-surface);
  border: 1px solid var(--ac-border);
  border-radius: var(--ac-radius-lg);
  box-shadow: var(--ac-shadow-sm);
  padding: var(--ac-space-5);
  transition: transform var(--ac-duration) var(--ac-ease), box-shadow var(--ac-duration) var(--ac-ease);
  height: 100%;
}
.topprograssstart:hover { transform: translateY(-2px); box-shadow: var(--ac-shadow-md); }
.topprograssstart .pro-border {
  font-weight: 700;
  font-size: 13px;
  color: var(--ac-text);
  padding-bottom: 10px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--ac-border);
}
a.topprograssstart { display: block; color: inherit; text-decoration: none; cursor: pointer; }
a.topprograssstart:hover { color: inherit; text-decoration: none; border-color: var(--ac-primary); }

/* dashboard widget headings/titles that link through to a report/list page */
.ac-widget-link { color: inherit; text-decoration: none; cursor: pointer; }
.ac-widget-link:hover { color: var(--ac-primary); text-decoration: none; }
.ac-widget-link:hover .fa { color: var(--ac-primary); }

/* ---------- 12. hero (dashboard) ---------- */

.ac-hero {
  position: relative;
  border-radius: var(--ac-radius-xl);
  padding: var(--ac-space-8) var(--ac-space-8);
  background: var(--ac-hero-gradient);
  color: #fff;
  overflow: hidden;
  box-shadow: var(--ac-shadow-lg);
  margin-bottom: var(--ac-space-6);
}
.ac-hero::before, .ac-hero::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  filter: blur(2px);
  opacity: 0.35;
  background: radial-gradient(circle, rgba(255,255,255,0.5) 0%, transparent 70%);
  animation: ac-float 9s var(--ac-ease) infinite;
}
.ac-hero::before { width: 220px; height: 220px; top: -80px; right: 10%; }
.ac-hero::after { width: 140px; height: 140px; bottom: -50px; right: 30%; animation-delay: 2.5s; }
@keyframes ac-float {
  0%, 100% { transform: translateY(0) scale(1); }
  50% { transform: translateY(-14px) scale(1.05); }
}
.ac-hero-eyebrow { color: rgba(255,255,255,0.75); font-size: 13px; font-weight: 600; letter-spacing: 0.03em; text-transform: uppercase; }
.ac-hero-title { font-family: var(--ac-font-display); font-size: 26px; font-weight: 800; margin: 4px 0 6px; }
.ac-hero-meta { color: rgba(255,255,255,0.85); font-size: 13.5px; display: flex; gap: 18px; flex-wrap: wrap; }
.ac-hero-meta span { display: inline-flex; align-items: center; gap: 6px; }
.ac-hero-actions { margin-top: var(--ac-space-6); display: flex; gap: 10px; flex-wrap: wrap; }
.ac-hero-actions .btn {
  background: rgba(255,255,255,0.14);
  border: 1px solid rgba(255,255,255,0.25);
  color: #fff;
  backdrop-filter: blur(8px);
}
.ac-hero-actions .btn:hover { background: rgba(255,255,255,0.22); }

/* ---------- 13. stat cards ---------- */

.ac-stat-card {
  background: var(--ac-surface);
  border: 1px solid var(--ac-border);
  border-radius: var(--ac-radius-lg);
  padding: var(--ac-space-5);
  box-shadow: var(--ac-shadow-sm);
  transition: transform var(--ac-duration) var(--ac-ease), box-shadow var(--ac-duration) var(--ac-ease);
  display: flex;
  align-items: flex-start;
  gap: var(--ac-space-4);
}
.ac-stat-card:hover { transform: translateY(-3px); box-shadow: var(--ac-shadow-md); }
a.ac-stat-card-link { color: inherit; text-decoration: none; cursor: pointer; }
a.ac-stat-card-link:hover { color: inherit; text-decoration: none; border-color: var(--ac-primary); }
.ac-stat-icon {
  width: 44px; height: 44px;
  border-radius: var(--ac-radius-md);
  display: flex; align-items: center; justify-content: center;
  font-size: 18px;
  background: rgba(var(--ac-primary-rgb), 0.12);
  color: var(--ac-primary);
  flex-shrink: 0;
}
.ac-stat-value { font-family: var(--ac-font-display); font-size: 24px; font-weight: 800; color: var(--ac-text); line-height: 1.2; }
.ac-stat-label { color: var(--ac-text-muted); font-size: 12.5px; font-weight: 600; }
.ac-stat-trend { font-size: 12px; font-weight: 700; display: inline-flex; align-items: center; gap: 4px; margin-top: 4px; }
.ac-stat-trend.up { color: var(--ac-success); }
.ac-stat-trend.down { color: var(--ac-danger); }

/* ---------- 13b. dashboard summary (financial / population / staff) ----------
   Used by application/views/admin/dashboard.php's "at a glance" summary,
   built from three independent, reusable pieces:
   .ac-summary-group/.ac-summary-heading/.ac-summary-grid (generic section
   scaffold), .ac-fin-card (financial tier -- the visually heaviest), and
   .ac-pop-card + .ac-staff-tile (population/staff tiers, deliberately
   lighter). All three read the same tokens as the rest of this file, so
   they inherit dark mode/theming for free. */

.ac-summary-group { margin-bottom: var(--ac-space-6); }
.ac-summary-group:last-child { margin-bottom: 0; }
.ac-summary-heading {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--ac-font-sans);
  font-size: 11.5px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ac-text-muted);
  margin: 0 0 var(--ac-space-3);
}
.ac-summary-heading::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%;
  background: var(--ac-primary); flex-shrink: 0;
}

/* auto-fill (not auto-fit): column count still adapts to available width,
   but unfilled tracks stay reserved instead of stretching the few real
   cards to fill the row -- 2 financial cards on a wide monitor stay a
   sane ~200-260px each instead of blowing up to half the screen. */
.ac-summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--ac-space-4);
}

/* ---- financial tier: the most visually important cards on the dashboard ---- */
.ac-fin-card {
  background: var(--ac-surface);
  border: 1px solid var(--ac-border);
  border-radius: var(--ac-radius-lg);
  padding: var(--ac-space-5);
  box-shadow: var(--ac-shadow-sm);
  transition: transform var(--ac-duration) var(--ac-ease), box-shadow var(--ac-duration) var(--ac-ease);
  position: relative;
  overflow: hidden;
  display: block;
  color: var(--ac-text);
}
.ac-fin-card::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: var(--ac-fin-accent, var(--ac-primary));
}
a.ac-fin-card:hover {
  transform: translateY(-3px); box-shadow: var(--ac-shadow-md);
  color: var(--ac-text); text-decoration: none; border-color: var(--ac-border-strong);
}
.ac-fin-card-top { display: flex; align-items: center; gap: var(--ac-space-3); margin-bottom: var(--ac-space-3); }
.ac-fin-card-icon {
  width: 40px; height: 40px; min-width: 40px; border-radius: var(--ac-radius-md);
  display: flex; align-items: center; justify-content: center; font-size: 17px;
  background: rgba(var(--ac-fin-accent-rgb, var(--ac-primary-rgb)), 0.12);
  color: var(--ac-fin-accent, var(--ac-primary));
}
.ac-fin-card-label { font-size: 12.5px; font-weight: 700; color: var(--ac-text-muted); text-transform: uppercase; letter-spacing: 0.03em; }
.ac-fin-card-value { font-family: var(--ac-font-display); font-size: 25px; font-weight: 800; color: var(--ac-text); line-height: 1.15; word-break: break-word; }
.ac-fin-card-trend { display: inline-flex; align-items: center; gap: 4px; margin-top: var(--ac-space-2); font-size: 12px; font-weight: 700; padding: 3px 9px; border-radius: var(--ac-radius-full); }
.ac-fin-card-trend.up { color: var(--ac-success); background: var(--ac-success-bg); }
.ac-fin-card-trend.down { color: var(--ac-danger); background: var(--ac-danger-bg); }
.ac-fin-card-sub { font-size: 12px; color: var(--ac-text-muted); margin-top: var(--ac-space-2); }

.ac-fin-card.ac-fin-fees    { --ac-fin-accent: var(--ac-primary); --ac-fin-accent-rgb: var(--ac-primary-rgb); }
.ac-fin-card.ac-fin-expense { --ac-fin-accent: var(--ac-warning); --ac-fin-accent-rgb: var(--ac-warning-rgb); }
.ac-fin-card.ac-fin-net     { --ac-fin-accent: var(--ac-success); --ac-fin-accent-rgb: var(--ac-success-rgb); }
.ac-fin-card.ac-fin-net.is-negative { --ac-fin-accent: var(--ac-danger); --ac-fin-accent-rgb: var(--ac-danger-rgb); }

/* ---- population tier: same family as .ac-stat-card, deliberately lighter than financial ---- */
.ac-pop-card {
  background: var(--ac-surface);
  border: 1px solid var(--ac-border);
  border-radius: var(--ac-radius-lg);
  padding: var(--ac-space-4) var(--ac-space-5);
  box-shadow: var(--ac-shadow-sm);
  display: flex; align-items: center; gap: var(--ac-space-3);
  transition: transform var(--ac-duration) var(--ac-ease), box-shadow var(--ac-duration) var(--ac-ease);
  color: var(--ac-text);
}
a.ac-pop-card:hover { transform: translateY(-3px); box-shadow: var(--ac-shadow-md); color: var(--ac-text); text-decoration: none; }
.ac-pop-card-icon {
  width: 38px; height: 38px; min-width: 38px; border-radius: var(--ac-radius-md);
  display: flex; align-items: center; justify-content: center; font-size: 15px;
  background: rgba(var(--ac-pop-accent-rgb, var(--ac-accent-rgb)), 0.12);
  color: var(--ac-pop-accent, var(--ac-accent-600));
}
.ac-pop-card-value { font-family: var(--ac-font-display); font-size: 20px; font-weight: 800; color: var(--ac-text); line-height: 1.2; }
.ac-pop-card-label { font-size: 12px; font-weight: 600; color: var(--ac-text-muted); }
.ac-pop-card.ac-pop-present { --ac-pop-accent: var(--ac-success); --ac-pop-accent-rgb: var(--ac-success-rgb); }
.ac-pop-card.ac-pop-absent  { --ac-pop-accent: var(--ac-danger);  --ac-pop-accent-rgb: var(--ac-danger-rgb); }

/* ---- staff tier: compact auto-fill tile grid -- works for 3 roles or 20 ---- */
.ac-staff-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: var(--ac-space-3);
}
.ac-staff-tile {
  display: flex; align-items: center; gap: var(--ac-space-2);
  background: var(--ac-surface);
  border: 1px solid var(--ac-border);
  border-radius: var(--ac-radius-md);
  padding: 10px var(--ac-space-3);
  color: var(--ac-text);
  min-width: 0; /* lets the label ellipsis below actually take effect inside a grid item */
  transition: transform var(--ac-duration-fast) var(--ac-ease), box-shadow var(--ac-duration-fast) var(--ac-ease), border-color var(--ac-duration-fast) var(--ac-ease);
}
a.ac-staff-tile:hover { transform: translateY(-2px); box-shadow: var(--ac-shadow-sm); border-color: rgba(var(--ac-primary-rgb), 0.3); color: var(--ac-text); text-decoration: none; }
.ac-staff-tile-icon {
  width: 28px; height: 28px; min-width: 28px; border-radius: var(--ac-radius-sm);
  display: flex; align-items: center; justify-content: center; font-size: 12px;
  background: rgba(var(--ac-primary-rgb), 0.12); color: var(--ac-primary);
}
.ac-staff-tile-body { min-width: 0; }
.ac-staff-tile-count { font-family: var(--ac-font-display); font-size: 15px; font-weight: 800; color: var(--ac-text); line-height: 1.2; }
.ac-staff-tile-label {
  font-size: 10.5px; font-weight: 600; color: var(--ac-text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block;
}

@media (max-width: 480px) {
  .ac-summary-grid { grid-template-columns: 1fr; }
  .ac-staff-grid { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }
}

/* ---------- 14. quick action cards ---------- */

.ac-quick-action {
  display: flex; align-items: center; gap: var(--ac-space-4);
  background: var(--ac-surface);
  border: 1px solid var(--ac-border);
  border-radius: var(--ac-radius-lg);
  padding: var(--ac-space-4) var(--ac-space-5);
  transition: transform var(--ac-duration-fast) var(--ac-ease), box-shadow var(--ac-duration-fast) var(--ac-ease), border-color var(--ac-duration-fast) var(--ac-ease);
  color: var(--ac-text);
  text-decoration: none;
}
.ac-quick-action:hover {
  transform: translateY(-2px);
  box-shadow: var(--ac-shadow-md);
  border-color: rgba(var(--ac-primary-rgb), 0.35);
  color: var(--ac-text);
  text-decoration: none;
}
.ac-quick-action .ac-stat-icon { background: rgba(var(--ac-accent-rgb), 0.12); color: var(--ac-accent-600); }
.ac-quick-action-title { font-weight: 700; font-size: 13.5px; }
.ac-quick-action-desc { color: var(--ac-text-muted); font-size: 12px; }

/* ---------- 15. empty states ---------- */

.ac-empty {
  text-align: center;
  padding: var(--ac-space-12) var(--ac-space-6);
}
.ac-empty svg, .ac-empty img { max-width: 220px; margin: 0 auto var(--ac-space-5); }
.ac-empty-title { font-family: var(--ac-font-display); font-weight: 700; font-size: 17px; color: var(--ac-text); }
.ac-empty-desc { color: var(--ac-text-muted); font-size: 13.5px; max-width: 360px; margin: 6px auto var(--ac-space-5); }

.ac-empty-mini { padding: var(--ac-space-6) var(--ac-space-4); }
.ac-empty-mini svg { max-width: 120px; margin-bottom: var(--ac-space-3); }
.ac-empty-mini .ac-empty-desc { font-size: 12.5px; margin: 0; }

/* ---------- 16. skeleton loaders ---------- */

.ac-skeleton {
  position: relative;
  overflow: hidden;
  background: var(--ac-surface-2);
  border-radius: var(--ac-radius-md);
}
.ac-skeleton::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(var(--ac-surface-rgb), 0.7), transparent);
  animation: ac-shimmer 1.4s infinite;
}
@keyframes ac-shimmer {
  from { transform: translateX(-100%); }
  to { transform: translateX(100%); }
}

/* ---------- 17. animated counters (paired with acages-ui.js IntersectionObserver) ---------- */
.ac-counter { font-variant-numeric: tabular-nums; }

/* ---------- 18. utilities ---------- */
.ac-fade-in { animation: ac-fadein var(--ac-duration-slow) var(--ac-ease) both; }
@keyframes ac-fadein { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }

/* ---------- 19. dark-mode toggle switch (topbar) ---------- */
.ac-theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px;
  border-radius: var(--ac-radius-full);
  color: var(--ac-text-muted);
  transition: background var(--ac-duration-fast) var(--ac-ease), color var(--ac-duration-fast) var(--ac-ease);
  cursor: pointer;
}
.ac-theme-toggle:hover { background: var(--ac-surface-2); color: var(--ac-primary); }

/* ---------- 20. command palette (Ctrl+K) ---------- */
.ac-cmdk-backdrop {
  position: fixed; inset: 0; z-index: 20000;
  background: rgba(8, 12, 22, 0.55);
  backdrop-filter: blur(2px);
  display: flex; align-items: flex-start; justify-content: center;
  padding-top: 12vh;
  opacity: 0; pointer-events: none;
  transition: opacity var(--ac-duration) var(--ac-ease);
}
.ac-cmdk-backdrop.is-open { opacity: 1; pointer-events: auto; }
.ac-cmdk {
  width: min(600px, 92vw);
  border-radius: var(--ac-radius-xl);
  overflow: hidden;
  transform: translateY(-10px) scale(0.98);
  transition: transform var(--ac-duration) var(--ac-ease);
}
.ac-cmdk-backdrop.is-open .ac-cmdk { transform: translateY(0) scale(1); }
.ac-cmdk-input {
  width: 100%; border: none; outline: none;
  background: transparent; color: var(--ac-text);
  font-size: 16px; padding: var(--ac-space-5) var(--ac-space-6);
  border-bottom: 1px solid var(--ac-border);
}
.ac-cmdk-input::placeholder { color: var(--ac-text-faint); }
.ac-cmdk-results { max-height: 50vh; overflow-y: auto; padding: var(--ac-space-2); }
.ac-cmdk-item {
  display: flex; align-items: center; gap: var(--ac-space-3);
  padding: 10px 14px; border-radius: var(--ac-radius-md);
  color: var(--ac-text); cursor: pointer;
  transition: background var(--ac-duration-fast) var(--ac-ease);
}
.ac-cmdk-item.is-active, .ac-cmdk-item:hover { background: var(--ac-surface-2); }
.ac-cmdk-item small { color: var(--ac-text-muted); margin-left: auto; }
.ac-cmdk-empty { padding: var(--ac-space-8) var(--ac-space-6); text-align: center; color: var(--ac-text-muted); }
.ac-cmdk-hint {
  display: flex; justify-content: space-between; align-items: center;
  padding: var(--ac-space-3) var(--ac-space-5);
  border-top: 1px solid var(--ac-border);
  color: var(--ac-text-faint); font-size: 11.5px;
}
kbd.ac-key {
  background: var(--ac-surface-2);
  border: 1px solid var(--ac-border-strong);
  border-radius: 5px;
  padding: 1px 6px;
  font-size: 11px;
  font-family: var(--ac-font-sans);
}

/* ---------- 21. AOS fallback (no-JS / reduced motion) ---------- */
@media (prefers-reduced-motion: reduce) {
  [data-aos] { transition: none !important; transform: none !important; opacity: 1 !important; }
}

/* ---------- 22. responsive ---------- */
@media (max-width: 767px) {
  .ac-hero { padding: var(--ac-space-6); }
  .ac-hero-title { font-size: 21px; }
  .box-body, .panel-body { padding: var(--ac-space-4); }
  .table > thead > tr > th, .table > tbody > tr > td { padding: 10px; }
}

/* ---------- 23. stacked tables (mobile) ----------
   Turns a wide, many-column table into one card per row below the
   breakpoint, instead of forcing horizontal scroll. Usage: add class
   "ac-table-responsive" to the <table>, and data-label="Column Name"
   to every <td> (matching that column's header). Rows/cells with no
   data-label (e.g. an empty-state colspan row) render as plain text,
   no label prefix. */
@media (max-width: 767px) {
  /* The wrapping .table-responsive div sets overflow-x:auto for the
     (still horizontal-scrolling) tables that don't opt into this
     component -- once a table IS stacked, that overflow clips any
     box-shadow that would otherwise bleed past the card's own border
     box, and reserves scroll room that has no reason to exist since
     nothing should overflow horizontally anymore. Neutralize it. */
  .table-responsive:has(> .ac-table-responsive) { overflow: visible; }

  .ac-table-responsive thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .ac-table-responsive, .ac-table-responsive tbody, .ac-table-responsive tr, .ac-table-responsive td {
    display: block;
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
  }
  .ac-table-responsive tr {
    margin-bottom: var(--ac-space-4);
    border: 1px solid var(--ac-border);
    border-radius: var(--ac-radius-lg);
    padding: var(--ac-space-2) var(--ac-space-4);
    background: var(--ac-surface);
  }
  .ac-table-responsive td {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    column-gap: var(--ac-space-4);
    row-gap: 2px;
    text-align: right;
    padding: var(--ac-space-2) 0 !important;
    border: none !important;
    border-bottom: 1px solid var(--ac-border) !important;
    max-width: 100%;
    overflow-wrap: anywhere;
  }
  .ac-table-responsive td:last-child { border-bottom: none !important; }
  .ac-table-responsive td[data-label]::before {
    content: attr(data-label);
    font-weight: 700;
    font-size: 12.5px;
    text-align: left;
    color: var(--ac-text-muted);
    flex-shrink: 0;
    max-width: 100%;
  }
  .ac-table-responsive td:not([data-label]) { text-align: center; }
  /* Action-column button groups: let them wrap instead of overflowing
     the card width when there are 2-3 buttons on a narrow screen. */
  .ac-table-responsive td .btn { margin: 2px; }
}

/* ---------- 24. dashboard & topbar mobile fixes ----------
   legacy/vendor CSS (style-main.css) mixes ~100 one-off breakpoints
   that don't line up with Bootstrap 3's 768/992/1200 tiers, so widgets
   drift out of sync with each other at real phone widths. This section
   is the last stylesheet loaded (see header.php), so it wins the
   cascade without having to touch that file. */

/* Safety net: on a template this old, one overflowing child (usually
   the topbar icon row) can force the whole page wider than the
   viewport, which reads as "everything is shifted/cut off" on mobile.
   Clip at the root only (not body): overflow set on the root element is
   applied to the viewport itself per spec, with no intermediate
   scroll container created -- setting it on body too would make body
   its own scrolling ancestor instead, which breaks position:sticky
   (used below for the mobile header) for anything inside it. */
@media (max-width: 767px) {
  html { overflow-x: hidden; }
}

/* The topbar carries two separate search UIs: the inline
   #header_search_form text box and the compact Ctrl+K command palette
   trigger (data-ac-cmdk-open). There isn't room for both once the
   sidebar-session label, currency/language pickers and notification
   icons are all fighting for the same row on a phone, so drop the
   wide inline box below tablet width -- the command palette covers
   the same student-search use case in a fraction of the space. */
@media (max-width: 991px) {
  #header_search_form.search-form { display: none; }
}

/* Dashboard grid: guarantee breathing room between stacked
   cards/boxes/charts on phones and small tablets even where the
   markup didn't add an explicit mb10 utility class. */
@media (max-width: 767px) {
  .content-wrapper .row > [class*="col-"] { margin-bottom: var(--ac-space-4); }
  .content-wrapper .row > [class*="col-"]:last-child { margin-bottom: 0; }
  .ac-hero-actions .btn { flex: 1 1 auto; text-align: center; }
}

/* ---------- 25. topbar icon buttons: consistent alignment ----------
   The topbar icons come from three different centering systems layered
   over time: plain <a> padding + line-height (AdminLTE's default, ~50px
   box), a fixed 34x34 flex box added later for .ac-theme-toggle only, and
   whatever line-height each icon glyph ships with (icon fonts carry
   internal leading that line-height alone doesn't strip). Centering some
   icons by padding/line-height and others by flexbox makes their glyphs
   land at different heights even inside boxes of the same height -- that
   mismatch is what reads as icons bobbing up/down across the row. Put
   every topbar icon button through the same fixed-size flex box, at one
   shared icon size, so they all share one visual baseline regardless of
   which system or glyph they started from. Also gives every button a
   consistent touch target, which the padding-based boxes didn't
   guarantee at the narrow paddings mobile needs.
   Zeroing out each <a>'s own padding also removes the ~15px/side gap
   Bootstrap's default nav-link padding used to provide BETWEEN icons --
   replace it with an explicit gap on the row itself (section 26 covers
   the row's own width/wrapping). */
.headertopmenu { gap: 6px; }
.headertopmenu > li { display: flex; align-items: center; }
.headertopmenu > li > a {
  display: inline-flex !important;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0 !important;
  line-height: 1;
  border-radius: var(--ac-radius-md);
}
.headertopmenu > li > a > i {
  font-size: 17px;
  line-height: 1;
}
.navbar-nav > .user-menu .topuser-image {
  margin-top: 0; /* was compensating for the old padding-centered box; the flex box above centers it without help */
}
/* Unread badges were tuned (top:8/right:5) against the old ~50-56px
   anchor box; pull them into the corner of the new, smaller fixed box. */
.todo-indicator { top: 2px; right: 2px; }

@media (max-width: 767px) {
  .headertopmenu { gap: 4px; }
  .headertopmenu > li > a {
    width: 34px;
    height: 34px;
  }
  .headertopmenu > li > a > i {
    font-size: 16px;
  }
}

@media (max-width: 360px) {
  .headertopmenu { gap: 2px; }
  .headertopmenu > li > a {
    width: 30px;
    height: 30px;
  }
  .headertopmenu > li > a > i {
    font-size: 15px;
  }
}

/* ---------- 26. topbar structural layout (brand / search / actions) ----------
   header.php's nav markup is a Bootstrap 3 two-column grid split
   (col-lg-4 brand / col-lg-8 search+icons) that only really works at the
   exact widths that split was tuned for -- at every other width the
   right column's own float-based children (the search form and the icon
   list, both float:right) fight each other for space inside a column
   that's a fixed 33/66 (or 25/75, or 20/80 depending on tier) split of
   the viewport regardless of how much either one actually needs. That's
   why the search box, the school name, and the icon row all crowd or
   clip independently of each other rather than sharing space.
   Replace the grid math with one flex row (ac-topbar) with three parts:
     .ac-topbar-brand  -- sidebar toggle's neighbor; shrinks, never wraps
     .ac-topbar-search -- flexes between a floor and a ceiling width
     .ac-topbar-actions -- icon row; fixed to its own content width
   These ac-topbar-* classes are added alongside the original Bootstrap
   grid classes in the markup (harmless, unused once flex is applied) so
   nothing else that might reference col-lg-4/col-lg-8 there breaks. */
.main-header .navbar.ac-topbar {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  padding: 0 16px;
  gap: 12px;
}
.ac-topbar > .sidebar-toggle { flex: 0 0 auto; }
.ac-topbar-brand {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  overflow: hidden;
  /* undo the Bootstrap grid column this div still carries */
  width: auto;
  float: none;
  padding: 0;
}
.sidebar-session {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* No font-size was ever set for this element anywhere in the app's
     CSS -- it silently inherited whatever size was live at its call
     site, which happened to look fine back when it sat inline next to
     the icons. Pin it explicitly now that it's a standalone label. */
  font-size: 15px;
}
.ac-topbar-right {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  width: auto;
  float: none;
  padding: 0;
}
.ac-topbar-row {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  width: 100%;
  float: none;
  margin: 0;
}
.ac-topbar-search {
  /* flex-grow:0 is the point -- this used to be `flex:1 1 320px`, which
     grew to eat every pixel of free space in the row (and on a wide
     screen there's a lot of it), squeezing .ac-topbar-actions down to
     whatever it left over. A search box has no reason to be 800px wide;
     give it a fixed, comfortable basis instead and only let it *shrink*
     under real pressure. */
  flex: 0 1 320px;
  min-width: 140px;
  max-width: 320px;
  margin: 0;
  padding: 0;
  border: none;
  float: none;
}
.ac-topbar-search .input-group { width: 100%; margin-top: 0; }
/* A pre-existing rule (max-width:200px, 768-1079px) was written against
   the old .search-form class alone, targeting both the <form> and the
   <input> that both carry it. It lands inside the one window where this
   form is still visible (>=992px, since it's hidden entirely below that
   -- see #header_search_form.search-form above) and would otherwise cap
   it under the width set above. Out-specificity it on both elements,
   scoped to that same window so it doesn't also fight the larger width
   set for >=1200px below. Left as-is outside that window since it still
   does useful work for other .search-form elsewhere in the app. */
@media (min-width: 992px) and (max-width: 1079px) {
  #header_search_form.ac-topbar-search { max-width: 320px; }
  .ac-topbar-search input.search-form { max-width: none; }
}
.ac-topbar-actions {
  flex: 0 0 auto;
  float: none;
  display: flex;
  align-items: center;
  gap: 10px;
}

@media (min-width: 1200px) {
  .main-header .navbar.ac-topbar { gap: 20px; padding: 0 20px; }
  /* Redeclare the whole shorthand, not just max-width: flex-basis stays
     320px (from the base rule above) unless restated here too, and with
     flex-grow:0 a bare max-width bump alone wouldn't actually make the
     box any wider. */
  .ac-topbar-search { flex: 0 1 420px; max-width: 420px; }
}

/* Tablet: the inline search box is already hidden below 992px (see
   #header_search_form.search-form above), so .ac-topbar-right just
   holds the icon row here -- give it a little less breathing room than
   full desktop so it doesn't look like it's floating in empty space. */
@media (min-width: 768px) and (max-width: 991px) {
  .main-header .navbar.ac-topbar { padding: 0 12px; gap: 8px; }
  .ac-topbar-row { gap: 8px; }
}

/* Mobile: the school name and the logo bar above the nav row are both
   dropped below tablet width -- the school name is still shown in the
   dashboard hero, so it isn't lost, just not duplicated in the topbar.
   With the brand column gone there's nothing left to fight the icon row
   for space, so this goes back to one compact row: hamburger on the
   left, icon actions on the right. */
@media (max-width: 767px) {
  .main-header .logo { display: none; }
  .main-header .navbar.ac-topbar {
    flex-wrap: nowrap;
    padding: 6px 10px;
    gap: 6px;
    /* Pinned, not left to "natural" sizing: the sidebar drawer (below)
       is position:fixed, so unlike .content-wrapper it can't rely on
       being pushed down by normal flow -- it needs an explicit
       padding-top that actually matches this row's real height. Fixing
       both ends to the same known number keeps them honest instead of
       two independent guesses that can drift apart again. */
    min-height: 56px;
  }
  .ac-topbar-brand { display: none; }
  .ac-topbar-right { flex: 1 1 auto; justify-content: flex-end; }
  .ac-topbar-row { justify-content: flex-end; gap: 4px; }
}

/* Mobile header height: style-main.css keeps .main-header permanently
   position:fixed (its own @media(max-width:767px){position:relative}
   attempt is dead code -- a later, unconditional .main-header{position:
   fixed} further down the same file wins by source order regardless of
   viewport) and compensates with a hardcoded .content-wrapper{padding-
   top:100px} guess. That guess was tuned for the OLD single-row layout
   accidentally wrapping; it doesn't match this file's real two-row
   .logo + .ac-topbar stack, so the fixed header now overlaps the page
   content instead of sitting above it (the dashboard hero card visibly
   showing through/behind the icon row).
   Fix the mismatch at its root instead of re-guessing a new pixel
   number: make the header sticky instead of fixed on mobile. Sticky
   still keeps it pinned while scrolling, but -- unlike fixed -- it stays
   in normal flow, so the browser reserves exactly however much height
   the header actually renders at, automatically, no matter how the logo
   or the two ac-topbar rows end up sized on a given phone/font. That
   also makes the .content-wrapper padding-top compensation unnecessary
   (and actively wrong, now a source of overlap on its own), so drop it
   to a small breathing gap instead of trying to match it to a height. */
@media (max-width: 767px) {
  .main-header {
    position: sticky;
    top: 0;
    max-height: none;
  }
  .content-wrapper {
    padding-top: 10px !important;
  }
}

/* ---------- 27. mobile sidebar: true overlay drawer, not a push ----------
   AdminLTE's stock mobile "open sidebar" behavior doesn't overlay the
   dashboard -- it PUSHES it: body.sidebar-open translates .content-wrapper
   240px to the right (the same distance the sidebar itself slides in), so
   the two end up sitting side-by-side sharing the screen, squeezing the
   dashboard into whatever's left (e.g. ~150px on a 390px phone). That's a
   two-column layout wearing an overlay's clothes. A real drawer leaves the
   content underneath at full width and only visually dims it.
   Cancel the push, then reproduce the overlay with a dimmed backdrop. No
   new JS is needed for click-outside-to-close: AdminLTE's own
   pushMenu.activate() (backend/dist/js/app.js) already binds a
   .content-wrapper click handler that removes body.sidebar-open on small
   screens -- the backdrop below is pointer-events:none specifically so
   clicks pass through to that existing handler instead of needing a
   second one. */
@media (max-width: 767px) {
  body.sidebar-open .content-wrapper,
  body.sidebar-open .right-side,
  body.sidebar-open .main-footer,
  body.sidebar-open .topaleart {
    transform: none;
  }

  /* A fixed 240px (the desktop rail's own width) doesn't leave much of
     the dimmed dashboard visible as an obvious "tap to close" affordance
     on small phones, and can crowd the very smallest ones. Cap it
     independently of the desktop rail width instead. */
  .main-sidebar,
  .left-side {
    width: min(300px, 82vw);
    /* The stock rules slide this by a hardcoded -240px/0, which would
       either leave a sliver of a narrower drawer on screen or overshoot
       a wider one in the closed state. -100% always matches whatever
       width is actually in effect above, at any phone size. */
    transform: translate(-100%, 0);
  }
  body.sidebar-open .main-sidebar,
  body.sidebar-open .left-side {
    transform: translate(0, 0);
  }

  /* Dim everything behind the open drawer. Anchored as the
     content-wrapper's own ::before so it needs no markup change; fixed
     positioning still targets the true viewport, since nothing between
     it and <html> carries a transform once the push above is cancelled.
     z-index sits above ordinary page content but below both the drawer
     itself (main-sidebar's pre-existing z-index:810) and the header
     (1030, see section 26) -- so the header stays visibly on top of the
     drawer exactly as it already did before this section, and the
     drawer stays on top of the dimmed page. */
  body.sidebar-open .content-wrapper::before {
    content: '';
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.35);
    z-index: 805;
    pointer-events: none;
  }

  /* Stop the dashboard underneath from scrolling independently while
     the drawer is open (the drawer's own nav list keeps its own
     internal scroll regardless, since it's a separate scroll
     container). */
  body.sidebar-open {
    overflow: hidden;
  }

  /* The drawer is position:fixed (anchored to the viewport, not to
     document flow), so its own padding-top is the only thing standing
     between it and the header -- style-main.css sets this to a
     hardcoded 100px, sized for the OLD header (logo bar + wrapped nav,
     often 100px+ tall). Now that the mobile header is a single pinned
     56px row with no logo (see above), that legacy value left a large
     dead gap above the drawer's search box and session/quick-links
     bar, making them look disconnected from the header rather than
     docked under it. Match it to the header's real height instead. */
  .main-sidebar,
  .left-side {
    padding-top: 56px;
  }
}
