/*
 * Q-Mgr Layout Styles
 * Pure Bootstrap-based layout with custom theming
 */

/* ================================
   Layout Variables
   ================================ */
:root {
    --header-height: 64px;
    --sidebar-width: 280px;
    --sidebar-collapsed-width: 72px;
    --footer-height: 50px;
}

/* ================================
   App Container
   ================================ */
.qm-app {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

/* ================================
   Header Styles
   ================================ */
.qm-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: var(--header-height);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 20px;
    z-index: 1030;
    transition: all 0.3s ease;
}

/* Dark Mode Header */
[data-theme="dark"] .qm-header {
    background: linear-gradient(135deg, var(--qm-bg-dark) 0%, var(--qm-bg-card) 100%);
    border-bottom: 1px solid var(--qm-border);
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}

/* Light Mode Header */
[data-theme="light"] .qm-header {
    background: linear-gradient(135deg, var(--qm-bg-card) 0%, var(--qm-bg-elevated) 100%);
    border-bottom: 1px solid var(--qm-border);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
}

.header-left,
.header-right {
    display: flex;
    align-items: center;
    gap: 12px;
}

/* Sidebar Toggle */
.sidebar-toggle {
    width: 40px;
    height: 40px;
    border: none;
    border-radius: var(--qm-radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.2s ease;
    font-size: var(--qm-text-stat);
}

[data-theme="dark"] .sidebar-toggle {
    background: transparent;
    color: var(--qm-text-primary);
}

[data-theme="dark"] .sidebar-toggle:hover {
    background: var(--qm-primary-light);
    color: var(--qm-primary);
}

[data-theme="light"] .sidebar-toggle {
    background: transparent;
    color: var(--qm-text-primary);
}

[data-theme="light"] .sidebar-toggle:hover {
    background: rgba(var(--qm-primary-rgb), 0.1);
    color: var(--qm-primary);
}

/* Brand */
.brand {
    display: flex;
    align-items: center;
    gap: 10px;
    text-decoration: none;
}

.brand-logo {
    width: 38px;
    height: 38px;
    border-radius: var(--qm-radius-md);
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: all 0.3s ease;
}

.brand-logo img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

/* Dark theme brand logo background */
[data-theme="dark"] .brand-logo {
    background: var(--qm-bg-dark);
    border: 1px solid rgba(var(--qm-primary-rgb), 0.3);
}

/* Light theme brand logo background */
[data-theme="light"] .brand-logo {
    background: var(--qm-bg-card);
    border: 1px solid var(--qm-border-hover);
    box-shadow: 0 2px 10px rgba(var(--qm-primary-rgb), 0.2);
}

.brand:hover .brand-logo {
    transform: scale(1.05);
}

.brand-icon {
    width: 38px;
    height: 38px;
    background: var(--qm-primary);
    border-radius: var(--qm-radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--qm-text-xl);
    color: var(--qm-text-on-primary);
}

.brand-text {
    font-size: var(--qm-text-xl);
    font-weight: 700;
    font-family: 'Poppins', sans-serif;
    letter-spacing: -0.02em;
}

[data-theme="dark"] .brand-text {
    color: var(--qm-text-primary);
}

[data-theme="light"] .brand-text {
    color: var(--qm-text-primary);
}

/* Header Buttons */
.header-btn {
    width: 40px;
    height: 40px;
    border: none;
    border-radius: var(--qm-radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.2s ease;
    font-size: var(--qm-text-lg);
}

[data-theme="dark"] .header-btn {
    background: transparent;
    color: var(--qm-text-secondary);
}

[data-theme="dark"] .header-btn:hover {
    background: var(--qm-primary-light);
    color: var(--qm-primary);
}

[data-theme="light"] .header-btn {
    background: transparent;
    color: var(--qm-text-secondary);
}

[data-theme="light"] .header-btn:hover {
    background: rgba(var(--qm-primary-rgb), 0.1);
    color: var(--qm-primary);
}

/* Header Notifications */
.header-notifications {
    position: relative;
}

/* "Sound off — tap to turn on" (notificationSound.js): a chime was due and the browser would not play it before a tap.
   Quiet on purpose — no animation, no colour of alarm; the toast already carried the notice. */
.qm-sound-unlock {
    white-space: nowrap;
    flex: 0 0 auto;
}

/* Header Date/Time */
.header-datetime {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    padding: 0 16px;
    line-height: 1.3;
}

[data-theme="dark"] .header-datetime {
    border-left: 1px solid var(--qm-border);
    border-right: 1px solid var(--qm-border);
}

[data-theme="light"] .header-datetime {
    border-left: 1px solid var(--qm-border);
    border-right: 1px solid var(--qm-border);
}

.header-datetime .date {
    font-size: var(--qm-text-xs);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

[data-theme="dark"] .header-datetime .date {
    color: var(--qm-text-muted);
}

[data-theme="light"] .header-datetime .date {
    color: var(--qm-text-muted);
}

.header-datetime .time {
    font-size: var(--qm-text-lg);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

[data-theme="dark"] .header-datetime .time {
    color: var(--qm-text-primary);
}

[data-theme="light"] .header-datetime .time {
    color: var(--qm-text-primary);
}

/* QClock (W11): the header clock on the SCHOOL's clock, ticking once a minute. Two lines, right
   aligned: the day above, the time below. Hidden on a phone and a tablet (below), where the shell
   does not render it at all — the rule is the backstop, not the mechanism. The digits use the
   product's own family with tabular figures; a third font family is not added for a clock. */
.qm-clock {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    justify-content: center;
    padding: 0 16px;
    border-left: 1px solid var(--qm-border);
    border-right: 1px solid var(--qm-border);
    line-height: 1.25;
    white-space: nowrap;
}

.qm-clock__date {
    font-size: var(--qm-text-xs);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--qm-text-muted);
}

.qm-clock__time {
    font-size: var(--qm-text-md);
    font-weight: 600;
    font-variant-numeric: tabular-nums lining-nums;
    color: var(--qm-text-primary);
}

@media (max-width: 992px) {
    .qm-clock { display: none; }
}

/* ================================
   App banner (.qm-app-banner) — ONE slim line inside the shell, above the page, for a message
   the app itself needs to say: an app message, a mandatory update, "you are offline". Tokens only,
   so a white-labelled school's banner is theirs. The text truncates rather than wrapping; the
   whole sentence is on its title. Variants: default (brand), --update (amber), --alert (red).
   Markup: .qm-app-banner > .qm-app-banner__icon + .qm-app-banner__text
           [+ a.qm-app-banner__action] [+ button.qm-app-banner__dismiss]
   ================================ */
.qm-app-banner {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 36px;
    padding: 6px var(--qm-page-pad, 16px);
    background: var(--qm-primary-light);
    color: var(--qm-text-primary);
    border-bottom: 1px solid var(--qm-border);
    font-size: var(--qm-text-sm);
    line-height: 1.3;
}

.qm-app-banner__icon {
    flex: 0 0 auto;
    color: var(--qm-primary);
    font-size: var(--qm-text-md);
}

.qm-app-banner__text {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.qm-app-banner__text strong { font-weight: 600; }

.qm-app-banner__action {
    flex: 0 0 auto;
    font-weight: 600;
    color: var(--qm-primary);
    text-decoration: underline;
    text-underline-offset: 2px;
    white-space: nowrap;
}

.qm-app-banner__dismiss {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--q-control-h-sm, 28px);
    height: var(--q-control-h-sm, 28px);
    margin-right: -6px;
    padding: 0;
    border: 0;
    border-radius: var(--qm-radius-md);
    background: none;
    color: var(--qm-text-muted);
    font: inherit;
    cursor: pointer;
}

.qm-app-banner__dismiss:hover { background: rgba(var(--qm-primary-rgb), 0.1); color: var(--qm-text-primary); }
.qm-app-banner__dismiss:focus-visible { outline: 2px solid var(--qm-primary); outline-offset: 1px; }

.qm-app-banner--update {
    background: rgba(var(--qm-warning-rgb), 0.14);
}
.qm-app-banner--update .qm-app-banner__icon { color: var(--qm-warning); }

.qm-app-banner--alert {
    background: rgba(var(--qm-danger-rgb), 0.12);
}
.qm-app-banner--alert .qm-app-banner__icon { color: var(--qm-danger); }

@media (max-width: 768px) {
    /* A finger, not a pointer: the dismiss clears the project's 40px phone floor. */
    .qm-app-banner__dismiss { width: 40px; height: 40px; margin: -6px -10px -6px 0; }
}

/* User Dropdown */
.user-dropdown {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px 6px 6px;
    border: none;
    border-radius: 50px;
    cursor: pointer;
    transition: all 0.2s ease;
}

[data-theme="dark"] .user-dropdown {
    background: rgba(255, 255, 255, 0.05);
    color: var(--qm-text-secondary);
}

[data-theme="dark"] .user-dropdown:hover {
    background: var(--qm-primary-light);
}

[data-theme="light"] .user-dropdown {
    background: rgba(0, 0, 0, 0.05);
    color: var(--qm-text-secondary);
}

[data-theme="light"] .user-dropdown:hover {
    background: var(--qm-primary-light);
}

/* The header avatar is QAvatar; this only sizes it for the header row and keeps it round. */
.q-avatar.header-avatar {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    font-weight: 600;
    font-size: var(--qm-text-base);
}

/* Dropdown Menu */
[data-theme="dark"] .dropdown-menu {
    background: var(--qm-bg-card);
    border: 1px solid var(--qm-border);
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
}

[data-theme="dark"] .dropdown-header {
    color: var(--qm-text-primary);
}

[data-theme="dark"] .dropdown-item {
    color: var(--qm-text-secondary);
}

[data-theme="dark"] .dropdown-item:hover {
    background: var(--qm-primary-light);
    color: var(--qm-primary);
}

[data-theme="dark"] .dropdown-divider {
    border-color: var(--qm-border);
}

[data-theme="light"] .dropdown-menu {
    background: var(--qm-bg-card);
    border: 1px solid var(--qm-border);
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
}

[data-theme="light"] .dropdown-header {
    color: var(--qm-text-primary);
}

[data-theme="light"] .dropdown-item {
    color: var(--qm-text-secondary);
}

[data-theme="light"] .dropdown-item:hover {
    background: rgba(var(--qm-primary-rgb), 0.1);
    color: var(--qm-primary);
}

[data-theme="light"] .dropdown-divider {
    border-color: var(--qm-border);
}

/* ================================
   Sidebar Styles
   ================================ */
.qm-sidebar {
    position: fixed;
    top: var(--header-height);
    left: 0;
    bottom: 0;
    width: var(--sidebar-width);
    display: flex;
    flex-direction: column;
    z-index: 1020;
    transition: all 0.3s ease;
    overflow: hidden;
}

.qm-sidebar.collapsed {
    width: var(--sidebar-collapsed-width);
}

/* Icon-only collapsed state on desktop */
@media (min-width: 993px) {
    .qm-sidebar.collapsed {
        transform: translateX(0);
    }

    .qm-sidebar.collapsed .sidebar-header {
        padding: 16px 12px;
        justify-content: center;
    }

    .qm-sidebar.collapsed .sidebar-title {
        display: none;
    }

    .qm-sidebar.collapsed .nav-link {
        justify-content: center;
        padding: 12px;
    }

    .qm-sidebar.collapsed .nav-link__label,
    .qm-sidebar.collapsed .nav-link .submenu-arrow,
    .qm-sidebar.collapsed .nav-link .q-count {
        display: none;
    }

    .qm-sidebar.collapsed .nav-link i:first-child {
        font-size: var(--qm-text-xl);
        width: auto;
    }

    .qm-sidebar.collapsed .submenu {
        display: none;
    }

    .qm-sidebar.collapsed .sidebar-footer {
        padding: 12px;
    }

    .qm-sidebar.collapsed .branch-selector {
        justify-content: center;
        padding: 12px;
    }

    .qm-sidebar.collapsed .branch-info,
    .qm-sidebar.collapsed .branch-selector > i:last-child {
        display: none;
    }

    .qm-sidebar.collapsed .branch-selector > i:first-child {
        margin: 0;
    }

    /* Hover expand effect */
    .qm-sidebar.collapsed:hover {
        width: var(--sidebar-width);
        box-shadow: 10px 0 40px rgba(0, 0, 0, 0.3);
    }

    .qm-sidebar.collapsed:hover .sidebar-title,
    .qm-sidebar.collapsed:hover .nav-link__label,
    .qm-sidebar.collapsed:hover .nav-link .submenu-arrow,
    .qm-sidebar.collapsed:hover .branch-info,
    .qm-sidebar.collapsed:hover .branch-selector > i:last-child {
        display: block;
    }

    .qm-sidebar.collapsed:hover .nav-link .q-count {
        display: inline-flex;
    }

    .qm-sidebar.collapsed:hover .sidebar-header {
        padding: 20px 20px 12px;
        justify-content: flex-start;
    }

    .qm-sidebar.collapsed:hover .nav-link {
        justify-content: flex-start;
        padding: 12px 16px;
    }

    .qm-sidebar.collapsed:hover .nav-link i:first-child {
        font-size: var(--qm-text-lg);
        width: 24px;
    }

    .qm-sidebar.collapsed:hover .submenu {
        display: block;
    }

    .qm-sidebar.collapsed:hover .sidebar-footer {
        padding: 16px;
    }

    .qm-sidebar.collapsed:hover .branch-selector {
        justify-content: flex-start;
        padding: 14px 16px;
    }

    /* Main content adjustment for collapsed sidebar. Qualified by .qm-app because the base rule further down,
       .qm-main.expanded { margin-left: 0 }, has the same specificity and comes later, so it won: on a desktop with the
       sidebar collapsed (the hamburger, or a window widened from phone width) the first 72px of every page sat under the
       icon rail, tick boxes included (found 2026-09-27 by roster-bulk-select). */
    .qm-app .qm-main.expanded {
        margin-left: var(--sidebar-collapsed-width);
    }
}

[data-theme="dark"] .qm-sidebar {
    background: linear-gradient(180deg, var(--qm-bg-dark) 0%, var(--qm-bg-card) 100%);
    border-right: 1px solid var(--qm-border);
}

[data-theme="light"] .qm-sidebar {
    background: var(--qm-gradient-sidebar);
    border-right: 1px solid var(--qm-border);
}

/* Sidebar Header */
.sidebar-header {
    padding: 20px 20px 12px;
}

[data-theme="dark"] .sidebar-header {
    border-bottom: 1px solid var(--qm-border);
}

[data-theme="light"] .sidebar-header {
    border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}

.sidebar-title {
    font-size: var(--qm-text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.15em;
}

[data-theme="dark"] .sidebar-title {
    color: var(--qm-text-muted);
}

[data-theme="light"] .sidebar-title {
    color: var(--qm-text-muted);
}

/* Sidebar Navigation */
.sidebar-nav {
    flex: 1;
    overflow-y: auto;
    padding: 12px 0;
}

.nav-menu {
    list-style: none;
    padding: 0;
    margin: 0;
}

.nav-item {
    margin: 2px 8px;
}

.nav-link {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    border-radius: var(--qm-radius-md);
    text-decoration: none;
    transition: all 0.2s ease;
    font-size: var(--qm-text-base);
    font-weight: 500;
}

.nav-link i:first-child {
    font-size: var(--qm-text-lg);
    width: 24px;
    text-align: center;
}

[data-theme="dark"] .nav-link {
    color: var(--qm-text-secondary);
}

[data-theme="dark"] .nav-link:hover {
    background: rgba(var(--qm-primary-rgb), 0.1);
    color: var(--qm-primary);
}

[data-theme="dark"] .nav-link.active {
    background: var(--qm-gradient-primary);
    color: var(--qm-text-on-primary);
}

[data-theme="light"] .nav-link {
    color: var(--qm-text-secondary);
}

[data-theme="light"] .nav-link:hover {
    background: rgba(var(--qm-primary-rgb), 0.1);
    color: var(--qm-primary);
}

[data-theme="light"] .nav-link.active {
    background: var(--qm-gradient-primary);
    color: var(--qm-text-on-primary);
}

/* The link TEXT has its own class (W1, 2026-09-28). Every rule that shows or hides the label
   selects .nav-link__label — never ".nav-link span", which also matched the count pill beside it
   and turned it into a block. nav-label-check.mjs (guards) fails that selector coming back. */
.nav-link__label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Submenu Arrow */
.submenu-arrow {
    margin-left: auto;
    font-size: var(--qm-text-sm);
    transition: transform 0.2s ease;
}

.nav-item.has-submenu.open .submenu-arrow {
    transform: rotate(180deg);
}

/* Submenu */
.submenu {
    list-style: none;
    padding: 0;
    margin: 0;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease;
}

.nav-item.has-submenu.open .submenu {
    max-height: 500px;
}

.submenu li {
    margin: 2px 0;
}

.submenu a {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 16px 10px 52px;
    border-radius: var(--qm-radius-md);
    text-decoration: none;
    font-size: var(--qm-text-base);
    transition: all 0.2s ease;
}

.submenu a i {
    font-size: var(--qm-text-base);
    width: 18px;
    text-align: center;
}

[data-theme="dark"] .submenu a {
    color: var(--qm-text-muted);
}

[data-theme="dark"] .submenu a:hover {
    background: rgba(var(--qm-primary-rgb), 0.08);
    color: var(--qm-primary);
}

[data-theme="dark"] .submenu a.active {
    background: var(--qm-primary-light);
    color: var(--qm-primary);
}

[data-theme="light"] .submenu a {
    color: var(--qm-text-secondary);
}

[data-theme="light"] .submenu a:hover {
    background: rgba(var(--qm-primary-rgb), 0.08);
    color: var(--qm-primary);
}

[data-theme="light"] .submenu a.active {
    background: rgba(var(--qm-primary-rgb), 0.15);
    color: var(--qm-primary);
}

/* Sidebar Footer */
.sidebar-footer {
    padding: 16px;
    margin-top: auto;
}

[data-theme="dark"] .sidebar-footer {
    border-top: 1px solid var(--qm-border);
}

[data-theme="light"] .sidebar-footer {
    border-top: 1px solid rgba(0, 0, 0, 0.08);
}

/* Branch Selector */
.branch-selector {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 16px;
    border-radius: var(--qm-radius-md);
    cursor: pointer;
    transition: all 0.2s ease;
}

[data-theme="dark"] .branch-selector {
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--qm-border);
}

[data-theme="dark"] .branch-selector:hover {
    background: rgba(var(--qm-primary-rgb), 0.1);
    border-color: rgba(var(--qm-primary-rgb), 0.3);
}

[data-theme="dark"] .branch-selector i {
    color: var(--qm-primary);
}

[data-theme="light"] .branch-selector {
    background: rgba(0, 0, 0, 0.03);
    border: 1px solid rgba(0, 0, 0, 0.08);
}

[data-theme="light"] .branch-selector:hover {
    background: rgba(var(--qm-primary-rgb), 0.1);
    border-color: rgba(var(--qm-primary-rgb), 0.3);
}

[data-theme="light"] .branch-selector i {
    color: var(--qm-primary);
}

.branch-info {
    flex: 1;
    min-width: 0;
}

.branch-label {
    display: block;
    font-size: var(--qm-text-xs);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

[data-theme="dark"] .branch-label {
    color: var(--qm-text-muted);
}

[data-theme="light"] .branch-label {
    color: var(--qm-text-muted);
}

.branch-name {
    display: block;
    font-size: var(--qm-text-base);
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

[data-theme="dark"] .branch-name {
    color: var(--qm-text-primary);
}

[data-theme="light"] .branch-name {
    color: var(--qm-text-primary);
}

/* ================================
   Main Content Styles
   ================================ */
.qm-main {
    margin-left: var(--sidebar-width);
    margin-top: var(--header-height);
    min-height: calc(100vh - var(--header-height));
    display: flex;
    flex-direction: column;
    transition: all 0.3s ease;
}

.qm-main.expanded {
    margin-left: 0;
}

[data-theme="dark"] .qm-main {
    background: var(--qm-bg-dark);
}

[data-theme="light"] .qm-main {
    background: var(--qm-bg-dark);
}

.page-container {
    flex: 1;
    padding: 24px;
}

/* ================================
   Footer Styles
   ================================ */
.qm-footer {
    height: var(--footer-height);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 24px;
    font-size: var(--qm-text-base);
}

[data-theme="dark"] .qm-footer {
    background: var(--qm-bg-dark);
    border-top: 1px solid var(--qm-border);
    color: var(--qm-text-muted);
}

[data-theme="light"] .qm-footer {
    background: var(--qm-bg-darker);
    border-top: 1px solid rgba(0, 0, 0, 0.08);
    color: var(--qm-text-muted);
}

.footer-links {
    display: flex;
    align-items: center;
    gap: 20px;
}

.footer-links a {
    text-decoration: none;
}

[data-theme="dark"] .footer-links a {
    color: var(--qm-primary);
}

[data-theme="light"] .footer-links a {
    color: var(--qm-primary);
}

.footer-links .version {
    font-variant-numeric: tabular-nums;
}

/* ================================
   Modal Styles
   ================================ */
[data-theme="dark"] .modal-content {
    background: var(--qm-bg-card);
    border: 1px solid var(--qm-border);
}

[data-theme="dark"] .modal-header {
    border-bottom: 1px solid var(--qm-border);
}

[data-theme="dark"] .modal-title {
    color: var(--qm-text-primary);
}

[data-theme="dark"] .btn-close {
    filter: invert(1);
}

[data-theme="light"] .modal-content {
    background: var(--qm-bg-card);
    border: 1px solid var(--qm-border);
}

[data-theme="light"] .modal-header {
    border-bottom: 1px solid var(--qm-border);
}

[data-theme="light"] .modal-title {
    color: var(--qm-text-primary);
}

/* Branch List */
.branch-list {
    padding: 8px;
}

.branch-item {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 16px;
    border-radius: var(--qm-radius-md);
    cursor: pointer;
    transition: all 0.2s ease;
    margin-bottom: 4px;
}

[data-theme="dark"] .branch-item {
    color: var(--qm-text-secondary);
}

[data-theme="dark"] .branch-item:hover {
    background: rgba(var(--qm-primary-rgb), 0.1);
}

[data-theme="dark"] .branch-item.active {
    background: var(--qm-primary-light);
    border: 1px solid rgba(var(--qm-primary-rgb), 0.3);
}

[data-theme="dark"] .branch-item.active i {
    color: var(--qm-primary);
}

[data-theme="light"] .branch-item {
    color: var(--qm-text-secondary);
}

[data-theme="light"] .branch-item:hover {
    background: rgba(var(--qm-primary-rgb), 0.1);
}

[data-theme="light"] .branch-item.active {
    background: rgba(var(--qm-primary-rgb), 0.15);
    border: 1px solid rgba(var(--qm-primary-rgb), 0.3);
}

[data-theme="light"] .branch-item.active i {
    color: var(--qm-primary);
}

.branch-item i {
    font-size: var(--qm-text-xl);
}

.branch-item-info {
    flex: 1;
}

.branch-item-name {
    display: block;
    font-weight: 600;
}

[data-theme="dark"] .branch-item-name {
    color: var(--qm-text-primary);
}

[data-theme="light"] .branch-item-name {
    color: var(--qm-text-primary);
}

.branch-item-address {
    display: block;
    font-size: var(--qm-text-sm);
}

[data-theme="dark"] .branch-item-address {
    color: var(--qm-text-muted);
}

[data-theme="light"] .branch-item-address {
    color: var(--qm-text-muted);
}

/* ================================
   Notifications Panel
   ================================ */
.notifications-panel {
    position: fixed;
    top: calc(var(--header-height) + 10px);
    right: 20px;
    width: 380px;
    max-height: 500px;
    border-radius: var(--qm-radius-lg);
    overflow: hidden;
    z-index: 1050;
}

[data-theme="dark"] .notifications-panel {
    background: var(--qm-bg-card);
    border: 1px solid var(--qm-border);
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
}

[data-theme="light"] .notifications-panel {
    background: var(--qm-bg-card);
    border: 1px solid var(--qm-border);
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
}

.notifications-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 16px 20px;
}

[data-theme="dark"] .notifications-header {
    border-bottom: 1px solid var(--qm-border);
}

[data-theme="dark"] .notifications-header h6 {
    color: var(--qm-text-primary);
    margin: 0;
}

[data-theme="light"] .notifications-header {
    border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}

[data-theme="light"] .notifications-header h6 {
    color: var(--qm-text-primary);
    margin: 0;
}

.notifications-list {
    max-height: 400px;
    overflow-y: auto;
}

/* Token-only, so it needs no [data-theme] counterpart — the header above predates the token
   layer, which is why that one still carries a pair of overrides. */
.notifications-footer {
    border-top: 1px solid var(--qm-border);
}

.notifications-footer a {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 20px;
    font-size: var(--qm-text-base);
    color: var(--qm-text-secondary);
    text-decoration: none;
    transition: background 0.15s ease, color 0.15s ease;
}

.notifications-footer a:hover {
    background: var(--qm-bg-elevated);
    color: var(--qm-primary);
}

.notifications-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 48px 20px;
}

[data-theme="dark"] .notifications-empty {
    color: var(--qm-text-muted);
}

[data-theme="light"] .notifications-empty {
    color: var(--qm-text-muted);
}

.notifications-empty i:not(.q-btn__icon) {
    font-size: var(--qm-empty-icon);
    margin-bottom: 4px;
}

/* A <button> since 2026-09-22 — the row is the control that opens the notification — so it has to
   un-inherit the button defaults the element brings with it. */
.notification-item {
    display: flex;
    align-items: center;
    gap: 14px;
    width: 100%;
    padding: 12px 16px;
    min-height: var(--q-control-h-lg);
    font: inherit;
    font-size: var(--q-control-font);
    color: inherit;
    text-align: left;
    background: none;
    border: 0;
    cursor: pointer;
    transition: background 0.2s ease;
}

.notification-item:hover { background: var(--qm-bg-card-hover); }

.notification-item:focus-visible {
    outline: 2px solid var(--qm-primary);
    outline-offset: -2px;
}

/* The unread mark and the "this one goes somewhere" chevron, mirroring /notifications' own rows. */
.notification-dot {
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--qm-primary);
}

.notification-go { flex: 0 0 auto; color: var(--qm-text-muted); }

[data-theme="dark"] .notification-item {
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

[data-theme="dark"] .notification-item:hover {
    background: rgba(255, 255, 255, 0.03);
}

[data-theme="dark"] .notification-item.unread {
    background: rgba(var(--qm-primary-rgb), 0.08);
}

[data-theme="light"] .notification-item {
    border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}

[data-theme="light"] .notification-item:hover {
    background: rgba(0, 0, 0, 0.03);
}

[data-theme="light"] .notification-item.unread {
    background: rgba(var(--qm-primary-rgb), 0.08);
}

.notification-icon {
    width: 40px;
    height: 40px;
    border-radius: var(--qm-radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.notification-icon.info {
    background: rgba(var(--qm-primary-rgb), 0.15);
    color: var(--qm-primary);
}

.notification-icon.success {
    background: rgba(var(--qm-success-rgb), 0.15);
    color: var(--qm-accent-green);
}

.notification-icon.warning {
    background: rgba(var(--qm-warning-rgb), 0.15);
    color: var(--qm-warning);
}

.notification-icon.error {
    background: rgba(var(--qm-danger-rgb), 0.15);
    color: var(--qm-danger);
}

.notification-content {
    flex: 1;
    min-width: 0;
}

.notification-title {
    display: block;
    font-weight: 600;
    font-size: var(--qm-text-base);
}

[data-theme="dark"] .notification-title {
    color: var(--qm-text-primary);
}

[data-theme="light"] .notification-title {
    color: var(--qm-text-primary);
}

.notification-message {
    display: block;
    font-size: var(--qm-text-base);
    margin-top: 2px;
}

[data-theme="dark"] .notification-message {
    color: var(--qm-text-secondary);
}

[data-theme="light"] .notification-message {
    color: var(--qm-text-secondary);
}

.notification-time {
    display: block;
    font-size: var(--qm-text-xs);
    margin-top: 6px;
}

[data-theme="dark"] .notification-time {
    color: var(--qm-text-muted);
}

[data-theme="light"] .notification-time {
    color: var(--qm-text-muted);
}

/* ================================
   Responsive Styles
   ================================ */

/* Tablet - Icon-only sidebar auto-collapsed */
@media (max-width: 992px) {
    :root {
        --header-height: 60px;
        --sidebar-collapsed-width: 64px;
    }

    /* Auto-collapse to icon-only on tablet */
    .qm-sidebar {
        width: var(--sidebar-collapsed-width);
    }

    .qm-sidebar .sidebar-header {
        padding: 16px 10px;
        display: flex;
        justify-content: center;
    }

    .qm-sidebar .sidebar-title {
        display: none;
    }

    .qm-sidebar .nav-link {
        justify-content: center;
        padding: 12px 10px;
    }

    .qm-sidebar .nav-link__label,
    .qm-sidebar .nav-link .submenu-arrow,
    .qm-sidebar .nav-link .q-count {
        display: none;
    }

    .qm-sidebar .nav-link i:first-child {
        font-size: var(--qm-text-xl);
        width: auto;
    }

    .qm-sidebar .submenu {
        display: none;
    }

    .qm-sidebar .sidebar-footer {
        padding: 10px;
    }

    .qm-sidebar .branch-selector {
        justify-content: center;
        padding: 10px;
    }

    .qm-sidebar .branch-info,
    .qm-sidebar .branch-selector > i:last-child {
        display: none;
    }

    /* Expand on hover for tablet */
    .qm-sidebar:hover {
        width: var(--sidebar-width);
        box-shadow: 10px 0 40px rgba(0, 0, 0, 0.5);
        z-index: 1025;
    }

    .qm-sidebar:hover .sidebar-title,
    .qm-sidebar:hover .nav-link__label,
    .qm-sidebar:hover .nav-link .submenu-arrow,
    .qm-sidebar:hover .branch-info,
    .qm-sidebar:hover .branch-selector > i:last-child {
        display: block;
    }

    .qm-sidebar:hover .nav-link .q-count {
        display: inline-flex;
    }

    .qm-sidebar:hover .sidebar-header {
        padding: 20px 20px 12px;
        justify-content: flex-start;
    }

    .qm-sidebar:hover .nav-link {
        justify-content: flex-start;
        padding: 12px 16px;
    }

    .qm-sidebar:hover .nav-link i:first-child {
        font-size: var(--qm-text-lg);
        width: 24px;
    }

    .qm-sidebar:hover .submenu {
        display: block;
    }

    .qm-sidebar:hover .sidebar-footer {
        padding: 16px;
    }

    .qm-sidebar:hover .branch-selector {
        justify-content: flex-start;
        padding: 14px 16px;
    }

    /* Hidden on collapsed toggle */
    .qm-sidebar.collapsed {
        transform: translateX(-100%);
        width: 0;
    }

    .qm-main {
        margin-left: var(--sidebar-collapsed-width);
    }

    .qm-main.expanded {
        margin-left: 0;
    }

    .header-datetime {
        display: none;
    }

    /* Sidebar overlay when fully expanded on mobile */
    .sidebar-overlay {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.5);
        /* Over the header (1030) and the phone bar (1025), under the drawer (1040): while the
           drawer is open a press ANYWHERE outside it lands here and closes it, and goes no further.
           The reader meant "close", not "press the thing behind" (§1a D-1, D-2, D-3). */
        z-index: 1035;
        opacity: 0;
        visibility: hidden;
        transition: opacity 0.25s ease, visibility 0.25s ease;
        -webkit-tap-highlight-color: transparent;
    }

    .sidebar-overlay.visible {
        opacity: 1;
        visibility: visible;
    }

    .page-container {
        padding: 20px;
    }

    /* Footer adjustments */
    .qm-footer {
        flex-direction: column;
        gap: 8px;
        height: auto;
        padding: 12px 20px;
        text-align: center;
    }

    .footer-links {
        flex-wrap: wrap;
        justify-content: center;
        gap: 12px;
    }

    /* Brand adjustments for tablet */
    .brand-logo {
        width: 34px;
        height: 34px;
    }

    .brand-text {
        font-size: var(--qm-text-lg);
    }
}

/* ==========================================================================
   THE PHONE AND TABLET DRAWER (≤992px, MOBILE_AND_SHELL_AUDIT §1a). Below 992px the sidebar is a
   drawer and nothing else: closed it is off-screen, open it lies over the page with a dimmed
   backdrop (.sidebar-overlay.visible, rendered by MainLayout only while it is open) that closes it.
   Layering, highest first — keep these numbers in step:
     1040  .qm-sidebar.expanded     the drawer
     1035  .sidebar-overlay.visible the backdrop: covers the header and the phone bar too
     1030  .qm-header
     1025  .qm-mobilenav
   While it is open MainLayout puts .qm-drawer-open on <html>, which stops the page behind scrolling
   under a finger resting on the drawer's edge (D-5).
   ========================================================================== */
@media (max-width: 992px) {
    .qm-sidebar.expanded,
    .qm-sidebar.expanded:hover {
        transform: translateX(0);
        width: var(--sidebar-width);
        z-index: 1040;
        box-shadow: 10px 0 40px rgba(0, 0, 0, 0.35);
    }

    /* An open drawer shows everything the icon rail hides. */
    .qm-sidebar.expanded .sidebar-header {
        padding: 20px 20px 12px;
        justify-content: flex-start;
    }

    .qm-sidebar.expanded .sidebar-title {
        display: block;
    }

    .qm-sidebar.expanded .nav-link {
        justify-content: flex-start;
        padding: 12px 16px;
    }

    .qm-sidebar.expanded .nav-link__label,
    .qm-sidebar.expanded .nav-link .submenu-arrow {
        display: block;
    }

    /* The count is a pill, never a block: it was a <span> matched by the old ".nav-link span" rule,
       which is how Billing's "4" lost its centre on a phone (W1). */
    .qm-sidebar.expanded .nav-link .q-count {
        display: inline-flex;
    }

    .qm-sidebar.expanded .nav-link i:first-child {
        font-size: var(--qm-text-lg);
        width: 24px;
    }

    .qm-sidebar.expanded .submenu {
        display: block;
    }

    .qm-sidebar.expanded .sidebar-footer {
        padding: 16px;
    }

    .qm-sidebar.expanded .branch-selector {
        justify-content: flex-start;
        padding: 14px 16px;
    }

    .qm-sidebar.expanded .branch-info,
    .qm-sidebar.expanded .branch-selector > i:last-child {
        display: block;
    }

    /* A drawer lies OVER the page; the page never makes room for it. */
    .qm-main,
    .qm-main.expanded {
        margin-left: 0;
    }

    html.qm-drawer-open,
    html.qm-drawer-open body {
        overflow: hidden;
        overscroll-behavior: none;
    }
}

/* The drawer stops at the phone bar, so its footer — Current Branch, the only branch switch on a
   phone until More gained one — is never hidden under the bar (W4, D-2). */
@media (max-width: 768px) {
    .qm-sidebar {
        bottom: var(--qm-mobilenav-total);
    }
}

@media (prefers-reduced-motion: reduce) {
    .qm-sidebar,
    .sidebar-overlay {
        transition: none;
    }
}

/* Mobile phones */
@media (max-width: 576px) {
    :root {
        --header-height: 56px;
        --sidebar-collapsed-width: 0px;
    }

    .qm-header {
        padding: 0 12px;
    }

    /* Hide sidebar completely on mobile - use toggle to show */
    .qm-sidebar {
        transform: translateX(-100%);
        width: var(--sidebar-width);
    }

    /* Brand adjustments for mobile */
    .brand-logo {
        width: 32px;
        height: 32px;
        border-radius: var(--qm-radius-md);
    }

    .brand-text {
        display: none;
    }

    .brand-icon {
        width: 32px;
        height: 32px;
        font-size: var(--qm-text-md);
        border-radius: var(--qm-radius-md);
    }

    /* Ensure touch targets are at least 44px */
    .sidebar-toggle,
    .header-btn {
        width: 44px;
        height: 44px;
        min-width: 44px;
        min-height: 44px;
    }

    .header-right {
        gap: 4px;
    }

    .header-left {
        gap: 8px;
    }

    /* User dropdown improvements for mobile */
    .user-dropdown {
        padding: 4px 8px 4px 4px;
    }

    .user-dropdown .user-name {
        display: none;
    }

    .q-avatar.header-avatar {
        width: 32px;
        height: 32px;
        font-size: var(--qm-text-base);
    }

    .notifications-panel {
        width: calc(100% - 24px);
        left: 12px;
        right: 12px;
        top: calc(var(--header-height) + 8px);
        max-height: calc(100vh - var(--header-height) - 20px);
    }

    .page-container {
        padding: 12px;
    }

    /* Sidebar full width on small screens */
    .qm-sidebar {
        width: 100%;
        max-width: 320px;
    }

    .qm-sidebar.expanded {
        box-shadow: 0 0 50px rgba(0, 0, 0, 0.8);
    }

    /* Larger touch targets for nav items */
    .nav-link {
        padding: 14px 16px;
        min-height: 48px;
    }

    .submenu a {
        padding: 12px 16px 12px 52px;
        min-height: 44px;
    }

    /* Branch selector touch target */
    .branch-selector {
        padding: 16px;
        min-height: 56px;
    }

    /* Footer for small screens */
    .qm-footer {
        font-size: var(--qm-text-sm);
        padding: 10px 12px;
    }

    .footer-links {
        gap: 8px;
    }

    /* Modal improvements for mobile */
    .modal-dialog {
        margin: 8px;
        max-width: calc(100% - 16px);
    }

    .modal-content {
        border-radius: var(--qm-radius-lg);
    }

    .branch-item {
        padding: 14px;
        min-height: 56px;
    }
}

/* Very small screens (320px and below) */
@media (max-width: 360px) {
    .qm-header {
        padding: 0 8px;
    }

    .header-left {
        gap: 4px;
    }

    .brand-icon {
        width: 32px;
        height: 32px;
        border-radius: var(--qm-radius-md);
        font-size: var(--qm-text-md);
    }

    .sidebar-toggle,
    .header-btn {
        width: 40px;
        height: 40px;
        min-width: 40px;
        min-height: 40px;
    }

    .page-container {
        padding: 8px;
    }

    .notifications-panel {
        width: calc(100% - 16px);
        left: 8px;
        right: 8px;
    }
}

/* Landscape orientation on mobile */
@media (max-height: 500px) and (orientation: landscape) {
    :root {
        --header-height: 48px;
    }

    .qm-sidebar {
        top: var(--header-height);
    }

    .sidebar-nav {
        padding: 8px 0;
    }

    .nav-link {
        padding: 10px 16px;
        min-height: 40px;
    }

    .sidebar-header {
        padding: 12px 16px 8px;
    }

    .sidebar-footer {
        padding: 12px;
    }

    .branch-selector {
        padding: 10px 12px;
    }
}

/* Touch device optimizations */
@media (hover: none) and (pointer: coarse) {
    /* Disable hover effects on touch devices for better performance */
    .nav-link:hover,
    .submenu a:hover,
    .header-btn:hover,
    .sidebar-toggle:hover,
    .branch-selector:hover,
    .branch-item:hover {
        transition: none;
    }

    /* Ensure all interactive elements have adequate touch targets */
    .nav-link,
    .submenu a,
    .branch-item,
    .branch-selector {
        -webkit-tap-highlight-color: rgba(var(--qm-primary-rgb), 0.2);
    }
}

/* The page and header gutters, per breakpoint, as variables — so the safe-area rule below can take
   the larger of the gutter and the notch. It used to say max(12px, …) OUTSIDE any media query, which
   every current browser applies, so every page on every desktop had a 12px gutter instead of 24px
   and the full-bleed .page-header (margin -24px) hung 12px past the edge. Measured 2026-09-16. */
:root { --qm-page-pad: 24px; --qm-header-pad: 20px; }
@media (max-width: 992px) { :root { --qm-page-pad: 20px; } }
@media (max-width: 576px) { :root { --qm-page-pad: 12px; --qm-header-pad: 12px; } }
@media (max-width: 360px) { :root { --qm-page-pad: 8px; --qm-header-pad: 8px; } }

/* Safe area insets for notched devices */
@supports (padding: max(0px)) {
    .qm-header {
        padding-left: max(var(--qm-header-pad), env(safe-area-inset-left));
        padding-right: max(var(--qm-header-pad), env(safe-area-inset-right));
    }

    .qm-sidebar {
        padding-left: env(safe-area-inset-left);
    }

    .page-container {
        padding-left: max(var(--qm-page-pad), env(safe-area-inset-left));
        padding-right: max(var(--qm-page-pad), env(safe-area-inset-right));
        padding-bottom: max(var(--qm-page-pad), env(safe-area-inset-bottom));
    }

    .qm-footer {
        padding-bottom: max(12px, env(safe-area-inset-bottom));
    }
}

/* Print styles */
@media print {
    .qm-header,
    .qm-sidebar,
    .qm-footer,
    .sidebar-overlay {
        display: none !important;
    }

    .qm-main {
        margin: 0 !important;
    }

    .page-container {
        padding: 0 !important;
    }
}

/* ================================
   Scrollbar Styles
   ================================ */
.sidebar-nav::-webkit-scrollbar {
    width: 6px;
}

[data-theme="dark"] .sidebar-nav::-webkit-scrollbar-track {
    background: transparent;
}

[data-theme="dark"] .sidebar-nav::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.1);
    border-radius: var(--qm-radius-sm);
}

[data-theme="dark"] .sidebar-nav::-webkit-scrollbar-thumb:hover {
    background: rgba(var(--qm-primary-rgb), 0.3);
}

[data-theme="light"] .sidebar-nav::-webkit-scrollbar-track {
    background: transparent;
}

[data-theme="light"] .sidebar-nav::-webkit-scrollbar-thumb {
    background: var(--qm-border);
    border-radius: var(--qm-radius-sm);
}

[data-theme="light"] .sidebar-nav::-webkit-scrollbar-thumb:hover {
    background: rgba(var(--qm-primary-rgb), 0.3);
}

/* ==========================================================================
   MOBILE OVERFLOW — the page must never scroll sideways
   --------------------------------------------------------------------------
   Reported as "overlapping items" on a phone: on the welfare timeline the
   heading, the points chips, the buttons and the record cards were all clipped
   on the LEFT, and "Notify Guardian" sat outside its own card.

   Nothing was overlapping. One row was wider than the phone, so the document
   itself grew past the viewport and the browser scrolled right — moving every
   other element with it. The row was .timeline-card-actions: four buttons in a
   `display:flex` with no `flex-wrap`.

   Three layers, because the bug needed all three to happen:

     1. min-width:0 — a flex/grid child defaults to min-width:auto, so it
        REFUSES to shrink below its content. That is what let one row widen the
        whole page instead of just itself. This is the actual root cause and
        the reason the symptom appeared page-wide rather than in one card.
     2. overflow-x:clip on the scroll container, so a row that still manages to
        be too wide is contained rather than moving the layout. `clip` rather
        than `hidden` because `hidden` creates a scroll container and would
        break `position:sticky` inside it.
     3. flex-wrap on the recurring row families below.

   Wide tables keep their own .table-scroll / overflow-x:auto wrappers: the aim
   is that THE PAGE never scrolls sideways, not that content gets cut off.
   ========================================================================== */

.qm-main {
    min-width: 0;
    overflow-x: clip;
}

/* Long unbroken strings — a pasted URL, a badge code, an email address — are
   the other way a child outgrows its column. */
.qm-main,
.admin-page,
.q-card,
.q-modal__body {
    overflow-wrap: break-word;
}

/* Direct children of a page or card can shrink. Without this every rule below
   is cosmetic: the row wraps, and the grid track it sits in stays too wide. */
.admin-page > *,
.q-card > *,
.page-header > *,
.filter-row > *,
.stats-grid > *,
.report-columns > * {
    min-width: 0;
}

/* The page header's button row has ONE definition (2026-09-17). About twenty pages each set their own gap
   (8, 10, 12, 14 or 16px) and the rest set none, where Blazor's trimmed whitespace left buttons touching,
   so two pages side by side never matched. Button sizes come from the control scale in q-components.css.
   NOTE: this block must stay separate from the min-width rule above. Merging them (which happened once,
   2026-09-17) puts `display: flex` on EVERY direct child of every page and card — it makes a component's
   own <style> element visible on the page, and it drops the min-width:0 the sideways-scroll fix rests on. */
.header-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--q-row-gap, 8px);
}

