/* The vendor theme's own common.css already sets body's font-family to
   "Manrope" (light/common.css:21052), and the font files themselves
   have been sitting in public/fonts/Manrope/ all along — but nothing
   anywhere ever declared an @font-face for it, so that rule was
   silently falling through to the sans-serif fallback (the OS system
   font) on every page. This actually loads it, so the app renders in
   the font it was already designed for instead of a generic stand-in. */
@font-face {
    font-family: "Manrope";
    src: url("../fonts/Manrope/Manrope-Regular.woff2") format("woff2"),
         url("../fonts/Manrope/Manrope-Regular.woff") format("woff");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: "Manrope";
    src: url("../fonts/Manrope/Manrope-Medium.woff2") format("woff2"),
         url("../fonts/Manrope/Manrope-Medium.woff") format("woff");
    font-weight: 500;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: "Manrope";
    src: url("../fonts/Manrope/Manrope-SemiBold.woff2") format("woff2"),
         url("../fonts/Manrope/Manrope-SemiBold.woff") format("woff");
    font-weight: 600;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: "Manrope";
    src: url("../fonts/Manrope/Manrope-Bold.woff2") format("woff2"),
         url("../fonts/Manrope/Manrope-Bold.woff") format("woff");
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: "Manrope";
    src: url("../fonts/Manrope/Manrope-ExtraBold.woff2") format("woff2"),
         url("../fonts/Manrope/Manrope-ExtraBold.woff") format("woff");
    font-weight: 800;
    font-style: normal;
    font-display: swap;
}

/* Deliberately NOT the .d-flex utility (display: flex !important) — our
   own body.app-sidebar-collapsed rule below hides this exact element
   when the sidebar is collapsed to icon-only, and an !important flex
   here would outrank that non-important display:none regardless of
   specificity, forcing the full logo to stay visible alongside the
   mini one (the double-logo bug). Plain, lower-specificity display
   here lets that rule win when it needs to. */
.navbar-brand.brand-logo {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 800;
  letter-spacing: -0.01em;
}
/* A two-tone, single-line logotype (GSMArena/TechCrunch-style — the
   word part and the acronym part run together on one line in two
   different colors/weights instead of looking like plain, uniform
   text) rather than the plain single-color wordmark the vendor theme's
   .navbar-brand originally had. The word/acronym split (see
   brandWordmarkParts() in app/helpers.php) drives which part gets
   which color; a name that doesn't split cleanly just renders as one
   plain-colored line1 with no line2 — see that helper's fallback.
   .navbar-brand-wrapper turned out to have real slack to its right
   (the welcome-text sibling has its own left padding) rather than
   being a hard boundary, so this is sized to actually use that room
   instead of being squeezed into an artificially tiny box. Colors are
   picked to match the book-mark icon's own palette (navy pages, gold
   ribbon) so the icon and wordmark read as one cohesive mark; dark
   mode gets lighter equivalents of the same two colors below. */
.brand-wordmark {
  display: inline-flex;
  align-items: baseline;
  font-size: 1.1rem;
  font-weight: 800;
  letter-spacing: -0.02em;
}
/* No overflow/max-width truncation here on purpose — this has now
   clipped real content twice already from two different causes (first
   text-overflow:ellipsis silently failing on a flex container and
   hard-cropping instead; then, once moved to these line spans,
   overflow:hidden combined with the vendor's line-height:1 clipping
   the natural vertical "overshoot" round letters like the S in "AMS"
   are drawn with, for optical correction against flat-topped letters).
   brandWordmarkParts() only ever produces short pieces from an
   operator-controlled APP_NAME, not arbitrary user input, so there's
   nothing realistic to truncate — .brand-attribution's own hide-below-
   xl rule (below) is the actual width safety net for this whole area. */
.brand-wordmark-line1,
.brand-wordmark-line2 {
  display: inline-block;
  white-space: nowrap;
  vertical-align: baseline;
}
.brand-wordmark-line1 {
  color: #223874;
}
.brand-wordmark-line2 {
  /* Was the same gold (#b8842c) as the roster-medal accent — out of
     step with the rest of the UI, which is built around this app's
     blue palette (see the brand-colors reference). "AMS" now uses the
     brand's own light blue instead, distinct enough from "Watan"'s
     deep navy (.brand-wordmark-line1, above) to keep the two-tone
     effect, just within the blue family instead of navy+gold. */
  color: #3457d5;
}
/* Flat, single-gradient rounded-square "app badge" — the Linear/Vercel-
   style icon tile that replaced the old 3D page-flip book drawing as
   the logo. One plain diagonal gradient, one flat white glyph, no
   shadow/depth layered on — sized entirely off its own font-size so
   one rule serves the full navbar logo, the collapsed icon-only
   sidebar, and the auth-page hero alike. Gradient stops reuse this
   app's own established brand navy (the old book drawing's page
   colors) so the icon doesn't visually disagree with the wordmark
   next to it. The loading spinner further down (.brand-tile-stack)
   reuses these same colors in its own animated card-stack rather than
   this exact class, since it needs extra layered "cards" this static
   badge doesn't. */
.brand-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.9em;
  height: 1.9em;
  border-radius: 0.45em;
  background: linear-gradient(135deg, #3457d5, #1c2e6b);
  vertical-align: middle;
  flex-shrink: 0;
}
.brand-tile i {
  color: #fff;
  font-size: 0.62em;
  line-height: 1;
}
.brand-logo .brand-tile {
  font-size: 22px;
}
/* The collapsed icon-only brand box is small in every context that
   shows it — the mobile navbar-brand-wrapper is only 55px wide by
   ~46px tall (55px width from the vendor's own <991px rule; 46px
   height from this file's own 70px-tall/12px-padding navbar override
   above), and the desktop icon-only-sidebar wrapper isn't much roomier
   either. 30px (a ~57px tile, taller than the mobile box itself) had
   the tile overflow and get clipped by its own container — this
   showed up as the icon looking "cut off"/malformed once the sidebar
   was collapsed or the viewport went to mobile width. 20px keeps the
   tile (~38px) safely inside the tightest of those boxes, matching
   the scale the old .book-mark icon used successfully in this exact
   spot (19px font-size, ~39px tall) before it was replaced. */
