/* =========================================================================
   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 ---------- */

/* Single source of truth for the sidebar's effective width, desktop only
   (<768px is the off-canvas drawer, handled separately in section 27 --
   it doesn't push the content at all, so it has no "current width" to
   derive from). `--ac-sidebar-current-width` is body-scoped and switches
   with the `sidebar-collapse` class; every box that needs to react to the
   sidebar's width (the sidebar itself, the content it displaces, the
   footer, the header's nav row, the header's logo slot) reads the SAME
   variable instead of each hardcoding its own copy of "238px" / "65px" --
   so they can never drift out of sync with each other again.
   This replaces two previously-separate bugs at once:
   - the sidebar's own expanded/collapsed width was already correct (the
     base theme's `.sidebar-mini.sidebar-collapse .main-sidebar` rule at
     65px !important out-specifies a plain `.main-sidebar` selector), but
   - `.content-wrapper`'s collapsed-width rule in the base theme
     (`.sidebar-mini.sidebar-collapse .content-wrapper { margin-left: 65px }`)
     has no !important, so the old unconditional `.content-wrapper {
     margin-left: var(--ac-sidebar-width) !important }` below permanently
     won that fight and pinned the content 238px from the left even while
     the sidebar had visually collapsed to 65px -- the "huge empty gap"
     bug. Deriving both from one variable makes that class of drift
     structurally impossible instead of chasing each symptom with another
     hardcoded override. */
@media (min-width: 768px) {
  body { --ac-sidebar-current-width: var(--ac-sidebar-width); }
  body.sidebar-collapse { --ac-sidebar-current-width: var(--ac-sidebar-collapsed-width); }

  .main-sidebar,
  .left-side { width: var(--ac-sidebar-current-width) !important; }
  .content-wrapper,
  .right-side,
  .main-footer { margin-left: var(--ac-sidebar-current-width) !important; }
  .main-header > .navbar { margin-left: var(--ac-sidebar-current-width) !important; }
  .main-header .logo { width: var(--ac-sidebar-current-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;
}

/* Three-part row -- [icon][label][chevron] -- as a real flex layout instead
   of the base theme's inline-content-plus-`float:right` chevron. The float
   approach only "works" as long as the label text is short enough to never
   reach the floated icon; a long label (or a sidebar squeezed narrower than
   expected) just flows text underneath/into it instead of stopping short,
   which read as the icon and label "colliding". Flex fixes the failure mode
   itself: the label is the only flexible part (`flex:1 1 auto; min-width:0`)
   and clips with an ellipsis rather than wrapping into the chevron's column,
   while the icon and chevron are fixed-size end caps that can never be
   pushed around by label length. */
.sidebar-menu > li > a {
  display: flex;
  align-items: center;
  gap: 8px;
  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);
}

/* margin-right: 0 overrides the shared `i:first-child { margin-right: 8px }`
   rule below -- the flex row's own `gap: 8px` already spaces icon from
   label, so without this the two would stack into a 16px gap here (while
   submenu items, which aren't flex, still need that margin-right to get
   any spacing at all -- so it can't just be deleted at the source). */
.sidebar-menu > li > a > .ac-nav-icon { flex: 0 0 auto; margin-right: 0; }

.sidebar-menu > li > a > .ac-nav-label {
  flex: 1 1 auto;
  min-width: 0;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* `.pull-right`'s float is inert on a flex child -- `margin-left: auto` is
   what actually pins the chevron to the row's far end here, the float class
   stays in the markup only so the base theme's existing collapse-mode
   `...> .pull-right { display: none !important }` rule still matches it. */
.sidebar-menu > li > a > .ac-nav-chevron {
  flex: 0 0 auto;
  margin-left: auto;
}

/* Collapsed rail: with the row now a flex container, `text-align: center`
   (the base theme's way of centering the lone visible icon) no longer has
   any effect -- flex layout ignores it. Center via justify-content instead
   so the icon still lands in the middle of the 65px rail. */
.sidebar-mini.sidebar-collapse .sidebar-menu > li > a {
  justify-content: center;
}

.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: var(--ac-sidebar-collapsed-width);
  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;
}

/* Scoped to .main-sidebar: this dark, semi-transparent styling is tuned for
   the dark sidebar's own student-search box. Unscoped, the same selector
   also matched the light topbar's #header_search_form (any .search-form
   descendant .form-control), rendering white text on a near-white
   background there -- see section 26a below for that box's own styling. */
.main-sidebar .search-form2 .form-control,
.main-sidebar .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);
}
.main-sidebar .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; }
}