/* Every other row of buttons uses the same gap. These are the row names this codebase actually uses;
   a page rule that sets its own gap on one of them puts the drift back. */
.form-actions,
.modal-actions,
.card-actions,
.filter-actions,
.dialog-actions,
.q-modal__footer,
.branch-footer,
.ct-person-actions,
.tts-actions,
.q-filter-bar {
    gap: var(--q-row-gap, 8px);
}

/* The buttons at the foot of a dialog: a right-aligned row, clear of the last field. Five
   components carried an identical copy in their own <style>, each with a 24px top margin. */
.dialog-actions {
    display: flex;
    justify-content: flex-end;
    margin-top: var(--qm-block-gap, 12px);
}

/* A card following the filter bar needs air above it. Nine components wrote this rule into their
   own <style> — six of them byte-identical — under three different container names. Adjacent
   block margins collapse, so this does not stack with the bar's own margin-bottom. */
.admin-page .q-card,
.lessons-page .q-card,
.tts-page .q-card,
.myday-page .q-card {
    margin-top: var(--qm-block-gap, 12px);
}

/* One page title and one body size inside the app shell. Print sheets and public screens are outside
   .qm-main and keep their own paper and large-format sizes. */
.qm-main { font-size: var(--qm-text-base); }

.qm-main h1 {
    font-family: var(--qm-font-display);
    /* THE ONE HOME FOR THE TITLE SIZE. 28px until 2026-09-18, when it was set to 24px here and
       nothing changed on screen: qm-theme.css also carried `.page-header h1 { font-size: 28px }`,
       equal specificity and later in the cascade, so it won every time. That rule is gone (see the
       note there) and this one finally applies. 20px now — a title is the loudest thing on a page,
       it need not also be the tallest, and on a data-driven screen those pixels are a row of data.
       uniform-check.mjs asserts the computed size against this number on every page, AND that the
       rule winning the cascade is this one. */
    font-size: var(--qm-text-xl);
    font-weight: 700;
    line-height: 1.25;
}