.brand-logo-mini .brand-tile {
  font-size: 20px;
}
/* Login/account-setup pages show the same tile at hero size, not the
   small navbar one — .auth .brand-logo is more specific than the plain
   .brand-logo rule above, so this wins for that context only. */
.auth .brand-logo .brand-tile {
  font-size: 52px;
}
/* Branded loading state — a card-stack version of the .brand-tile logo
   (see partial/loading-book.blade.php), continuously flipping over its
   own left edge like someone flicking through index cards in a filing
   cabinet while a record is looked up, instead of a generic spinner.
   Used wherever the app needs a "working on it" moment (DataTables
   init, an AJAX roster fetch, a full-page navigation — see the
   .page-transition-loader rules below) — see the Students list for a
   reference usage. */
.brand-loader {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 48px 16px;
}
.brand-tile-stack {
  position: relative;
  display: inline-block;
  width: 1.9em;
  height: 1.9em;
  font-size: 44px;
  perspective: 400px;
}
/* Two faint, static tiles peeking out from behind the animated front
   one — purely a "this is a stack of cards" cue, they don't move
   themselves, only the front card (.brand-tile-flip) does. Each one
   carries its own copy of the icon too (see loading-book.blade.php),
   so whichever card the flip currently reveals is already showing the
   logo rather than a blank card turning to reveal the mark only once
   the front one settles. */