/* ---------- 26a2. topbar "more" menu (mobile) ----------
   Search (Ctrl+K), theme toggle and branch-switch used to be three
   permanent top-level icons at every width, on top of the language/
   currency pickers, the notification bell, the todo checklist and the
   calendar/chat kebab -- on a phone that's 8+ separate controls
   fighting for one 56px-tall row (the reported "cramped mobile
   header"). Fold the three lowest-frequency ones into the existing
   calendar+chat kebab instead of adding a second overflow mechanism.
   NOT using the pre-existing d-lg-none/d-sm-block/d-sm-none utility
   classes (style-main.css) for this -- .d-lg-none fires display:none
   from 520px up with no upper bound while .d-sm-block only restores
   it below 640px, so anything relying on that pair alone is silently
   invisible for the entire ~641-991px window (confirmed by reading
   both rules; this is why the pre-existing calendar/chat kebab using
   that same pair could vanish on tablet). Give this its own single,
   unambiguous breakpoint matching .ac-topbar-brand's above instead. */
@media (min-width: 768px) {
  .ac-topbar-more-toggle { display: none !important; }
}
@media (max-width: 767px) {
  .ac-topbar-icon-desktop-only { display: none !important; }
}

/* ---------- 26a. topbar search: visual redesign ----------
   Previously this box was two visibly separate pieces: an outer
   .search-form .input-group pill (style-main.css: 1px border, radius 30)
   wrapping an <input> that carried its own Bootstrap .form-control border
   and square corners, plus a .topsidesearchbtn glued on the right with its
   own !important background/radius. Nothing clipped the button to the
   pill's curve, so the seam between input and button showed as a visible
   notch, and the button's own off-white background never quite matched the
   input's. Rebuild it as one bordered pill (overflow:hidden clips both
   children to the same curve, so there's no seam), themed with the same
   surface/border/text tokens as the rest of the topbar so it also renders
   correctly in dark mode (body.dark redefines these tokens -- see
   acages-tokens.css) instead of the sidebar's dark-only styling that used
   to leak in here (see the .main-sidebar-scoped rule above). */
.ac-topbar-search .input-group {
  display: flex;
  align-items: center;
  height: 38px;
  background: var(--ac-surface-2);
  border: 1px solid var(--ac-border);
  border-radius: var(--ac-radius-full);
  overflow: hidden;
  transition: border-color var(--ac-duration) var(--ac-ease), box-shadow var(--ac-duration) var(--ac-ease);
}
.ac-topbar-search .input-group:focus-within {
  border-color: var(--ac-primary);
  box-shadow: 0 0 0 3px rgba(var(--ac-primary-rgb), 0.15);
}
.ac-topbar-search input.form-control.search-form {
  flex: 1 1 auto;
  height: 100%;
  min-width: 0;
  background: transparent;
  border: none;
  box-shadow: none;
  color: var(--ac-text);
  font-size: 13px;
  padding: 0 4px 0 16px !important; /* beats main.css's input.form-control.search-form{padding-left:10px !important} */
}
.ac-topbar-search input.form-control.search-form:focus {
  background: transparent;
  border: none;
  box-shadow: none;
}
.ac-topbar-search input.form-control.search-form::placeholder { color: var(--ac-text-muted); }
.ac-topbar-search .topsidesearchbtn {
  flex: 0 0 auto;
  width: 38px;
  height: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent !important;
  border: none;
  border-radius: 0 !important;
  color: var(--ac-text-muted);
}
.ac-topbar-search .topsidesearchbtn:hover,
.ac-topbar-search .topsidesearchbtn:focus {
  background: var(--ac-surface) !important;
  color: var(--ac-primary);
}