@media (max-width: 640px) {
    .qm-main h1 { font-size: var(--qm-text-lg); }
}

/* THE OTHER HEADINGS ARE ON THE SCALE TOO (2026-09-19). Only the h1 had a size. An h2, h3 or h4 with
   no class rule of its own fell through to BOOTSTRAP's defaults — 32, 28 and 24px — so a section
   heading like "Additional Metrics" or a role name in Users & Roles rendered LARGER than the page
   title above it. Found by type-sweep-all.mjs across 54 pages.

   :where() keeps these at the specificity of a bare element selector, so any class rule a component
   sets (.q-card__title, .q-empty__title, a page's own heading class) still wins, and this only
   catches a heading nobody sized. It beats Bootstrap's equal-specificity rule on load order alone:
   bootstrap.min.css is linked first in App.razor. */
:where(.qm-main) h2 { font-size: var(--qm-text-lg); }
:where(.qm-main) h3,
:where(.qm-main) h4 { font-size: var(--qm-text-md); }
:where(.qm-main) h5,
:where(.qm-main) h6 { font-size: var(--qm-text-base); }



.page-header > *,
.filter-row > *,
.stats-grid > *,
.report-columns > * {
    min-width: 0;
}

@media (max-width: 768px) {
    /* Action and control rows wrap. Named by the families that actually recur
       across this codebase rather than by a blanket `div { flex-wrap: wrap }`,
       which would also wrap things that must not (see tabs below). */
    [class$="-actions"],
    [class*="-actions "],
    [class$="-footer"],
    [class$="-controls"],
    [class$="-toolbar"],
    .header-actions,
    .filter-row,
    .filter-actions,
    .form-actions,
    .card-actions,
    .modal-actions,
    .batch-bar,
    .points-summary {
        flex-wrap: wrap !important;
    }

    /* Tabs scroll, they do not wrap. A wrapped tab strip reads as broken in a
       way a wrapped button row does not. */
    .report-tabs,
    .tabs,
    .filter-bar,
    .nav-tabs {
        flex-wrap: nowrap !important;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: thin;
    }
    .report-tabs > *,
    .tabs > *,
    .filter-bar > *,
    .nav-tabs > * {
        flex-shrink: 0;
    }

    /* A button in a wrapped row should shrink its label, not push the row. */
    .q-btn {
        max-width: 100%;
    }

    /* Multi-column grids collapse rather than forcing a minimum track wider
       than the screen — minmax(320px, 1fr) on a 360px phone leaves no room for
       the page's own padding. */
    .report-columns,
    .form-row,
    .settings-grid {
        grid-template-columns: 1fr !important;
    }
}