.brand-tile-card {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 0.45em;
  background: linear-gradient(135deg, #3457d5, #1c2e6b);
}
.brand-tile-card i {
  color: #fff;
  font-size: 0.62em;
  line-height: 1;
}
.brand-tile-card-2 {
  transform: translate(8%, 12%) scale(0.92);
  opacity: 0.28;
}
.brand-tile-card-1 {
  transform: translate(4%, 6%) scale(0.96);
  opacity: 0.55;
}
.brand-tile-stack .brand-tile {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
}
.brand-tile-flip {
  transform-style: preserve-3d;
  transform-origin: left center;
  box-shadow: 0 6px 16px rgba(20, 30, 70, .28);
  animation: brand-tile-flip 1.9s ease-in-out infinite;
}
/* A card flipping shut to the left over its own left (hinge) edge,
   holding briefly edge-on at the midpoint — like a card thin enough to
   nearly vanish mid-turn — before swinging the rest of the way around
   and settling. One full 360deg loop reads as two consecutive "flip to
   the next card" motions rather than a plain spin, closer to searching
   a card catalog than a wheel-style spinner. */
@keyframes brand-tile-flip {
  0%, 6% { transform: rotateY(0deg); }
  38% { transform: rotateY(-172deg); }
  50% { transform: rotateY(-180deg); }
  62% { transform: rotateY(-188deg); }
  94%, 100% { transform: rotateY(-360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .brand-tile-flip {
    animation: none;
  }
}
/* Full-page navigation loader (see partial/page-transition-loader
   .blade.php, included once from layouts/app.blade.php) — this app's
   list/form pages are deliberately plain GET-and-reload/POST-and-redirect
   navigations, not AJAX (see this project's own notes on why: an
   earlier AJAX-driven attendance page broke on a datepicker quirk). A
   real, in-page navigation still has a "did my click register, is
   something happening" gap between click and the next page painting —
   this overlay covers exactly that gap with the same branded book-flip
   animation instead of just the browser's own tab spinner. Fixed to the
   viewport (not a container) since it needs to cover the whole page
   regardless of scroll position or which page triggered it. */
/* Visible by default (not toggled on via JS) — this is what covers a
   plain browser refresh (F5) too, not just an in-app link/form
   navigation: the very first thing painted on any full document load is
   this overlay, masking whatever flash of unstyled/half-rendered
   content would otherwise show while the page settles in, exactly the
   same "anti-flash" idea as the inline dark-mode theme script in every
   layout's <head>. Hidden by JS once *this* page's own `load` event
   fires (see partial/page-transition-loader.blade.php) — which happens
   for every full document load regardless of how it was triggered. */
.page-transition-loader {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(2px);
  opacity: 1;
  transition: opacity 0.2s ease;
}
.page-transition-loader.is-hidden {
  opacity: 0;
  pointer-events: none;
}
[data-bs-theme="dark"] .page-transition-loader {
  background: rgba(26, 29, 33, 0.85);
}

/* Bootstrap's own .fixed-top utility (already on this <nav>, via
   navbar.blade.php's class list) hardcodes z-index:1030 on the element
   itself. Combined with position:fixed, that makes <nav> its own
   stacking context — meaning NOTHING inside it, no matter how high a
   z-index is set on that descendant (see .scroll-progress-track
   above), can ever paint above an element outside <nav> once the
   comparison against .app-sidebar's z-index:1040 happens at the <nav>
   level first and loses. This is the actual fix for the scroll-
   progress handle rendering underneath the sidebar wherever it pokes
   a few px below the navbar's own bottom edge — raising the whole
   navbar's own stacking level above the sidebar's, not just one
   descendant's. */
.navbar.fixed-top {
  z-index: 1050;
}

/* Shrink the navbar to its "scrolled" compact height (70px) permanently,
   instead of only after scrolling past 97px (template.js's .headerLight
   toggle, which only ever changed background/shadow, never height —
   the height drop only ever happened via .navbar.headerLight's own
   height:70px rule further down in style.css). Every place elsewhere in
   this vendor theme that hardcodes the navbar's original 97px height for
   layout math (sidebar/content offset, mobile off-canvas, mobile dropdown
   position) has to move to 70px too, or content would start too low,
   leaving a gap under the now-shorter navbar. */
.navbar .navbar-brand-wrapper {
  height: 70px;
  padding: 12px 24px;
  /* Anchor for .brand-toggle below — the collapse control is pinned to
     this box's own right edge rather than sharing the flex row with
     the logo (see navbar.blade.php's comment on why: it used to live
     inline and fight the icon for space in the ~70px collapsed box). */
  position: relative;
}
@media (max-width: 991.98px) {
  .navbar .navbar-brand-wrapper {
    padding: 12px 0;
    /* The vendor's own mobile rule (width:55px, brand-logo hidden/
       brand-logo-mini forced) still applies unconditionally here — it
       has nothing to do with our own app-sidebar-collapsed state
       below, but the icon still needs centering since the wrapper's
       inline justify-content-start (navbar.blade.php) would otherwise
       leave it flush left. */
    justify-content: center !important;
  }
  /* The collapse-to-icons toggle is a desktop-only concept — mobile
     already forces icon-only brand display unconditionally via the
     vendor's own width/display rules above, and the sidebar itself is
     either fully open or fully off-screen at this width (see the
     mobile .app-sidebar rules further down), so a control that
     "collapses to icons" has nothing to do here. */
  .brand-toggle {
    display: none;
  }
}
/* Desktop "collapse sidebar to icons only" — our own body class
   (app-sidebar-collapsed, toggled by navbar.blade.php's script), not
   the vendor's sidebar-icon-only. Reclaims the brand-wrapper's full
   70px-wide box and centers the icon (the row's only child once the
   toggle is out of it — see .brand-toggle below), same fix as before,
   just keyed off a class the vendor's own scripts can't also react to. */
@media (min-width: 992px) {
  body.app-sidebar-collapsed .navbar .navbar-brand-wrapper {
    /* This was the actual bug behind "the toggle button doesn't move
       when collapsed": the vendor's base rule (style.css) hardcodes
       this wrapper to 220px, and only ITS OWN .sidebar-icon-only class
       (which this app never applies) shrinks it to 70px. Without an
       explicit width here, app-sidebar-collapsed centered the icon
       inside a box that was still 220px wide, so .brand-toggle — which
       is anchored via `right: -13px` off this wrapper's own edge —
       stayed sitting at the expanded-width position instead of
       following the sidebar's real 70px collapsed edge. */
    width: 70px !important;
    padding: 0 !important;
    justify-content: center !important;
  }
  /* Vendor's own base rules hide .brand-logo-mini and show .brand-logo
     by default (style.css) — this is the collapsed-state flip that
     .sidebar-icon-only used to drive; app-sidebar-collapsed drives it
     now instead, so the two logo variants still swap correctly. */
  body.app-sidebar-collapsed .navbar-brand-wrapper .brand-logo {
    display: none;
  }
  body.app-sidebar-collapsed .navbar-brand-wrapper .brand-logo-mini {
    display: inline-block;
  }
}
/* The edge-anchored collapse control itself — a small floating circle
   on the brand-wrapper's own right border, present in both expanded
   and collapsed states so there's exactly one toggle location to learn,
   not two. Colors are theme-neutral here; light/dark surface tints
   live in dark.css, same split as everywhere else in this app. */
.brand-toggle {
  position: absolute;
  top: 50%;
  right: -13px;
  transform: translateY(-50%);
  width: 26px;
  height: 26px;
  padding: 0;
  border-radius: 50%;
  background: #fff;
  border: 1px solid #dee2e6;
  box-shadow: 0 2px 6px rgba(20, 24, 40, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #6b7280;
  cursor: pointer;
  z-index: 5;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.brand-toggle:hover {
  color: #223874;
  border-color: #223874;
}
.brand-toggle:focus-visible {
  outline: 2px solid #3457d5;
  outline-offset: 2px;
}
.brand-toggle i {
  font-size: 15px;
  line-height: 1;
  transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}
body.app-sidebar-collapsed .brand-toggle i {
  transform: rotate(180deg);
}
@media (prefers-reduced-motion: reduce) {
  .brand-toggle i {
    transition: none;
  }
}

/* Scroll-position slider — a thin draggable track along the fixed
   navbar's own bottom edge, tracking the whole document's scroll
   (.main-panel still scrolls normally, not as a bounded box) instead
   of a traditional scrollbar off in the page's right margin. Click
   anywhere on the track or drag the dot to jump to a position — see
   navbar.blade.php for the scroll/drag logic. */
.scroll-progress-track {
  position: absolute;
  /* A few px in from the wrapper's own left edge — at a flush left:0,
     the handle's own leftmost position sat exactly on the sidebar's
     boundary line with no breathing room, which read as clipped even
     once the stacking fix below stopped it from actually being
     painted over. */
  left: 4px;
  right: 0;
  bottom: -3px;
  height: 4px;
  background: rgba(0, 0, 0, 0.06);
  cursor: pointer;
  /* Raising THIS element's z-index alone doesn't reach far enough —
     see the .navbar.fixed-top rule further down for the actual fix
     (Bootstrap's own fixed-top utility caps the whole <nav> at
     z-index:1030, which is what was really capping this descendant
     against .app-sidebar's 1040, no matter how high a value went
     here). Still kept above the rest of the navbar's own content,
     for good measure. */
  z-index: 1045;
}
/* On mobile, .app-sidebar is off-canvas (position:fixed, transformed
   fully off-screen — see the app-sidebar mobile rules further down), so
   it occupies zero width in normal flow. The navbar itself still keeps
   its own small .navbar-brand-wrapper column on the left (logo +
   hamburger, ~55px, a normal always-visible mobile nav pattern, not the
   sidebar), which is what left:0/right:0 above was actually measuring
   against — anchored to .navbar-menu-wrapper, so the track only ever
   spanned everything AFTER that brand column. That made sense on
   desktop (avoiding a stray line across the sidebar's own column,
   deliberately), but on mobile there's no sidebar column left to avoid,
   so leaving a dead gap under the logo just reads as the bar being
   stuck/short. Breaking out of .navbar-menu-wrapper's own box via
   position:fixed here — anchored to the viewport instead of any
   wrapper — makes it span edge-to-edge the same width the (currently
   invisible) sidebar would otherwise have shared the row with. */
@media (max-width: 991.98px) {
  .scroll-progress-track {
    position: fixed;
    left: 0;
    right: 0;
    top: 69px;
    bottom: auto;
  }
}
.scroll-progress-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0%;
  /* Same navy gradient as the brand-tile icon, not a navy-to-gold blend
     — the two brand colors read as a muddy transition mid-bar when
     mixed directly, whereas keeping the track navy and the handle gold
     (below) echoes the wordmark's own two-tone "Watan"/"AMS" split
     without smearing the colors into each other. */
  background: linear-gradient(90deg, #3457d5, #1c2e6b);
  border-radius: 0 3px 3px 0;
}
.scroll-progress-handle {
  position: absolute;
  top: 50%;
  left: 0%;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fff;
  /* Was a gold border (#b8842c), the only gold accent left outside the
     roster medals — switched to the brand's own light blue to match
     the fill gradient it sits on and the rest of the UI's blue accents. */
  border: 2px solid #3457d5;
  transform: translate(-50%, -50%);
  cursor: grab;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  transition: transform 0.12s ease;
}
.scroll-progress-handle:hover,
.scroll-progress-handle.is-dragging {
  transform: translate(-50%, -50%) scale(1.25);
}
.scroll-progress-track:hover .scroll-progress-handle {
  transform: translate(-50%, -50%) scale(1.15);
}

/* ============================================================
   Custom sidebar shell — .app-sidebar-* (partial/sidebar.blade.php)
   ============================================================
   Deliberately its own class namespace, not the vendor theme's
   .sidebar/.nav/.nav-item/.sidebar-offcanvas classes — those are what
   off-canvas.js, hoverable-collapse.js, and template.js hook into by
   selector, and reusing them kept producing regressions where a fix
   for one script's behavior silently re-triggered another (most
   recently: adding sidebar-fixed for independent scrolling also
   switched hoverable-collapse.js from "flyout one item on hover" to
   "expand the whole sidebar on any hover", since that script branches
   on sidebar-fixed's presence). None of those three vendor scripts can
   match anything in this markup, so none of them can interfere here
   again — all interaction (collapse toggle, mobile open/close, hover-
   vs-tap flyouts, active-link highlighting) is our own script in
   navbar.blade.php.

   Also: submenus no longer expand inline (the old View/Add links
   pushing everything below them down the page) — they're a hover
   flyout instead (desktop, pure CSS :hover) or tap-to-expand (mobile,
   JS-toggled .is-open), so the sidebar's own height is just "number of
   top-level categories" and never needs a scrollbar of its own at all,
   which is what made two independent scrollbars (sidebar + page) fight
   for the same visual space in the first place. */
.app-sidebar {
  position: fixed;
  top: 70px;
  left: 0;
  bottom: 0;
  width: 220px;
  z-index: 1040;
  background: #F4F5F7;
  border-right: 1px solid rgba(0, 0, 0, 0.08);
  overflow: visible;
  transition: width 0.25s ease;
}
body.app-sidebar-collapsed .app-sidebar {
  width: 70px;
}
/* .main-panel isn't a flex sibling of .app-sidebar in any meaningful
   sense anymore (the sidebar is position:fixed, out of normal flow
   entirely) — this margin is what actually reserves the sidebar's own
   width so content doesn't render underneath it. Vendor's own
   unconditioned .main-panel{width:calc(100% - 220px)} rule (style.css)
   still applies underneath this and happens to combine correctly:
   a box that's 220px narrower than its container, then shifted 220px
   right, exactly fills the remaining space next to a 220px sidebar. */
body.app-shell .main-panel {
  margin-left: 220px;
}
body.app-shell.app-sidebar-collapsed .main-panel {
  margin-left: 70px;
  width: calc(100% - 70px);
}

.app-sidebar-nav {
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  height: 100%;
  /* overflow: visible, deliberately, not overflow-y: auto — the entire
     point of moving submenus to a hover flyout was that this list
     never needs to scroll (see the file-level comment above), so
     there's nothing to bound. overflow-x: hidden here (left over from
     the pre-flyout design) was clipping the flyout panels themselves,
     since they render at left:100% — outside this element's own
     horizontal box — which is what made them fail to show at all on
     hover; CSS overflow doesn't allow "clip X, don't clip Y" on the
     same element once either axis is non-visible, so this has to stay
     fully visible on both axes. */
  overflow: visible;
}
.app-sidebar-section {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #8a8f9e;
  padding: 14px 10px 6px;
}
.app-sidebar-section:first-child {
  padding-top: 4px;
}
body.app-sidebar-collapsed .app-sidebar-section {
  opacity: 0;
  height: 0;
  padding: 0;
  overflow: hidden;
}
.app-sidebar-item {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 10px;
  border-radius: 8px;
  color: #4b5163;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  position: relative;
}
.app-sidebar-item:hover {
  background: rgba(0, 0, 0, 0.04);
  color: #223874;
}
.app-sidebar-item i:not(.app-sidebar-caret) {
  font-size: 18px;
  width: 18px;
  text-align: center;
  flex-shrink: 0;
}
.app-sidebar-item.is-active {
  background: linear-gradient(135deg, #3457d5, #1c2e6b);
  color: #fff;
}
.app-sidebar-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}
body.app-sidebar-collapsed .app-sidebar-item {
  justify-content: center;
  padding: 9px;
}
body.app-sidebar-collapsed .app-sidebar-label,
body.app-sidebar-collapsed .app-sidebar-caret {
  display: none;
}

/* Height-responsive "compact" mode — a separate mechanism from the
   width-based collapsed-to-icons state above, and deliberately NOT a
   scrollbar (the whole point of the hover-flyout redesign was to never
   need one on the sidebar — see the file-level comment above). On a
   short viewport (a small/split window, not just a phone in portrait),
   the full category-labelled list can run out of vertical room before
   reaching the last item, with nothing below the fold to reach.
   navbar.blade.php's script measures whether #appSidebarNav's content
   actually overflows its available height and toggles this class live
   — not a fixed viewport-height media query, since how much height is
   actually needed depends on the signed-in role (root_admin has more
   sections than teacher). Hides just the category headers and packs
   items a little tighter; icons/labels/flyouts are otherwise untouched.
   Skips the extra item-padding tightening while ALSO icon-collapsed,
   since that state already saves far more space on its own. */
body.app-sidebar-compact .app-sidebar-section {
  display: none;
}
body.app-sidebar-compact:not(.app-sidebar-collapsed) .app-sidebar-item {
  padding: 6px 10px;
}
body.app-sidebar-compact .app-sidebar-nav {
  gap: 0;
}

/* Second, more aggressive tier — navbar.blade.php's script only adds
   this ON TOP OF app-sidebar-compact (categories already hidden above)
   when even that isn't enough to fit everything (a very short viewport
   combined with a role that has a lot of sections/items, e.g.
   root_admin). Shrinks the items themselves rather than hiding
   anything else, so every link stays reachable at any window size —
   nothing below this ever needs a scrollbar. */
body.app-sidebar-ultra-compact:not(.app-sidebar-collapsed) .app-sidebar-item {
  padding: 4px 8px;
  font-size: 12px;
}
body.app-sidebar-ultra-compact .app-sidebar-item i:not(.app-sidebar-caret) {
  font-size: 15px;
  width: 15px;
}

/* Opposite direction from the two tiers above — a tall/2K+ display has
   far more vertical room than the item list needs at its normal size,
   which otherwise just reads as a big empty gap under the last link.
   navbar.blade.php's script only applies this when there's comfortably
   more room than the content needs (not just barely fitting), and
   backs it back out if growing would tip the list into overflowing
   after all — normal size always wins over spacious in that case. */
body.app-sidebar-spacious:not(.app-sidebar-collapsed) .app-sidebar-item {
  padding: 12px 14px;
  gap: 13px;
  font-size: 14.5px;
}
body.app-sidebar-spacious:not(.app-sidebar-collapsed) .app-sidebar-item i:not(.app-sidebar-caret) {
  font-size: 20px;
  width: 20px;
}
body.app-sidebar-spacious:not(.app-sidebar-collapsed) .app-sidebar-section {
  font-size: 11px;
  padding: 20px 10px 8px;
}
body.app-sidebar-spacious:not(.app-sidebar-collapsed) .app-sidebar-nav {
  gap: 5px;
}

/* Collapsed-state name tooltip for PLAIN items (Dashboard, Attendance,
   Reports, etc. — anything without a flyout). .has-flyout items already
   get a hover popup (the flyout itself, below), but a plain item's own
   .app-sidebar-label is display:none in collapsed mode with nothing to
   replace it, so hovering an icon-only link gave no clue what it was.
   Reuses data-label (see sidebar.blade.php) via attr() rather than the
   hidden <span>, since CSS content: can only read an element's own
   attributes, not a descendant's text. Desktop-only (min-width guard) —
   mobile never collapses to icon-only at all, see the mobile .app-sidebar
   override further down, so labels are always visible there already. */
@media (min-width: 992px) {
  body.app-sidebar-collapsed .app-sidebar-item:not(.has-flyout)::after {
    content: attr(data-label);
    position: absolute;
    left: 100%;
    top: 50%;
    transform: translateY(-50%);
    margin-left: 8px;
    background: #fff;
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.18);
    padding: 7px 12px;
    font-size: 12.5px;
    font-weight: 600;
    color: #333a4d;
    white-space: nowrap;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.14s ease, visibility 0.14s;
    z-index: 1041;
    pointer-events: none;
  }
  body.app-sidebar-collapsed .app-sidebar-item:not(.has-flyout):hover::after {
    opacity: 1;
    visibility: visible;
  }
  @media (prefers-reduced-motion: reduce) {
    body.app-sidebar-collapsed .app-sidebar-item:not(.has-flyout)::after {
      transition: none;
    }
  }
}

/* Submenu flyout — hover on desktop (plain CSS :hover, no JS needed),
   tap-to-toggle on mobile (navbar.blade.php's script toggles .is-open
   there instead, since hover doesn't exist on touch — see the mobile
   override further down that re-lays this out as an inline block). */
.app-sidebar-caret {
  margin-left: auto;
  font-size: 16px !important;
  opacity: 0.5;
  flex-shrink: 0;
  transition: transform 0.15s ease;
}
.app-sidebar-flyout {
  position: absolute;
  left: 100%;
  top: -6px;
  margin-left: 8px;
  min-width: 190px;
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 10px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.18);
  padding: 6px;
  opacity: 0;
  visibility: hidden;
  transform: translateX(-6px);
  transition: opacity 0.14s ease, transform 0.14s ease, visibility 0.14s;
  z-index: 1041;
}
.has-flyout:hover .app-sidebar-flyout,
.app-sidebar-flyout:hover {
  opacity: 1;
  visibility: visible;
  transform: translateX(0);
}
.app-sidebar-flyout-link {
  display: block;
  padding: 8px 10px;
  border-radius: 6px;
  font-size: 12.5px;
  font-weight: 600;
  color: #333a4d;
  text-decoration: none;
  white-space: nowrap;
}
.app-sidebar-flyout-link:hover {
  background: rgba(0, 0, 0, 0.05);
}
.app-sidebar-flyout-link.is-active {
  color: #3457d5;
}
@media (prefers-reduced-motion: reduce) {
  .app-sidebar-flyout {
    transition: none;
  }
}

/* Mobile: the sidebar leaves the layout entirely and becomes a slide-
   over sheet instead of a persistent column — no icon-only collapsed
   state exists at this width (it's either fully open or fully off-
   screen), so .brand-toggle (the desktop collapse control) has
   nothing to do here either. */
@media (max-width: 991.98px) {
  .app-sidebar {
    width: 260px;
    transform: translateX(-100%);
    transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
    box-shadow: 0 0 32px rgba(0, 0, 0, 0.35);
  }
  .app-sidebar.is-open {
    transform: translateX(0);
  }
  body.app-shell .main-panel {
    margin-left: 0 !important;
    width: 100% !important;
  }
  .brand-toggle {
    display: none !important;
  }

  /* Flyouts re-lay-out as inline expanding blocks under the item
     instead of a floating side panel, which would have nowhere to go
     on a 260px sheet anyway. */
  .app-sidebar-flyout {
    position: static;
    margin-left: 0;
    margin-top: 4px;
    box-shadow: none;
    border: none;
    background: transparent;
    padding: 0 0 0 27px;
    opacity: 1;
    visibility: visible;
    transform: none;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.2s ease;
  }
  .has-flyout.is-open .app-sidebar-flyout {
    max-height: 240px;
  }
  .has-flyout.is-open .app-sidebar-caret {
    transform: rotate(90deg);
  }
}
@media (max-width: 991.98px) and (prefers-reduced-motion: reduce) {
  .app-sidebar {
    transition: none;
  }
}

/* Dark scrim behind the open mobile sheet, closing it on tap — see
   partial/sidebar.blade.php and navbar.blade.php's script. Sits just
   under the sidebar's own z-index:1040 above. */
.app-sidebar-scrim {
  display: none;
}
@media (max-width: 991.98px) {
  .app-sidebar-scrim {
    position: fixed;
    inset: 0;
    background: rgba(10, 12, 20, 0.5);
    z-index: 1035;
  }
  .app-sidebar-scrim.is-visible {
    display: block;
  }
}

.navbar .navbar-menu-wrapper {
  height: 70px;
  padding-top: 14px;
  padding-bottom: 14px;
  /* Anchor for .scroll-progress-track — without this, the track's
     position:absolute falls back to the next positioned ancestor,
     which is the full-width <nav> itself (fixed-top), stretching the
     bar back across the sidebar's column despite the track markup
     already being moved inside this wrapper. */
  position: relative;
  /* This was the real cause of "the progress bar doesn't move when the
     sidebar collapses": Bootstrap's base .navbar rule only sets
     justify-content:space-between on the <nav> row, which pushes this
     wrapper flush against the row's own right edge — it does NOT make
     this wrapper actually fill "100% minus the brand-wrapper's width".
     Left alone (flex: 0 1 auto, the default), this box only ever sizes
     to its own content (welcome-text + the icon cluster), so the
     .scroll-progress-track anchored to its left:0/right:0 edges was
     really tracking that content width, not the sidebar's edge — which
     only ever looked roughly right by coincidence, and had no way to
     react at all when .navbar-brand-wrapper's own width changed on
     collapse. flex-basis:0 + flex-grow:1 makes this wrapper actually
     claim 100% of whatever space is left in the row, so it (and the
     progress track inside it) automatically follows the sidebar's real
     width in both collapsed and expanded states, with no extra rule
     needed per breakpoint. min-width:0 stops a long academy name in
     .welcome-text from fighting that basis and overflowing instead of
     wrapping/ellipsizing. */
  flex: 1 1 0%;
  min-width: 0;
}
/* Bounded shell — html/body/.container-scroller never scroll (the
   browser's own page scrollbar is gone entirely); .main-panel is the
   one actual scrolling region for page content, same model the
   sidebar already uses for its own link list. This is what actually
   resolves the "two scrollbars" look for good — before this, a long
   page had BOTH the sidebar's own scrollbar AND the whole document's
   native one, which some browsers only draw one of at rest and both
   of on hover, looking like the sidebar's scrollbar was multiplying.
   With content bounded too, there are exactly two clearly-separate
   scroll regions (sidebar nav list, main content pane) and nothing
   else competing with either.

   Scoped to body.app-shell (authenticated pages only, see layouts/
   app.blade.php's note) rather than applied globally — guest pages
   (login, error pages, the public student ID verification page) have
   no .main-panel to catch overflow at all, so capping .container-
   scroller's height unconditionally would silently clip any guest
   page taller than the viewport with no scrollbar left to reach the
   rest of it. html/body's own height:100% is harmless on guest pages
   on its own (it's .container-scroller's fixed height + overflow:
   hidden together that actually clips), so that part stays global. */
html, body {
  height: 100%;
}
body.app-shell .container-scroller {
  height: 100vh;
  /* overflow: hidden is already the vendor's own default here
     (style.css) — restated by neither file explicitly, this comment
     just documents that it's load-bearing now, not incidental. */
}
body.app-shell .page-body-wrapper {
  padding-top: 70px;
  height: 100vh;
  overflow: hidden;
}
body.app-shell .main-panel {
  height: calc(100vh - 70px);
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  /* No custom scrollbar styling here — a CSS-styled scrollbar doubled
     up with Windows 11's own overlay-scrollbar hover-widen behavior in
     every browser tested (Chrome, Edge, Firefox), so this stays a
     plain native scrollbar. */
}
@media (max-width: 991.98px) {
  body.app-shell .main-panel {
    height: calc(100vh - 70px) !important;
  }
}
@media (max-width: 991px) {
  .navbar .navbar-menu-wrapper .navbar-nav .nav-item.dropdown .navbar-dropdown {
    top: 70px;
  }
}

/* The academy name (.welcome-text/.welcome-sub-text) is hidden entirely
   once the navbar hits its scrolled "headerLight" state — the vendor's
   own !important rule for this, so it needs matching !important to
   restore the visible state instead of just being unhidden by specificity
   alone. */
.with-welcome-text .navbar.headerLight .welcome-text,
.with-welcome-text .navbar.headerLight .welcome-sub-text {
  display: block !important;
}

/* The vendor .count badge (notification bell) is only a 7x7px dot with no
   room for the digit text it's meant to display — the count number was
   getting clipped/invisible in every theme, not just dark mode. Resized
   into a proper pill anchored to the icon's top-right corner instead of
   a fixed-size dot centered over it. */
.navbar .navbar-menu-wrapper .navbar-nav .nav-item.dropdown .count-indicator .count {
  width: auto;
  min-width: 17px;
  height: 17px;
  padding: 0 4px;
  left: auto;
  right: -4px;
  top: -6px;
  font-size: 10px;
  line-height: 15px;
}

/* Navbar/content boundary — a soft shadow instead of the vendor's plain
   "box-shadow: none" (light mode had no visible divider at all) or a hard
   1px border. dark.css supplies the dark-theme equivalent with a heavier
   shadow, since this same rgba value would be invisible on a dark navbar. */
.navbar {
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08) !important;
}

/* start dataset */


.dataTables_wrapper .dataTables_paginate {
  margin-bottom: 30px !important;
}


table.dataTable thead th.sorting:hover,
table.dataTable thead th.sorting_asc:hover,
table.dataTable thead th.sorting_desc:hover {
  color: #7686a0 !important; /* Darker on hover */
}


/* Dropdown (Show entries select box) */
.dataTables_length select {
  border: 1px solid #7793b1 !important;
  border-radius: 4px;
  padding-right: 25px;
  margin: 3px 3px 10px 3px;
}

/* end dataset */


/* Error pages (404/403/429/javascript-required) — a small hero shown
   inside the same .auth-form-light card the login page uses, so guest-
   facing chrome (branding, dark mode, RTL, footer) never has two
   different looks depending which guest page you're on. */
.error-icon-circle {
  width: 84px;
  height: 84px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 1.25rem;
  font-size: 40px;
}

.error-code {
  font-size: 4rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
  margin-bottom: 0.25rem;
}


.form-group input {
  border-radius: 20px;
}

.form-group label {
  margin-left: 15px;
}

/* Vendor's .content-wrapper pads the left edge (3.5rem) noticeably more
   than the right (2.187rem) — a lopsided gap between the sidebar and the
   actual page content that just wasted width rather than adding any
   visual breathing room. Balanced down to match the right side instead
   of removing it outright, so content still has margin but isn't pushed
   in an extra ~1.3rem for no reason. Loads after style.css, so this wins
   on source order alone without needing !important. */
.content-wrapper {
  padding-left: 2.187rem;
}

.form-group select {
  border-radius: 20px;
}

.button-container button {
  border-radius: 20px;
}

.btn {
  border-radius: 20px;
}

/* Vendor's .btn-outline-danger sets resting text to a washed-out light
   pink (#fcbbb6) in every theme — a color tuned for nothing in
   particular, not actually legible against a light card, and dark.css
   only ever brightened this variant's border, not its text (the prior
   fix there judged the text "already bright enough" — it wasn't).
   Matching text to the border's own dark red reads as one deliberate
   color instead of two accidents. Affects every destructive action
   using this class app-wide (Suspend Academy, Restore, Upload &
   Restore, etc.), not just one page. */
.btn-outline-danger {
  color: #ad1206;
}

/* partial/avatar.blade.php's icon fallback (no uploaded photo) — sizing
   itself is inline (per-instance, via the partial's $size param), so this
   only needs the shared look: same colors as every other icon-avatar
   fallback introduced this session (navbar, profile hero), reused here so
   every table/list in the app has one consistent "no photo" appearance
   instead of the blurry default-avatar.png raster. */
.avatar-thumb-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: #eef1f5;
  color: #1F3BB3;
  flex-shrink: 0;
}

/* Every modal in this app uses the old Bootstrap-4-style
   <button class="close" data-dismiss="modal"><span>&times;</span></button>
   markup, which this vendor theme never actually styles (confirmed: no
   ".close" rule anywhere in style.css) — it was rendering as a bare
   browser-default button, which is the "old classic" look reported.
   Restyled as a small circular icon button, applied globally so every
   modal gets it at once rather than fixing them one at a time. */
.modal-header .close {
  width: 32px;
  height: 32px;
  padding: 0;
  margin: 0;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.06);
  color: #6c757d;
  font-size: 20px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s ease, color 0.15s ease;
  flex-shrink: 0;
}

