/* =============================================================================
   LapTrack — Design System
   Palette: White + Deep Navy + Orange accent (institutional, operational tool)
   ============================================================================= */

:root {
    /* Brand */
    --navy-950: #0b1220;
    --navy-900: #0f1b30;
    --navy-800: #16233c;
    --navy-700: #1e2d4a;
    --navy-600: #2b3d5e;
    --navy-500: #445876;
    --navy-400: #6b7d99;
    --navy-300: #9aa9c0;
    --navy-100: #e7ebf2;

    --orange-700: #c85a12;
    --orange-600: #e0670f;
    --orange-500: #f2760f;
    --orange-400: #ff8a2b;
    --orange-100: #fdead9;
    --orange-50:  #fff4ea;

    --green-600: #1a8a4a;
    --green-100: #dff5e7;
    --amber-600: #b3790a;
    --amber-100: #fdf0d5;
    --red-600: #d1352c;
    --red-100: #fbe2e0;
    --blue-600: #2765c9;
    --blue-100: #e2ecfc;

    /* Surfaces (light) */
    --bg: #f6f7fb;
    --surface: #ffffff;
    --surface-alt: #f1f3f8;
    --border: #e3e7ef;
    --text: var(--navy-900);
    --text-muted: var(--navy-500);
    --text-faint: var(--navy-300);

    --shadow-sm: 0 1px 2px rgba(15, 27, 48, 0.06);
    --shadow-md: 0 4px 14px rgba(15, 27, 48, 0.08);
    --shadow-lg: 0 12px 32px rgba(15, 27, 48, 0.14);

    --radius-sm: 8px;
    --radius-md: 14px;
    --radius-lg: 20px;
    --radius-pill: 999px;

    --font-display: 'Sora', 'Segoe UI', system-ui, sans-serif;
    --font-body: 'Inter', 'Segoe UI', system-ui, sans-serif;

    --sidebar-w: 208px;
    --topbar-h: 68px;
    --spa-progress-h: 3px;
    --mobile-nav-height: 74px;
    --mobile-nav-active-size: 58px;
    --mobile-nav-active-lift: 25.5px;
    --mobile-nav-radius: 26px;
    --mobile-nav-transition: transform .42s cubic-bezier(.22, 1.18, .36, 1);
}

[data-theme="dark"] {
    --bg: var(--navy-950);
    --surface: var(--navy-900);
    --surface-alt: var(--navy-800);
    --border: #24304a;
    --text: #f2f4f8;
    --text-muted: #a7b2c6;
    --text-faint: #6b7690;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
    --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.35);
    --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

/* =============================================================================
   Remove the mobile "flash"/highlight box browsers draw on tap so buttons,
   links, cards and other interactive elements feel native rather than like
   a web page. Paired with `touch-action: manipulation` to also drop the
   ~300ms tap delay some mobile browsers add (which is what causes the
   highlight to visibly linger). Focus rings are preserved separately for
   keyboard users via :focus-visible throughout this file.
   ============================================================================= */
html, body {
    -webkit-tap-highlight-color: transparent;
}
a, button, input, textarea, select,
.btn, .icon-btn, .nav-link, .card, [role="button"], [role="tab"], [onclick] {
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
}

/* =============================================================================
   Stop the browser's own pull-to-refresh / rubber-band bounce at the edges
   of the page and its scrollable panes. That bounce is one of the biggest
   visual "this is a website" tells on mobile; native apps only scroll the
   content that actually has more to show. `contain` (not `none`) is used on
   the individual scrollers so they can still be scrolled by touch/mouse —
   only the overscroll *chaining/bounce* is suppressed, not scrolling itself.
   ============================================================================= */
html, body {
    overscroll-behavior: none;
}
.sidebar-nav, .main-area, main, .modal-body {
    overscroll-behavior: contain;
}

/* =============================================================================
   Global scrollbar styling — thin, dark-navy thumb, no arrow buttons, no track
   background. Applies to the whole app (WebKit/Chromium browsers + Firefox
   fallback). Rules are duplicated onto html/body/.main-area>main explicitly
   (not just the `*` selector) because some Chromium/Edge builds ignore
   universal-selector scrollbar rules on the actual scrolling container.
   ============================================================================= */