/* 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 full-height overlay drawer ----------
   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/disables it.
   Cancel the push, then rebuild it as a proper overlay: fixed to the
   viewport (not `position:absolute; min-height:100%` of the page, which
   made the old drawer only as tall as the page's own content and
   dependent on `body{overflow:hidden}` alone to keep it from drifting
   with a scroll -- fixed positioning removes that dependency entirely),
   spanning the full dynamic viewport height, elevated above the app's own
   header (not just the dashboard) so the drawer's own header (below) is
   the only header visible while open, with a dimmed backdrop covering
   everything else -- header sliver included -- and closing on tap. */
@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;
  }

  /* .main-sidebar's own direct children are now [.ac-drawer-header,
     .search-form2, section.sidebar] stacked in plain block flow (the
     desktop rail never needed .main-sidebar itself to be a flex
     container -- only the .sidebar section inside it is, per section 3).
     Made a flex column here so the header/search stay their natural
     content height and section.sidebar (which sets itself to
     `height: 100%` in section 3) gets the remaining space via `flex: 1`
     instead of 100% of the WHOLE drawer -- without this, that 100%
     would ignore the header/search siblings above it and overflow the
     drawer's own height by however tall they are, silently clipping that
     much off the bottom of the scrollable nav list (this element's own
     `overflow: hidden`, added below for the rounded corners, would hide
     the overflow rather than showing it, making the bug easy to miss). */
  .main-sidebar,
  .left-side {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 0;
    left: 0;
    /* dvh tracks the browser's actual visible viewport (address bar
       collapsed or not); vh alone can leave a gap or an overshoot under
       it on mobile browser chrome. */
    height: 100dvh;
    min-height: 100dvh;
    padding-top: 0; /* the drawer owns its own header now (below) instead of tucking under the app's persistent topbar */
    /* Approximately 85-90vw, capped around 390px -- wide enough to read
       comfortably on a phone, narrow enough to always leave an obvious
       "tap here to close" sliver of the dimmed dashboard showing, and to
       stay a sensible fixed-ish width rather than ballooning on a
       7"-tablet's ~600-700px portrait viewport (still under this 768px
       breakpoint). The 280px floor matters at the small end: 88vw of a
       320px phone is already a comfortable 281px, but a smaller/zoomed
       viewport would otherwise let 88vw shrink the drawer below what a
       48px-tall nav row + icon + label can lay out without its own
       text needing to truncate any harder than it already does. */
    width: clamp(280px, 88vw, 390px);
    /* 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);
    /* Rounded leading edge + shadow so the drawer reads as its own
       elevated surface floating over the dimmed app, not a flush panel. */
    border-radius: 0 var(--ac-radius-xl) var(--ac-radius-xl) 0;
    box-shadow: var(--ac-shadow-lg);
    /* Deliberately NOT `overflow: hidden` here (even though it would also
       clip the corners): section 27a makes .sidebar's own background the
       same flat color as .main-sidebar's, so there's no seam to hide --
       and the Quick Links mega-dropdown (layout/top_sidemenu.php) is
       intentionally wider than this drawer on small screens (its own
       `.side-navbar-width { max-width: calc(100vw - 0px) }` at this same
       breakpoint, style-main.css). Clipping here would cut that dropdown
       off at the drawer's edge instead of letting it overlay the rest of
       the screen as designed. */
    /* Above the app header's own z-index:1030 (section 26) so the drawer
       sits above the header it's replacing while open -- but deliberately
       BELOW Bootstrap's own .modal-backdrop/.modal (1040/1050, bootstrap.
       min.css). The session-switch modal (#sessionModal) is reachable
       from a link inside this very drawer (top_sidemenu.php's "Current
       Session" row), so opening it while the drawer is still open is a
       real, easy-to-hit path, not just a hypothetical -- and a modal
       opened from inside the drawer should render on top of it, the same
       way it would from anywhere else in the app. 1035 threads that
       needle: above the header, below any Bootstrap modal. */
    z-index: 1035;
  }
  body.sidebar-open .main-sidebar,
  body.sidebar-open .left-side {
    transform: translate(0, 0);
  }
  .main-sidebar > .ac-drawer-header {
    flex: 0 0 auto;
  }
  .main-sidebar > .sidebar {
    flex: 1 1 auto;
    min-height: 0; /* let it shrink to the remaining space instead of its own `height: 100%` forcing the drawer taller than 100dvh */
  }

  /* Dim + disable everything behind the open drawer -- the dashboard AND
     the app header sliver still peeking out past the drawer's width, not
     just .content-wrapper. Anchored as the content-wrapper's own ::before
     so it needs no markup change; `position: fixed; inset: 0` renders it
     across the FULL viewport regardless of where .content-wrapper itself
     is laid out (behind the header included), and a click anywhere on a
     pseudo-element is dispatched with its host element as the target --
     so a tap anywhere in that dimmed area, header sliver included,
     already reaches AdminLTE's existing `.content-wrapper` click handler
     (app.js) that closes the drawer on small screens. That only works
     because this is `pointer-events: auto` now instead of the previous
     pass-through trick: with `none`, a tap here fell through to whatever
     REAL element sits underneath instead (a dashboard button, a header
     icon), which fired that element's own handler in addition to
     whatever bubbled -- the opposite of "background content must be
     non-interactive". z-index sits above the header (1030) so the sliver
     is actually dimmed too, but below both the drawer (1035) and any
     Bootstrap modal (1040/1050 -- see the drawer's own z-index comment
     above) so neither ends up underneath its own backdrop. */
  body.sidebar-open .content-wrapper::before {
    content: '';
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
    z-index: 1032;
    pointer-events: auto;
  }

  /* 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 AI assistant launcher (layout/ai_assistant_widget.php, #aiAssistantBtn)
     is a real, separately-gated feature (module + RBAC), not sidebar UI --
     but it's `position: fixed` at z-index:10500, floating above literally
     everything else including this drawer (1035), so with the drawer open
     it visually reads as a stray purple button sitting on top of the nav
     for no reason. Hide it (and its chat panel, in case it was left open)
     only while the drawer covers the screen; it's a plain display toggle
     keyed off body.sidebar-open, so it reappears the instant the drawer
     closes -- nothing about the feature itself changes. */
  body.sidebar-open #aiAssistantBtn,
  body.sidebar-open #aiAssistantPanel {
    display: none !important;
  }
}