/* ---- Vertical density (user direction 2026-09-18) --------------------------------------------
   "this project is data driven, and therefore all forms and pages have to be compacted so user
   does not have to scroll infinitely."

   Reported against the School Day page, but the shapes named — an oversized page-header block, card
   headers with a large icon tile, one control per row — are in these shared stylesheets and are
   therefore on EVERY page. So this is one scale change, not a patch to one screen, which is the
   same reasoning the 2026-09-17 size scale followed.

   What a page cost before, measured from the CSS rather than guessed: the page header alone spent
   109px (h1 35 + its 8px margin + a 21px subtitle + 20px padding + 24px margin + a border) before
   any content, and each card header spent 64px (20px padding top and bottom around a 24px row) to
   say one word. A screen with two cards and six fields spent roughly 690px before showing data.

   These are TOKENS and shared rules on purpose. A page must still not set its own title size,
   control height or row gap — that rule is what stopped the drift these numbers came from. */

:root {
    /* The vertical rhythm every page block is built from. */
    --qm-page-header-gap: 6px;      /* below the title, above the rule. Was 8, 12, then 44. */
    --qm-block-gap: 10px;           /* between cards and sections; was 12, 16, then 24. */
    --qm-field-gap: 12px;

    /* An empty state: one line of news, not a poster (2026-09-19). Both empty-state families read
       these, and no page sets its own. */
    --qm-empty-pad: 18px 16px;
    --qm-empty-icon: 28px;           /* between form fields; was 14, and 20 before that. */

    /* =========================================================================================
       ONE TYPE SCALE (2026-09-19). Six steps and a figure size — that is all of them.

       Measured before it existed, with scripts/e2e/browser/type-audit.mjs: EIGHTEEN distinct
       sizes were on screen across fifteen pages — 10, 11, 11.5, 12, 12.5, 12.8, 13, 13.5, 13.6,
       14, 15, 15.2, 16, 18, 20, 22, 24, 28 — most of them accidents of a rem value against a
       16px root (0.8rem = 12.8px, 0.85rem = 13.6px, 0.95rem = 15.2px). Body text was 14px and
       read as heavy on a data screen; it is 13px now, the same size as the text in a control, so
       a table cell and the input above it finally agree.

       CHANGE THE TOKEN, NEVER A PAGE. A raw px font-size in a page rule is the drift coming back,
       and type-audit.mjs fails when the count of distinct sizes climbs.

       The excluded set keeps its own sizes and is deliberately outside this: the kiosk, the public
       display and signage, public feedback, booking, ticket status, shared documents, the print
       sheets and the sign-in pages are read across a room or off paper.
       ========================================================================================= */
    --qm-text-xs:   11px;   /* chip, stat label, table meta, timestamp */
    --qm-text-sm:   12px;   /* secondary text, hints, table cells */
    --qm-text-base: 13px;   /* body, controls, almost everything */
    --qm-text-md:   15px;   /* card title, h3, h4 */
    --qm-text-lg:   17px;   /* h2, section heading */
    --qm-text-xl:   20px;   /* h1, the page title */
    --qm-text-stat: 20px;   /* a figure on a stat tile — the one place a number outranks its label */
}