html {
    scrollbar-width: thin;
    scrollbar-color: var(--navy-700) transparent;
}
*,
html,
body,
.main-area > main {
    scrollbar-width: thin;
    scrollbar-color: var(--navy-700) transparent;
}
*::-webkit-scrollbar,
html::-webkit-scrollbar,
body::-webkit-scrollbar,
.main-area > main::-webkit-scrollbar {
    width: 2px;
    height: 2px;
}
*::-webkit-scrollbar-track,
html::-webkit-scrollbar-track,
body::-webkit-scrollbar-track,
.main-area > main::-webkit-scrollbar-track {
    background: transparent;
}
*::-webkit-scrollbar-thumb,
html::-webkit-scrollbar-thumb,
body::-webkit-scrollbar-thumb,
.main-area > main::-webkit-scrollbar-thumb {
    background-color: var(--navy-700);
    border-radius: var(--radius-pill);
    border: none;
    background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover,
html::-webkit-scrollbar-thumb:hover,
body::-webkit-scrollbar-thumb:hover,
.main-area > main::-webkit-scrollbar-thumb:hover {
    background-color: var(--navy-800);
}
*::-webkit-scrollbar-button,
html::-webkit-scrollbar-button,
body::-webkit-scrollbar-button,
.main-area > main::-webkit-scrollbar-button {
    display: none;
    width: 0;
    height: 0;
}
*::-webkit-scrollbar-corner,
html::-webkit-scrollbar-corner,
body::-webkit-scrollbar-corner,
.main-area > main::-webkit-scrollbar-corner {
    background: transparent;
}

/* Global safe default for every inline icon SVG in the app. Specific
   contexts (buttons, nav, empty-state, scan-frame, etc.) override this
   below with their own intentional sizes — this rule only guards against
   icons rendering at unconstrained intrinsic size when used bare (e.g.
   directly inside a heading, alert, or paragraph). */
svg { width: 1em; height: 1em; flex-shrink: 0; vertical-align: -0.125em; }

h1 svg, h2 svg, h3 svg, h4 svg { width: 1.05em; height: 1.05em; margin-right: .15em; }

body {
    margin: 0;
    font-family: var(--font-body);
    background: var(--bg);
    color: var(--text);
    font-size: 15px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    transition: background-color .2s ease, color .2s ease;
}

h1, h2, h3, h4 {
    font-family: var(--font-display);
    color: var(--text);
    margin: 0 0 .4em;
    letter-spacing: -0.01em;
}
h1 { font-size: 1.7rem; font-weight: 700; }
h2 { font-size: 1.3rem; font-weight: 700; }
h3 { font-size: 1.05rem; font-weight: 600; }
p { margin: 0 0 1em; color: var(--text-muted); }
a { color: var(--orange-600); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible {
    outline: 2px solid var(--orange-500);
    outline-offset: 2px;
    border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}

/* =============================================================================
   Layout shell
   ============================================================================= */

html, body { height: 100%; }
body.has-shell { overflow: hidden; }
/* Belt-and-braces background scroll lock while a modal is open — see
   SLAS.openModal/closeModal in app.js. body.has-shell is already
   overflow:hidden on every authenticated page, and app.js also directly
   locks the real scrolling element (.main-area > main), but iOS Safari in
   particular can still rubber-band/scroll a hidden-overflow ancestor via
   touch, so this also blocks touch-scroll gestures on the body itself
   while the class is present, and covers any future page that uses a
   modal without the .has-shell layout. */
body.modal-scroll-lock { overflow: hidden; touch-action: none; }

.app-shell {
    display: flex;
    height: 100vh;
}

.sidebar {
    width: var(--sidebar-w);
    background: var(--navy-950);
    color: #cdd6e6;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 0; left: 0; bottom: 0;
    z-index: 40;
    transition: transform .25s ease;
    box-shadow: 1px 0 0 rgba(255,255,255,0.04);
}
.sidebar.dragging { transition: none; }

.sidebar-brand {
    display: flex;
    align-items: center;
    gap: .6rem;
    height: var(--topbar-h);
    padding: 0 1rem;
    box-sizing: border-box;
    border-bottom: 1px solid rgba(255,255,255,0.06);
}
.sidebar-brand .mark {
    width: 34px; height: 34px;
    border-radius: 6px;
    background: #fff;
    display: flex; align-items: center; justify-content: center;
    color: #fff; font-weight: 800; font-family: var(--font-display);
    flex-shrink: 0;
    font-size: .85rem;
    overflow: hidden;
}
.sidebar-brand .mark img { width: 100%; height: 100%; object-fit: contain; display: block; }
.sidebar-brand .name { font-family: var(--font-display); font-weight: 700; color: #fff; font-size: 1rem; line-height: 1.2; }
.sidebar-brand .sub { font-size: .72rem; color: #8f9ebc; }

.sidebar-nav {
    flex: 1; overflow-y: auto; padding: .7rem .6rem;
    scrollbar-width: none; -ms-overflow-style: none;
}
.sidebar-nav::-webkit-scrollbar { display: none; width: 0; height: 0; }

.sidebar-scroll-btn {
    display: none; position: absolute; right: 14px;
    /* Fallback (used only for the instant before JS measures the real
       footer height) matches the footer's actual default height now that
       the "Install app" button is visible for every visitor rather than
       conditionally — see assets/js/pwa-install.js. */
    bottom: calc(var(--sidebar-foot-h, 156px) + 16px);
    width: 30px; height: 30px; border-radius: 50%;
    background: #fff; border: none; box-shadow: var(--shadow-md);
    align-items: center; justify-content: center; cursor: pointer;
    z-index: 41; padding: 0; transition: transform .15s ease, box-shadow .15s ease, bottom .15s ease;
}
.sidebar-scroll-btn svg { width: 15px; height: 15px; color: var(--navy-800); transition: transform .2s ease; }
.sidebar-scroll-btn:hover { transform: translateY(2px); box-shadow: var(--shadow-lg); }
.sidebar-scroll-btn.visible { display: flex; }
.sidebar-scroll-btn.at-bottom svg { transform: rotate(180deg); }
.nav-section-label {
    font-size: .68rem; text-transform: uppercase; letter-spacing: .08em;
    color: #5f6d8c; padding: 1rem .6rem .35rem;
    font-weight: 700;
}
.nav-link {
    display: flex; align-items: center; gap: .6rem;
    padding: .58rem .6rem; border-radius: var(--radius-sm);
    color: #b7c1d6; font-weight: 500; font-size: .85rem;
    margin-bottom: 2px;
}
.nav-link svg { width: 18px; height: 18px; flex-shrink: 0; opacity: .85; }
.nav-link:hover { background: rgba(255,255,255,0.06); color: #fff; text-decoration: none; }
.nav-link.active { background: var(--orange-600); color: #fff; }
.nav-link.active svg { opacity: 1; }

.sidebar-foot { padding: .9rem .85rem; border-top: 1px solid rgba(255,255,255,0.06); flex-shrink: 0; }
.sidebar-foot .btn-ghost { background: #fff; border-color: #fff; color: var(--navy-800); }
.sidebar-foot .btn-ghost:hover { background: var(--surface-alt); border-color: var(--surface-alt); color: var(--navy-900); }
.user-chip { display: flex; align-items: center; gap: .6rem; }
.user-chip .avatar {
    width: 34px; height: 34px; border-radius: 50%;
    background: var(--navy-700); color: #fff;
    display: flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: .8rem; flex-shrink: 0;
}
.user-chip .info { min-width: 0; }
.user-chip .info .n { color: #fff; font-size: .85rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.user-chip .info .r { color: #8f9ebc; font-size: .74rem; }

.main-area { flex: 1; margin-left: var(--sidebar-w); padding-top: calc(var(--topbar-h) + env(safe-area-inset-top)); min-width: 0; height: 100vh; display: flex; flex-direction: column; overflow: hidden; }

/* Topbar is fixed with viewport-fit=cover + a translucent status bar (see
   includes/header.php meta tags), so its own box must pad itself down by
   env(safe-area-inset-top) rather than just sitting at top:0 — otherwise
   the status bar/notch overlaps the topbar's own content. .main-area above
   mirrors the same inset in its padding-top so page content clears the
   topbar's true rendered height (bar height + inset) instead of just
   --topbar-h, which is what was letting page content start underneath it. */
.topbar {
    min-height: var(--topbar-h);
    box-sizing: content-box;
    flex-shrink: 0;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    display: flex; align-items: center; justify-content: space-between;
    padding: 0 1.5rem;
    padding-top: env(safe-area-inset-top);
    position: fixed; top: 0; left: var(--sidebar-w); right: 0; z-index: 30;
}
#spa-progress {
    position: fixed; top: calc(var(--topbar-h) + env(safe-area-inset-top)); left: var(--sidebar-w); right: 0;
    height: var(--spa-progress-h); z-index: 31; pointer-events: none;
    opacity: 0; overflow: hidden; background: transparent;
}
#spa-progress::after {
    content: ''; display: block; width: 35%; height: 100%;
    background: var(--orange-500); transform: translateX(-120%);
}
#spa-progress.is-loading { opacity: 1; }
#spa-progress.is-loading::after { animation: spa-progress 1.15s ease-in-out infinite; }
#spa-progress.is-complete::after { animation: none; width: 100%; transform: translateX(0); transition: width .16s ease; }
@keyframes spa-progress {
    0% { transform: translateX(-120%); }
    55% { transform: translateX(180%); }
    100% { transform: translateX(300%); }
}
/* Page transition: opacity + transform only (both GPU-compositable, no
   layout/paint cost) so the fade is smooth even on low-end devices —
   filter/blur was intentionally dropped, since blurring a full page is
   expensive to rasterize every frame and made the transition feel
   sluggish. Durations are kept short (120ms out / 150ms in) since the
   network wait already happens before either animation starts.

   will-change is scoped to only the two transition classes, not applied
   to #app-content permanently. A `transform` (even a translateY(0) one)
   or `will-change: transform` creates a new CSS stacking context, which
   would trap every position:fixed descendant painted inside #app-content
   — including every .modal-backdrop, since modal markup lives inside
   <main id="app-content"> — so its z-index could then only ever compete
   with #app-content's own children, never with siblings of #app-content
   like .topbar or .mobile-bottom-nav (both declared later in the DOM,
   outside <main>, see includes/footer.php). That trap doesn't announce
   itself as a z-index bug: the modal still opens, still shows a backdrop,
   still gets its own padding — it just can never actually paint above
   the topbar/bottom-nav's own fixed layer, no matter how high its
   z-index is set, which is what produced the un-dimmed topbar strip and
   the bottom nav sitting in front of the modal's footer. Limiting
   will-change (and therefore the stacking context) to only the brief
   window an actual transition is running fixes that for every modal at
   once, without touching the transition's own smoothness. */
#app-content {
    transition: opacity .12s ease-out, transform .12s ease-out;
}
#app-content.is-replacing,
#app-content.is-entering {
    will-change: opacity, transform;
}
#app-content.is-replacing { opacity: 0; transform: translateY(4px); }
#app-content.is-entering { animation: spa-content-enter .15s ease-out; }
#app-content.is-loading { cursor: progress; }
@keyframes spa-content-enter {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
    #app-content, #app-content.is-replacing { transition: opacity .08s linear; transform: none; }
    #app-content.is-entering { animation: none; }
}
.topbar .page-title { font-family: var(--font-display); font-weight: 700; font-size: 1.05rem; }
.topbar .page-title.has-sub { display: flex; flex-direction: column; line-height: 1.2; gap: .05rem; }
.topbar .page-title.has-sub .page-title-main { font-size: .78rem; font-weight: 600; color: var(--text-muted); }
.topbar .page-title.has-sub .page-title-sub { font-size: 1.05rem; font-weight: 700; }
.topbar-actions { display: flex; align-items: center; gap: .6rem; }
.icon-btn {
    width: 38px; height: 38px; border-radius: var(--radius-sm);
    border: 1px solid var(--border); background: var(--surface);
    display: flex; align-items: center; justify-content: center;
    color: var(--text-muted); cursor: pointer;
}
.icon-btn:hover { background: var(--surface-alt); color: var(--text); }
.icon-btn svg { width: 18px; height: 18px; }
.menu-toggle { display: none; }

.offline-indicator {
    display: flex; align-items: center; gap: .3rem;
    height: 38px; padding: 0 .6rem; border-radius: var(--radius-sm);
    background: #fef3c7; color: #92400e; border: 1px solid #fcd34d;
    font-size: .78rem; font-weight: 600;
}
.offline-indicator svg { width: 16px; height: 16px; }
[data-theme="dark"] .offline-indicator { background: #3f2d0a; color: #fbbf24; border-color: #78530f; }

/* Connection status pill (online). The offline-indicator above already
   covers the "offline" state on its own; this covers "online" and lives
   inside the topbar-more group so it travels into the mobile dropdown. */
.connection-status {
    display: flex; align-items: center; gap: .4rem;
    height: 38px; padding: 0 .7rem; border-radius: var(--radius-sm);
    background: var(--green-100); color: var(--green-600); border: 1px solid var(--green-100);
    font-size: .78rem; font-weight: 600; flex-shrink: 0;
}
.connection-status svg { width: 15px; height: 15px; flex-shrink: 0; }
[data-theme="dark"] .connection-status.is-online { background: #0f2e1c; color: #4ade80; border-color: #145c31; }
.connection-status.is-offline { background: #fef3c7; color: #92400e; border-color: #fcd34d; }
[data-theme="dark"] .connection-status.is-offline { background: #3f2d0a; color: #fbbf24; border-color: #78530f; }

/* Topbar "more" group: on desktop this is just an inline wrapper around the
   connection status + academic year switcher (renders exactly as before).
   On mobile it becomes a dropdown panel triggered by the kebab button. */
.topbar-more-group { display: flex; align-items: center; gap: .6rem; }
.topbar-more { display: none; position: relative; }
.topbar-more-btn { position: relative; }
.topbar-more-dot {
    position: absolute; top: 6px; right: 6px;
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--orange-500); border: 2px solid var(--surface);
}
/* The mobile-only "Download QR Code(s)" entry inside the kebab dropdown is
   irrelevant on desktop, where the real button already renders inline in
   the topbar (.topbar-qr-export-desktop) — hidden here, shown instead by
   the max-width: 900px override below. */
.topbar-more-qr-export { display: none; }

/* Academic year switcher (topbar) */
.year-switcher { position: relative; }
.year-switcher-btn {
    display: flex; align-items: center; gap: .5rem;
    height: 38px; padding: 0 .85rem; border-radius: var(--radius-sm);
    border: 1px solid var(--border); background: var(--surface);
    color: var(--text); font-family: var(--font-body); font-weight: 600; font-size: .85rem;
    cursor: pointer; transition: border-color .15s ease, background-color .15s ease;
}
.year-switcher-btn:hover { background: var(--surface-alt); }
.year-switcher-btn svg { width: 16px; height: 16px; flex-shrink: 0; color: var(--text-muted); }
.year-switcher-btn svg:last-child { width: 14px; height: 14px; transition: transform .15s ease; }
.year-switcher[aria-expanded="true"] .year-switcher-btn svg:last-child,
.year-switcher-btn[aria-expanded="true"] svg:last-child { transform: rotate(180deg); }
.year-switcher-menu {
    position: absolute; top: calc(100% + .5rem); right: 0; z-index: 60;
    min-width: 240px; max-width: 320px;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg); padding: .4rem; max-height: 70vh; overflow-y: auto;
}
.year-switcher-menu-label {
    padding: .5rem .65rem .35rem; font-size: .72rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint);
}
.year-switcher-item {
    display: flex; align-items: center; justify-content: space-between; gap: .6rem;
    width: 100%; padding: .6rem .65rem; border-radius: calc(var(--radius-sm) - 2px);
    border: none; background: transparent; color: var(--text);
    font-family: var(--font-body); font-size: .87rem; font-weight: 500;
    text-align: left; cursor: pointer; transition: background-color .12s ease;
}
.year-switcher-item:hover { background: var(--surface-alt); }
.year-switcher-item.is-active { font-weight: 700; color: var(--orange-600); background: var(--orange-50); }
[data-theme="dark"] .year-switcher-item.is-active { background: var(--navy-700); color: #fff; }
.year-switcher-item .badge { flex-shrink: 0; }
.year-switcher-item.is-add { color: var(--orange-600); font-weight: 600; gap: .5rem; justify-content: flex-start; }
.year-switcher-item.is-add svg { width: 15px; height: 15px; flex-shrink: 0; }
.year-switcher-divider { height: 1px; background: var(--border); margin: .35rem .2rem; }

/* Scrollable content area — begins directly under the (non-scrolling) topbar,
   so scrolling always starts from the top of the page content, not the topbar. */
.main-area > main {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
}

.page-container { padding: 1.5rem; width: 100%; }

/* =============================================================================
   Cards, badges, buttons
   ============================================================================= */

.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
}
.card-pad { padding: 1.25rem; }
.card-header {
    display: flex; align-items: center; justify-content: space-between;
    padding: 1.1rem 1.25rem; border-bottom: 1px solid var(--border);
    flex-wrap: wrap; row-gap: .4rem; min-width: 0;
}
.card-header h3 { margin: 0; min-width: 0; overflow-wrap: anywhere; }

/* Reports page: the "Unresolved" card header sits directly above the
   desktop data table, so its border-bottom doubles up visually with the
   table's own header border on large screens. Remove it there only —
   mobile keeps the divider above the stacked cards. */
@media (min-width: 701px) {
    .card-header--unresolved { border-bottom: none; }
}

.eyebrow {
    display: inline-flex; align-items: center; gap: .4rem;
    background: var(--orange-50); color: var(--orange-700);
    border: 1px solid var(--orange-100);
    font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
    padding: .3rem .7rem; border-radius: var(--radius-pill);
    max-width: 100%; white-space: nowrap; overflow: hidden;
}
.eyebrow span,
.eyebrow { font-size: clamp(.56rem, 2.6vw, .72rem); }
[data-theme="dark"] .eyebrow { background: rgba(242,118,15,0.12); border-color: rgba(242,118,15,0.25); }

.badge {
    display: inline-flex; align-items: center; gap: .3rem;
    font-size: .72rem; font-weight: 700; padding: .28rem .62rem;
    border-radius: var(--radius-pill); letter-spacing: .01em; white-space: nowrap;
}
.badge-success { background: var(--green-100); color: var(--green-600); }
.badge-warning { background: var(--amber-100); color: var(--amber-600); }
.badge-danger  { background: var(--red-100); color: var(--red-600); }
.badge-info    { background: var(--blue-100); color: var(--blue-600); }
.badge-neutral { background: var(--surface-alt); color: var(--text-muted); }
.settings-status-badge svg { width: 12px; height: 12px; }

/* School logo uploader (Settings > General, super admin only) */
.settings-logo-form { margin-top: .5rem; }

/* Dotted drag-and-drop dropzone: click-to-browse and drag/drop both target
   the same hidden file input (positioned to cover the whole zone so it's
   clickable everywhere, not just on a nested button). */
.settings-logo-dropzone {
    position: relative;
    display: flex;
    align-items: center;
    gap: 1.1rem;
    padding: 1.1rem 1.25rem;
    border: 2px dashed var(--border);
    border-radius: var(--radius-md);
    background: var(--surface-alt);
    cursor: pointer;
    transition: border-color .15s ease, background-color .15s ease;
}
.settings-logo-dropzone:hover { border-color: var(--text-faint); }
.settings-logo-dropzone:focus-visible {
    outline: none;
    border-color: var(--orange-600);
    box-shadow: 0 0 0 3px var(--orange-100);
}
.settings-logo-dropzone.is-dragover {
    border-color: var(--orange-600);
    background: var(--orange-50);
}
[data-theme="dark"] .settings-logo-dropzone.is-dragover { background: rgba(234,88,12,.12); }
.settings-logo-dropzone__input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}
.settings-logo-dropzone__preview {
    flex-shrink: 0; width: 72px; height: 72px; border-radius: var(--radius-sm);
    border: 1px solid var(--border); background: var(--surface);
    display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.settings-logo-dropzone__preview img { width: 100%; height: 100%; object-fit: contain; }
.settings-logo-dropzone__empty-icon { color: var(--text-faint); display: flex; }
.settings-logo-dropzone__empty-icon svg { width: 26px; height: 26px; }
.settings-logo-dropzone__copy { flex: 1; min-width: 0; }
.settings-logo-dropzone__title { margin: 0; font-size: .88rem; color: var(--text); }
.settings-logo-dropzone__hint { margin: .2rem 0 0; font-size: .78rem; color: var(--text-muted); }
.settings-logo-dropzone__filename { margin: .3rem 0 0; font-size: .78rem; color: var(--orange-600); font-weight: 600; overflow-wrap: anywhere; }
.settings-logo-actions { display: flex; align-items: center; gap: .6rem; margin-top: .85rem; }

@media (max-width: 520px) {
    .settings-logo-dropzone { flex-direction: column; text-align: center; }
}

[data-theme="dark"] .badge-success { background: rgba(26,138,74,.18); }
[data-theme="dark"] .badge-warning { background: rgba(179,121,10,.18); }
[data-theme="dark"] .badge-danger  { background: rgba(209,53,44,.18); }
[data-theme="dark"] .badge-info    { background: rgba(39,101,201,.18); }

.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
    font-family: var(--font-body); font-weight: 600; font-size: .88rem;
    padding: .62rem 1.1rem; border-radius: var(--radius-sm);
    border: 1px solid transparent; cursor: pointer;
    transition: background-color .15s ease, border-color .15s ease, transform .1s ease;
    text-decoration: none;
}
.btn[hidden] { display: none; }
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn svg { width: 16px; height: 16px; }
.btn-primary { background: var(--orange-600); border-color: var(--orange-600); color: #fff; }
.btn-primary:hover { background: var(--orange-700); }
/* "Download QR Code(s)" removed from the main topbar per product request —
   Scan and the theme toggle now sit next to each other. The button stays in
   the DOM (JS listeners untouched) but is hidden on all breakpoints. */
.topbar-qr-export-desktop { display: none !important; }

.btn-secondary { background: var(--surface); border-color: var(--border); color: var(--text); }
.btn-secondary:hover { background: var(--surface-alt); }
.btn-danger { background: var(--red-600); border-color: var(--red-600); color: #fff; }
.btn-danger:hover { background: #b02b23; }
.btn-success { background: var(--green-600); border-color: var(--green-600); color: #fff; }
.btn-success:hover { background: #147a41; }
.btn-ghost { background: transparent; border-color: transparent; color: var(--text-muted); }
.btn-ghost:hover { background: var(--surface-alt); color: var(--text); }
/* Ghost-style button reserved for destructive row actions (e.g. Delete user)
   that should read as text-only but still signal danger via color. */
.btn-danger-text { background: transparent; border-color: transparent; color: var(--red-600); }
.btn-danger-text:hover { background: var(--red-100); color: #b02b23; }
.btn-block { width: 100%; }
.btn-sm { padding: .4rem .75rem; font-size: .8rem; }
.btn-lg { padding: .85rem 1.5rem; font-size: .95rem; border-radius: var(--radius-md); }
.btn:disabled { opacity: .55; cursor: not-allowed; }

/* =============================================================================
   Forms
   ============================================================================= */

label { display: block; font-size: .82rem; font-weight: 600; color: var(--text); margin-bottom: .35rem; }
.form-group { margin-bottom: 1.1rem; }
.form-hint { font-size: .78rem; color: var(--text-muted); margin-top: .3rem; }
.form-error { font-size: .78rem; color: var(--red-600); margin-top: .3rem; }
.ay-info-list { margin: 0; padding-left: 1.1rem; display: flex; flex-direction: column; gap: .65rem; }
.ay-info-list li { font-size: .88rem; line-height: 1.5; color: var(--text-muted); }
.ay-info-list li strong { color: var(--text); }

input[type="text"], input[type="email"], input[type="password"], input[type="date"],
input[type="number"], input[type="tel"], input[type="search"], input[type="time"],
select, textarea {
    width: 100%; padding: .65rem .8rem; font-size: .9rem; font-family: var(--font-body);
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--surface); color: var(--text);
}
textarea { resize: vertical; min-height: 90px; }
input:focus, select:focus, textarea:focus {
    border-color: var(--orange-500); outline: none;
    box-shadow: 0 0 0 3px rgba(242,118,15,0.15);
}
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.checkbox-row { display: flex; align-items: center; gap: .5rem; }
.checkbox-row input { width: auto; }

/* ---- Drag-and-drop file input (Excel/CSV import) ---- */
.file-dropzone {
    position: relative;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    text-align: center;
    gap: .4rem;
    padding: 1.75rem 1.25rem;
    border: 1.5px dashed var(--border);
    border-radius: var(--radius-md);
    background: var(--surface-alt);
    cursor: pointer;
    transition: border-color .15s ease, background .15s ease;
}
.file-dropzone:hover { border-color: var(--orange-300, var(--orange-500)); background: var(--orange-50); }
.file-dropzone:focus-visible { outline: none; border-color: var(--orange-500); box-shadow: 0 0 0 3px rgba(242,118,15,0.15); }
.file-dropzone.is-dragover { border-color: var(--orange-500); background: var(--orange-50); }
.file-dropzone.has-file { border-style: solid; border-color: var(--orange-500); background: var(--orange-50); }
.file-dropzone-input {
    position: absolute; inset: 0; width: 100%; height: 100%;
    opacity: 0; cursor: pointer; margin: 0; padding: 0;
}
.file-dropzone-icon {
    width: 42px; height: 42px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--surface); color: var(--orange-600);
    box-shadow: var(--shadow-sm);
    margin-bottom: .2rem;
}
.file-dropzone-icon svg { width: 20px; height: 20px; }
.file-dropzone-text { font-size: .87rem; color: var(--text); }
.file-dropzone-text strong { color: var(--orange-600); }
.file-dropzone-hint { font-size: .76rem; color: var(--text-muted); }
.file-dropzone-filename {
    display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
    margin: .55rem auto 0; padding: .45rem 1.1rem;
    max-width: 100%;
    background: var(--surface); border: 1px solid var(--border); border-radius: 999px;
    box-shadow: var(--shadow-sm);
    font-size: .78rem; font-weight: 600; color: var(--text);
    text-align: center; white-space: normal; word-break: break-word;
}
.file-dropzone-filename[hidden] { display: none; }

/* ---- Import column-mapping step ---- */
.import-map-row {
    display: grid; grid-template-columns: 1fr; gap: .4rem;
    align-items: stretch;
    padding: .6rem 0;
    border-bottom: 1px solid var(--border);
}
.import-map-row:last-child { border-bottom: none; }
.import-map-row.import-map-row--combine { grid-template-columns: 1fr; align-items: stretch; }
.import-map-field { display: flex; align-items: center; gap: .4rem; font-size: .85rem; font-weight: 600; color: var(--text); }
.import-map-field .badge-required { font-size: .66rem; font-weight: 700; color: var(--red-600); text-transform: uppercase; letter-spacing: .03em; }
.import-map-row select { padding: .5rem .65rem; font-size: .84rem; width: 100%; box-sizing: border-box; }
.import-map-combine-toggle {
    display: flex; align-items: center; gap: .4rem; margin-top: .1rem;
    font-size: .76rem; font-weight: 500; color: var(--text-muted);
    cursor: pointer; user-select: none;
}
.import-map-combine-toggle input { width: 14px; height: 14px; margin: 0; accent-color: var(--orange-600); cursor: pointer; }
.import-map-combine-panel {
    grid-column: 1 / -1; margin-top: .5rem; padding: .75rem;
    background: var(--surface-alt); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.import-map-combine-cols { display: flex; flex-wrap: wrap; gap: .4rem .9rem; }
.import-map-combine-col {
    display: flex; align-items: center; gap: .4rem; font-size: .82rem; color: var(--text);
    cursor: pointer; user-select: none;
}
.import-map-combine-col input { width: 14px; height: 14px; margin: 0; accent-color: var(--orange-600); cursor: pointer; }
.import-map-combine-order { font-size: .72rem; color: var(--text-muted); margin-top: .5rem; }
.import-map-combine-preview { font-size: .78rem; color: var(--text-muted); margin-top: .5rem; }
.import-map-combine-preview strong { color: var(--text); font-weight: 600; }
.import-summary-stat { display: flex; align-items: center; justify-content: space-between; padding: .55rem 0; border-bottom: 1px solid var(--border); font-size: .87rem; }
.import-summary-stat:last-child { border-bottom: none; }
.import-summary-stat .n { font-weight: 700; }
.import-error-list { margin: .75rem 0 0; padding-left: 1.1rem; font-size: .8rem; color: var(--text-muted); max-height: 160px; overflow-y: auto; }
.import-error-list li { margin-bottom: .25rem; }

.alert {
    padding: .85rem 1rem; border-radius: var(--radius-sm); font-size: .87rem;
    border: 1px solid transparent; margin-bottom: 1rem; display: flex; gap: .6rem; align-items: flex-start;
}
.alert svg { width: 20px; height: 20px; flex-shrink: 0; margin-top: 1px; }
.alert-success { background: var(--green-100); color: var(--green-600); border-color: rgba(26,138,74,.25); }
.alert-danger  { background: var(--red-100); color: var(--red-600); border-color: rgba(209,53,44,.25); }
.alert-warning { background: var(--amber-100); color: var(--amber-600); border-color: rgba(179,121,10,.25); }
.alert-info    { background: var(--blue-100); color: var(--blue-600); border-color: rgba(39,101,201,.25); }

/* Device scan page: "This laptop is in punishment" banner. Reuses
   .alert-danger's colors; adds a slightly emphasized quoted-reason line
   and tightens spacing for the extra sentences this alert carries versus
   the single-line alerts elsewhere in the app. */
.punishment-alert { align-items: flex-start; }
.punishment-alert-reason {
    margin-top: .5rem; font-style: italic; opacity: .9;
    border-left: 2px solid currentColor; padding-left: .6rem;
}

/* Public (unauthenticated) QR scan page — "Report" button. Full-width and
   comfortably tappable at any viewport width since .public-device-card is
   already fluid (width:100%, max-width:480px); no extra breakpoint needed. */
.public-report-row .btn { font-size: .92rem; }

/* =============================================================================
   Global "In Punishment" floating alert — a blinking red button fixed to
   the bottom-right corner, present on every authenticated page (rendered
   once in footer.php, outside #app-content, so it survives SPA route
   swaps). Shown only while at least one punishment record is unreviewed;
   see assets/js/punishment-alert.js for the polling/blink-cycle logic.
   ============================================================================= */
.punishment-fab {
    position: fixed;
    right: max(18px, env(safe-area-inset-right));
    bottom: max(18px, env(safe-area-inset-bottom));
    z-index: 70;
    width: 58px; height: 58px;
    border-radius: 50%;
    border: none;
    background: var(--red-600);
    color: #fff;
    display: none;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 8px 24px rgba(209, 53, 44, .45), 0 2px 8px rgba(11, 18, 32, .18);
    transition: transform .15s ease, box-shadow .15s ease, bottom .15s ease, right .15s ease;
}
.punishment-fab svg { width: 26px; height: 26px; }
.punishment-fab:hover { transform: translateY(-2px) scale(1.04); box-shadow: 0 12px 28px rgba(209, 53, 44, .55), 0 2px 8px rgba(11, 18, 32, .22); }
.punishment-fab:active { transform: translateY(0) scale(.97); }
.punishment-fab.is-visible { display: flex; }

.punishment-fab-count {
    position: absolute; top: -4px; right: -4px;
    min-width: 20px; height: 20px; padding: 0 5px;
    border-radius: 999px;
    background: #fff; color: var(--red-600);
    font-size: .68rem; font-weight: 800; line-height: 20px; text-align: center;
    border: 2px solid var(--red-600);
    box-shadow: 0 1px 3px rgba(0,0,0,.25);
}

/* Blink cycle: driven by a class toggled on a 1-minute-on / 2-minute-off
   timer from JS (see punishment-alert.js). Kept as a CSS animation rather
   than JS-driven opacity so the blink itself stays smooth even while
   other work is happening on the main thread. */
@keyframes punishmentFabBlink {
    0%, 100% { opacity: 1; box-shadow: 0 8px 24px rgba(209, 53, 44, .45), 0 2px 8px rgba(11, 18, 32, .18); }
    50% { opacity: .35; box-shadow: 0 8px 24px rgba(209, 53, 44, .15), 0 2px 8px rgba(11, 18, 32, .1); }
}
.punishment-fab.is-blinking { animation: punishmentFabBlink 1s ease-in-out infinite; }

/* Sit just above the mobile bottom nav (instead of overlapping it) on
   narrow viewports, mirroring the same safe-area/height math the nav
   itself uses. */
@media (max-width: 880px) {
    .punishment-fab {
        bottom: calc(max(10px, env(safe-area-inset-bottom)) + var(--mobile-nav-height) + 26px);
        width: 52px; height: 52px;
    }
    .punishment-fab svg { width: 23px; height: 23px; }
}

/* Academic Years — "pending promotion" nudge banner. Desktop: icon + text
   inline, centered, button flush to the right. Mobile stacking (button
   full-width below the text) lives in mobile-data-cards.css. */
.ay-pending-alert { align-items: center; }
.ay-pending-alert-text { flex: 1; }
.ay-pending-alert-btn { flex-shrink: 0; }

/* Toast notifications: fixed just below the topbar (not overlapping it)
   so it never covers the page title/actions, and never overlaps the
   mobile bottom nav or its active-link styling either. Same rule for
   every alert type (success/danger/warning/info) since they all share
   the .toast class. */
.toast {
    position: fixed;
    top: calc(var(--topbar-h) + env(safe-area-inset-top) + 14px);
    right: 1.25rem;
    z-index: 25;
    max-width: 340px;
    box-shadow: 0 8px 24px rgba(0,0,0,.2);
    margin-bottom: 0;
}
@media (max-width: 880px) {
    .toast {
        top: max(calc(var(--topbar-h) + 14px), calc(env(safe-area-inset-top) + var(--topbar-h) + 14px));
        left: max(10px, env(safe-area-inset-left));
        right: max(10px, env(safe-area-inset-right));
        max-width: none;
    }
}

/* =============================================================================
   Tables
   ============================================================================= */

.table-wrap { overflow-x: auto; border-radius: var(--radius-md); padding-top: .75rem; }

table.data-table {
    width: 100%;
    table-layout: auto;
    border-collapse: collapse;
    font-size: .87rem;
}

/* Let the first column (always the primary "name" column across every
   table in the app) absorb the extra space so tables fill the full card
   width instead of shrink-wrapping to the narrower status/date columns
   and leaving dead whitespace on the right. */
.data-table th:first-child,
.data-table td:first-child { width: 100%; }
.data-table th:not(:first-child),
.data-table td:not(:first-child) { white-space: nowrap; }

.data-table th {
    text-align: left; font-size: .72rem; text-transform: uppercase; letter-spacing: .06em;
    font-weight: 700; color: var(--navy-500); padding: 1.15rem 1.1rem .85rem;
    border-bottom: 2px solid var(--border); background: var(--surface-alt);
}
[data-theme="dark"] .data-table th { color: var(--text-muted); }
.data-table th:first-child { border-top-left-radius: var(--radius-md); }
.data-table th:last-child { border-top-right-radius: var(--radius-md); }

.data-table td { padding: .85rem 1.1rem; border-bottom: 1px solid var(--border); vertical-align: middle; }
.data-table tr:last-child td { border-bottom: none; }
.data-table tbody tr:hover { background: var(--surface-alt); }
.cell-muted { color: var(--text-muted); }
.cell-strong { font-weight: 600; }

/* Maintenance table: dedicated actions column (separate from Status) so the
   Start work / Mark resolved buttons never clip off the right edge of the
   table on narrower desktop widths. */
.data-table th.col-actions,
.data-table td.col-actions { white-space: normal; }
.maint-actions {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: .5rem;
}
.maint-actions form { display: inline-flex; flex: 0 0 auto; }
.maint-actions .btn { white-space: nowrap; flex: 0 0 auto; }

/* Punishment table: Review / Free / View must stay on a single row rather
   than stacking vertically when the column is squeezed. */
.punishment-row-actions {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: .5rem;
}
.punishment-row-actions form { display: inline-flex; flex: 0 0 auto; }
.punishment-row-actions .btn,
.punishment-row-actions > a.btn { white-space: nowrap; flex: 0 0 auto; }
.data-table td.col-actions:has(.punishment-row-actions) { white-space: normal; }
.btn-maint-start { background: var(--blue-100); border-color: var(--blue-100); color: var(--blue-600); white-space: nowrap; }
.btn-maint-start:hover { background: var(--blue-600); border-color: var(--blue-600); color: #fff; }
.btn-maint-resolve { background: var(--green-100); border-color: var(--green-100); color: var(--green-600); white-space: nowrap; }
.btn-maint-resolve:hover { background: var(--green-600); border-color: var(--green-600); color: #fff; }
[data-theme="dark"] .btn-maint-start { background: rgba(37,99,235,.18); border-color: rgba(37,99,235,.18); color: #93c5fd; }
[data-theme="dark"] .btn-maint-start:hover { background: rgba(37,99,235,.28); border-color: rgba(37,99,235,.28); }
[data-theme="dark"] .btn-maint-resolve { background: rgba(26,138,74,.18); border-color: rgba(26,138,74,.18); color: #86efac; }
[data-theme="dark"] .btn-maint-resolve:hover { background: rgba(26,138,74,.28); border-color: rgba(26,138,74,.28); }

/* ---- Mobile: tables are replaced by .mobile-data-card-list markup below the
   breakpoint (see mobile-data-cards.css/.js) rather than reflowed with CSS,
   so .table-wrap itself is hidden here instead of restyled. The desktop
   table markup is left completely untouched above this breakpoint. ---- */
@media (max-width: 700px) {
    .table-wrap:not(.desktop-only-table-force) { display: none; }
}

/* =============================================================================
   Dashboard — Accountability trend chart
   Premium chart card shown above the metric grid. Reuses the app's card,
   radius, shadow, and navy/orange design tokens rather than introducing a
   new palette.
   ============================================================================= */

.acct-row {
    display: grid;
    grid-template-columns: 1.4fr 1fr;
    gap: 1.25rem;
    align-items: stretch;
}
.acct-row .acct-chart-card,
.acct-row .progress-card { margin-bottom: 0; }

.acct-chart-card {
    padding: 1.5rem 1.5rem 1.25rem;
    margin-bottom: 1.5rem;
    background: linear-gradient(165deg, var(--surface) 0%, var(--surface) 60%, var(--orange-50) 145%);
    position: relative;
    overflow: hidden;
}
[data-theme="dark"] .acct-chart-card {
    background: linear-gradient(165deg, var(--surface) 0%, var(--surface) 55%, rgba(242,118,15,0.07) 150%);
}

.acct-chart-top {
    display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem;
    margin-bottom: 1.1rem; flex-wrap: wrap;
}

.acct-seg {
    display: inline-flex; align-items: center; gap: .15rem;
    background: var(--surface-alt); border: 1px solid var(--border);
    border-radius: var(--radius-pill); padding: .25rem;
}
.acct-seg-btn {
    border: none; background: transparent; cursor: pointer;
    font-family: var(--font-body); font-weight: 600; font-size: .82rem;
    color: var(--text-muted); padding: .45rem 1rem; border-radius: var(--radius-pill);
    transition: background-color .18s ease, color .18s ease, box-shadow .18s ease;
    white-space: nowrap;
}
.acct-seg-btn:hover { color: var(--text); }
.acct-seg-btn.is-active {
    background: var(--surface); color: var(--navy-900); box-shadow: var(--shadow-sm);
}
[data-theme="dark"] .acct-seg-btn.is-active { color: #fff; }
.acct-seg-btn:focus-visible { outline: 2px solid var(--orange-500); outline-offset: 2px; }

/* Users page — filter by role. Lives inside .records-filter-card, in the
   same position other pages put their .seg-filter-group blocks (right
   above the table/db-outer split view), reusing the .acct-seg segmented-
   button look. Unlike .seg-filter-group's other uses (which are
   mobile-only filter summaries, hidden on desktop in favor of a <select>),
   Users has no other desktop filter control, so this one opts back in
   with its own always-on display, overriding the
   `.seg-filter-group { display: none }` default — visible as proper
   filter buttons on large screens as well as mobile. */
.users-role-filter.seg-filter-group {
    display: block;
    min-width: 0;
    max-width: 100%;
    margin-bottom: 1.1rem;
}
.users-role-filter .seg-filter-label {
    font-size: .72rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .04em; color: var(--text-faint); margin-bottom: .5rem;
}
.users-role-filter .seg-filter-current { color: var(--orange-600); font-weight: 700; }
.users-role-filter .acct-seg {
    max-width: 100%; box-sizing: border-box;
    overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.users-role-filter .acct-seg::-webkit-scrollbar { display: none; }
.users-role-filter .acct-seg-btn { text-decoration: none; display: inline-block; }
.users-role-filter .acct-seg-btn:hover { text-decoration: none; }

.acct-total { text-align: right; }
.acct-total-value {
    font-family: var(--font-display); font-weight: 800; font-size: 1.35rem; line-height: 1.1;
    color: var(--navy-900); letter-spacing: -.01em; white-space: nowrap;
}
[data-theme="dark"] .acct-total-value { color: #fff; }
.acct-total-label { font-size: .78rem; color: var(--text-muted); font-weight: 600; margin-top: .3rem; }

.acct-chart-heading { margin-bottom: 1rem; }
.acct-chart-heading h3 {
    display: flex; align-items: center; gap: .5rem; margin: 0 0 .25rem;
    font-size: 1.05rem;
}
.acct-chart-heading h3 svg { width: 18px; height: 18px; color: var(--orange-600); }
.acct-chart-heading p { margin: 0; font-size: .85rem; color: var(--text-muted); }

.acct-chart-body { position: relative; min-height: 230px; }

.acct-chart-inner { position: relative; width: 100%; }
.acct-svg { display: block; width: 100%; height: 230px; overflow: visible; }
.acct-line { filter: drop-shadow(0 3px 6px rgba(242,118,15,0.25)); }
.acct-area { opacity: .9; }
.acct-indicator { stroke: var(--navy-300); stroke-width: 1.5; stroke-dasharray: 3 3; }
[data-theme="dark"] .acct-indicator { stroke: var(--navy-600); }
.acct-pt { transition: r .18s ease, fill .18s ease; }
.acct-hit { cursor: pointer; }

.acct-tooltip {
    position: absolute; top: 0; transform: translate(-50%, calc(-100% - 11px));
    background: var(--navy-900); color: #fff;
    font-family: var(--font-display); font-weight: 700; font-size: 1rem;
    padding: .45rem .85rem; border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg); pointer-events: none;
    transition: left .25s cubic-bezier(.22,.9,.3,1), top .25s cubic-bezier(.22,.9,.3,1), transform .25s cubic-bezier(.22,.9,.3,1);
    white-space: nowrap;
    --arrow-shift: 0px;
}
[data-theme="dark"] .acct-tooltip { background: #f2f4f8; color: var(--navy-900); }
.acct-tooltip::after {
    content: ''; position: absolute; left: calc(50% + var(--arrow-shift)); bottom: -5px; transform: translateX(-50%);
    width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent;
    border-top: 5px solid var(--navy-900);
}
[data-theme="dark"] .acct-tooltip::after { border-top-color: #f2f4f8; }

.acct-tooltip-month {
    position: absolute; bottom: -1.55rem; transform: translateX(-50%);
    font-size: .72rem; font-weight: 700; color: var(--orange-600);
    background: var(--orange-50); border: 1px solid var(--orange-100);
    padding: .1rem .5rem; border-radius: var(--radius-sm);
    transition: left .25s cubic-bezier(.22,.9,.3,1), transform .25s cubic-bezier(.22,.9,.3,1); pointer-events: none;
}
[data-theme="dark"] .acct-tooltip-month { background: rgba(242,118,15,0.14); border-color: rgba(242,118,15,0.3); }

.acct-labels {
    display: flex; justify-content: space-between; margin-top: 1.9rem;
    padding: 0 26px; font-size: .76rem; color: var(--text-faint); font-weight: 600;
}
.acct-labels span {
    transition: opacity .2s ease, color .15s ease;
    cursor: pointer;
    padding: .2rem .3rem;
    margin: -.2rem -.3rem;
    border-radius: var(--radius-sm);
}
.acct-labels span:hover { color: var(--text); }
.acct-labels span:focus-visible { outline: 2px solid var(--orange-500); outline-offset: 1px; }
.acct-labels span.is-active-label { opacity: 0; }

.acct-chart-skeleton { padding-top: 2.5rem; }
.acct-skel-line {
    height: 160px; border-radius: var(--radius-md);
    background: linear-gradient(100deg, var(--surface-alt) 30%, var(--border) 50%, var(--surface-alt) 70%);
    background-size: 200% 100%;
    animation: acctSkelShimmer 1.4s ease-in-out infinite;
}
@keyframes acctSkelShimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
    .acct-skel-line { animation: none; }
}

.acct-chart-empty {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: .5rem; min-height: 200px; text-align: center; padding: 1rem;
}
.acct-chart-empty p { margin: 0; font-weight: 600; color: var(--text); }
.acct-chart-error p { color: var(--red-600); }

@media (max-width: 700px) {
    .acct-chart-card { padding: 1.1rem 1rem 1rem; }
    .acct-chart-top { flex-direction: column; align-items: stretch; gap: .85rem; }
    .acct-total { text-align: left; }
    .acct-total-value { font-size: 1.2rem; }
    .acct-seg { width: 100%; }
    .acct-seg-btn { flex: 1; text-align: center; padding: .45rem .5rem; font-size: .78rem; }
    .acct-labels { padding: 0 8px; font-size: .68rem; }
    .acct-svg { height: 190px; }
    .acct-chart-body { min-height: 190px; }
}

@media (max-width: 1024px) {
    .acct-row { grid-template-columns: 1fr; }
}

/* =============================================================================
   Dashboard — Accountability progress donut card
   Sits directly under the trend chart. Reuses the same card/gradient
   treatment and navy/orange tokens so the two chart cards read as a pair.
   ============================================================================= */

.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

.progress-card {
    padding: 1.5rem 1.5rem 1.4rem;
    margin-bottom: 1.5rem;
    background: linear-gradient(165deg, var(--surface) 0%, var(--surface) 60%, var(--orange-50) 145%);
    position: relative;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}
[data-theme="dark"] .progress-card {
    background: linear-gradient(165deg, var(--surface) 0%, var(--surface) 55%, rgba(242,118,15,0.07) 150%);
}

.progress-card-top {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    margin-bottom: 1.25rem; flex-wrap: wrap;
}
.progress-card-top h3 {
    display: flex; align-items: center; gap: .5rem; margin: 0; font-size: 1.05rem;
}
.progress-card-top h3 svg { width: 18px; height: 18px; color: var(--orange-600); }

.progress-period { position: relative; display: inline-flex; align-items: center; }
.progress-period-select {
    appearance: none; -webkit-appearance: none;
    font-family: var(--font-body); font-weight: 600; font-size: .82rem;
    color: var(--text); background: var(--surface-alt); border: 1px solid var(--border);
    border-radius: var(--radius-sm); padding: .45rem 2.1rem .45rem .85rem;
    cursor: pointer; transition: border-color .18s ease, box-shadow .18s ease;
}
.progress-period-select:hover { border-color: var(--navy-400); }
.progress-period-select:focus-visible { outline: 2px solid var(--orange-500); outline-offset: 2px; }
.progress-period svg {
    position: absolute; right: .7rem; width: 12px; height: 12px; color: var(--text-muted);
    pointer-events: none;
}

#progressPeriodSeg { flex-wrap: wrap; }
#progressPeriodSeg .acct-seg-btn { padding: .4rem .75rem; font-size: .78rem; }

@media (max-width: 1300px) and (min-width: 1025px) {
    #progressPeriodSeg .acct-seg-btn { padding: .38rem .55rem; font-size: .72rem; }
}

.progress-card-body { display: flex; align-items: center; justify-content: center; gap: 2rem; flex-wrap: wrap; flex: 1; }

.progress-ring-wrap { position: relative; width: 152px; height: 152px; flex: 0 0 auto; }
.progress-ring { width: 152px; height: 152px; display: block; }
.progress-ring-track {
    fill: none; stroke: var(--surface-alt); stroke-width: 13;
}
[data-theme="dark"] .progress-ring-track { stroke: var(--navy-700); }
.progress-ring-arc {
    fill: none; stroke: var(--navy-600); stroke-width: 13; stroke-linecap: round;
    filter: drop-shadow(0 3px 6px rgba(242,118,15,0.2));
}
[data-theme="dark"] .progress-ring-arc { stroke: var(--orange-400); }

.progress-ring-center {
    position: absolute; inset: 0; display: flex; flex-direction: column;
    align-items: center; justify-content: center; text-align: center; pointer-events: none;
    padding: 0 1.4rem;
}
.progress-ring-pct {
    font-family: var(--font-display); font-weight: 800; font-size: 1.9rem; line-height: 1;
    color: var(--navy-900); letter-spacing: -.01em;
}
[data-theme="dark"] .progress-ring-pct { color: #fff; }
.progress-ring-label {
    font-size: .72rem; font-weight: 600; color: var(--text-muted); margin-top: .4rem; line-height: 1.25;
}

.progress-breakdown-wrap { flex: 1 1 200px; display: flex; flex-direction: column; min-width: 180px; }
.progress-breakdown { display: flex; flex-direction: column; gap: .65rem; }
.progress-row-count { margin-left: auto; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.progress-row-divider { height: 1px; background: var(--border); margin: .35rem -.3rem; padding: 0; pointer-events: none; border-radius: 0; }
.progress-row {
    display: flex; align-items: center; gap: .55rem; font-size: .86rem;
    border-radius: var(--radius-sm); padding: .15rem .3rem; margin: -.15rem -.3rem;
    transition: background-color .15s ease;
}
.progress-row:hover, .progress-row:focus-visible { background: var(--surface-alt); }
.progress-row:focus-visible { outline: 2px solid var(--orange-500); outline-offset: 1px; }
.progress-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.progress-row-label { color: var(--text); font-weight: 500; }
.progress-row-pct { margin-left: auto; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }

.progress-skeleton { display: flex; align-items: center; gap: 2rem; width: 100%; }
.progress-skel-ring {
    width: 152px; height: 152px; border-radius: 50%; flex: 0 0 auto;
    background: linear-gradient(100deg, var(--surface-alt) 30%, var(--border) 50%, var(--surface-alt) 70%);
    background-size: 200% 100%; animation: acctSkelShimmer 1.4s ease-in-out infinite;
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 13px), #000 calc(100% - 13px));
    mask: radial-gradient(farthest-side, transparent calc(100% - 13px), #000 calc(100% - 13px));
}
.progress-skel-list { flex: 1; display: flex; flex-direction: column; gap: .6rem; }
.progress-skel-row {
    height: 14px; border-radius: var(--radius-sm); max-width: 220px;
    background: linear-gradient(100deg, var(--surface-alt) 30%, var(--border) 50%, var(--surface-alt) 70%);
    background-size: 200% 100%; animation: acctSkelShimmer 1.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
    .progress-skel-ring, .progress-skel-row { animation: none; }
}

.progress-empty {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: .5rem; min-height: 120px; width: 100%; text-align: center; padding: 1rem;
}
.progress-empty p { margin: 0; font-weight: 600; color: var(--text); }
.progress-error p { color: var(--red-600); }

@media (max-width: 700px) {
    .progress-card { padding: 1.1rem 1rem 1.1rem; }
    .progress-card-top { flex-direction: column; align-items: stretch; gap: .85rem; }
    .progress-period-select { width: 100%; padding: .5rem .85rem; }
    #progressPeriodSeg { width: 100%; }
    #progressPeriodSeg .acct-seg-btn { flex: 1 1 auto; text-align: center; padding: .45rem .5rem; font-size: .78rem; }
    .progress-card-body { flex-direction: column; align-items: center; gap: 1.4rem; text-align: center; margin-bottom: 0; }
    .progress-ring-wrap { width: 172px; height: 172px; }
    .progress-ring { width: 172px; height: 172px; }
    .progress-skel-ring { width: 172px; height: 172px; }
    .progress-ring-pct { font-size: 2.1rem; }
    .progress-breakdown-wrap { width: 100%; max-width: 320px; margin-bottom: 0; }
    .progress-breakdown { width: 100%; }
    .progress-breakdown .progress-row:last-child { margin-bottom: -.15rem; }
    .progress-row-label { text-align: left; }
}

/* =============================================================================
   Dashboard metrics
   ============================================================================= */

.metric-grid {
    display: grid; grid-template-columns: repeat(6, 1fr); gap: 1rem; margin-bottom: 1.5rem;
}
.metric-card {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md);
    padding: 1.1rem; box-shadow: var(--shadow-sm);
}
.metric-card .label { font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); font-weight: 700; margin-bottom: .5rem; }
.metric-card .value { font-family: var(--font-display); font-size: 1.9rem; font-weight: 700; line-height: 1; }
.metric-card.accent-orange .value { color: var(--orange-600); }
.metric-card.accent-green .value { color: var(--green-600); }
.metric-card.accent-red .value { color: var(--red-600); }
.metric-card.accent-amber .value { color: var(--amber-600); }
.metric-card.accent-navy .value { color: var(--navy-700); }
[data-theme="dark"] .metric-card.accent-navy .value { color: #e7ebf2; }

/* =============================================================================
   Records bulk-select (multi-select + bulk delete)
   Shared by the Classes and Laptops pages (and any future records page that
   adopts the same "checkbox column + sticky bulk action bar" pattern).
   Class-specific selectors (.classes-row-checkbox, #classesSelectAllHeader,
   .classes-select-col, .classes-bulk-bar, etc.) are kept as-is and simply
   listed alongside the shared .records-select-col / .records-row-checkbox /
   .records-bulk-bar equivalents so existing Classes markup keeps working
   unchanged while Laptops (and anything else) can opt in via the shared
   class names instead of duplicating this whole block per page.
   ============================================================================= */
.records-select-col,
.records-select-col.records-select-col,
.classes-select-col,
.classes-select-col.classes-select-col { width: 2.4rem; min-width: 2.4rem; max-width: 2.4rem; text-align: center; padding-left: .6rem; padding-right: .6rem; }
/* first-child cells default to width:100% in .data-table — override it here
   since the select column is the first column in these tables. */
.data-table th.records-select-col:first-child,
.data-table td.records-select-col:first-child,
.data-table th.classes-select-col:first-child,
.data-table td.classes-select-col:first-child { width: 2.4rem; min-width: 2.4rem; max-width: 2.4rem; }

/* Custom checkbox styling for bulk-select: dark navy border/track, orange
   fill + check when selected. Applies everywhere a select checkbox appears
   — the desktop table column, the bulk-bar "select all", and the mobile
   card view ("Select" row) — so PC and mobile match exactly. The tick/dash
   marks are centered with flexbox (not fixed pixel offsets) so they land
   dead-center on the box regardless of box size. */
.records-select-col input[type="checkbox"],
.records-row-checkbox,
.records-select-all,
.classes-select-col input[type="checkbox"],
.classes-row-checkbox,
#classesSelectAllHeader {
    appearance: none; -webkit-appearance: none; -moz-appearance: none;
    width: 18px; height: 18px; margin: 0; flex-shrink: 0;
    border: 2px solid var(--navy-600); border-radius: 5px;
    background: var(--surface); cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    vertical-align: middle;
    transition: background-color .15s ease, border-color .15s ease;
}
[data-theme="dark"] .records-select-col input[type="checkbox"],
[data-theme="dark"] .records-row-checkbox,
[data-theme="dark"] .records-select-all,
[data-theme="dark"] .classes-select-col input[type="checkbox"],
[data-theme="dark"] .classes-row-checkbox,
[data-theme="dark"] #classesSelectAllHeader {
    border-color: var(--navy-300); background: var(--navy-800);
}
.records-select-col input[type="checkbox"]:hover,
.records-row-checkbox:hover,
.records-select-all:hover,
.classes-select-col input[type="checkbox"]:hover,
.classes-row-checkbox:hover,
#classesSelectAllHeader:hover { border-color: var(--orange-500); }
.records-select-col input[type="checkbox"]:focus-visible,
.records-row-checkbox:focus-visible,
.records-select-all:focus-visible,
.classes-select-col input[type="checkbox"]:focus-visible,
.classes-row-checkbox:focus-visible,
#classesSelectAllHeader:focus-visible {
    outline: 2px solid var(--orange-400); outline-offset: 1px;
}
.records-select-col input[type="checkbox"]:checked,
.records-row-checkbox:checked,
.records-select-all:checked,
.classes-select-col input[type="checkbox"]:checked,
.classes-row-checkbox:checked,
#classesSelectAllHeader:checked {
    background: var(--orange-500); border-color: var(--orange-600);
}
.records-select-col input[type="checkbox"]:checked::after,
.records-row-checkbox:checked::after,
.records-select-all:checked::after,
.classes-select-col input[type="checkbox"]:checked::after,
.classes-row-checkbox:checked::after,
#classesSelectAllHeader:checked::after {
    content: "";
    width: 5px; height: 9px;
    /* Small upward nudge to optically center the checkmark glyph itself
       (its visual weight sits slightly below its bounding box once rotated). */
    margin-top: -1px;
    border: solid var(--navy-950);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}
.records-select-col input[type="checkbox"]:indeterminate,
.records-row-checkbox:indeterminate,
.records-select-all:indeterminate,
.classes-select-col input[type="checkbox"]:indeterminate,
.classes-row-checkbox:indeterminate,
#classesSelectAllHeader:indeterminate {
    background: var(--orange-500); border-color: var(--orange-600);
}
.records-select-col input[type="checkbox"]:indeterminate::after,
.records-row-checkbox:indeterminate::after,
.records-select-all:indeterminate::after,
.classes-select-col input[type="checkbox"]:indeterminate::after,
.classes-row-checkbox:indeterminate::after,
#classesSelectAllHeader:indeterminate::after {
    content: "";
    width: 10px; height: 2px; background: var(--navy-950);
}
/* .checkbox-row input { width: auto } (used for the mobile "Select" row and
   other plain checkbox-row instances) would otherwise win over the 18px box
   above by source order — pin the size back down wherever it's a bulk-select
   checkbox, on mobile included. */
.checkbox-row .records-row-checkbox,
.checkbox-row .records-select-all,
.checkbox-row .classes-row-checkbox,
.checkbox-row #classesSelectAllHeader {
    width: 18px; height: 18px;
}
/* Bulk selection bar: modern pill-style bar with a subtle accent to make it
   clear it's a transient, selection-driven action bar rather than a static
   toolbar. The checkbox/count group and the actions group (Delete + Clear)
   are two independent flex items — the bar itself may wrap them onto
   separate rows on narrow screens, but Delete and Clear always stay
   together as one unbreakable row via .records-bulk-actions /
   .classes-bulk-actions, never splitting apart from each other. */
.records-bulk-bar,
.classes-bulk-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem 1rem;
    flex-wrap: wrap;
    padding: .7rem .85rem .7rem 1.1rem;
    margin: 0 1rem;
    border-radius: var(--radius-md, 12px);
    background: var(--orange-50, #fff7ed);
    border: 1px solid var(--orange-200, #fed7aa);
    box-shadow: var(--shadow-sm);
}
.records-bulk-bar[hidden],
.classes-bulk-bar[hidden] { display: none; }
[data-theme="dark"] .records-bulk-bar,
[data-theme="dark"] .classes-bulk-bar {
    background: rgba(242,118,15,0.10);
    border-color: rgba(242,118,15,0.28);
}
.records-bulk-selectall,
.classes-bulk-selectall {
    font-weight: 600;
    font-size: .875rem;
    color: var(--text);
    display: flex;
    align-items: center;
    gap: .55rem;
}
.records-bulk-actions,
.classes-bulk-actions {
    display: flex;
    align-items: center;
    gap: .6rem;
    flex-wrap: nowrap;
    flex-shrink: 0;
}
.records-bulk-actions .btn,
.classes-bulk-actions .btn { height: 34px; box-sizing: border-box; }
.records-mobile-select,
.classes-mobile-select { font-size: .875rem; }
.metric-grid-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) {
    .metric-grid-4 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
    .metric-grid-4 { grid-template-columns: 1fr 1fr; }
}

.classes-count-note { white-space: nowrap; }

.class-namecell { display: flex; align-items: center; gap: .65rem; }
.class-icon-badge {
    width: 30px; height: 30px; border-radius: var(--radius-sm);
    background: var(--green-100); color: var(--green-600);
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
[data-theme="dark"] .class-icon-badge { background: rgba(26,138,74,0.18); }
.class-icon-badge svg { width: 16px; height: 16px; }

.badge-count {
    background: var(--green-100); color: var(--green-600);
    font-weight: 700;
}
[data-theme="dark"] .badge-count { background: rgba(26,138,74,0.18); }

.class-dist { display: flex; align-items: center; gap: .7rem; min-width: 180px; }
.class-dist-track {
    flex: 1 1 auto; height: 8px; border-radius: var(--radius-pill);
    background: var(--surface-alt); overflow: hidden; min-width: 90px;
}
.class-dist-fill {
    height: 100%; border-radius: var(--radius-pill);
    background: linear-gradient(90deg, var(--green-600), #23a35a);
    transition: width .3s ease;
}
.class-dist-pct { font-size: .8rem; color: var(--text-muted); font-weight: 600; width: 2.6rem; text-align: right; flex-shrink: 0; }

.classes-actions-col { width: 1%; white-space: nowrap; }
.class-row-actions { display: flex; align-items: center; gap: .4rem; justify-content: flex-end; }
.class-action-btn {
    width: 32px; height: 32px; border-radius: var(--radius-sm);
    display: flex; align-items: center; justify-content: center;
    background: var(--surface-alt); color: var(--text-muted); border: 1px solid var(--border);
    cursor: pointer; text-decoration: none; flex-shrink: 0;
    transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.class-action-btn svg { width: 15px; height: 15px; }
.class-action-btn:hover { text-decoration: none; }
.class-action-btn.is-view:hover { background: var(--blue-100); color: var(--blue-600); border-color: var(--blue-100); }
.class-action-btn.is-edit:hover { background: var(--green-100); color: var(--green-600); border-color: var(--green-100); }
.class-action-btn.is-delete { background: var(--red-100); color: var(--red-600); border-color: var(--red-100); }
.class-action-btn.is-delete:hover { background: var(--red-600); color: #fff; border-color: var(--red-600); }
[data-theme="dark"] .class-action-btn.is-view:hover { background: rgba(39,101,201,0.2); }
[data-theme="dark"] .class-action-btn.is-edit:hover { background: rgba(26,138,74,0.2); }
[data-theme="dark"] .class-action-btn.is-delete { background: rgba(209,53,44,0.18); }

.classes-actions-col .btn + .btn { margin-left: 0; }
.mobile-data-card__actions .btn[data-class-rename],
.mobile-data-card__actions .btn[data-class-delete] { margin-top: .4rem; margin-right: .4rem; }

.dash-grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: 1.25rem; }

.activity-item { display: flex; gap: .8rem; padding: .8rem 0; border-bottom: 1px solid var(--border); }
.activity-item:last-child { border-bottom: none; }
.activity-time { font-size: .74rem; color: var(--text-faint); width: 52px; flex-shrink: 0; padding-top: 2px; }
.activity-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--orange-500); margin-top: 6px; flex-shrink: 0; }
.activity-text { font-size: .86rem; }
.activity-text .who { color: var(--text-muted); }

/* =============================================================================
   Empty / loading states
   ============================================================================= */

.empty-state {
    text-align: center; padding: 3rem 1.5rem; color: var(--text-muted);
}
.empty-state svg { width: 52px; height: 52px; color: var(--text-faint); margin-bottom: 1rem; }
.empty-state h2, .empty-state h3 { color: var(--text); }

.skeleton {
    background: linear-gradient(90deg, var(--surface-alt) 25%, var(--border) 37%, var(--surface-alt) 63%);
    background-size: 400% 100%;
    animation: skeleton-loading 1.4s ease infinite;
    border-radius: 6px;
}
@keyframes skeleton-loading { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

/* =============================================================================
   Auth / public pages
   ============================================================================= */

.auth-wrap {
    min-height: 100vh; display: flex; align-items: center; justify-content: center;
    background: linear-gradient(160deg, var(--navy-950) 0%, var(--navy-800) 60%, var(--navy-700) 100%);
    padding: 1.5rem;
}
.auth-card {
    width: 100%; max-width: 400px; background: var(--surface); border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg); padding: 2.2rem 2rem;
}
.auth-brand { display: flex; align-items: center; gap: .7rem; margin-bottom: 1.6rem; }
.auth-brand .mark {
    width: 42px; height: 42px; border-radius: 12px;
    background: #fff;
    display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 800;
    font-family: var(--font-display);
    overflow: hidden;
}
.auth-brand .mark img { width: 100%; height: 100%; object-fit: contain; display: block; }
.auth-brand .name { font-family: var(--font-display); font-weight: 700; font-size: 1rem; }
.auth-brand .sub { font-size: .78rem; color: var(--text-muted); }

/* =============================================================================
   Auth v2 — split desktop panel (Ladepuls-style) + mobile hero-card (Figma-style)
   Reuses existing navy/orange tokens; scoped under .authv2 so nothing else
   in the app is affected.
   ============================================================================= */

.authv2 { min-height: 100vh; min-height: 100dvh; background: var(--bg); }
.authv2-shell {
    min-height: 100vh; min-height: 100dvh; display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr);
}

/* ---- Left / form column ---- */
.authv2-form-col {
    display: flex; flex-direction: column; justify-content: center;
    padding: 2.5rem 3.2rem; background: var(--surface);
}
.authv2-form-inner { width: 100%; max-width: 380px; margin: 0 auto; }
.authv2-brand { display: flex; align-items: center; gap: .6rem; margin-bottom: 2.4rem; }
.authv2-brand .mark {
    width: 38px; height: 38px; border-radius: 10px; flex-shrink: 0;
    background: #fff;
    display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 800;
    font-family: var(--font-display); font-size: .95rem;
    overflow: hidden;
}
.authv2-brand .mark img { width: 100%; height: 100%; object-fit: contain; display: block; }
.authv2-brand .name { font-family: var(--font-display); font-weight: 700; font-size: 1.02rem; color: var(--text); }
.authv2-title { font-family: var(--font-display); font-weight: 700; font-size: 1.6rem; color: var(--text); margin: 0 0 .35rem; }
.authv2-subtitle { font-size: .9rem; color: var(--text-muted); margin: 0 0 1.8rem; }

.authv2-field { margin-bottom: 1.15rem; }
.authv2-field label {
    display: block; font-size: .8rem; font-weight: 600; color: var(--text); margin-bottom: .4rem;
}
.authv2-input-wrap { position: relative; display: flex; align-items: center; }
.authv2-input-wrap svg.field-icon {
    position: absolute; left: .85rem; width: 17px; height: 17px; color: var(--text-faint); pointer-events: none;
}
.authv2-input-wrap input {
    width: 100%; padding: .8rem .9rem .8rem 2.6rem; font-size: .92rem; font-family: var(--font-body);
    border: 1px solid var(--border); border-radius: var(--radius-md);
    background: var(--surface-alt); color: var(--text); transition: border-color .15s ease, box-shadow .15s ease;
}
.authv2-input-wrap input:focus {
    border-color: var(--orange-500); outline: none; background: var(--surface);
    box-shadow: 0 0 0 3px rgba(242,118,15,0.15);
}
.authv2-input-wrap .toggle-pw {
    position: absolute; right: .7rem; background: none; border: none; padding: .3rem; cursor: pointer;
    color: var(--text-faint); display: flex; align-items: center; justify-content: center;
    border-radius: 6px;
}
.authv2-input-wrap .toggle-pw:hover { color: var(--text-muted); background: var(--border); }
.authv2-input-wrap .toggle-pw svg { width: 18px; height: 18px; }

.authv2-row-between { display: flex; justify-content: flex-end; margin: -.5rem 0 1.3rem; }
.authv2-forgot { font-size: .78rem; color: var(--orange-600); text-decoration: none; font-weight: 600; }
.authv2-forgot:hover { text-decoration: underline; }

.authv2-submit {
    width: 100%; display: flex; align-items: center; justify-content: center; gap: .5rem;
    padding: .85rem 1.1rem; font-family: var(--font-body); font-weight: 700; font-size: .92rem;
    border-radius: var(--radius-md); border: none; cursor: pointer; color: #fff;
    background: linear-gradient(135deg, var(--orange-500), var(--orange-700));
    box-shadow: 0 8px 20px rgba(224,103,15,0.28);
    transition: filter .15s ease, transform .1s ease;
}
.authv2-submit:hover { filter: brightness(1.06); }
.authv2-submit:active { transform: translateY(1px); }

.authv2-install-btn {
    width: 100%; align-items: center; justify-content: center; gap: .5rem;
    padding: .8rem 1.1rem; margin-top: .7rem; font-family: var(--font-body); font-weight: 700; font-size: .92rem;
    border-radius: var(--radius-md); border: 1.5px solid var(--navy-800); cursor: pointer;
    color: var(--navy-800); background: transparent; transition: background-color .15s ease;
    display: flex;
}
.authv2-install-btn[hidden] { display: none; }
.authv2-install-btn:hover { background: var(--surface-alt); }
.authv2-install-btn:disabled { cursor: default; }

.authv2-footnote { text-align: center; font-size: .82rem; color: var(--text-muted); margin-top: 1.6rem; }
.authv2-footnote a { color: var(--orange-600); font-weight: 600; text-decoration: none; }
.authv2-footnote a:hover { text-decoration: underline; }

/* ---- Right / showcase column (desktop only) ---- */
.authv2-showcase {
    position: relative; overflow: hidden; display: flex; flex-direction: column; justify-content: center;
    padding: 3rem 3.4rem; color: #fff;
    background: linear-gradient(160deg, var(--navy-950) 0%, var(--navy-800) 55%, var(--navy-700) 100%);
}
.authv2-showcase .deco-grid { position: absolute; inset: 0; width: 100%; height: 100%; color: rgba(255,255,255,.18); pointer-events: none; }
.authv2-showcase-content { position: relative; z-index: 1; max-width: 440px; }
.authv2-showcase-title {
    font-family: var(--font-display); font-weight: 700; font-size: 1.9rem; line-height: 1.25; margin: 0 0 .8rem;
    color: #fff;
}
.authv2-showcase-sub { font-size: .95rem; color: rgba(255,255,255,.72); margin: 0 0 2.2rem; }
.authv2-mockcard {
    position: relative; z-index: 1; background: var(--navy-900); border: 1px solid rgba(255,255,255,.08);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); padding: 1.3rem; max-width: 440px;
}
.authv2-mockcard-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
.authv2-mockcard-header .badge {
    display: inline-flex; align-items: center; gap: .35rem; font-size: .68rem; font-weight: 700;
    padding: .28rem .6rem; border-radius: var(--radius-pill); background: rgba(255,138,43,0.15); color: var(--orange-400);
}
.authv2-mockcard-row { display: flex; align-items: center; gap: .7rem; padding: .55rem 0; border-bottom: 1px solid rgba(255,255,255,.06); }
.authv2-mockcard-row:last-child { border-bottom: none; }
.authv2-mockcard-row .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--orange-400); flex-shrink: 0; }
.authv2-mockcard-row .lines { flex: 1; }
.authv2-mockcard-row .lines .line-1 { height: 8px; border-radius: 4px; background: rgba(255,255,255,.16); width: 70%; margin-bottom: .3rem; }
.authv2-mockcard-row .lines .line-2 { height: 6px; border-radius: 3px; background: rgba(255,255,255,.08); width: 45%; }
.authv2-trustbar { position: relative; z-index: 1; display: flex; align-items: center; gap: 1.6rem; margin-top: 2.2rem; opacity: .5; flex-wrap: wrap; }
.authv2-trustbar span { font-family: var(--font-display); font-weight: 700; font-size: .82rem; letter-spacing: .02em; }

/* ---- Mobile hero variant (Figma-style) ---- */
.authv2-mobile-hero {
    display: none; position: relative; overflow: hidden;
    background: linear-gradient(150deg, var(--navy-900) 0%, var(--navy-700) 100%);
    padding: 2.6rem 1.6rem 4rem; color: #fff;
}
.authv2-mobile-hero .deco-grid { position: absolute; inset: 0; width: 100%; height: 100%; color: rgba(255,255,255,.08); pointer-events: none; }
.authv2-mobile-hero-title { position: relative; z-index: 1; font-family: var(--font-display); font-weight: 800; font-size: 1.5rem; margin: 0 0 .3rem; letter-spacing: .01em; }
.authv2-mobile-hero-sub { position: relative; z-index: 1; font-size: .85rem; color: rgba(255,255,255,.75); margin: 0 0 1rem; }

.authv2-mobile-hero-top { position: relative; z-index: 1; display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.6rem; }
.authv2-mobile-hero-logo {
    width: 40px; height: 40px; border-radius: 10px; background: var(--surface); overflow: hidden;
    display: flex; align-items: center; justify-content: center; flex-shrink: 0; box-shadow: var(--shadow-md);
}
.authv2-mobile-hero-logo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.authv2-brand-pill {
    display: inline-flex; align-items: center; font-family: var(--font-display); font-weight: 700;
    font-size: .82rem; letter-spacing: .01em; color: #fff; background: rgba(255,255,255,.14);
    border: 1px solid rgba(255,255,255,.18); border-radius: var(--radius-pill); padding: .4rem .85rem;
}

@media (max-width: 900px) {
    html:has(.authv2), body:has(.authv2) { height: auto; min-height: 100%; overflow-y: auto; }
    /* Form shell and form column share the same white background on mobile
       (matching .authv2-form-col's var(--surface)) instead of the shell
       showing the app's grey page background (var(--bg)) behind/around the
       white form card, which read as a visible color mismatch/seam. */
    .authv2 { min-height: 100vh; min-height: 100dvh; height: auto; overflow: visible; display: flex; flex-direction: column; position: relative; z-index: 1000000; background: var(--surface); }
    .authv2-shell {
        grid-template-columns: 1fr; flex: 1 1 auto; min-height: 100vh; min-height: 100dvh; height: auto; display: flex; flex-direction: column;
        overflow-y: visible; overscroll-behavior: contain; background: var(--surface);
    }
    .authv2-showcase { display: none; }
    .authv2-mobile-hero {
        flex: 0 0 auto; display: flex; flex-direction: column; justify-content: center;
        padding: 1.4rem 1.4rem 5.2rem; min-height: 42vh; min-height: 42dvh; position: relative; z-index: 1;
    }
    .authv2-form-col {
        padding: 0 1.1rem 1.6rem; position: relative; z-index: 998;
        flex: 1 1 auto;
    }
    .authv2-form-inner {
        max-width: 460px; width: 100%; background: var(--surface); border-radius: var(--radius-lg);
        box-shadow: var(--shadow-lg); padding: 1.4rem 1.4rem 1.2rem; margin: -50px auto 0;
        position: relative; z-index: 999;
    }
    .authv2-brand { display: none; }
    .authv2-title { font-size: 1.35rem; }
    .authv2-subtitle { margin-bottom: 1.1rem; }
    .authv2-field { margin-bottom: .8rem; }
    .authv2-footnote { margin-top: 1rem; }
}
@media (max-width: 900px) and (max-height: 700px) {
    .authv2-mobile-hero { padding: 1rem 1.4rem 3.6rem; min-height: 38vh; min-height: 38dvh; }
    .authv2-mobile-hero-top { margin-bottom: 1rem; }
    .authv2-mobile-hero-title { font-size: 1.3rem; }
    .authv2-form-inner { margin-top: -36px; padding: 1.2rem 1.3rem 1rem; }
}
/* Short viewports (landscape phones / small window heights): prevent the
   hero + card combination from ever exceeding the visible height, which is
   what caused the login card to be pushed down and clipped off-screen. */
@media (max-width: 900px) and (max-height: 620px) {
    .authv2-mobile-hero { padding: .7rem 1.4rem 2.4rem; min-height: 34vh; min-height: 34dvh; }
    .authv2-mobile-hero-top { margin-bottom: .6rem; }
    .authv2-mobile-hero-title { font-size: 1.15rem; margin-bottom: .15rem; }
    .authv2-mobile-hero-sub { margin-bottom: .4rem; font-size: .8rem; }
    .authv2-form-inner { margin-top: -24px; padding: 1rem 1.1rem .9rem; }
    .authv2-title { font-size: 1.2rem; }
    .authv2-subtitle { margin-bottom: .8rem; }
    .authv2-field { margin-bottom: .6rem; }
    .authv2-footnote { margin-top: .7rem; }
}
@media (max-width: 420px) {
    .authv2-form-col { padding: 0 .8rem 1.4rem; }
    .authv2-form-inner { padding: 1.2rem 1.1rem 1rem; }
}
/* Taller mobile/tablet viewports: keep the card anchored near the top of
   the hero instead of drifting toward the vertical center of a very tall
   screen, so it never appears to float low and get clipped at the bottom. */
@media (max-width: 900px) and (min-height: 760px) {
    .authv2-shell { justify-content: flex-start; }
    .authv2-mobile-hero { flex: 0 0 auto; justify-content: flex-start; padding-top: 2.6rem; min-height: 0; }
    .authv2-form-col { flex: 0 0 auto; padding-bottom: 2rem; }
}

.public-device-wrap {
    min-height: 100vh; background: var(--bg); padding: 1.5rem;
    display: flex; align-items: flex-start; justify-content: center;
}
.public-device-card { width: 100%; max-width: 480px; margin-top: 2rem; }
.public-device-header {
    background: var(--navy-950); color: #fff; border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    padding: 1.5rem; text-align: center;
}
.device-status-hero { padding: 1.5rem; text-align: center; border-bottom: 1px solid var(--border); }
.device-status-hero .status-value { font-family: var(--font-display); font-size: 1.5rem; font-weight: 700; margin: .4rem 0; }
.kv-row { display: flex; justify-content: space-between; align-items: flex-start; gap: .75rem; padding: .7rem 0; border-bottom: 1px solid var(--border); font-size: .88rem; }
.kv-row:last-child { border-bottom: none; }
.kv-row .k { color: var(--text-muted); flex: 0 0 auto; }
.kv-row .v { font-weight: 600; text-align: right; word-break: break-word; overflow-wrap: anywhere; min-width: 0; }

/* =============================================================================
   Scan screen
   ============================================================================= */

.scan-hero { text-align: center; padding: 1rem 0 1.5rem; }
.scan-frame-wrap { position: relative; width: 240px; margin: 0 auto 1.2rem; }
.scan-frame {
    width: 240px; height: 240px; margin: 0 auto;
    border: 3px dashed var(--orange-400); border-radius: var(--radius-lg);
    display: flex; align-items: center; justify-content: center;
    background: var(--orange-50); position: relative; overflow: hidden;
}
[data-theme="dark"] .scan-frame { background: rgba(242,118,15,0.08); }
#qr-video { width: 100%; height: 100%; object-fit: cover; border-radius: calc(var(--radius-lg) - 3px); }
.scan-frame svg { width: 64px; height: 64px; color: var(--orange-400); }

.scan-torch-btn {
    position: absolute; bottom: -16px; right: -16px; z-index: 2;
    width: 44px; height: 44px; border-radius: 50%; border: 3px solid var(--surface); cursor: pointer;
    background: var(--navy-800); color: #fff;
    display: flex; align-items: center; justify-content: center;
    box-shadow: var(--shadow-md);
    transition: background-color .15s ease, transform .1s ease;
}
.scan-torch-btn svg { width: 20px; height: 20px; color: #fff; }
.scan-torch-btn:hover { background: var(--navy-700); }
.scan-torch-btn:active { transform: scale(.94); }
.scan-torch-btn[aria-pressed="true"] {
    background: var(--orange-500); box-shadow: var(--shadow-md), 0 0 0 3px rgba(242,118,15,0.28);
}
.scan-torch-btn[aria-pressed="true"]:hover { background: var(--orange-600); }

/* Invitation-to-tap cue shown in low light while the torch is off and the
   user hasn't used it yet this session — draws the eye without turning the
   light on for them. Runs a handful of quick blinks, pauses, then repeats,
   so it stays noticeable without being a nonstop, annoying flicker. */
.scan-torch-btn.scan-torch-btn--blink {
    animation: scanTorchBlink 2.4s ease-in-out infinite;
}
.scan-torch-btn.scan-torch-btn--blink svg { animation: scanTorchBlinkIcon 2.4s ease-in-out infinite; }
@keyframes scanTorchBlink {
    0%, 8%, 16%, 24%, 32%, 100% { background: var(--navy-800); box-shadow: var(--shadow-md); }
    4%, 12%, 20%, 28% { background: var(--orange-500); box-shadow: var(--shadow-md), 0 0 0 5px rgba(242,118,15,0.32); }
}
@keyframes scanTorchBlinkIcon {
    0%, 8%, 16%, 24%, 32%, 100% { opacity: 1; }
    4%, 12%, 20%, 28% { opacity: .55; }
}

.device-result-card { max-width: 480px; margin: 0 auto; }
.custody-box {
    background: var(--surface-alt); border: 1px solid var(--border); border-radius: var(--radius-md);
    padding: 1rem; margin: 1rem 0;
}

/* =============================================================================
   Modal
   ============================================================================= */

.modal-backdrop {
    position: fixed; inset: 0; background: rgba(8,12,22,0.78);
    display: none; align-items: center; justify-content: center; z-index: 100; padding: 1rem;
    /* Keep the modal box itself clear of the fixed topbar/bottom-nav rather
       than relying on max-height alone — without this, a flex-centered box
       taller than the remaining space can still render with its top edge
       under the topbar or its bottom edge under the bottom nav before
       max-height clips it, and on very short viewports the centering math
       can place it there even when it fits. The backdrop keeps covering the
       full viewport (so the dimmed overlay reaches edge-to-edge and the
       bars underneath still visually darken); only the reserved padding
       that keeps the box itself out of that space is safe-area aware. */
    box-sizing: border-box;
    overflow-y: auto;
    /* body.modal-scroll-lock sets touch-action:none on body while any
       modal is open (see app.css layout-shell section) so the page behind
       the backdrop can't be touch-scrolled — re-enable normal vertical pan
       here so that lock doesn't also block scrolling the backdrop/modal
       content itself, which is the one thing that should still scroll. */
    touch-action: pan-y;
}
.modal-backdrop.open { display: flex; }
.modal-box {
    background: var(--surface); border-radius: var(--radius-md); box-shadow: var(--shadow-lg);
    width: 100%; max-width: 440px; max-height: 90vh;
    /* The box itself no longer scrolls — see .modal-body below. A rounded
       element's own scrollbar track is a plain rectangle that the browser
       does not clip to the border-radius, so on a tall modal the bar used
       to visibly run straight through/past the rounded top and bottom
       corners instead of stopping where the curve starts. Making the box
       a flex column that clips to its own radius, and moving the actual
       overflow-y onto .modal-body (already a distinct child, no markup
       changes needed anywhere), keeps the header and footer fixed in
       place and confines the scrollbar to the flat-edged middle section —
       it now starts right under the header and ends right above the
       footer, never crossing a rounded corner. */
    overflow: hidden;
    display: flex;
    flex-direction: column;
    /* Belt-and-braces: even if a parent ever centers this without the
       backdrop's own reserved padding (e.g. a future custom wrapper),
       the box still can't grow into the fixed bars' space itself. */
    margin: auto;
    touch-action: pan-y;
}
.modal-box-sm { max-width: 380px; }
.modal-box-lg { max-width: 680px; }
.modal-header { display: flex; align-items: center; justify-content: space-between; padding: 1.1rem 1.25rem; border-bottom: 1px solid var(--border); flex-shrink: 0; }
.modal-body {
    padding: 1.25rem;
    /* The one scrolling region inside the modal — see the comment on
       .modal-box above for why the scroll lives here instead of on the
       rounded outer box. min-height:0 lets this shrink below its content
       size inside the flex column so it actually scrolls rather than
       pushing the box (and the footer with it) taller than max-height. */
    overflow-y: auto;
    min-height: 0;
    /* Reserve a stable gutter for the scrollbar so content doesn't
       reflow/jump by a few pixels depending on whether a given modal's
       content is tall enough to need one. */
    scrollbar-gutter: stable;
    touch-action: pan-y;
}
/* A <form> that wraps .modal-body/.modal-footer (the common pattern in
   this app: .modal-box > .modal-header + <form> > .modal-body +
   .modal-footer) needs to be a flex column too, so the body inside it can
   still claim "the remaining space" the same way — without this, the
   form is a plain block box and .modal-body's min-height:0 flex trick
   has nothing to size itself against. */
.modal-box > form { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
.modal-box > form[hidden] { display: none; }
.modal-footer { padding: 1rem 1.25rem; border-top: 1px solid var(--border); display: flex; flex-wrap: wrap; justify-content: flex-end; gap: .6rem; flex-shrink: 0; }
/* Footers with several buttons (e.g. the students Excel-import wizard's
   Back / Next / Import / Done steps) can overflow a narrow modal and get
   visually crushed into tall, skinny buttons. Wrap onto multiple rows and
   let each button size to its content instead of being squeezed. */
.modal-footer .btn { flex: 0 0 auto; white-space: nowrap; }
@media (max-width: 480px) {
    .modal-footer { justify-content: stretch; }
    .modal-footer .btn { flex: 1 1 auto; }
}

/* ---- Student search (register laptop modal's required "assign to
   student" field) ------------------------------------------------------- */
.student-search { position: relative; }
.student-search-input {
    width: 100%; height: 40px; padding: 0 .7rem; border-radius: var(--radius-sm);
    border: 1px solid var(--border); background: var(--surface); color: var(--text);
    font-size: .92rem;
}
.student-search-input:focus { outline: 2px solid var(--orange-500, #f97316); outline-offset: 1px; }
.student-search-results {
    position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 30;
    max-height: 240px; overflow-y: auto;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
    box-shadow: 0 10px 24px rgba(15, 23, 42, .12);
    padding: .3rem;
}
.student-search-item {
    width: 100%; display: flex; align-items: center; justify-content: space-between; gap: .6rem;
    padding: .55rem .6rem; border-radius: calc(var(--radius-sm) - 3px);
    border: none; background: transparent; color: var(--text); text-align: left;
    cursor: pointer; font: inherit;
}
.student-search-item:hover, .student-search-item:focus { background: var(--surface-alt); }
.student-search-item-main { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.student-search-item-name { font-weight: 600; font-size: .88rem; }
.student-search-item-meta { font-size: .76rem; color: var(--text-muted); }
.student-search-badge {
    flex-shrink: 0; font-size: .68rem; font-weight: 700; letter-spacing: .02em; text-transform: uppercase;
    padding: .2rem .5rem; border-radius: var(--radius-pill);
    background: rgba(249, 115, 22, .14); color: var(--orange-600, #c2410c);
    white-space: nowrap;
}
.student-search-empty { padding: .7rem .6rem; font-size: .85rem; color: var(--text-muted); }
.student-assign-hint { margin-top: .4rem; margin-bottom: 0; }
.text-danger { color: #dc2626; }

/* Desktop/tablet (no fixed bottom nav, topbar sits at --topbar-h): keep the
   modal clear of the topbar the same way, in case a very tall modal is
   opened on a short browser window. */
@media (min-width: 881px) {
    .modal-backdrop {
        padding-top: calc(var(--topbar-h) + 1rem);
        padding-bottom: 1rem;
    }
    .modal-box { max-height: calc(100vh - var(--topbar-h) - 2rem); }
}

/* Mobile/tablet: the topbar spans full width (see .topbar { left: 0 }
   above) and the pill-shaped bottom nav floats above the safe area, so the
   modal box needs clearance on both edges — not just the backdrop's flat
   1rem padding — or a tall modal's header/footer can render underneath
   either bar, still reachable by scrolling the backdrop but visually
   hidden while off-screen. Both sides reserve the same 16px breathing
   margin beyond the bar's own footprint, so the gap above the modal and
   the gap below it read as equal even though the two bars themselves are
   different heights:
     - top: topbar height + safe-area inset (the topbar sits flush at the
       very top, see .topbar's top:0 above) + 16px margin.
     - bottom: the bottom nav's own floating offset from the viewport edge
       (max(10px, safe-area-inset-bottom) — see .mobile-bottom-nav's
       `bottom` above, it doesn't sit flush at 0) + its height + 16px
       margin, so the modal clears the nav's actual top edge, not just an
       approximation of it. */
@media (max-width: 880px) {
    .modal-backdrop {
        padding-top: calc(var(--topbar-h) + env(safe-area-inset-top) + 16px);
        padding-bottom: calc(max(10px, env(safe-area-inset-bottom)) + var(--mobile-nav-height) + 16px);
    }
    .modal-box {
        max-height: calc(100vh - (var(--topbar-h) + env(safe-area-inset-top) + 16px) - (max(10px, env(safe-area-inset-bottom)) + var(--mobile-nav-height) + 16px));
    }
}

.modal-tabs { display: flex; gap: .3rem; padding: .75rem 1.25rem 0; border-bottom: 1px solid var(--border); flex-shrink: 0; }
.modal-tab {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .55rem .9rem; font-size: .84rem; font-weight: 600;
    color: var(--text-muted); background: transparent; border: none;
    border-bottom: 2px solid transparent; cursor: pointer; margin-bottom: -1px;
}
.modal-tab svg { width: 15px; height: 15px; }
.modal-tab.is-active { color: var(--orange-600); border-bottom-color: var(--orange-600); }
.modal-tab:hover { color: var(--orange-600); }

/* Promote / repeat students modal (classes.php) */
.promote-selection-bar {
    display: flex; align-items: center; justify-content: space-between; gap: .75rem; flex-wrap: wrap;
    padding: .6rem .75rem; margin-bottom: .6rem;
    background: var(--surface-alt); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.promote-selection-count { font-size: .82rem; font-weight: 600; color: var(--text-muted); }
.promote-modal-list {
    max-height: 15rem; overflow-y: auto;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    margin-bottom: .25rem;
}
.promote-modal-row {
    display: flex; align-items: center; gap: .6rem;
    padding: .6rem .75rem; font-size: .87rem; color: var(--text);
    border-bottom: 1px solid var(--border); cursor: pointer;
}
.promote-modal-row:last-child { border-bottom: none; }
.promote-modal-row:hover { background: var(--surface-alt); }
.promote-modal-row input[type="checkbox"] { width: auto; flex-shrink: 0; }
.promote-year-history-item {
    display: flex; align-items: center; justify-content: space-between; gap: .75rem;
    padding: .65rem 0; border-bottom: 1px solid var(--border); font-size: .84rem; color: var(--text);
}
.promote-year-history-item:last-child { border-bottom: none; }

/* =============================================================================
   Utility
   ============================================================================= */

.flex { display: flex; }
.flex-between { display: flex; align-items: center; justify-content: space-between; }
.flex-wrap { flex-wrap: wrap; }
.gap-sm { gap: .5rem; } .gap-md { gap: 1rem; } .gap-lg { gap: 1.5rem; }
.items-center { align-items: center; }
.mt-0 { margin-top: 0; } .mb-0 { margin-bottom: 0; }
.mt-sm { margin-top: .5rem; } .mb-sm { margin-bottom: .5rem; }
.mt-md { margin-top: 1rem; } .mb-md { margin-bottom: 1rem; }
.mt-lg { margin-top: 1.5rem; } .mb-lg { margin-bottom: 1.5rem; }
.text-muted { color: var(--text-muted); }
.text-sm { font-size: .82rem; }
.text-center { text-align: center; }
.w-full { width: 100%; }
.grid { display: grid; }
.grid-2 { grid-template-columns: 1fr 1fr; }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
/* Laptop detail page (detail card + QR card): 1.3fr/1fr split on desktop,
   collapses to a single column at the mobile breakpoint below instead of
   using an inline style that would override the .grid-2 responsive rule. */
.laptop-detail-grid { grid-template-columns: 1.3fr 1fr; }
.toolbar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1.25rem; flex-wrap: wrap; }

/* Classes page toolbar: "Create new" and "Promotion" sized to match each
   other instead of each hugging its own label width. */
.classes-toolbar-btn { min-width: 9.5rem; justify-content: center; }

/* Reports page toolbar: date filter + Download PDF button kept on one row
   at every viewport width (desktop already showed these inline; this just
   gives the wrapping row its own explicit flex layout so the mobile
   single-row override in mobile-data-cards.css has a consistent base to
   start from). */
.reports-toolbar-row { display: flex; align-items: stretch; gap: .6rem; }
.reports-toolbar-row .reports-date-form { display: flex; align-items: stretch; }
.reports-toolbar-row .reports-date-form input[type="date"],
.reports-toolbar-row .reports-download-btn {
    box-sizing: border-box;
    display: flex;
    align-items: center;
    height: 38px;
    line-height: 1;
}
.reports-toolbar-row .reports-date-form input[type="date"] {
    padding-top: .4rem;
    padding-bottom: .4rem;
}
.dash-toolbar-actions { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.btn-compact { padding: .5rem 1rem; font-size: .86rem; border-radius: var(--radius-sm); }
.btn-compact svg { width: 15px; height: 15px; }
@media (max-width: 700px) {
    .dash-toolbar-actions { width: 100%; flex-direction: column; align-items: stretch; }
    .dash-toolbar-actions .btn { width: 100%; }
}
.filter-bar { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; }
.filter-bar select, .filter-bar input { width: auto; min-width: 150px; }
.pagination {
    display: flex; align-items: center; justify-content: center; gap: .5rem;
    margin-top: 1.25rem; flex-wrap: wrap; max-width: 100%;
}
.pagination-pages {
    display: flex; align-items: center; gap: .3rem; flex-wrap: wrap; justify-content: center;
    max-width: 100%;
}
.pagination a, .pagination span {
    padding: .4rem .7rem; border-radius: var(--radius-sm); border: 1px solid var(--border);
    font-size: .82rem; color: var(--text); line-height: 1.2; min-width: 2.1rem; text-align: center;
    box-sizing: border-box;
}
.pagination a { text-decoration: none; transition: background .15s, border-color .15s, color .15s; }
.pagination a:hover { border-color: var(--orange-600); color: var(--orange-600); }
.pagination .current { background: var(--orange-600); border-color: var(--orange-600); color: #fff; }
.pagination-ellipsis { border: none; padding: .4rem .2rem; color: var(--text-muted); min-width: auto; }
.pagination-nav {
    display: inline-flex; align-items: center; gap: .35rem; white-space: nowrap;
}
.pagination-nav svg { width: 15px; height: 15px; flex-shrink: 0; }
.pagination-nav.is-disabled { opacity: .4; pointer-events: none; }
.pagination-status {
    border: none !important;
    box-shadow: none !important;
    background: transparent !important;
    color: var(--text-muted); padding: .4rem .2rem; font-size: .78rem; width: 100%; text-align: center; order: 5;
}

@media (max-width: 640px) {
    .pagination { gap: .35rem; }
    .pagination-nav-label { display: none; }
    .pagination-nav { padding: .5rem .55rem; }
    .pagination a, .pagination span { padding: .38rem .58rem; font-size: .78rem; min-width: 1.9rem; }
    .pagination-pages { gap: .25rem; max-width: calc(100% - 5.5rem); overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
    .pagination-pages::-webkit-scrollbar { display: none; }
}

/* =============================================================================
   Mobile bottom navigation
   ============================================================================= */
.mobile-bottom-nav { display: none; }

@media (max-width: 880px) {
    .mobile-bottom-nav {
        --mobile-nav-count: 5;
        --mobile-nav-index: 0;
        position: fixed;
        z-index: 45;
        left: max(10px, env(safe-area-inset-left));
        right: max(10px, env(safe-area-inset-right));
        bottom: max(10px, env(safe-area-inset-bottom));
        height: var(--mobile-nav-height);
        display: block;
        color: var(--text);
        user-select: none;
        -webkit-user-select: none;
        touch-action: manipulation;
        isolation: isolate;
    }
    .mobile-bottom-nav::before {
        content: '';
        position: absolute;
        inset: 15px 0 0;
        z-index: -2;
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: var(--mobile-nav-radius);
        box-shadow: 0 12px 30px rgba(11, 18, 32, .18), 0 2px 8px rgba(11, 18, 32, .08);
    }
    [data-theme="dark"] .mobile-bottom-nav::before {
        box-shadow: 0 14px 34px rgba(0, 0, 0, .48), 0 2px 8px rgba(0, 0, 0, .28);
    }
    
    .mobile-nav-indicator {
        position: absolute;
        z-index: 1;
        top: -14px;
        left: calc((100% / var(--mobile-nav-count)) * (var(--mobile-nav-index) + .5));
        width: var(--mobile-nav-active-size);
        height: var(--mobile-nav-active-size);
        transform: translateX(-50%);
        border-radius: 50%;
        display: grid;
        place-items: center;
        background: var(--surface);
        border: 3px solid var(--orange-500);
        box-shadow: 0 5px 12px rgba(11, 18, 32, .16), 0 1px 2px rgba(11, 18, 32, .18);
        pointer-events: none;
        transition: left .42s cubic-bezier(.22, 1.18, .36, 1), box-shadow .2s ease;
    }
    .mobile-nav-indicator span {
        position: absolute;
        inset: 13px;
        border-radius: 50%;
        background: var(--orange-500);
        opacity: .08;
    }
    .mobile-nav-items { position: absolute; inset: 0; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); }
    .mobile-nav-item {
        position: relative;
        z-index: 2;
        min-width: 0;
        height: 100%;
        padding: 30px 2px 5px;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: flex-start;
        gap: 3px;
        color: var(--text-muted);
        background: transparent;
        border: 0;
        font: inherit;
        text-decoration: none;
        cursor: pointer;
        -webkit-tap-highlight-color: transparent;
    }
    .mobile-nav-item:hover { text-decoration: none; color: var(--text); }
    .mobile-nav-icon { width: 21px; height: 21px; display: grid; place-items: center; transition: transform .34s cubic-bezier(.22, 1.18, .36, 1), color .2s ease, opacity .2s ease, width .28s ease, height .28s ease, margin .28s ease, border-radius .28s ease, background .28s ease, box-shadow .28s ease, border-color .28s ease; }
    .mobile-nav-icon svg { width: 20px; height: 20px; stroke-width: 1.65; }
    .mobile-nav-label { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .64rem; line-height: 1; font-weight: 600; letter-spacing: -.01em; transition: color .2s ease, transform .34s cubic-bezier(.22, 1.18, .36, 1); }
    .mobile-nav-item.is-active { color: var(--text); }
    .mobile-nav-item.is-active .mobile-nav-icon { transform: translateY(calc(-1 * var(--mobile-nav-active-lift))) scale(1.12); color: var(--orange-600); }
    .mobile-nav-item.is-active .mobile-nav-label { transform: translateY(-1px); color: var(--text); }
    .mobile-nav-item:active .mobile-nav-icon { transform: scale(.9); }
    .mobile-nav-item.is-active:active .mobile-nav-icon { transform: translateY(calc(-1 * var(--mobile-nav-active-lift))) scale(1.04); }
    body.has-shell main#app-content { padding-bottom: calc(var(--mobile-nav-height) + env(safe-area-inset-bottom) + 40px); }

    /* Scan tab: a raised solid FAB-style badge when inactive, distinct from
       the other tabs; falls back to the shared active-indicator styling
       (and drops all of this) once it becomes the active tab. */
    .mobile-nav-item[data-mobile-nav-key="scan"]:not(.is-active) .mobile-nav-icon {
        width: 46px;
        height: 46px;
        margin-top: -22px;
        border-radius: 50%;
        background: linear-gradient(155deg, var(--orange-500), var(--orange-600));
        color: #fff;
        box-shadow: 0 10px 20px rgba(237, 118, 30, .38), 0 2px 6px rgba(11, 18, 32, .16);
        border: 3px solid var(--surface);
    }
    .mobile-nav-item[data-mobile-nav-key="scan"]:not(.is-active) .mobile-nav-icon svg {
        width: 21px;
        height: 21px;
        stroke-width: 2;
    }
    .mobile-nav-item[data-mobile-nav-key="scan"]:not(.is-active) .mobile-nav-label {
        color: var(--orange-600);
        font-weight: 700;
    }
    .mobile-nav-item[data-mobile-nav-key="scan"]:not(.is-active):active .mobile-nav-icon { transform: scale(.92); }
    .mobile-nav-item[data-mobile-nav-key="scan"].is-active .mobile-nav-icon {
        width: 21px;
        height: 21px;
        margin-top: 0;
        border-radius: 0;
        background: transparent;
        box-shadow: none;
        border: 0;
    }
    .menu-toggle { display: none !important; }

    /* Bottom nav hides (slides down) while the sidebar/"More" menu is open */
    .mobile-bottom-nav {
        transition: transform var(--mobile-nav-transition), opacity .3s ease;
    }
    .mobile-bottom-nav.is-hidden {
        transform: translateY(calc(100% + env(safe-area-inset-bottom) + 20px));
        opacity: 0;
        pointer-events: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .mobile-nav-indicator, .mobile-nav-icon, .mobile-nav-label { transition-duration: .001ms !important; }
}

/* =============================================================================
   Students — Database (desktop split-view redesign)
   Table + right-side profile panel, PC/large-screen only (min-width: 701px).
   Mobile keeps the existing hero + card-list layout entirely untouched;
   below the breakpoint this whole block is inert (see the closing
   `display:none` rules under the mobile query at the bottom of this block).
   ============================================================================= */

/* On desktop/PC the standalone full-page student profile (students.php's
   original ?view= detail page) is replaced by the .students-db-side panel
   rendered next to the list, so the old standalone block is hidden there.
   Below the mobile breakpoint it's unaffected — still the only detail view. */
@media (min-width: 701px) {
    .mobile-only-student-profile { display: none; }
}

.students-db-toolbar {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; flex-wrap: wrap; margin-bottom: 1.1rem;
}
.students-db-tabs { display: flex; align-items: center; gap: .35rem; background: var(--surface-alt); border-radius: var(--radius-sm); padding: .3rem; }
.students-db-tab {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .48rem 1rem; border-radius: calc(var(--radius-sm) - 2px);
    font-size: .83rem; font-weight: 600; color: var(--text-muted);
    background: transparent; border: none; cursor: default;
}
.students-db-tab.is-active { background: var(--surface); color: var(--navy-900); box-shadow: var(--shadow-sm); }
[data-theme="dark"] .students-db-tab.is-active { color: #fff; }

.students-db-actions { display: flex; align-items: center; gap: .6rem; }
.students-db-actions .filter-bar { gap: .6rem; }
.students-db-actions select { min-width: 130px; padding: .55rem .75rem; font-size: .82rem; }

.students-db-outer {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 1.75rem;
    align-items: stretch;
    width: 100%;
    margin-bottom: 1.5rem;
}
.students-db-outer-main { min-width: 0; display: flex; flex-direction: column; }

/* .students-db-main now carries its own .card styling (it moved out of the
   filter card to sit as an independent division beside the side panel). */
.students-db-main { min-width: 0; width: 100%; overflow: hidden; padding: 0; border-radius: var(--radius-md); margin-top: 1.1rem; }
.students-db-main .table-wrap { padding-top: 0; }
.students-db-main table.data-table { font-size: .85rem; }
.students-db-main .data-table th { padding: .95rem 1rem; }
.students-db-main .data-table td { padding: .7rem 1rem; }

.students-db-row { cursor: pointer; }
.students-db-row td { border-bottom: 1px solid var(--border); }

.students-db-namecell {
    display: flex; align-items: center; gap: .65rem;
    color: inherit; text-decoration: none;
}
.students-db-namecell:hover { text-decoration: none; color: inherit; }
.students-db-avatar {
    width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0;
    background: var(--navy-100); color: var(--navy-700);
    display: flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: .78rem; overflow: hidden;
}
[data-theme="dark"] .students-db-avatar { background: var(--navy-700); color: #fff; }
.students-db-namecell .n { font-weight: 600; color: var(--text); line-height: 1.25; }
.students-db-namecell .c { font-size: .78rem; color: var(--text-muted); }

/* Active/selected row uses the app's orange accent, per the reference design,
   including in dark mode where a plain tint would lose contrast. */
tr.students-db-row.is-active {
    background: var(--orange-600) !important;
}
tr.students-db-row.is-active td { color: #fff; border-bottom-color: var(--orange-600); }
tr.students-db-row.is-active .students-db-namecell .n,
tr.students-db-row.is-active .students-db-namecell .c { color: #fff; }
tr.students-db-row.is-active .cell-strong,
tr.students-db-row.is-active .cell-muted,
tr.students-db-row.is-active .text-muted,
tr.students-db-row.is-active .text-sm { color: rgba(255,255,255,.85); }
tr.students-db-row.is-active .badge { background: rgba(255,255,255,.22); color: #fff; }
tr.students-db-row.is-active .students-db-avatar { background: rgba(255,255,255,.25); color: #fff; }
tr.students-db-row.is-active a,
tr.students-db-row.is-active .btn-ghost,
tr.students-db-row.is-active .btn-danger-text { color: #fff; }
tr.students-db-row.is-active .btn-ghost:hover,
tr.students-db-row.is-active .btn-danger-text:hover { background: rgba(255,255,255,.22); color: #fff; }

/* ---- Right-side profile panel ---- */
.students-db-side {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
    overflow-y: auto;
    overflow-x: hidden;
    position: sticky;
    top: 1rem;
    align-self: stretch;
    margin-bottom: 1rem;
    max-height: calc(100vh - var(--topbar-h) - 2rem);
}
.students-db-side-empty {
    padding: 2.5rem 1.5rem; text-align: center; color: var(--text-muted);
}
.students-db-side-empty svg { width: 34px; height: 34px; color: var(--text-faint); margin-bottom: .75rem; }
.students-db-side-empty p { margin: 0; font-size: .85rem; }

.students-db-side-top { padding: 1.4rem 1.25rem 1.1rem; text-align: center; border-bottom: 1px solid var(--border); }
.students-db-side-avatar {
    width: 72px; height: 72px; border-radius: 50%; margin: 0 auto .8rem;
    background: var(--navy-800); color: #fff;
    display: flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: 1.5rem; font-family: var(--font-display);
    box-shadow: 0 0 0 4px var(--orange-50);
}
[data-theme="dark"] .students-db-side-avatar { box-shadow: 0 0 0 4px rgba(242,118,15,0.15); }
.students-db-side-name { font-family: var(--font-display); font-weight: 700; font-size: 1.02rem; color: var(--text); }
.students-db-side-role { font-size: .8rem; color: var(--text-muted); margin-top: .15rem; }
.students-db-side-code { margin-top: .55rem; }

.students-db-side-quickactions { display: flex; align-items: center; justify-content: center; gap: .5rem; margin-top: .9rem; }
.students-db-side-quickactions a {
    width: 34px; height: 34px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--surface-alt); color: var(--text-muted); border: 1px solid var(--border);
}
.students-db-side-quickactions a svg { width: 15px; height: 15px; }
.students-db-side-quickactions a:hover { background: var(--orange-50); color: var(--orange-600); border-color: var(--orange-100); }

.students-db-side-section { padding: 1.1rem 1.25rem; border-bottom: 1px solid var(--border); }
.students-db-side-section:last-child { border-bottom: none; }
.students-db-side-section h4 {
    margin: 0 0 .65rem; font-size: .7rem; text-transform: uppercase; letter-spacing: .06em;
    color: var(--text-faint); font-weight: 700;
}
.students-db-side-section p { margin: 0; font-size: .84rem; color: var(--text-muted); line-height: 1.55; }

.students-db-info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .9rem 1rem; }
.students-db-info-grid .students-db-info-item .label { font-size: .7rem; color: var(--text-faint); margin-bottom: .2rem; }
.students-db-info-grid .students-db-info-item .value { font-size: .84rem; font-weight: 600; color: var(--text); word-break: break-word; }

.students-db-classmates { display: flex; align-items: center; }
.students-db-classmates .students-db-avatar {
    width: 30px; height: 30px; font-size: .68rem;
    margin-left: -8px; border: 2px solid var(--surface);
}
.students-db-classmates .students-db-avatar:first-child { margin-left: 0; }
.students-db-classmates .more { margin-left: .5rem; font-size: .78rem; color: var(--text-muted); font-weight: 600; }

.students-db-doclink {
    display: flex; align-items: center; gap: .6rem;
    padding: .55rem .65rem; border-radius: var(--radius-sm);
    background: var(--surface-alt); font-size: .82rem; color: var(--text);
    text-decoration: none; font-weight: 600;
}
.students-db-doclink:hover { background: var(--orange-50); color: var(--orange-700); text-decoration: none; }
.students-db-doclink svg { width: 16px; height: 16px; color: var(--orange-600); flex-shrink: 0; }
.students-db-doclink + .students-db-doclink { margin-top: .5rem; }

/* AJAX loading state for the side panel (row select / refresh) — the panel
   stays exactly where it is (no reflow/scroll jump), just dims briefly. */
.students-db-side.is-loading { opacity: .55; pointer-events: none; transition: opacity .15s ease; }

.students-db-side-quickactions a.is-danger:hover { background: #fdecea; color: var(--red-600); border-color: #f6c8c4; }
.students-db-side-quickactions a.is-disabled { cursor: not-allowed; opacity: .5; }
.students-db-side-quickactions a.is-disabled:hover { background: var(--surface-alt); color: var(--text-muted); border-color: var(--border); }

/* Below the app's mobile breakpoint, this entire redesign is inert — the
   original hero + mobile-data-card layout (mobile-data-cards.css) takes
   over completely, unchanged. */
@media (max-width: 700px) {
    .students-db-toolbar,
    .students-db-outer,
    .students-db-side { display: none !important; }
}

/* PC / large-screen side margins, matching the generic .db-outer treatment
   below — active only above the 1100px breakpoint where the two-column
   split view is in effect. */
@media (min-width: 1101px) {
    .students-db-main { margin-left: 1.25rem; }
    .students-db-side { margin-left: .5rem; margin-right: 1.25rem; }
}

/* =============================================================================
   Generic PC/large-screen table + right-side detail panel ("db panel")
   ============================================================================= */
/* Same desktop split-view pattern as the Students page (.students-db-*
   above), generalized with a `.db-` prefix so every records page — Daily
   Accountability, Transactions, Maintenance, Laptops, Reports,
   Notifications, Activity Logs and Users — can reuse identical table and
   side-panel styling. Purely additive: nothing here changes existing
   `.data-table` / mobile card behavior, and it is inert below 701px where
   the original mobile hero + card-list layout is unaffected. */

@media (min-width: 701px) {
    .mobile-only-detail-block { display: none; }
}

.db-outer {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 1.75rem;
    align-items: stretch;
    width: 100%;
    margin-bottom: 1.5rem;
}
.db-outer-main { min-width: 0; display: flex; flex-direction: column; }

.db-main { min-width: 0; width: 100%; overflow: hidden; padding: 0; border-radius: var(--radius-md); margin-top: 1.1rem; }
.db-main .table-wrap { padding-top: 0; }
.db-main table.data-table { font-size: .85rem; }
.db-main .data-table th { padding: .95rem 1rem; }
.db-main .data-table td { padding: .7rem 1rem; }

/* Equal-width columns: every db-panel table lays its columns out evenly
   instead of letting the first column absorb the remaining space, so
   headers and cells line up into uniform-width columns across the row. */
.db-main table.data-table {
    table-layout: fixed;
}
.db-main .data-table th,
.db-main .data-table td {
    width: auto;
    min-width: 90px;
    overflow: hidden;
    text-overflow: ellipsis;
}
.db-main .data-table th:first-child,
.db-main .data-table td:first-child { width: auto; min-width: 160px; }
.db-main .data-table th:not(:first-child),
.db-main .data-table td:not(:first-child) { white-space: nowrap; }
.db-main .data-table td:first-child { white-space: normal; }
.db-main .table-wrap { overflow-x: auto; }
/* Maintenance table's Actions column needs to wrap its buttons instead of
   the fixed-layout ellipsis/nowrap treatment above (that's what caused the
   Start work / Mark resolved buttons to render clipped off the right edge
   of the table). Also let it size to its content rather than the uniform
   90px min-width every other column gets. */
.db-main .data-table th.col-actions,
.db-main .data-table td.col-actions {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    min-width: 190px;
}
/* Maintenance's actions cell holds two buttons (.maint-actions) that must
   stay on a single row rather than stacking — give the column enough
   width up front (instead of relying on shrink-then-wrap) so it never
   needs to wrap, and keep it inside the table's own scroll container
   rather than letting it get squeezed by table-layout:fixed. */
.db-main .data-table td.col-actions:has(.maint-actions) {
    width: 1%;
    min-width: 250px;
    white-space: nowrap;
}

/* Users table actions cell: fallback sizing that does not depend on
   :has() support (older WebViews used to package/preview the PWA may not
   support it), so the Edit/Disable/Delete buttons always get enough
   column width up front instead of overflowing past the cell edge and
   rendering on top of the row/columns next to them. The :has() rule
   further above still applies on browsers that support it and simply
   confirms the same sizing. */
.db-main .data-table td.col-actions {
    width: 1%;
    min-width: 220px;
    white-space: nowrap;
    overflow: visible;
}
.users-row-actions {
    display: flex;
    max-width: 100%;
}
.users-row-actions form { display: inline-flex; flex: 0 0 auto; }
.users-row-actions .btn { flex: 0 0 auto; white-space: nowrap; }

.db-row { cursor: pointer; }
.db-row td { border-bottom: 1px solid var(--border); }

.db-namecell {
    display: flex; align-items: center; gap: .65rem;
    color: inherit; text-decoration: none;
}
.db-namecell:hover { text-decoration: none; color: inherit; }
.db-avatar {
    width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0;
    background: var(--navy-100); color: var(--navy-700);
    display: flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: .78rem; overflow: hidden;
}
[data-theme="dark"] .db-avatar { background: var(--navy-700); color: #fff; }
.db-namecell .n { font-weight: 600; color: var(--text); line-height: 1.25; overflow: hidden; text-overflow: ellipsis; }
.db-namecell .c { font-size: .78rem; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; }

/* Active/selected row uses the app's orange accent, matching the Students
   page reference design, including in dark mode. */
tr.db-row.is-active {
    background: var(--orange-600) !important;
}
tr.db-row.is-active td { color: #fff; border-bottom-color: var(--orange-600); }
tr.db-row.is-active .db-namecell .n,
tr.db-row.is-active .db-namecell .c { color: #fff; }
tr.db-row.is-active .cell-strong,
tr.db-row.is-active .cell-muted,
tr.db-row.is-active .text-muted,
tr.db-row.is-active .text-sm { color: rgba(255,255,255,.85); }
tr.db-row.is-active .badge { background: rgba(255,255,255,.22); color: #fff; }
tr.db-row.is-active .db-avatar { background: rgba(255,255,255,.25); color: #fff; }
tr.db-row.is-active a,
tr.db-row.is-active .btn-ghost,
tr.db-row.is-active .btn-danger-text { color: #fff; }
tr.db-row.is-active .btn-ghost:hover,
tr.db-row.is-active .btn-danger-text:hover { background: rgba(255,255,255,.22); color: #fff; }

/* Users page row actions (Edit / Disable-Enable / Delete): give the
   otherwise-borderless .btn-ghost buttons a visible border/background so
   they read as distinct buttons instead of plain text, and keep them on
   one line on wider screens. */
.users-row-actions { flex-wrap: nowrap; justify-content: flex-end; }
.users-row-actions .btn-ghost {
    background: var(--surface-alt);
    border-color: var(--border);
}
.users-row-actions .btn-ghost:hover { background: var(--surface); border-color: var(--text-faint); }
.users-row-actions .btn-danger-text { background: var(--red-100); border-color: var(--red-100); }
.users-row-actions .btn-danger-text:hover { background: var(--red-600); color: #fff; border-color: var(--red-600); }
[data-theme="dark"] .users-row-actions .btn-danger-text { background: rgba(209,53,44,0.18); }

/* Keep the Users table's Actions column (buttons + "You" label) from being
   clipped behind the table edge on large screens: let it size to its
   content instead of the uniform fixed-layout column width, and pin it to
   the right edge of the table so the buttons never overflow off-screen.
   Scoped to rows that actually contain .users-row-actions so this doesn't
   leak onto other tables (e.g. Maintenance) that reuse the same generic
   .col-actions class name for a differently-shaped actions cell. */
.db-main .data-table td.col-actions:has(.users-row-actions) {
    width: 1%;
    min-width: 200px;
    text-align: right;
}
.db-main .data-table td.col-actions .users-row-actions { margin-left: auto; }

/* Active/selected row: the row-level `.btn-ghost { color:#fff }` override
   above makes the Users action buttons unreadable against their own light
   backgrounds. Force dark text back on these specific buttons (they keep
   their own background/border already, so white text was the only real
   issue) for correct contrast on the orange active row. */
tr.db-row.is-active .users-row-actions .btn-ghost {
    color: var(--text);
}
tr.db-row.is-active .users-row-actions .btn-ghost:hover {
    color: var(--text);
}
tr.db-row.is-active .users-row-actions .btn-danger-text {
    color: var(--red-600);
}
tr.db-row.is-active .users-row-actions .btn-danger-text:hover {
    color: #fff;
}

/* ---- Right-side detail panel ---- */
.db-side {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
    overflow-y: auto;
    overflow-x: hidden;
    position: sticky;
    top: 1rem;
    align-self: stretch;
    margin-bottom: 1rem;
    max-height: calc(100vh - var(--topbar-h) - 2rem);
}
.db-side-empty {
    padding: 2.5rem 1.5rem; text-align: center; color: var(--text-muted);
}
.db-side-empty svg { width: 34px; height: 34px; color: var(--text-faint); margin-bottom: .75rem; }
.db-side-empty p { margin: 0; font-size: .85rem; }

.db-side-top { padding: 1.4rem 1.25rem 1.1rem; text-align: center; border-bottom: 1px solid var(--border); }
.db-side-avatar {
    width: 72px; height: 72px; border-radius: 50%; margin: 0 auto .8rem;
    background: var(--navy-800); color: #fff;
    display: flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: 1.5rem; font-family: var(--font-display);
    box-shadow: 0 0 0 4px var(--orange-50);
}
[data-theme="dark"] .db-side-avatar { box-shadow: 0 0 0 4px rgba(242,118,15,0.15); }
.db-side-name { font-family: var(--font-display); font-weight: 700; font-size: 1.02rem; color: var(--text); word-break: break-word; }
.db-side-role { font-size: .8rem; color: var(--text-muted); margin-top: .15rem; }
.db-side-code { margin-top: .55rem; }

.db-side-quickactions { display: flex; align-items: center; justify-content: center; gap: .5rem; margin-top: .9rem; flex-wrap: wrap; }
.db-side-quickactions a,
.db-side-quickactions button {
    width: 34px; height: 34px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--surface-alt); color: var(--text-muted); border: 1px solid var(--border);
    cursor: pointer;
}
.db-side-quickactions a svg,
.db-side-quickactions button svg { width: 15px; height: 15px; }
.db-side-quickactions a:hover,
.db-side-quickactions button:hover { background: var(--orange-50); color: var(--orange-600); border-color: var(--orange-100); }
.db-side-quickactions a.is-danger:hover,
.db-side-quickactions button.is-danger:hover { background: #fdecea; color: var(--red-600); border-color: #f6c8c4; }
.db-side-quickactions a.is-disabled,
.db-side-quickactions button.is-disabled { cursor: not-allowed; opacity: .5; }
.db-side-quickactions a.is-disabled:hover,
.db-side-quickactions button.is-disabled:hover { background: var(--surface-alt); color: var(--text-muted); border-color: var(--border); }

.db-side-section { padding: 1.1rem 1.25rem; border-bottom: 1px solid var(--border); }
.db-side-section:last-child { border-bottom: none; }
.db-side-section h4 {
    margin: 0 0 .65rem; font-size: .7rem; text-transform: uppercase; letter-spacing: .06em;
    color: var(--text-faint); font-weight: 700;
}
.db-side-section p { margin: 0; font-size: .84rem; color: var(--text-muted); line-height: 1.55; }

.db-info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .9rem 1rem; }
.db-info-grid .db-info-item .label { font-size: .7rem; color: var(--text-faint); margin-bottom: .2rem; }
.db-info-grid .db-info-item .value { font-size: .84rem; font-weight: 600; color: var(--text); word-break: break-word; }

.db-doclink {
    display: flex; align-items: center; gap: .6rem;
    padding: .55rem .65rem; border-radius: var(--radius-sm);
    background: var(--surface-alt); font-size: .82rem; color: var(--text);
    text-decoration: none; font-weight: 600;
}
.db-doclink:hover { background: var(--orange-50); color: var(--orange-700); text-decoration: none; }
.db-doclink svg { width: 16px; height: 16px; color: var(--orange-600); flex-shrink: 0; }
.db-doclink + .db-doclink { margin-top: .5rem; }

/* AJAX/refresh loading state for the side panel (row select) — panel
   stays put (no reflow/scroll jump), just dims briefly. */
.db-side.is-loading { opacity: .55; pointer-events: none; transition: opacity .15s ease; }

/* Below the app's mobile breakpoint this entire split-view is inert — the
   original hero + mobile-data-card layout takes over completely, unchanged. */
@media (max-width: 700px) {
    .db-outer,
    .db-side { display: none !important; }
}

/* PC / large-screen side margins: applied system-wide to every page using
   the shared .db-outer split-view (Laptops, Users, Reports, Daily Records,
   Notifications, Activity Logs, Maintenance, Transactions). Only active
   above the 1100px breakpoint where the two-column layout is in effect —
   at/below that it stacks to a single column (see the max-width:1100px
   rule) and side margins would look unbalanced, so they're left untouched
   there and on mobile. */
@media (min-width: 1101px) {
    .db-main { margin-left: 1.25rem; }
    .db-side { margin-left: .5rem; margin-right: 1.25rem; }
}

/* =============================================================================
   Responsive
   ============================================================================= */

@media (max-width: 1100px) {
    .students-db-outer { grid-template-columns: 1fr; }
    .students-db-side { position: static; align-self: auto; max-height: none; }
    .db-outer { grid-template-columns: 1fr; }
    .db-side { position: static; align-self: auto; max-height: none; }
    .metric-grid { grid-template-columns: repeat(3, 1fr); }
    .dash-grid { grid-template-columns: 1fr; }
    .form-row { grid-template-columns: 1fr; }
}

@media (max-width: 880px) {
    .sidebar { transform: translateX(-100%); box-shadow: var(--shadow-lg); }
    .sidebar.open { transform: translateX(0); }
    .main-area { margin-left: 0; padding-top: calc(var(--topbar-h) + env(safe-area-inset-top)); }
    .topbar { left: 0; }
    #spa-progress { left: 0; }
    .menu-toggle { display: flex; }
    .metric-grid { grid-template-columns: repeat(2, 1fr); }
    .grid-4 { grid-template-columns: repeat(2, 1fr); }
    .grid-3 { grid-template-columns: 1fr; }
    .grid-2,
    .laptop-detail-grid { grid-template-columns: 1fr; }
    .grid-2 > *,
    .laptop-detail-grid > * { min-width: 0; }
    .page-container { padding: 1rem; max-width: 100%; box-sizing: border-box; }
    .topbar { padding: 0 1rem; }
    .sidebar-scrim {
        position: fixed; inset: 0; background: rgba(0,0,0,.4); z-index: 39; display: none;
        opacity: 0; transition: opacity .25s ease;
    }
    .sidebar-scrim.open { display: block; opacity: 1; }
    .sidebar-scrim.dragging { transition: none; }

    /* Collapse the connection status + academic year switcher behind a
       single kebab (3-dot) trigger so the mobile topbar stays compact. */
    .topbar-more { display: block; }
    .topbar-more-group {
        position: absolute; top: calc(100% + .5rem); right: 1rem; z-index: 60;
        flex-direction: column; align-items: stretch; gap: .5rem;
        min-width: 240px; max-width: calc(100vw - 2rem);
        background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md);
        box-shadow: var(--shadow-lg); padding: .6rem;
        opacity: 0; visibility: hidden; transform: translateY(-6px);
        transition: opacity .15s ease, transform .15s ease, visibility .15s ease;
    }
    .topbar-more-group.is-open { opacity: 1; visibility: visible; transform: translateY(0); }
    .topbar-more-group .connection-status { width: 100%; justify-content: flex-start; }
    .topbar-more-group .year-switcher { width: 100%; }
    .topbar-more-group .year-switcher-btn { width: 100%; justify-content: space-between; }
    .topbar-more-group .year-switcher-btn > span { flex: 1; text-align: left; }
    .topbar-more-group .year-switcher-menu {
        position: static; margin-top: .5rem; box-shadow: none; border: 1px solid var(--border);
        max-width: none; min-width: 0;
    }

    /* "Download QR Code(s)" relocates from the main topbar into this
       dropdown on mobile — the always-visible topbar button is hidden and
       this full-width orange primary button takes its place instead,
       styled to stand out as the dropdown's primary action. */
    .topbar-qr-export-desktop { display: none; }
    .topbar-more-qr-export {
        display: flex; align-items: center; justify-content: center; gap: .5rem;
        width: 100%; order: -1;
    }
}

@media (max-width: 560px) {
    .metric-grid { grid-template-columns: repeat(2, 1fr); }
    .auth-card { padding: 1.6rem 1.3rem; }
    .toolbar { flex-direction: column; align-items: stretch; }
    .toolbar > div:first-child { min-width: 0; max-width: 100%; }
    .filter-bar select, .filter-bar input { width: 100%; min-width: 0; }
    .scan-frame-wrap { width: 200px; }
    .scan-frame { width: 200px; height: 200px; }
    .scan-torch-btn { width: 40px; height: 40px; bottom: -14px; right: -14px; }
    .scan-torch-btn svg { width: 18px; height: 18px; }
}

/* =============================================================================
   Custom select — modern replacement UI for native <select> elements
   (assets/js/custom-select.js). The native <select> stays in the DOM
   (visually hidden) so forms, validation, and existing JS keep working.
   ============================================================================= */

.cs-select {
    position: relative;
    display: inline-block;
    width: 100%;
    font-family: var(--font-body);
}

/* The real <select> is kept for form submission / JS compatibility, but
   visually hidden and unreachable — the button below is the real control. */
.cs-select select.cs-native {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    pointer-events: none;
    z-index: -1;
}

.cs-trigger {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .6rem;
    width: 100%;
    padding: .65rem .85rem;
    font-size: .9rem;
    font-family: var(--font-body);
    font-weight: 500;
    text-align: left;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
}
.cs-trigger:hover { border-color: var(--orange-300, var(--orange-500)); background: var(--surface-alt); }
.cs-trigger:focus-visible,
.cs-select.is-open .cs-trigger {
    border-color: var(--orange-500);
    outline: none;
    box-shadow: 0 0 0 3px rgba(242, 118, 15, 0.15);
}

.cs-trigger-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.cs-select.cs-placeholder .cs-trigger-label { color: var(--text-muted); }

.cs-chevron {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    color: var(--text-muted);
    transition: transform .2s cubic-bezier(.4, 0, .2, 1), color .15s ease;
}
.cs-select.is-open .cs-chevron { transform: rotate(180deg); color: var(--orange-600); }

.cs-select.is-disabled .cs-trigger { opacity: .55; cursor: not-allowed; }
.cs-select.is-disabled .cs-trigger:hover { background: var(--surface); border-color: var(--border); }

.cs-panel {
    position: fixed;
    z-index: 1000;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
    transform-origin: top center;
    transform: translateY(-6px) scaleY(.96);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity .16s ease, transform .16s cubic-bezier(.4, 0, .2, 1), visibility 0s linear .16s;
}
/* The panel is reparented to <body> while open (see custom-select.js) so it
   is never clipped by a scrollable/overflow ancestor — so its open state is
   driven by a class on the panel itself (.cs-panel.is-open), not by an
   ancestor-descendant selector, since it's no longer inside .cs-select
   when open. */
.cs-panel.is-open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0) scaleY(1);
    transition: opacity .16s ease, transform .16s cubic-bezier(.4, 0, .2, 1), visibility 0s linear 0s;
}
.cs-panel.cs-open-up { transform-origin: bottom center; }

.cs-list {
    list-style: none;
    margin: 0;
    padding: .35rem;
    max-height: 15.5rem;
    overflow-y: auto;
}

.cs-option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .6rem;
    padding: .55rem .65rem;
    border-radius: calc(var(--radius-sm) - 3px);
    font-size: .87rem;
    color: var(--text);
    cursor: pointer;
    transition: background-color .12s ease, color .12s ease;
}
.cs-option-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-option:hover,
.cs-option.is-focused { background: var(--orange-50); color: var(--orange-700); }
[data-theme="dark"] .cs-option:hover,
[data-theme="dark"] .cs-option.is-focused { background: var(--navy-700); color: #fff; }

.cs-option.is-selected { font-weight: 700; color: var(--orange-600); }
.cs-option.is-selected.is-focused,
.cs-option.is-selected:hover { color: var(--orange-700); }

.cs-option.is-disabled { color: var(--text-faint); cursor: not-allowed; }
.cs-option.is-disabled:hover { background: transparent; }

.cs-check {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
    color: var(--orange-600);
    opacity: 0;
    transform: scale(.6);
    transition: opacity .12s ease, transform .12s ease;
}
.cs-option.is-selected .cs-check { opacity: 1; transform: scale(1); }

/* Scrollbar polish inside the dropdown panel (desktop only; harmless elsewhere) */
.cs-list::-webkit-scrollbar { width: 7px; }
.cs-list::-webkit-scrollbar-thumb { background-color: var(--navy-300); border-radius: var(--radius-pill); }
[data-theme="dark"] .cs-list::-webkit-scrollbar-thumb { background-color: var(--navy-600); }

/* Contexts where selects previously had fixed/min widths — the wrapper
   takes over sizing so the trigger button matches the old footprint. */
.filter-bar .cs-select,
.students-db-actions .cs-select { width: auto; min-width: 150px; }
.students-db-actions .cs-select .cs-trigger { min-width: 130px; padding: .55rem .75rem; font-size: .82rem; }
.import-map-row .cs-select .cs-trigger { padding: .5rem .65rem; font-size: .84rem; }
.progress-period-select.cs-select { display: inline-block; width: auto; }

@media (max-width: 560px) {
    .filter-bar .cs-select { width: 100%; min-width: 0; }
}
@media (max-width: 700px) {
    .progress-period-select.cs-select { width: 100%; }
}

/* =============================================================================
   Custom date/time picker — themed replacement UI for native
   <input type="date"> and <input type="time"> elements
   (assets/js/date-picker.js). The native input stays in the DOM (visually
   hidden) so forms, validation, and existing JS keep working exactly as
   before — same pattern as .cs-select above.
   ============================================================================= */

/* The calendar's Month/Year <select>s are enhanced by custom-select.js too
   (so tapping them opens our themed dropdown instead of the phone's native
   OS wheel/list picker). Both panels reparent to <body> while open at the
   same z-index, so bump .cs-panel a step above .dp-panel here to guarantee
   the select's dropdown always renders on top of the calendar it lives in,
   regardless of DOM insertion order — safe globally since a select's own
   dropdown should never sit behind whatever panel triggered it. */
.cs-panel { z-index: 1001; }

/* .cs-select defaults to a full-width block sized for form fields; the
   calendar title row is a tight inline flex strip, so size the wrapper and
   its trigger down to match the original .dp-cal-title-select look instead. */
.dp-cal-title .cs-select { display: inline-block; width: auto; }
.dp-cal-title .cs-select .cs-trigger {
    font-family: var(--font-display); font-weight: 700; font-size: .85rem; color: var(--text);
    background: var(--surface-alt); border: 1px solid var(--border); border-radius: var(--radius-sm);
    padding: .25rem .4rem; gap: .25rem;
}
.dp-cal-title .cs-select .cs-chevron { width: 13px; height: 13px; }
.dp-cal-title .cs-select:has([data-dp-month-select]) { max-width: 7.4rem; min-width: 4.6rem; }
/* Year trigger must always show the full 4-digit year (e.g. 2027) — never
   truncate it to "2.." like the month select is allowed to. Give it a
   fixed width sized for 4 digits + chevron rather than a tight max-width,
   and turn off the ellipsis/nowrap clipping on its label specifically. */
.dp-cal-title .cs-select:has([data-dp-year-select]) { max-width: none; width: 5.6rem; min-width: 5.6rem; flex: 0 0 auto; }
.dp-cal-title .cs-select:has([data-dp-year-select]) .cs-trigger { padding: .25rem .35rem; gap: .2rem; }
.dp-cal-title .cs-select:has([data-dp-year-select]) .cs-trigger-label {
    overflow: visible;
    text-overflow: clip;
    white-space: nowrap;
    flex: 0 1 auto;
    min-width: 2.6rem;
    letter-spacing: -0.01em;
}
/* The open dropdown panel is sized in JS to match the trigger's rendered
   width (see custom-select.js repositionPanel), so widening the trigger
   above already gives the panel enough room for a 4-digit year. */

.dp-field {
    position: relative;
    display: inline-flex;
    width: 100%;
    font-family: var(--font-body);
}

.dp-field input.dp-native {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    pointer-events: none;
    z-index: -1;
}

.dp-trigger {
    display: flex;
    align-items: center;
    gap: .6rem;
    width: 100%;
    padding: .65rem .85rem;
    font-size: .9rem;
    font-family: var(--font-body);
    font-weight: 500;
    text-align: left;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
}
.dp-trigger:hover { border-color: var(--orange-300, var(--orange-500)); background: var(--surface-alt); }
.dp-trigger:focus-visible,
.dp-field.is-open .dp-trigger {
    border-color: var(--orange-500);
    outline: none;
    box-shadow: 0 0 0 3px rgba(242, 118, 15, 0.15);
}
.dp-field.is-disabled .dp-trigger { opacity: .55; cursor: not-allowed; }
.dp-field.is-disabled .dp-trigger:hover { background: var(--surface); border-color: var(--border); }

.dp-trigger-icon {
    display: flex; align-items: center; justify-content: center;
    width: 18px; height: 18px; flex-shrink: 0; color: var(--orange-600);
}
.dp-trigger-icon svg { width: 17px; height: 17px; }

.dp-trigger-label {
    flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dp-field.dp-placeholder .dp-trigger-label { color: var(--text-muted); }

.dp-panel {
    position: fixed;
    z-index: 1000;
    width: 296px;
    max-width: calc(100vw - 16px);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
    padding: .85rem;
    transform-origin: top center;
    transform: translateY(-6px) scaleY(.96);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity .16s ease, transform .16s cubic-bezier(.4, 0, .2, 1), visibility 0s linear .16s;
}
.dp-panel.is-open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0) scaleY(1);
    transition: opacity .16s ease, transform .16s cubic-bezier(.4, 0, .2, 1);
}
.dp-panel.dp-open-up { transform-origin: bottom center; }

.dp-panel--time { width: 220px; padding: .7rem; }

/* ---- Calendar ---- */
.dp-cal-header {
    display: flex; align-items: center; justify-content: space-between;
    margin-bottom: .6rem;
}
.dp-cal-title {
    font-family: var(--font-display); font-weight: 700; font-size: .92rem; color: var(--text);
    display: flex; align-items: center; gap: .35rem;
}
.dp-cal-title-select {
    font-family: var(--font-display); font-weight: 700; font-size: .85rem; color: var(--text);
    background: var(--surface-alt); border: 1px solid var(--border); border-radius: var(--radius-sm);
    padding: .25rem .4rem; cursor: pointer; appearance: auto; max-width: 7.4rem;
    /* Selects size to their longest <option> automatically, but give both
       a floor via min-width too so a 4-digit year (e.g. "2027") is never
       visually clipped/ellipsized on browsers that render the closed
       select tightly around its current value. */
    min-width: 4.6rem;
}
.dp-cal-title-select--year { max-width: none; width: 5.4rem; min-width: 5.4rem; text-overflow: clip; }
.dp-cal-title-select:hover { border-color: var(--orange-300); }
.dp-cal-title-select:focus-visible { outline: 2px solid var(--orange-500); outline-offset: 1px; }
.dp-cal-nav {
    width: 28px; height: 28px; border-radius: var(--radius-sm);
    display: flex; align-items: center; justify-content: center;
    background: var(--surface-alt); color: var(--text-muted); border: 1px solid var(--border);
    cursor: pointer;
}
.dp-cal-nav svg { width: 15px; height: 15px; }
.dp-cal-nav:hover { background: var(--orange-50); color: var(--orange-600); border-color: var(--orange-100); }
[data-theme="dark"] .dp-cal-nav:hover { background: var(--navy-700); color: #fff; }

.dp-cal-weekdays {
    display: grid; grid-template-columns: repeat(7, 1fr);
    text-align: center; margin-bottom: .3rem;
}
.dp-cal-weekdays span { font-size: .68rem; font-weight: 700; color: var(--text-faint); text-transform: uppercase; }

.dp-cal-grid {
    display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px;
}
.dp-cal-day {
    aspect-ratio: 1 / 1;
    display: flex; align-items: center; justify-content: center;
    font-size: .82rem; font-weight: 500; color: var(--text);
    background: transparent; border: none; border-radius: var(--radius-sm);
    cursor: pointer;
}
.dp-cal-day.is-muted { color: var(--text-faint); }
button.dp-cal-day:hover:not(.is-disabled) { background: var(--orange-50); color: var(--orange-700); }
[data-theme="dark"] button.dp-cal-day:hover:not(.is-disabled) { background: var(--navy-700); color: #fff; }
.dp-cal-day.is-today { font-weight: 800; color: var(--orange-600); }
.dp-cal-day.is-selected { background: var(--orange-600) !important; color: #fff !important; font-weight: 700; }
.dp-cal-day.is-disabled { color: var(--text-faint); cursor: not-allowed; opacity: .4; }

.dp-cal-footer {
    display: flex; align-items: center; justify-content: space-between;
    margin-top: .7rem; padding-top: .65rem; border-top: 1px solid var(--border);
}
.dp-cal-today-btn, .dp-cal-clear-btn {
    font-size: .8rem; font-weight: 600; padding: .4rem .7rem;
    border-radius: var(--radius-sm); cursor: pointer; border: 1px solid transparent;
    background: transparent;
}
.dp-cal-today-btn { color: var(--orange-600); }
.dp-cal-today-btn:hover { background: var(--orange-50); }
[data-theme="dark"] .dp-cal-today-btn:hover { background: rgba(242,118,15,0.15); }
.dp-cal-clear-btn { color: var(--text-muted); }
.dp-cal-clear-btn:hover { background: var(--surface-alt); }

/* ---- Time picker ---- */
.dp-time-cols {
    display: grid; grid-template-columns: 1fr 1fr; gap: .5rem;
    max-height: 220px;
}
.dp-time-col {
    display: flex; flex-direction: column; gap: 2px;
    overflow-y: auto; max-height: 220px;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    padding: .3rem;
}
.dp-time-col::-webkit-scrollbar { width: 6px; }
.dp-time-col::-webkit-scrollbar-thumb { background-color: var(--navy-300); border-radius: var(--radius-pill); }
[data-theme="dark"] .dp-time-col::-webkit-scrollbar-thumb { background-color: var(--navy-600); }

.dp-time-opt {
    flex-shrink: 0;
    padding: .4rem .5rem; text-align: center;
    font-size: .84rem; font-weight: 600; color: var(--text);
    background: transparent; border: none; border-radius: var(--radius-sm);
    cursor: pointer;
}
.dp-time-opt:hover { background: var(--orange-50); color: var(--orange-700); }
[data-theme="dark"] .dp-time-opt:hover { background: var(--navy-700); color: #fff; }
.dp-time-opt.is-selected { background: var(--orange-600); color: #fff; }

@media (max-width: 560px) {
    .filter-bar .dp-field { width: 100%; }
}

/* =============================================================================
   Print (used by the Reports page "Download PDF" button, which triggers the
   browser's native print/Save-as-PDF dialog). Hides app chrome that has no
   place in a printed/PDF report — sidebar, topbar, bottom nav, action
   buttons, filter/search controls — and lets the report content flow as a
   plain, full-width document.
   ============================================================================= */
@media print {
    .sidebar, .topbar, .mobile-bottom-nav, #spa-progress,
    .reports-toolbar-actions, .records-hero, .db-side, .students-db-side {
        display: none !important;
    }
    .main-area { margin-left: 0 !important; padding-top: 0 !important; }
    .main-area > main { overflow: visible !important; }
    .page-container { padding: 0 !important; }
    body, html { background: #fff !important; }
    .card { box-shadow: none !important; border: 1px solid #ccc !important; }
}

/* =============================================================================
   QR Export modal (.qrx-*) — "Download QR Code(s)" PDF export tool.
   Markup: includes/partials/qr-export-modal.php
   Logic:  assets/js/qr-export.js
   Uses the shared design-system variables so light/dark themes both apply;
   the preview page mock is intentionally hardcoded to paper-white in both.
   ============================================================================= */
.qrx-box { max-width: 720px; }
.qrx-body { padding: 1.1rem 1.25rem 1.25rem; }

.qrx-section { margin-bottom: 1.15rem; }
.qrx-section:last-child { margin-bottom: 0; }
.qrx-section-title {
    font-family: var(--font-display);
    font-size: .8rem;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin-bottom: .55rem;
}

/* Export type cards */
.qrx-type-row { display: grid; grid-template-columns: 1fr 1fr; gap: .6rem; }
.qrx-type-card {
    display: flex; flex-direction: column; align-items: flex-start; gap: .15rem;
    padding: .8rem .9rem; text-align: left; cursor: pointer;
    background: var(--surface); border: 1.5px solid var(--border); border-radius: var(--radius-md);
    transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
}
.qrx-type-card:hover { border-color: var(--navy-300); }
.qrx-type-card.is-active { border-color: var(--orange-500); box-shadow: 0 0 0 3px var(--orange-100); }
.qrx-type-icon { color: var(--orange-600); margin-bottom: .25rem; }
.qrx-type-icon svg { width: 20px; height: 20px; }
.qrx-type-name { font-weight: 600; font-size: .95rem; color: var(--text); }
.qrx-type-hint { font-size: .78rem; color: var(--text-muted); }

/* Search + results */
.qrx-search-wrap {
    display: flex; align-items: center; gap: .5rem;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
    padding: 0 .75rem;
}
.qrx-search-wrap:focus-within { border-color: var(--orange-500); }
.qrx-search-wrap svg { width: 16px; height: 16px; color: var(--text-faint); flex-shrink: 0; }
.qrx-search-input {
    flex: 1; border: none !important; background: transparent !important;
    padding: .6rem 0 !important; font-size: .92rem; color: var(--text); min-width: 0;
}
.qrx-search-input:focus { outline: none; box-shadow: none !important; }

.qrx-results {
    margin-top: .55rem; border: 1px solid var(--border); border-radius: var(--radius-sm);
    max-height: 216px; overflow-y: auto; background: var(--surface);
}
.qrx-results:empty { display: none; }
.qrx-result-row {
    display: flex; align-items: center; gap: .6rem; width: 100%;
    padding: .55rem .8rem; cursor: pointer; text-align: left;
    border: none; border-bottom: 1px solid var(--border); background: var(--surface);
    font: inherit; color: var(--text);
}
.qrx-result-row:last-child { border-bottom: none; }
.qrx-result-row:hover { background: var(--surface-alt); }
.qrx-result-check { cursor: pointer; }
.qrx-result-check input { width: auto; flex-shrink: 0; accent-color: var(--orange-500); }
.qrx-result-main { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.qrx-result-main strong { font-size: .9rem; font-weight: 600; }
.qrx-result-sub { font-size: .78rem; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qrx-result-badge {
    font-size: .72rem; color: var(--text-muted); background: var(--surface-alt);
    border: 1px solid var(--border); border-radius: var(--radius-pill);
    padding: .15rem .55rem; white-space: nowrap; flex-shrink: 0;
}
.qrx-empty { padding: .9rem; text-align: center; color: var(--text-muted); font-size: .88rem; }

/* Single selection summary */
.qrx-single-chosen {
    margin-top: .65rem; display: flex; gap: .9rem; align-items: center;
    padding: .7rem; border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--surface-alt);
}
.qrx-single-card-img {
    width: 84px; height: auto; border-radius: var(--radius-sm);
    border: 1px solid var(--border); background: #fff; flex-shrink: 0;
}
.qrx-single-name { font-weight: 600; }
.qrx-single-sub { font-family: var(--font-display); font-weight: 700; color: var(--orange-600); font-size: 1.02rem; }

/* Bulk controls */
.qrx-filter-row { display: flex; gap: .6rem; align-items: flex-end; flex-wrap: wrap; }
.qrx-class-filter { display: flex; flex-direction: column; gap: .25rem; min-width: 150px; }
.qrx-class-filter select { min-height: 0; padding: .55rem 2rem .55rem .7rem; font-size: .9rem; }
.qrx-grow { flex: 1; min-width: 180px; }
.qrx-mini-label { font-size: .74rem; font-weight: 600; color: var(--text-muted); letter-spacing: .02em; }
.qrx-bulk-toolbar { display: flex; align-items: center; gap: .5rem; margin-top: .6rem; flex-wrap: wrap; }
.qrx-count-pill {
    margin-left: auto; font-size: .8rem; font-weight: 700;
    background: var(--orange-100); color: var(--orange-700);
    padding: .3rem .75rem; border-radius: var(--radius-pill); white-space: nowrap;
}
[data-theme="dark"] .qrx-count-pill { background: rgba(242, 118, 15, .18); color: var(--orange-400); }
.qrx-results-bulk { margin-top: .55rem; }

/* Layout option cards (1 / 4 / 6 / Custom per page) */
.qrx-layout-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: .6rem; }
.qrx-layout-card {
    display: flex; flex-direction: column; align-items: center; gap: .3rem;
    padding: .7rem .5rem; cursor: pointer;
    background: var(--surface); border: 1.5px solid var(--border); border-radius: var(--radius-md);
    transition: border-color .15s ease, box-shadow .15s ease;
}
.qrx-layout-card:hover { border-color: var(--navy-300); }
.qrx-layout-card.is-active { border-color: var(--orange-500); box-shadow: 0 0 0 3px var(--orange-100); }
.qrx-layout-name { font-weight: 600; font-size: .85rem; color: var(--text); }
.qrx-layout-hint { font-size: .72rem; color: var(--text-muted); }

/* Mini wireframe previews inside the layout cards */
.qrx-wire {
    display: grid; width: 64px; height: 46px; gap: 3px;
    padding: 4px; border: 1.5px solid var(--navy-300); border-radius: 4px; background: var(--surface-alt);
}
.qrx-wire i { background: var(--navy-100); border: 1px solid var(--navy-300); border-radius: 2px; }
.qrx-wire-1 { place-items: center; }
.qrx-wire-1 i { width: 26px; height: 34px; }
.qrx-wire-4 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.qrx-wire-6 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr 1fr; }
.qrx-wire-custom { place-items: center; }
.qrx-wire-custom svg { width: 22px; height: 22px; color: var(--text-muted); }
.qrx-layout-card.is-active .qrx-wire-custom svg { color: var(--orange-500); }

/* Custom cards-per-page count (shown when "Custom" layout is selected) */
.qrx-custom-per-page-row { margin-top: .6rem; }
.qrx-custom-per-page-row .qrx-custom-size-field { max-width: 200px; }
.qrx-custom-per-page-hint { margin-top: .45rem; }

/* Page size + spacing settings */
.qrx-settings-row { display: flex; gap: 1rem; margin-top: .75rem; flex-wrap: wrap; align-items: flex-start; }
.qrx-page-size-group { display: flex; flex-direction: column; gap: .3rem; }
.qrx-seg { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.qrx-seg-btn {
    padding: .42rem .95rem; font-size: .85rem; font-weight: 600; cursor: pointer;
    background: var(--surface); border: none; color: var(--text-muted);
}
.qrx-seg-btn + .qrx-seg-btn { border-left: 1px solid var(--border); }
.qrx-seg-btn.is-active { background: var(--navy-900); color: #fff; }
[data-theme="dark"] .qrx-seg-btn.is-active { background: var(--navy-600); }
.qrx-gap-group { display: flex; flex-direction: column; gap: .3rem; flex: 1; min-width: 220px; }
.qrx-gap-controls { display: flex; align-items: center; gap: .75rem; }
.qrx-gap-controls input[type="range"] { flex: 1; accent-color: var(--orange-500); padding: 0; }
.qrx-gap-value {
    font-weight: 700; font-size: .85rem; color: var(--text);
    background: var(--surface-alt); border: 1px solid var(--border);
    padding: .25rem .6rem; border-radius: var(--radius-sm); min-width: 3.6em; text-align: center;
}

/* Custom paper size (shown when "Custom" page size is selected) */
.qrx-custom-page-row { display: flex; gap: .6rem; align-items: flex-end; flex-wrap: wrap; margin-top: .6rem; }

/* Custom QR code size (width x height x unit) */
.qrx-custom-size-group { margin-top: .85rem; padding-top: .75rem; border-top: 1px dashed var(--border); }
.qrx-custom-size-toggle { display: inline-flex; align-items: center; gap: .5rem; cursor: pointer; }
.qrx-custom-size-toggle input[type="checkbox"] { accent-color: var(--orange-500); width: 16px; height: 16px; margin: 0; }
.qrx-custom-size-row { display: flex; gap: .6rem; align-items: flex-end; flex-wrap: wrap; margin-top: .6rem; }
.qrx-custom-size-field { display: flex; flex-direction: column; gap: .3rem; min-width: 110px; }
.qrx-custom-size-input {
    min-height: 0; padding: .5rem .65rem; font-size: .9rem;
    border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text);
}
.qrx-custom-size-unit { min-height: 0; padding: .5rem .6rem; font-size: .9rem; }
.qrx-custom-size-x { padding-bottom: .55rem; color: var(--text-muted); font-weight: 600; }
.qrx-custom-size-hint { margin-top: .5rem; }

/* Live preview — the page mock is always paper-white, like the printed sheet */
.qrx-preview-wrap { display: flex; flex-direction: column; gap: .5rem; align-items: center; }
.qrx-preview-page-box {
    width: 100%; max-width: 300px; background: #fff;
    border: 1px solid var(--border); border-radius: 4px;
    box-shadow: var(--shadow-md);
    position: relative; overflow: hidden;
}
.qrx-preview-cards { position: absolute; inset: 0; }
.qrx-preview-card {
    position: absolute; background: #fff; border: 1px solid #cbd2e0; border-radius: 2px;
    display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.qrx-preview-card img { width: 100%; height: 100%; object-fit: contain; background: #fff; display: block; }
.qrx-preview-stats { font-size: .8rem; color: var(--text-muted); text-align: center; }

/* Progress */
.qrx-progress {
    margin-top: .25rem; padding: .8rem .9rem;
    border: 1px solid var(--border); border-radius: var(--radius-md);
    background: var(--surface-alt);
}
.qrx-progress-head { display: flex; justify-content: space-between; font-weight: 600; font-size: .9rem; margin-bottom: .45rem; }
.qrx-progress-bar { height: 8px; background: var(--border); border-radius: var(--radius-pill); overflow: hidden; }
.qrx-progress-fill {
    height: 100%; width: 0%; border-radius: var(--radius-pill);
    background: linear-gradient(90deg, var(--orange-600), var(--orange-400));
    transition: width .25s ease;
}
.qrx-progress-detail { margin-top: .45rem; font-size: .8rem; color: var(--text-muted); }

.qrx-footer { gap: .6rem; }

/* Mobile */
@media (max-width: 640px) {
    .qrx-type-row, .qrx-layout-row { grid-template-columns: 1fr; }
    .qrx-layout-row { grid-template-columns: repeat(2, 1fr); gap: .4rem; }
    .qrx-settings-row { flex-direction: column; align-items: stretch; }
    /* Page size group (A4 / Letter / Custom) spans the full modal width so
       its 3-button segmented control can stretch edge-to-edge with each
       button getting an equal 1/3 share, instead of shrink-wrapping to
       content and leaving Letter/Custom cramped. */
    .qrx-page-size-group { width: 100%; }
    .qrx-seg--3 { display: flex; width: 100%; }
    .qrx-seg--3 .qrx-seg-btn { flex: 1 1 0; padding-left: .5rem; padding-right: .5rem; text-align: center; }
    .qrx-custom-page-row { flex-direction: column; align-items: stretch; }
    .qrx-custom-per-page-row .qrx-custom-size-field { max-width: none; }
    .qrx-custom-size-row { flex-direction: column; align-items: stretch; }
    .qrx-custom-size-x { display: none; }
    .qrx-single-chosen { flex-direction: row; }
    .topbar-qr-export-label { display: none; }
}

/* Keep the topbar button label visible on wider screens only */
@media (max-width: 900px) {
    .topbar-qr-export-label { display: none; }
}

/* The export modal toggles panes via the `hidden` attribute; flex/grid
   displays below would otherwise override it (the app has no global
   [hidden] rule), so restore hidden semantics scoped to this modal. */
#qrExportModal [hidden] { display: none !important; }