/* ---------- 27a. mobile drawer: light surface, not the dark desktop rail
   ----------
   The sidebar is deliberately dark on desktop (see section 3's own
   comment) -- a full-height icon rail reads fine that way. A full-screen
   mobile takeover reads better as a clean light surface instead (per the
   reference redesign), so every consumer of --ac-sidebar-* is
   re-themed at once by overriding the tokens themselves on .main-sidebar,
   rather than re-deriving light-mode colors rule-by-rule for the menu,
   session card, search boxes, etc. below.
   !important on every one of these: a tenant's saved Sidebar Appearance
   colors (Schsettings::savesidebarappearance()) are injected as a plain,
   non-!important `:root { --ac-sidebar-*: <custom> }` block later in
   <head> (see ac_sidebar_theme_css() in custom_helper.php) -- later
   source order would otherwise let that plain declaration beat this one
   at equal (.main-sidebar vs :root) specificity. The desktop dark rail
   stays fully customizable; the mobile drawer intentionally always
   overrides back to its own light palette regardless of that setting. */
@media (max-width: 767px) {
  .main-sidebar {
    --ac-sidebar-bg: var(--ac-surface) !important;
    --ac-sidebar-bg-2: var(--ac-surface) !important;
    --ac-sidebar-text: var(--ac-text) !important;
    --ac-sidebar-text-muted: var(--ac-text-muted) !important;
    --ac-sidebar-heading: var(--ac-text-faint) !important;
    --ac-sidebar-border: var(--ac-border) !important;
    --ac-sidebar-hover-bg: var(--ac-surface-2) !important;
    --ac-sidebar-hover-text: var(--ac-primary-700) !important;
    --ac-sidebar-hover-icon: var(--ac-primary-600) !important;
    --ac-sidebar-active-bg: var(--ac-primary-50) !important;
    --ac-sidebar-active-text: var(--ac-primary-700) !important;
    --ac-sidebar-active-icon: var(--ac-primary-600) !important;
  }
  /* Section 3's gradient formula still color-mixes in the brand color even
     with the tokens above flipped to light -- replace it with the flat
     surface outright so the drawer reads as a clean, single-tone card. */
  .main-sidebar,
  .main-sidebar .sidebar {
    background: var(--ac-surface) !important;
  }
  /* .sidebar's own opaque background (just set above) reaches .main-sidebar's
     bottom-right corner (it fills the remaining height below the header/
     search, per the flex-column rule above) -- without a matching radius
     here, its square corner would poke out past the drawer's rounded one,
     an odd light notch against the dark backdrop behind it. The other
     three corners don't need this: the top two belong to .ac-drawer-header,
     which is transparent and lets .main-sidebar's own (already-rounded)
     background show through directly, and the bottom-left corner isn't
     rounded in the first place (flush against the screen edge). */
  .main-sidebar > .sidebar {
    border-radius: 0 0 var(--ac-radius-xl) 0;
  }
}