/* A page is the frame the content sits in and has no padding of its own — the gutter belongs to
   .qm-main (--qm-page-pad). Seventeen components wrote `\.admin-page { padding: 0 }` into their
   own <style> to say this, and two more wrote `padding: 24px; max-width: 900px; margin: 0 auto`,
   which (a <style> block being global, not scoped) capped whatever hub happened to be hosting
   them — 312px of a 1212px page unused on Appearance. Declared once, here. */
.admin-page {
    padding: 0;
}

.form-group {
    margin-bottom: var(--qm-field-gap);
}

/* THE MISSING RULE, added 2026-09-18, and the single biggest cause of the report that started this:
   "all forms and pages have to be compacted so user does not have to scroll infinitely".

   .form-row is used 59 times across 23 admin pages and had NO base rule anywhere — only
   `.login-container .form-row`, scoped to the login page. So on every admin page it was an
   unstyled div and its children stacked full-width, one control per row, which is exactly what the
   School Day screenshot showed. The mobile block above has been collapsing `.form-row` to
   `grid-template-columns: 1fr` this whole time, overriding a property nothing ever set: proof the
   grid was intended and simply lost.

   auto-fit with a 240px floor means two controls sit side by side on a desktop and stack on a
   phone without needing a breakpoint of their own, and a row holding one control still fills the
   width as it did before. */