.modal-header .close:hover,
.modal-header .close:focus {
  background: rgba(0, 0, 0, 0.12);
  color: #212529;
  outline: none;
}

.modal-header .close span {
  line-height: 1;
}

.modal-title {
  font-weight: 700;
}

/* Every guest-facing "hero" page sharing the .content-wrapper.auth
   wrapper (login, error pages, student verification — see
   partial/error-hero.blade.php) needs min-height: 100vh so the card
   actually centers in the full viewport. This was previously only ever
   defined locally inside auth/login.blade.php's own page-styles push —
   fine for that one page, but every other page reusing the exact same
   wrapper markup never got it, leaving short content sitting at the
   top with a raw, un-themed gap below it (very visible on mobile,
   where the viewport is taller relative to the card). */
.content-wrapper.auth {
  min-height: 100vh;
}

/* Bootstrap's bg-opacity-* utilities (bg-success bg-opacity-10, etc.)
   are not part of this vendor theme's compiled CSS bundle at all — no
   --bs-bg-opacity custom property exists anywhere in vendors/css/
   vendor.bundle.base.css. Using them silently does nothing: bg-success
   renders fully solid, and text-success (or an icon in that color)
   ends up nearly the same hue as its own background — the "invisible
   icon on a solid color circle" bug reported on the student
   verification page, but it affected every error-hero icon circle and
   the verification status badge equally, just less obviously on the
   danger/warning ones. These are real, hand-rolled light-tint classes
   instead of relying on a utility this theme never shipped, with
   explicit dark-mode variants since a flat rgba mix that reads correctly
   on a white card looks muddy on a dark one. */