/* ---------- 27b. mobile drawer header ----------
   Logo + app name + tagline + close button -- the drawer's own identity
   row, replacing reliance on the app's persistent topbar (now covered by
   the drawer, see 27's z-index) while open. Markup: layout/sidebar.php
   .ac-drawer-header. Hidden entirely outside mobile: desktop already has
   this same identity in .main-header, itself outside the sidebar. */
.ac-drawer-header { display: none; }
@media (max-width: 767px) {
  .ac-drawer-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--ac-space-2);
    flex: 0 0 auto;
    /* Compact -- this row plus the search field below it should read as
       one tight identity block, not consume a quarter of a 320px-tall
       phone's visible drawer before any navigation shows. */
    padding: max(10px, env(safe-area-inset-top)) var(--ac-space-4) 10px;
    border-bottom: 1px solid var(--ac-border);
  }
  .ac-drawer-header-brand {
    display: flex;
    align-items: center;
    gap: var(--ac-space-2);
    min-width: 0;
  }
  .ac-drawer-header-logo {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    border-radius: var(--ac-radius-sm);
    object-fit: contain;
    background: var(--ac-surface-2);
    padding: 3px;
  }
  .ac-drawer-header-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.2;
  }
  .ac-drawer-header-name {
    font-family: var(--ac-font-display);
    font-weight: 700;
    font-size: 14.5px;
    color: var(--ac-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .ac-drawer-header-tagline {
    font-size: 11px;
    color: var(--ac-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .ac-drawer-close {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border: none;
    border-radius: var(--ac-radius-md);
    background: var(--ac-surface-2);
    color: var(--ac-text-muted);
    font-size: 14px;
  }
  .ac-drawer-close:hover,
  .ac-drawer-close:focus {
    background: var(--ac-border);
    color: var(--ac-text);
  }
}

/* ---------- 27c. mobile drawer: one search field, not two ----------
   True DOM separation, not one shared element repositioned by breakpoint:
   layout/sidebar.php now renders TWO independent filter boxes --
   .ac-sidebar-filter (desktop's original, untouched, still inside
   .ac-sidebar-scroll exactly as it was before this redesign, still
   scrolling away with the nav on every breakpoint) and
   .ac-sidebar-filter-mobile (a second copy sitting before the drawer
   header's session card, mobile-only). Each is a plain, unconditional
   display:none/flex toggle by breakpoint -- neither one's rules touch
   the other, so nothing here can leak a mobile behavior onto desktop's
   copy or vice versa. Both share the input's class
   (.ac-sidebar-filter-input) purely so acages-ui.js's existing
   initSidebarFilter (bound by class, filters the one shared nav list)
   keeps working for whichever copy is actually visible -- the hidden one
   is inert, not fought over.
   The drawer previously ALSO showed the real student-name search
   (search-form2 -- posts to admin/admin/search) stacked above this,
   which is why that one is hidden here instead of duplicated: the
   topbar's command palette trigger (header.php .ac-cmdk) stays visible
   at every width and already falls back to that exact same
   admin/admin/search for free-text queries with no command match, so
   it's still reachable without a second copy of it. */
@media (max-width: 767px) {
  .main-sidebar .search-form2 {
    display: none;
  }
  /* Desktop's original filter -- hidden on mobile; the -mobile copy
     below takes over there instead. */
  .main-sidebar > .sidebar .ac-sidebar-scroll > .ac-sidebar-filter {
    display: none;
  }

  /* No `order` needed: it's a direct child of .main-sidebar's flex column
     (section 27), positioned in the DOM right after .ac-drawer-header and
     before <section class="sidebar"> -- its natural source order already
     puts it exactly where it needs to be (header, then search, then
     session card/nav). */
  .ac-sidebar-filter-mobile {
    display: block;
    flex: 0 0 auto;
    margin: 0;
    padding: 0 var(--ac-space-4) var(--ac-space-3);
  }
}
/* Mobile-only copy: hidden by default (any width), shown only inside the
   max-width:767px block above -- same on/off pattern as .ac-drawer-header. */
.ac-sidebar-filter-mobile {
  display: none;
}

/* Search-icon and input styling: identical for either copy (both use the
   same .ac-sidebar-filter-input), but this whole ruleset only ever
   matters on mobile since only one copy is ever visible there. */
@media (max-width: 767px) {
  .ac-sidebar-filter i {
    color: var(--ac-text-muted) !important;
    left: calc(var(--ac-space-4) + 12px);
    font-size: 13px;
  }
  .ac-sidebar-filter-input {
    height: 44px;
    background: var(--ac-surface-2) !important;
    border: 1px solid var(--ac-border) !important;
    color: var(--ac-text) !important;
    border-radius: var(--ac-radius-full) !important;
    padding: 8px 16px 8px 40px;
  }
  .ac-sidebar-filter-input::placeholder { color: var(--ac-text-muted) !important; }
}

/* ---------- 27d. mobile drawer: session + quick-links card ----------
   layout/top_sidemenu.php already renders "Current Session" and "Quick
   Links" as two <li> inside one shared <ul class="sessionul"> -- the
   reference's unified card with an internal divider is that same markup,
   just given a real border/shadow so it reads as one distinct surface
   against the now-equally-light drawer background (the existing
   `background: var(--ac-primary-50)` card tint that used to pop against
   the DARK desktop rail has almost no contrast against the mobile
   drawer's own light surface without one). No "Liens rapides" nav item
   exists anywhere else -- this <li> inside this card is its only home. */
@media (max-width: 767px) {
  .sessionul.fixedmenu,
  .sessionul {
    border: 1px solid var(--ac-border);
    border-radius: var(--ac-radius-lg);
    box-shadow: var(--ac-shadow-sm);
    margin: var(--ac-space-3) var(--ac-space-4) !important;
    /* Explicitly NOT `overflow: hidden` (it was here before -- that's the
       actual bug this section fixes, see 27f below): the card's own
       rounded corners never needed it in the first place (its background
       is one flat color painted directly on this <ul>'s own box, which
       already respects its own border-radius with no clipping required),
       but it had the side effect of clipping the Quick Links dropdown-
       menu -- a child of this same <ul> -- to this small card's box the
       instant it opened, instead of letting it render at its own natural
       size. 27f moves that dropdown out to a proper bottom sheet instead
       of relying on this element to contain it, so nothing here needs to
       clip its children at all. */
    height: auto;
    min-height: 0;
  }
  /* :not(:last-child), not :first-child -- "Current Session" only renders
     with the quick_session_change privilege (top_sidemenu.php), so
     without it "Quick Links" becomes the lone, first AND last child; a
     first-child rule would then draw a pointless divider under the only
     row in the card. */
  .sessionul.fixedmenu > li:not(:last-child) > a,
  .sessionul > li:not(:last-child) > a {
    border-bottom: 1px solid var(--ac-border);
  }
  .sessionul.fixedmenu > li > a,
  .sessionul > li > a {
    padding: 14px !important;
  }
  /* The dropdown-menu (27f) is this <li>'s child but no longer positioned
     relative to it (position:fixed there) -- .dropdown's own
     `position: relative` from bootstrap.min.css is harmless to leave, but
     make sure this row itself never becomes a clipping/stacking trap for
     its now-detached child. */
  .sessionul .mega-dropdown {
    overflow: visible;
  }
}

/* ---------- 27f. mobile drawer: Quick Links as a bottom sheet ----------
   ROOT CAUSE of the clipped/warped Quick Links panel: this dropdown-menu
   (layout/top_sidemenu.php, .mega-dropdown > .dropdown-menu.verticalmenu)
   is Bootstrap's stock `position: absolute`, sized and positioned relative
   to its own small trigger <li> inside the session card -- the DESKTOP
   layout gives it room to escape sideways into open space, but on this
   narrow mobile drawer there IS no open space to escape into, so its
   panel rendered squeezed inside/against the session card's own tiny box.
   27d's card `overflow: hidden` (now removed) was papering over exactly
   this: without it the panel just overflowed the card instead of being
   clipped by it, but either way it was never a real, independent menu --
   it was laid out AS IF it were still content of the card.
   Fix: detach it from that layout entirely. `position: fixed` takes the
   panel out of the card's box model altogether (its offsets become
   viewport-relative, not "relative to the small <li> inside a card"), so
   it can be given its own deliberate size and position -- here, a bottom
   sheet -- completely independent of whatever the trigger's own box looks
   like. This is the same technique already used for the drawer itself
   (section 27) and its backdrop: a fixed-position element establishes its
   own layout, unconstrained by an ancestor's clipping or sizing. */
@media (max-width: 767px) {
  /* The trigger row itself: no longer needs to be a Bootstrap "relative"
     anchor for an absolutely-positioned child, and gets its own pressed/
     active feedback + an icon that visually flips to signal "open". */
  .mega-dropdown > .dropdown-toggle {
    /* padding: 14px already comes from 27d's shared
       `.sessionul > li > a` rule -- just the flex row here. */
    display: flex !important;
    align-items: center;
    justify-content: space-between;
  }
  .mega-dropdown > .dropdown-toggle:active {
    background: var(--ac-surface-2);
  }
  .mega-dropdown.open > .dropdown-toggle .fa-th {
    color: var(--ac-primary-600);
  }

  .mega-dropdown .dropdown-menu.verticalmenu {
    /* Escapes the session card's box model entirely -- see comment above. */
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    margin: 0;
    width: 100%;
    max-width: 100%;
    /* Never taller than most of the screen, and always scrollable on its
       own if the content (Quick Links list) runs long -- this sheet has
       its own scroll container, entirely separate from both the
       dashboard's scroll (locked, section 27) and the drawer nav's own
       scroll (section 27e), so none of the three ever fight each other. */
    max-height: min(70vh, calc(100dvh - 100px));
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    border: none;
    border-radius: var(--ac-radius-xl) var(--ac-radius-xl) 0 0;
    box-shadow: var(--ac-shadow-lg);
    padding: var(--ac-space-5) var(--ac-space-4) max(var(--ac-space-5), env(safe-area-inset-bottom)) !important;
    /* Above the drawer (1035) and its own backdrop below (1042), below any
       Bootstrap modal (1040 is already spoken for by 27's backdrop
       comment -- 1043 keeps a clear gap from both). */
    z-index: 1043;
  }
  /* The small triangular pointer that connects a normal dropdown to its
     trigger reads as a stray mark on a full-width sheet with no single
     anchor point to point at. */
  .mega-dropdown .dropdown-menu.verticalmenu::before {
    display: none;
  }
  /* This wrapper's own `margin-left: var(--bs-side-menu-width)` (style-
     main.css) assumes it's still sitting next to the DESKTOP sidebar --
     meaningless (and would shove the sheet's content sideways, off-
     screen) once the sheet is a full-width fixed panel. */
  .mega-dropdown .side-navbar-width {
    margin-left: 0;
    max-width: 100%;
    padding-left: 0;
    padding-right: 0;
  }
  /* A small drag-handle affordance at the top, the same visual language
     phones use for bottom sheets, so it reads as "swipe/tap away" rather
     than a bare rectangle. Purely decorative -- closing still works via
     the backdrop tap below or by tapping the trigger again (Bootstrap's
     own dropdown-toggle behavior, untouched). */
  .mega-dropdown .dropdown-menu.verticalmenu::after {
    content: '';
    position: absolute;
    top: 10px;
    left: 50%;
    transform: translateX(-50%);
    width: 36px;
    height: 4px;
    border-radius: var(--ac-radius-full);
    background: var(--ac-border-strong);
  }

  /* Dimmed backdrop behind the sheet, same mechanism as the drawer's own
     (section 27): a fixed, full-viewport pseudo-element anchored to the
     trigger <li> so opening/closing it needs no extra markup or JS --
     `.mega-dropdown.open` is the exact class Bootstrap's dropdown plugin
     already toggles. Clicking it dispatches a click targeting
     `.mega-dropdown` itself, which is outside `.dropdown-menu` -- exactly
     what Bootstrap's own document click-handler (bootstrap.min.js,
     dropdown.js) already treats as "clicked outside, close the open
     dropdown", so tap-to-close needs no new JS here either. */
  .mega-dropdown.open::after {
    content: '';
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
    z-index: 1042;
  }

  /* Quick Links' own internal grid of cards: comfortable full-width
     touch targets instead of the desktop's dense multi-column layout
     (style-main.css already collapses .card-columns-sidebar to a single
     column under 768px -- this just gives that single column proper
     card styling to match the sheet it now lives in). */
  .mega-dropdown .card-sidebar {
    margin-bottom: var(--ac-space-4);
  }
  .mega-dropdown .side-navbar-vertical ul {
    border-bottom: none;
    margin: 0;
    padding: 0;
  }
  .mega-dropdown .side-navbar-vertical ul li a {
    padding: 12px 14px !important;
    border-radius: var(--ac-radius-md);
  }
  .mega-dropdown .side-navbar-vertical ul li a:active {
    background: var(--ac-surface-2);
  }
}

/* ---------- 27e. mobile drawer: nav rows ----------
   Comfortable touch targets (48-52px) as a clean, flat LIST -- not the
   desktop rail's individually-margined, individually-rounded rows (each
   one a separate floating pill) carried over as-is, which read as a
   stack of small cards rather than one list. Full-width rows + a hairline
   separator between them instead: same [icon][label][chevron] flex row
   from section 3 (shared with desktop, untouched there), just with the
   per-row margin/radius zeroed out here and a border-bottom standing in
   for the "separation" that margin used to provide. The active row still
   reads as distinct via its own background + left accent bar (section 3,
   automatically re-themed light by 27a's token overrides) -- it doesn't
   need rounded corners to do that. */
@media (max-width: 767px) {
  .sidebar-menu > li > a {
    min-height: 48px;
    padding: 13px 16px !important;
    margin: 0;
    border-radius: 0;
    border-bottom: 1px solid var(--ac-border);
    font-size: 14.5px;
  }
  .sidebar-menu > li.header {
    padding: 16px 16px 6px !important;
    font-size: 11.5px;
  }
  .sidebar-menu .treeview-menu {
    margin: 0;
    border-radius: 0;
  }
  .sidebar-menu .treeview-menu > li > a {
    min-height: 44px;
    display: flex;
    align-items: center;
    margin: 0;
    border-radius: 0;
  }
  /* Leave breathing room below the last row for the phone's home-indicator
     / gesture bar instead of the last item sitting flush against it. */
  .ac-sidebar-scroll {
    padding-bottom: max(var(--ac-space-4), env(safe-area-inset-bottom));
  }
}