.form-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--qm-field-gap) 16px;
    align-items: start;
}

.form-row .form-group { margin-bottom: 0; }

/* A row is a field like any other: what follows it keeps the field gap (it touched the next row, fieldset or label). */
.form-row:not(:last-child) { margin-bottom: var(--qm-field-gap); }

/* A field that genuinely needs the whole width inside a row says so. */
.form-row .form-group--wide,
.form-row .form-row__wide { grid-column: 1 / -1; }

/* Mandatory-field indicators (2026-09-19). A form with required fields opens with one line saying what
   the asterisk means; each required label carries the same asterisk (QInput/QSelect Required="true",
   .q-input__required). A form that refused to submit says so once, under its fields, in .form-error —
   the per-field messages say what is wrong with each. */
.form-required-note {
    margin: 0 0 var(--qm-field-gap);
    font-size: var(--qm-text-xs);
    color: var(--qm-text-muted);
}

.form-error {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: var(--qm-field-gap) 0 0;
    font-size: var(--qm-text-sm);
    color: var(--qm-danger);
}

/* Radzen's data grid carries its own 14px (0.875rem), set through its own custom properties on a
   selector of higher specificity than anything in this file (`.rz-datatable-data td .rz-cell-data`),
   so a plain font-size rule here lost — measured, not assumed. It was the last off-scale size on a
   data page after the 2026-09-19 type sweep. Pointing Radzen's VARIABLES at the scale wins without a
   specificity fight, and keeps working if Radzen reshuffles its selectors. */