.soft-success   { background-color: rgba(25, 135, 84, 0.12) !important; color: #198754 !important; }
.soft-danger    { background-color: rgba(220, 53, 69, 0.12) !important; color: #dc3545 !important; }
.soft-warning   { background-color: rgba(255, 193, 7, 0.16) !important; color: #a3790a !important; }
.soft-secondary { background-color: rgba(108, 117, 125, 0.12) !important; color: #6c757d !important; }

[data-bs-theme="dark"] .soft-success   { background-color: rgba(25, 135, 84, 0.22) !important; color: #4ade80 !important; }
[data-bs-theme="dark"] .soft-danger    { background-color: rgba(220, 53, 69, 0.24) !important; color: #f87171 !important; }

/* Modern footer layout (partial/footer.blade.php) — a small brand mark
   (the same navy-gradient tile as the navbar logo, at footer scale)
   paired with the wordmark and vendor credit on the left, a version
   pill and minimal copyright on the right, replacing the old plain
   text row. .footer itself (background/color/padding/font-size) is
   still the vendor's own base rule from style.css plus dark.css's
   override — this only styles the new inner layout, not the footer
   bar itself. */
.footer-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 16px;
}
.footer-brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.footer-brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 6px;
  background: linear-gradient(135deg, #3457d5, #1c2e6b);
  flex-shrink: 0;
}
.footer-brand-mark i {
  color: #fff;
  font-size: 12px;
  line-height: 1;
}
.footer-brand-text strong {
  font-weight: 700;
}
.footer-brand-sep {
  margin: 0 4px;
  opacity: .5;
}
.footer-brand-text a {
  text-decoration: none;
}
.footer-brand-text a:hover {
  text-decoration: underline;
}
.footer-meta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
/* A quiet pill instead of a bare "v1.2.3" text fragment — same idea as
   the .soft-* badges above (a light tint of the brand color, not a
   Bootstrap bg-opacity utility this theme doesn't ship), so the
   version reads as a small badge rather than stray text. */
.footer-version {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 10px;
  border-radius: 999px;
  background: rgba(31, 59, 179, 0.08);
  color: inherit;
  font-size: 0.85em;
  text-decoration: none;
}
a.footer-version:hover {
  background: rgba(31, 59, 179, 0.14);
  color: inherit;
}
.footer-version i {
  font-size: 0.95em;
}
@media (max-width: 575.98px) {
  .footer-inner {
    justify-content: center;
    text-align: center;
  }
}
[data-bs-theme="dark"] .soft-warning   { background-color: rgba(255, 193, 7, 0.22) !important; color: #fbbf24 !important; }
[data-bs-theme="dark"] .soft-secondary { background-color: rgba(173, 181, 189, 0.18) !important; color: #ced4da !important; }

/* Filter pills — ported from the single-tenant LEMS app's
   .student-filter-pills (see d:\laravel\lems\resources\views\admin\
   student\index.blade.php), generalized here so any list page's status/
   filter tabs can use it, not just Students. Replaces Bootstrap's classic
   underlined .nav-tabs look (reported as "too old classic") with
   independently-rounded pill buttons.

   Deliberately NOT Bootstrap's .btn-group — that class's shared-border
   corner styling (first child rounded left, last child rounded right,
   everything between square) only makes sense for a single unbroken row.
   A filter bar with several options doesn't always fit one row on a
   narrow screen, and a wrapped .btn-group looks broken (a button
   mid-wrap keeps its "last child" rounded corner as if it were still at
   the row's end). Independently-rounded pills in a horizontally-
   scrollable strip can't break that way at any width. */
.filter-pills-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  max-width: 100%;
  padding-bottom: 2px; /* room for the scrollbar so it doesn't clip the pills' bottom edge */
}
.filter-pills {
  display: flex;
  gap: 6px;
  flex-wrap: nowrap;
}
.filter-pills .btn {
  border-radius: 999px !important;
  flex-shrink: 0;
  white-space: nowrap;
}

/* Class roster modal (classes.index + partial/ajax/class/roster.blade.php)
   — the class code becomes a button that AJAX-loads this into a shared
   modal. Top-3 ranking uses classic medal colors (gold/silver/bronze)
   deliberately distinct from the brand navy/gold pair used elsewhere —
   reusing the brand gold for 1st place specifically would make 2nd/3rd
   look like arbitrary, unrelated colors instead of a matched set. */
.roster-code-btn {
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  font-weight: 700;
  color: #3457d5;
  cursor: pointer;
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}
.roster-code-btn:hover {
  color: #1c2e6b;
}
.roster-enrolled-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.72rem;
  font-weight: 700;
  color: #565b6e;
  margin-left: 6px;
}

.roster-info-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  padding: 14px 16px;
  background: rgba(52, 87, 213, 0.05);
  border: 1px solid rgba(52, 87, 213, 0.12);
  border-radius: 10px;
  margin-bottom: 20px;
}
.roster-info-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 110px;
}
.roster-info-label {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #8a8fa3;
}
.roster-info-value {
  font-size: 0.88rem;
  font-weight: 700;
  color: #1a1d29;
}

.roster-podium {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  margin-bottom: 22px;
}
.roster-podium-card {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid transparent;
  position: relative;
  overflow: hidden;
}
.roster-podium-card::before {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.1;
}
.roster-podium-rank-1 { border-color: rgba(212, 175, 55, 0.45); background: rgba(212, 175, 55, 0.08); }
.roster-podium-rank-2 { border-color: rgba(168, 169, 173, 0.5); background: rgba(168, 169, 173, 0.1); }
.roster-podium-rank-3 { border-color: rgba(205, 127, 50, 0.45); background: rgba(205, 127, 50, 0.09); }
.roster-medal {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  font-size: 0.78rem;
  font-weight: 800;
  color: #fff;
  flex-shrink: 0;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.roster-medal-1 { background: linear-gradient(135deg, #f4d160, #d4af37); }
.roster-medal-2 { background: linear-gradient(135deg, #e2e3e6, #a8a9ad); }
.roster-medal-3 { background: linear-gradient(135deg, #e2a06a, #cd7f32); }
.roster-podium-name {
  font-weight: 700;
  font-size: 0.86rem;
  margin: 0;
  line-height: 1.25;
}
.roster-podium-score {
  font-size: 0.76rem;
  color: #565b6e;
  margin: 0;
}

.roster-rank-cell {
  width: 40px;
  text-align: center;
}
.roster-rank-cell .roster-medal {
  width: 26px;
  height: 26px;
  font-size: 0.68rem;
}

/* List-page search forms (students/teachers/users/payments/enrollments
   index.blade.php) — the input used to sit in a cramped fixed-width box
   (inline min-width/max-width styles, 220-320px) that stayed that same
   tiny size on mobile even after wrapping onto its own row with nothing
   else left to share it with. flex-grow here claims the whole row once
   wrapped, instead. The submit/clear buttons get their own small,
   brand-consistent style rather than the vendor theme's own
   .btn-outline-secondary, which this admin theme reskins to a bright
   cyan (#05C3FB, see style.css) that has nothing to do with this app's
   own navy/gold palette — that mismatch, not anything about the layout,
   was the real "wrong color" behind the button looking off. */
.list-search-form {
  display: flex;
  gap: 6px;
  flex: 1 1 220px;
  min-width: 0;
  /* Caps the desktop width close to what the old per-page inline
     min-width/max-width values (220-320px) already looked like — only
     the mobile override below actually changes behavior, dropping this
     cap so the field (and its button) can claim the full row width
     instead of staying pinned to a small fixed size that was cramped
     next to a full-width button on a narrow screen. */
  max-width: 320px;
}
@media (max-width: 575.98px) {
  .list-search-form {
    max-width: none;
    width: 100%;
  }
}
.list-search-form .form-control {
  flex: 1 1 auto;
  min-width: 0;
}
/* This vendor theme carries two separate, later-cascading .btn/.btn-sm
   blocks (see style.css) whose padding doesn't reliably line up with
   .form-control-sm's own padding, so the two ended up visibly different
   heights next to each other instead of the near-identical heights
   Bootstrap's own .btn-sm/.form-control-sm are designed to share.
   Pinning both to one explicit height here sidesteps that instead of
   chasing which vendor rule wins the cascade. */
.list-search-form .form-control,
.list-search-form .search-action-btn {
  height: 33px;
  box-sizing: border-box;
}
.search-action-btn {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 12px;
  border: 1px solid #d7dae3;
  color: #565b6e;
  background: #fff;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.search-action-btn:hover,
.search-action-btn:focus-visible {
  border-color: #3457d5;
  color: #3457d5;
  background-color: rgba(52, 87, 213, 0.06);
}
.search-action-btn:focus-visible {
  outline: 2px solid #3457d5;
  outline-offset: 1px;
}