.qm-main {
    --rz-grid-cell-font-size: var(--qm-text-base);
    --rz-grid-header-font-size: var(--qm-text-sm);
    --rz-grid-filter-font-size: var(--qm-text-base);
    --rz-pager-summary-font-size: var(--qm-text-sm);
    --rz-body-font-size: var(--qm-text-base);
    /* Chart text too (2026-09-24): the legend and the axis ticks carried Radzen's own 14px — found by
       type-sweep-all on the feedback and staff-records charts. */
    --rz-chart-legend-font-size: var(--qm-text-sm);
    --rz-chart-axis-font-size: var(--qm-text-sm);
    --rz-chart-tooltip-font-size: var(--qm-text-sm);
}

/* ==========================================================================
   MOBILE NAVIGATION BAR (2026-09-22) — the phone-only bottom bar.

   Under 768px every destination used to be behind the hamburger, and hiding the
   main navigation cuts discoverability almost in half (NN/g). Four role-chosen
   slots plus More, which is inside Material 3's 3–5 and keeps Apple's warning
   about over-using a More tab intact: More is overflow, never a daily task.

   THE BAND IS RESERVED, NOT OVERLAID. .qm-main gets the bar's height plus the
   phone's home-indicator inset as bottom padding, so nothing at the end of a
   page sits under it. Before this, six pages placed real controls in the bottom
   64px — dashboard tiles, roster and staff row actions, timetable cells, library
   card buttons — and a bar laid over them would have covered every one.
   ========================================================================== */
:root {
    --qm-mobilenav-h: 56px;
    /* env() resolves to 0 everywhere but a notched phone, so this costs nothing
       on a desktop and is the only thing standing between the bar and the home
       indicator on an iPhone. Nothing in this app reserved it before. */
    --qm-mobilenav-safe: env(safe-area-inset-bottom, 0px);
    --qm-mobilenav-total: calc(var(--qm-mobilenav-h) + var(--qm-mobilenav-safe));
}

@media (max-width: 768px) {
    .qm-main { padding-bottom: var(--qm-mobilenav-total); }
}

.qm-mobilenav {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    /* Over the page, under the header (1030), the drawer's backdrop (1035) and the drawer
       (1040): the bar must never sit above an open menu, and a press on it while the drawer is
       open lands on the backdrop and closes the drawer. */
    z-index: 1025;
    display: none;
    align-items: stretch;
    height: var(--qm-mobilenav-total);
    padding-bottom: var(--qm-mobilenav-safe);
    background: var(--qm-bg-card);
    border-top: 1px solid var(--qm-border);
    box-shadow: 0 -1px 8px rgba(0, 0, 0, .06);
}

@media (max-width: 768px) {
    .qm-mobilenav { display: flex; }
}

.qm-mobilenav__slot {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    /* 48px clears WCAG 2.5.5 (44px, AAA) and this project's own 40px phone floor.
       The bar is the one place a tap must never miss. */
    min-height: 48px;
    padding: 4px 2px;
    border: 0;
    background: none;
    font: inherit;
    color: var(--qm-text-muted);
    text-decoration: none;
    cursor: pointer;
    position: relative;
}

.qm-mobilenav__slot:hover { color: var(--qm-text-secondary); }

/* Tokens, never literals: a white-labelled tenant's bar is theirs. */
.qm-mobilenav__slot--active { color: var(--qm-primary); }

.qm-mobilenav__slot:focus-visible {
    outline: 2px solid var(--qm-primary);
    outline-offset: -3px;
}

.qm-mobilenav__icon { font-size: 19px; line-height: 1; }

/* LABELS ARE ALWAYS ON. Material 3 permits label-on-active-only; NN/g's
   discoverability finding argues against it — icon-only navigation is the
   hamburger problem at a smaller scale. */
.qm-mobilenav__label {
    font-size: var(--qm-text-xs);
    line-height: 1.1;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A slot's count (QCount Float Small) sits off the icon's top-right shoulder, not in the slot's
   far corner: the slot is a fifth of the screen wide and a pill at its edge belongs to nothing. */
.qm-mobilenav__slot .q-count--float {
    right: auto;
    left: 50%;
    margin-left: 4px;
}

/* ---- The More sheet ------------------------------------------------------ */
.qm-moresheet__backdrop {
    position: fixed;
    inset: 0;
    z-index: 1026;
    background: rgba(0, 0, 0, .45);
}

.qm-moresheet {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1027;
    max-height: 78vh;
    overflow-y: auto;
    padding: 8px 0 calc(12px + var(--qm-mobilenav-safe));
    background: var(--qm-bg-card);
    border-radius: var(--qm-radius-lg) var(--qm-radius-lg) 0 0;
    box-shadow: 0 -6px 24px rgba(0, 0, 0, .18);
}

.qm-moresheet__grip {
    width: 36px;
    height: 4px;
    margin: 4px auto 10px;
    border-radius: 999px;
    background: var(--qm-border);
}

.qm-moresheet__group {
    padding: 10px 16px 4px;
    font-size: var(--qm-text-xs);
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--qm-text-muted);
}

.qm-moresheet__item {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    min-height: 44px;
    padding: 10px 16px;
    border: 0;
    background: none;
    font: inherit;
    font-size: var(--qm-text-base);
    color: var(--qm-text-primary);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

.qm-moresheet__item:hover { background: var(--qm-bg-card-hover); }
.qm-moresheet__item i { color: var(--qm-text-muted); width: 18px; text-align: center; }
.qm-moresheet__item--active { color: var(--qm-primary); }
.qm-moresheet__item--active i { color: var(--qm-primary); }

/* Anything fixed to the foot of the screen has to clear the bar. (The date picker no longer docks
   there: q-select.js places it at its field, above the bar and clear of a dialog's footer — 2026-10-05.) */
@media (max-width: 768px) {
    /* The update banner is QUpdateBanner now (PLAN §23 D1) and clears the phone bar itself. */
}
