/*
 * Project overrides layered on top of AdminLTE 4.
 * Keep AdminLTE's own dist CSS untouched so `libman restore` stays safe.
 */

/* Theme ------------------------------------------------------------------ */

/* The sidebar follows the page theme rather than being pinned dark. In dark mode
   bg-body-secondary sits too close to the content background, so the rail needs an
   edge of its own to stay a distinct surface. */
[data-bs-theme="dark"] .app-sidebar {
    border-inline-end: 1px solid var(--bs-border-color);
}

/* .text-bg-light hard-codes near-white with black text in BOTH themes — Bootstrap
   does not remap --bs-light-rgb for dark — so the badges use body tokens instead.
   This rule is the safety net for any that are added later without noticing. */
[data-bs-theme="dark"] .badge.text-bg-light {
    background-color: var(--bs-tertiary-bg) !important;
    color: var(--bs-body-color) !important;
}

/* Palette ---------------------------------------------------------------------
 *
 * The school's brand colour arrives as the --ism-* custom properties, emitted per
 * request by _ThemePalette. Everything below is static: it only says which parts of
 * Bootstrap read them, so it stays cacheable and the inline block stays tiny.
 *
 * Why each of these is needed at all: Bootstrap 5.3 compiles $primary into most
 * component variables as a literal hex. Setting --bs-primary alone recolours
 * .text-primary and .bg-primary and nothing else — buttons, checkboxes, focus rings
 * and pagination would all stay blue. So each component's own variables are set here.
 *
 * Deliberately untouched: page and card backgrounds, body text, borders, and
 * success/danger/warning/info. A red delete button is a safety feature, and letting a
 * brand colour near the surfaces is how an unreadable interface gets shipped.
 */

:root {
    --bs-primary: var(--ism-brand);
    --bs-primary-rgb: var(--ism-brand-rgb);
    --bs-primary-text-emphasis: var(--ism-brand-emphasis);
    --bs-primary-bg-subtle: var(--ism-brand-subtle);
    --bs-primary-border-subtle: var(--ism-brand-border);

    --bs-link-color: var(--ism-brand);
    --bs-link-color-rgb: var(--ism-brand-rgb);
    --bs-link-hover-color: var(--ism-brand-hover);
    --bs-link-hover-color-rgb: var(--ism-brand-rgb);

    --bs-focus-ring-color: var(--ism-focus-ring);
}

.btn-primary {
    --bs-btn-bg: var(--ism-brand);
    --bs-btn-border-color: var(--ism-brand);
    --bs-btn-color: var(--ism-on-brand);
    --bs-btn-hover-bg: var(--ism-brand-hover);
    --bs-btn-hover-border-color: var(--ism-brand-hover);
    --bs-btn-hover-color: var(--ism-on-brand);
    --bs-btn-active-bg: var(--ism-brand-active);
    --bs-btn-active-border-color: var(--ism-brand-active);
    --bs-btn-active-color: var(--ism-on-brand);
    --bs-btn-disabled-bg: var(--ism-brand);
    --bs-btn-disabled-border-color: var(--ism-brand);
    --bs-btn-disabled-color: var(--ism-on-brand);
    --bs-btn-focus-shadow-rgb: var(--ism-brand-rgb);
}

.btn-outline-primary {
    --bs-btn-color: var(--ism-brand);
    --bs-btn-border-color: var(--ism-brand);
    --bs-btn-hover-bg: var(--ism-brand);
    --bs-btn-hover-border-color: var(--ism-brand);
    --bs-btn-hover-color: var(--ism-on-brand);
    --bs-btn-active-bg: var(--ism-brand);
    --bs-btn-active-border-color: var(--ism-brand);
    --bs-btn-active-color: var(--ism-on-brand);
    --bs-btn-disabled-color: var(--ism-brand);
    --bs-btn-disabled-border-color: var(--ism-brand);
    --bs-btn-focus-shadow-rgb: var(--ism-brand-rgb);
}

/* Bootstrap hard-codes color:#fff here, which is wrong for a pale brand — Slate in
   dark mode wants black on it. The measured choice comes in as --ism-on-brand. */
.text-bg-primary {
    background-color: var(--ism-brand) !important;
    color: var(--ism-on-brand) !important;
}

/* The switch knob stays white against a coloured track; only the track is branded. */
.form-check-input:checked {
    background-color: var(--ism-brand);
    border-color: var(--ism-brand);
}

.form-check-input:focus {
    border-color: var(--ism-brand-border);
    box-shadow: 0 0 0 .25rem var(--ism-focus-ring);
}

.form-control:focus,
.form-select:focus {
    border-color: var(--ism-brand-border);
    box-shadow: 0 0 0 .25rem var(--ism-focus-ring);
}

.pagination {
    --bs-pagination-color: var(--ism-brand);
    --bs-pagination-hover-color: var(--ism-brand-hover);
    --bs-pagination-focus-color: var(--ism-brand-hover);
    --bs-pagination-focus-box-shadow: 0 0 0 .25rem var(--ism-focus-ring);
    --bs-pagination-active-bg: var(--ism-brand);
    --bs-pagination-active-border-color: var(--ism-brand);
    --bs-pagination-active-color: var(--ism-on-brand);
}

.dropdown-menu {
    --bs-dropdown-link-active-bg: var(--ism-brand);
    --bs-dropdown-link-active-color: var(--ism-on-brand);
}

.list-group {
    --bs-list-group-active-bg: var(--ism-brand);
    --bs-list-group-active-border-color: var(--ism-brand);
    --bs-list-group-active-color: var(--ism-on-brand);
}

.progress {
    --bs-progress-bar-bg: var(--ism-brand);
}

.nav-pills {
    --bs-nav-pills-link-active-bg: var(--ism-brand);
    --bs-nav-pills-link-active-color: var(--ism-on-brand);
}

.accordion {
    --bs-accordion-active-bg: var(--ism-brand-subtle);
    --bs-accordion-active-color: var(--ism-brand-emphasis);
    --bs-accordion-btn-focus-box-shadow: 0 0 0 .25rem var(--ism-focus-ring);
}

.nav-tabs {
    --bs-nav-tabs-link-active-color: var(--ism-brand-emphasis);
}

/* The selected rail item wears the brand. All three selectors are listed because
   AdminLTE declares these at BOTH `.app-sidebar` and
   `[data-bs-theme=dark] .app-sidebar` — matching only the first would lose to the
   second in dark mode, and the sidebar would silently stay grey. */
.app-sidebar,
[data-bs-theme="dark"] .app-sidebar,
[data-bs-theme="dark"].app-sidebar {
    --lte-sidebar-menu-active-bg: var(--ism-brand);
    --lte-sidebar-menu-active-color: var(--ism-on-brand);
    --lte-sidebar-submenu-active-bg: var(--ism-brand);
    --lte-sidebar-submenu-active-color: var(--ism-on-brand);
}

/* Appearance menu swatches. The colour itself is set inline per option, because it
   is data — the size and shape belong here. */
.palette-swatch {
    width: 1rem;
    height: 1rem;
    border-radius: 50%;
    border: 1px solid rgba(var(--bs-emphasis-color-rgb), .25);
    display: inline-block;
    flex: 0 0 auto;
}

/* Brand ------------------------------------------------------------------ */
.app-sidebar .brand-link {
    display: flex;
    align-items: center;
}

.app-sidebar .brand-text {
    font-size: 1.05rem;
    letter-spacing: .02em;
}

/* Content ---------------------------------------------------------------- */
.app-content-header h3 {
    font-weight: 600;
    letter-spacing: -.01em;
}

/* Cards ------------------------------------------------------------------ */

/* AdminLTE floats .card-title so .card-tools can sit on its line. A subtitle
   written as the next block therefore flows UP beside the title instead of under
   it — "Needs attentionLearners the grades and registers say…" — everywhere the
   two are paired. Clearing the float puts the subtitle on its own line and leaves
   the tools where they were. */
.card-header .card-subtitle {
    clear: left;
    padding-top: .3rem;
}

.card-header .card-title {
    font-weight: 500;
}

/* Dashboard -------------------------------------------------------------- */

/* Every section is one block with the same breathing room, and its heading is
   the uppercase label the setup screens use. The cards inside a section keep the
   grid's gutter; the sections keep this. */
.dash-section {
    margin-bottom: 2rem;
}

.dash-section:last-child {
    margin-bottom: 0;
}

.dash-section-heading {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: .5rem 1rem;
    margin-bottom: .75rem;
}

.dash-section-heading .section-heading {
    margin-bottom: 0;
}

.dash-section-heading .dash-section-note {
    font-size: .85rem;
    color: var(--bs-secondary-color);
}

/* The jumbotron: the period on the first line of the card, the groups as tabs under
   it, and one pane. dashboard.js sizes the pane to the window on a laptop and wider
   and sets .dash-fit on <html>; everything below that only applies then. */
.dash-hero-header {
    padding-bottom: 0;
    background: linear-gradient(135deg, rgba(var(--ism-brand-rgb, var(--bs-primary-rgb)), .09), transparent 60%);
}

.dash-hero-period {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .4rem 1.25rem;
    margin-bottom: .75rem;
}

.dash-period-label {
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--bs-secondary-color);
    font-weight: 600;
}

.dash-tabs .nav-link {
    font-weight: 500;
    color: var(--bs-secondary-color);
}

.dash-tabs .nav-link.active {
    color: var(--ism-brand-emphasis, var(--bs-primary-text-emphasis));
}

.dash-panes {
    background: var(--bs-tertiary-bg);
}

/* A stack of rows: the tile strip at its own height, every other row sharing what
   is left. Natural height (and page scroll) until .dash-fit says otherwise. */
.dash-stack {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.dash-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
    gap: .75rem;
}

.dash-tile .card-body {
    padding: .7rem .9rem;
}

.dash-stack .card {
    height: 100%;
}

.dash-fit .dash-panes {
    overflow: hidden;
}

/* Every figure on one line at laptop width: a second row of tiles is height the
   charts below it do not get. */
@media (min-width: 992px) {
    .dash-fit .dash-tiles {
        grid-auto-flow: column;
        grid-auto-columns: minmax(0, 1fr);
        grid-template-columns: none;
    }

    .dash-fit .dash-tile .stat-tile-icon {
        width: 2.4rem;
        height: 2.4rem;
        font-size: 1.1rem;
    }

    .dash-fit .dash-tile .stat-tile-label {
        line-height: 1.2;
    }
}

/* Slimmer card chrome inside the fitted pane: the headers were a fifth of it. */
.dash-fit .dash-stack .card > .card-header {
    padding-top: .55rem;
    padding-bottom: .55rem;
}

.dash-fit .dash-stack .card > .card-footer {
    padding-top: .4rem;
    padding-bottom: .4rem;
}

.dash-fit .dash-stack .card-title {
    font-size: 1rem;
}

.dash-fit .dash-panes > .tab-pane.active,
.dash-fit .dash-stack {
    height: 100%;
}

.dash-fit .dash-fill-row {
    flex: 1 1 0;
    min-height: 0;
}

.dash-fit .dash-fill-row > [class*="col"] {
    height: 100%;
}

.dash-fit .dash-stack .card {
    display: flex;
    flex-direction: column;
    margin-bottom: 0;
}

.dash-fit .dash-stack .card > .card-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    display: flex;
    flex-direction: column;
}

/* Charts take what their card has left; a fixed one (a one-bar breakdown) keeps the
   height it asked for. The inline height is the natural-height layout's. */
.dash-fit .dash-stack .chart-box:not(.chart-fixed) {
    flex: 1 1 0;
    min-height: 110px;
    height: auto !important;
}

.dash-fit .dash-stack .chart-box canvas {
    max-height: none;
}

/* My classes and My department are lists, not charts: they scroll inside the pane. */
.dash-fit .dash-scroll {
    height: 100%;
    overflow: auto;
    padding-right: .25rem;
}

/* A stat tile: one figure, its name, and where it comes from. */
.stat-tile {
    display: flex;
    align-items: center;
    gap: .9rem;
    height: 100%;
}

.stat-tile-icon {
    width: 2.75rem;
    height: 2.75rem;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--bs-border-radius);
    font-size: 1.25rem;
}

.stat-tile-value {
    font-size: 1.6rem;
    font-weight: 600;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.stat-tile-label {
    font-size: .85rem;
    color: var(--bs-secondary-color);
}

/* Inline figures inside a card: a row of small labelled numbers. */
.figure-row {
    display: flex;
    flex-wrap: wrap;
    gap: .75rem 1.75rem;
}

.figure-row .figure-value {
    font-size: 1.35rem;
    font-weight: 600;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.figure-row .figure-label {
    font-size: .78rem;
    color: var(--bs-secondary-color);
    text-transform: uppercase;
    letter-spacing: .04em;
}

/* Charts. The canvas fills the box; the box sets the height so a chart cannot
   grow the card while it lays itself out. Colours are read off the theme tokens
   by charts.js — nothing here or there names a hex for a series. The accent is the
   one fixed second hue, chosen to stay apart from every palette's brand and from
   the status colours, in both modes; the muted and faint steps are what "not yet"
   is drawn in. */
:root {
    --ism-chart-accent: #e8590c;
    --ism-chart-muted: #adb5bd;
    --ism-chart-faint: #dee2e6;
}

[data-bs-theme="dark"] {
    --ism-chart-accent: #ff922b;
    --ism-chart-muted: #6c757d;
    --ism-chart-faint: #495057;
}

.chart-box {
    position: relative;
    width: 100%;
}

.chart-box canvas {
    width: 100% !important;
}

.chart-legend {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem 1rem;
    font-size: .8rem;
    color: var(--bs-secondary-color);
    margin-top: .5rem;
}

.chart-legend .chart-swatch {
    display: inline-block;
    width: .7rem;
    height: .7rem;
    border-radius: .15rem;
    margin-right: .35rem;
    vertical-align: -.05em;
}

@media print {
    .chart-box canvas {
        max-height: 16rem;
    }
}

/* Tables ----------------------------------------------------------------- */
.table-compact > :not(caption) > * > * {
    padding-top: .45rem;
    padding-bottom: .45rem;
}

.table thead th {
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--bs-secondary-color);
    font-weight: 600;
}

/* A table sitting flush in a card has no card body to indent it, so its leading
   and trailing cells take the card's own gutter. Without this the first column
   starts short of the heading and the filter row above it. */
.table-flush > :not(caption) > * > *:first-child {
    padding-left: var(--bs-card-spacer-x, 1rem);
}

.table-flush > :not(caption) > * > *:last-child {
    padding-right: var(--bs-card-spacer-x, 1rem);
}

/* Sortable headers (<th sort="…">) and the column-filter row under them. The filter
   row's cells hold inputs, not labels, so they drop the header's small caps. */
.table thead th.sortable a:hover {
    color: var(--bs-emphasis-color) !important;
}

.table thead tr.column-filters th {
    text-transform: none;
    letter-spacing: normal;
    font-weight: 400;
    padding-top: 0;
    min-width: 7rem;
}

.table thead tr.column-filters .form-control,
.table thead tr.column-filters .form-select {
    font-size: .8rem;
}

/* A live list (live-list.js) waiting on its answer. Dimmed rather than blanked: the
   rows on screen are still true of the filter that produced them. */
[data-live-region] {
    transition: opacity .15s ease-in-out;
}

[data-live-region].live-loading {
    opacity: .55;
}

@media (prefers-reduced-motion: reduce) {
    [data-live-region] {
        transition: none;
    }
}

/* Lists as cards on a phone (.table-cards, labelled by live-list.js) ------------
   `screen` and not just a width: a sheet of A4 is ~680px inside its margins, under
   this breakpoint too, and a printed register must stay a table. The header row goes
   (the search box above still filters everything); each row becomes a card, and each
   cell says which column it came from. A cell under an empty header — the avatar, the
   actions — carries no label and sits on its own line. */
@media screen and (max-width: 767.98px) {
    .table-cards thead {
        display: none;
    }

    .table-cards,
    .table-cards tbody,
    .table-cards tr,
    .table-cards td {
        display: block;
        width: 100%;
    }

    .table-cards tbody tr {
        margin: .75rem;
        width: auto;
        padding: .5rem .85rem;
        border: 1px solid var(--bs-border-color);
        border-radius: var(--bs-border-radius-lg);
        background: var(--bs-body-bg);
    }

    .table-cards > :not(caption) > * > * {
        border-bottom-width: 0;
        box-shadow: none;
    }

    .table-cards.table-flush > :not(caption) > * > *:first-child,
    .table-cards.table-flush > :not(caption) > * > *:last-child,
    .table-cards td {
        padding: .3rem 0 !important;
    }

    .table-cards td[data-label]:not([data-label=""]) {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        gap: 1rem;
        text-align: right;
    }

    .table-cards td[data-label]:not([data-label=""])::before {
        content: attr(data-label);
        flex-shrink: 0;
        text-align: left;
        font-size: .72rem;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: .04em;
        color: var(--bs-secondary-color);
    }

    /* The row's own name is the card's heading. The avatar beside it on a desktop
       row is decoration a phone has no width for. */
    .table-cards td[data-card-title] {
        display: block !important;
        text-align: left !important;
        font-size: 1rem;
        padding-bottom: .45rem !important;
        margin-bottom: .2rem;
        border-bottom: 1px solid var(--bs-border-color-translucent);
    }

    .table-cards td[data-card-title]::before {
        display: none !important;
    }

    .table-cards td[data-label=""]:has(> .person-avatar) {
        display: none;
    }

    /* The row's actions: a line of their own, to the right. */
    .table-cards td.text-end[data-label=""],
    .table-cards td.text-end[data-label="Actions"] {
        display: flex;
        justify-content: flex-end;
        gap: .25rem;
        border-top: 1px solid var(--bs-border-color-translucent);
        margin-top: .35rem;
        padding-top: .5rem !important;
    }

    .table-cards td.text-end[data-label="Actions"]::before {
        display: none;
    }

    .table-cards td.empty-state {
        text-align: center;
    }
}

/* Dialogs (SweetAlert2, driven from site.js: ismConfirm / ismAlert) ------------
   SweetAlert draws its own light-grey box. These rules put it on the page's own
   tokens instead, so a confirmation is the school's palette in both modes and the
   buttons are the Bootstrap buttons everything else is. No colour is written here. */
.swal2-container {
    z-index: 2000;
}

.swal2-container.swal2-backdrop-show {
    background: rgba(var(--bs-dark-rgb), .45);
}

.swal2-popup.ism-dialog {
    background: var(--bs-body-bg);
    color: var(--bs-body-color);
    border: 1px solid var(--bs-border-color-translucent);
    border-radius: var(--bs-border-radius-lg);
    box-shadow: var(--bs-box-shadow-lg);
    width: min(28rem, calc(100vw - 2rem));
    padding: 1.5rem 1.5rem 1.25rem;
    font-family: inherit;
}

.swal2-popup.ism-dialog .ism-dialog-title {
    color: var(--bs-emphasis-color);
    font-size: 1.15rem;
    font-weight: 600;
    line-height: 1.35;
    padding: .5rem 0 0;
}

.swal2-popup.ism-dialog .ism-dialog-text {
    color: var(--bs-secondary-color);
    font-size: .95rem;
    margin: .5rem 0 0;
    padding: 0;
}

.swal2-popup.ism-dialog .ism-dialog-actions {
    gap: .5rem;
    margin-top: 1.25rem;
    width: 100%;
    justify-content: flex-end;
}

.swal2-popup.ism-dialog .ism-dialog-actions .btn {
    margin: 0;
    min-width: 6rem;
}

.swal2-popup.ism-dialog .ism-dialog-icon {
    transform: scale(.72);
    margin: 0 auto -.5rem;
}

.swal2-icon.swal2-warning {
    border-color: var(--bs-warning);
    color: var(--bs-warning);
}

.swal2-icon.swal2-question,
.swal2-icon.swal2-info {
    border-color: var(--ism-brand, var(--bs-primary));
    color: var(--ism-brand, var(--bs-primary));
}

.swal2-icon.swal2-error {
    border-color: var(--bs-danger);
    color: var(--bs-danger);
}

.swal2-icon.swal2-error [class^='swal2-x-mark-line'] {
    background-color: var(--bs-danger);
}

@keyframes ism-dialog-in {
    from { opacity: 0; transform: translateY(.5rem) scale(.98); }
    to { opacity: 1; transform: none; }
}

@keyframes ism-dialog-out {
    from { opacity: 1; }
    to { opacity: 0; transform: scale(.98); }
}

.ism-dialog-in {
    animation: ism-dialog-in .16s ease-out;
}

.ism-dialog-out {
    animation: ism-dialog-out .12s ease-in forwards;
}

@media (prefers-reduced-motion: reduce) {
    .ism-dialog-in,
    .ism-dialog-out {
        animation: none;
    }
}

/* Searchable pickers (Tom Select, <select data-searchable>) ---------------
   Its Bootstrap 5 theme is compiled with light-mode colours; these put it on the
   page's tokens so it matches a plain .form-select in both modes. */
.ts-wrapper .ts-control,
.ts-wrapper.single.input-active .ts-control {
    background-color: var(--bs-body-bg);
    color: var(--bs-body-color);
    border-color: var(--bs-border-color);
}

.ts-wrapper.focus .ts-control {
    border-color: var(--ism-brand-border, var(--bs-primary));
    box-shadow: 0 0 0 .25rem var(--ism-focus-ring, rgba(var(--bs-primary-rgb), .25));
}

.ts-wrapper.is-invalid .ts-control {
    border-color: var(--bs-danger);
}

.ts-wrapper .ts-control input {
    color: var(--bs-body-color);
}

.ts-dropdown {
    background: var(--bs-body-bg);
    color: var(--bs-body-color);
    border-color: var(--bs-border-color);
    box-shadow: var(--bs-box-shadow);
    z-index: 1060;
}

.ts-dropdown .active {
    background-color: var(--ism-brand-subtle, var(--bs-primary-bg-subtle));
    color: var(--bs-emphasis-color);
}

.ts-dropdown .optgroup-header {
    background: var(--bs-tertiary-bg);
    color: var(--bs-secondary-color);
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .04em;
}

/* Problem reports: a log excerpt, readable and contained. */
.support-log {
    max-height: 18rem;
    overflow: auto;
    padding: .6rem .75rem;
    margin: 0;
    font-size: .72rem;
    line-height: 1.45;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    background: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    color: var(--bs-body-color);
}

/* Messaging -------------------------------------------------------------- */
/* A row per message; a group is consecutive messages from one person (see
   _MessageList). Groups are spaced apart, lines within one sit close. */
.message-row {
    display: flex;
    align-items: flex-end;
    gap: .5rem;
    margin-top: .2rem;
}

.message-row.group-start {
    margin-top: .9rem;
}

.message-row.mine {
    justify-content: flex-end;
}

.message-stack {
    display: flex;
    flex-direction: column;
    max-width: min(40rem, 80%);
    min-width: 0;
}

.message-row.mine .message-stack {
    align-items: flex-end;
}

.message-avatar {
    flex: 0 0 2rem;
    width: 2rem;
    height: 2rem;
    margin-bottom: 1.35rem; /* level with the last bubble, not with its name line */
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: .72rem;
    font-weight: 600;
    background: var(--bs-secondary-bg);
    color: var(--bs-secondary-color);
}

.message-row:not(.group-end) .message-avatar {
    visibility: hidden;
}

.message-bubble {
    padding: .5rem .8rem;
    border-radius: 1.1rem;
    background: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color-translucent);
    color: var(--bs-body-color);
    min-width: 0;
}

.message-row.mine .message-bubble {
    background: var(--ism-brand-subtle, var(--bs-primary-bg-subtle));
    border-color: var(--ism-brand-border, var(--bs-primary-border-subtle));
}

/* The tail: the corner nearest the speaker squares off on a group's last bubble,
   and the joined corners square off between bubbles of one group. */
.message-row.theirs:not(.group-start) .message-bubble { border-top-left-radius: .35rem; }
.message-row.theirs:not(.group-end) .message-bubble { border-bottom-left-radius: .35rem; }
.message-row.theirs.group-end .message-bubble { border-bottom-left-radius: .35rem; }
.message-row.mine:not(.group-start) .message-bubble { border-top-right-radius: .35rem; }
.message-row.mine:not(.group-end) .message-bubble { border-bottom-right-radius: .35rem; }
.message-row.mine.group-end .message-bubble { border-bottom-right-radius: .35rem; }

.message-body {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.message-meta {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem;
    margin-top: .25rem;
    padding: 0 .35rem;
    font-size: .75rem;
    color: var(--bs-secondary-color);
}

.message-read {
    color: var(--ism-brand-emphasis, var(--bs-primary-text-emphasis));
}

.message-day {
    display: flex;
    align-items: center;
    gap: .75rem;
    margin: .5rem 0;
    color: var(--bs-secondary-color);
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .04em;
}

.message-day::before,
.message-day::after {
    content: "";
    flex: 1;
    border-top: 1px solid var(--bs-border-color);
}

.message-image {
    display: block;
    border-radius: var(--bs-border-radius);
    overflow: hidden;
    border: 1px solid var(--bs-border-color);
    background: var(--bs-tertiary-bg);
}

.message-image img {
    display: block;
    min-width: 4rem;
    min-height: 4rem;
    max-width: 14rem;
    max-height: 11rem;
    object-fit: cover;
}

.message-file {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .4rem .65rem;
    max-width: 18rem;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    background: var(--bs-body-bg);
    color: var(--bs-body-color);
}

.message-file:hover {
    border-color: var(--ism-brand, var(--bs-primary));
}

.attach-chip {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    max-width: 20rem;
    padding: .2rem .35rem .2rem .6rem;
    font-size: .82rem;
    border: 1px solid var(--bs-border-color);
    border-radius: 999px;
    background: var(--bs-tertiary-bg);
}

.attach-chip .btn-close {
    width: .6rem;
    height: .6rem;
    padding: .25rem;
    background-size: .55rem;
}

/* Above the reply box, not below: the reply box sits at the bottom of the page and a
   picker opening downwards would be off screen. */
.emoji-popover {
    position: absolute;
    bottom: calc(100% + .35rem);
    left: 0;
    z-index: 1050;
    box-shadow: var(--bs-box-shadow-lg);
    border-radius: var(--bs-border-radius-lg);
    overflow: hidden;
}

.emoji-popover emoji-picker {
    --border-color: var(--bs-border-color);
    --background: var(--bs-body-bg);
    --input-border-color: var(--bs-border-color);
    --input-font-color: var(--bs-body-color);
    --indicator-color: var(--ism-brand, var(--bs-primary));
    --outline-color: var(--ism-brand, var(--bs-primary));
    width: min(22rem, calc(100vw - 2rem));
    height: 22rem;
}

/* Forms ------------------------------------------------------------------ */
.form-label {
    font-weight: 500;
    font-size: .9rem;
    margin-bottom: .25rem;
}

.field-validation-error {
    color: var(--bs-danger);
    font-size: .85rem;
}

.input-validation-error {
    border-color: var(--bs-danger);
}

/* Required-field marker driven by [Required] in the view models. */
.form-label.required::after {
    content: " *";
    color: var(--bs-danger);
}

/* Empty states ----------------------------------------------------------- */
.empty-state {
    padding: 3rem 1rem;
    text-align: center;
    color: var(--bs-secondary-color);
}

.empty-state .bi {
    font-size: 2.5rem;
    opacity: .35;
    display: block;
    margin-bottom: .75rem;
}

/* Auth screens ----------------------------------------------------------- */
/* A column, not a row. This was a default `flex-direction: row` for as long as the
   card was its only child — which made the second child (the developer's credit) a
   sibling *column* beside the card, vertically centred against it, where no amount of
   `mt-3` could move it. A wrapper that centres one child hides its own direction;
   adding a second is what asks the question. */
.auth-wrapper {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.25rem;
    padding: 1.5rem;
    background: var(--bs-tertiary-bg);
}

/* Both take the card's width so the credit wraps on the same measure rather than
   running the full width of the viewport under a 26rem card. */
.auth-card,
.auth-credit {
    width: 100%;
    max-width: 26rem;
}

/* Not `.card-title`, which AdminLTE floats left — right for a card *header*, where
   the tools sit beside it, and wrong in a card *body*, where it pulled the line
   underneath up alongside and rendered "Sign inUse your school-issued account." The
   214 other uses are all headers, so the fix belongs here rather than on their rule. */
.auth-title {
    font-size: 1.15rem;
    font-weight: 600;
}

/* Branding --------------------------------------------------------------- */
.brand-image-logo {
    max-height: 2rem;
    max-width: 2rem;
    object-fit: contain;
}

.auth-logo {
    max-height: 4.5rem;
    max-width: 100%;
    object-fit: contain;
}

.logo-preview {
    max-height: 5rem;
    max-width: 12rem;
    object-fit: contain;
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    padding: .5rem;
}

/* Setup screens ---------------------------------------------------------- */
.section-heading {
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--bs-secondary-color);
    font-weight: 600;
    margin-bottom: .75rem;
}

/* Curriculum and subject grids read better with a tighter definition list. */
.detail-list dt {
    font-weight: 500;
    color: var(--bs-secondary-color);
}

.detail-list dd {
    margin-bottom: .4rem;
}

/* People ----------------------------------------------------------------- */
.person-avatar {
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    object-fit: cover;
    flex: 0 0 auto;
}

.person-avatar-initials {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--bs-secondary-bg);
    color: var(--bs-secondary-color);
    font-size: .75rem;
    font-weight: 600;
    letter-spacing: .02em;
}

/* Profile header photo. */
.person-photo {
    width: 8rem;
    height: 8rem;
    border-radius: var(--bs-border-radius);
    object-fit: cover;
    border: 1px solid var(--bs-border-color);
}

.person-photo-initials {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 2.5rem;
    background: var(--bs-secondary-bg);
    color: var(--bs-secondary-color);
    font-weight: 600;
}

/* Credentials handed over once, on a coloured strip so they are not missed. */
.credential-callout code {
    font-size: 1rem;
    user-select: all;
}

/* --- Milestone 5: timetables and printed rosters ---------------------- */

/* One column per day, meetings stacked inside it. See _Timetable.cshtml for
   why this is not a proportional time grid. */
.timetable td {
    min-width: 9rem;
}

.timetable-block {
    border-left: 3px solid var(--bs-primary);
    background: var(--bs-tertiary-bg);
    border-radius: var(--bs-border-radius-sm);
    padding: 0.35rem 0.5rem;
    margin-bottom: 0.35rem;
}

.timetable-block:last-child {
    margin-bottom: 0;
}

/* A double-booked meeting has to be obvious at a glance, not on hover. */
.timetable-block-conflict {
    border-left-color: var(--bs-danger);
    background: var(--bs-danger-bg-subtle);
}

/* The class list a teacher carries into the room. The admin chrome is dropped
   so the page prints as a document rather than as a screenshot of one. */
.print-sheet {
    max-width: 60rem;
    margin: 0 auto;
}

.print-header .print-logo {
    height: 4rem;
    object-fit: contain;
}

/* The letterhead a screen-first page only wants once the admin chrome is gone.
   Kept out of the flow rather than merely invisible, so it costs no space. */
.print-only {
    display: none !important;
}

@media print {
    /* The header and footer partials, on the pages that hid them on screen.
       `display: block` rather than the flex the header uses on a print sheet:
       the header's own utility classes put the flex back where it belongs. */
    .print-only {
        display: block !important;
    }

    .print-header.print-only {
        display: flex !important;
    }

    .app-sidebar,
    .app-header,
    .app-footer,
    .app-content-header,
    .no-print {
        display: none !important;
    }

    .app-main,
    .app-wrapper {
        margin: 0 !important;
    }

    /* Paper is white whatever the screen is doing. Forcing the background alone was
       not enough once dark mode existed: the text tokens stayed light, so a roster
       printed from a dark screen came out pale grey on white. Remap the tokens the
       whole stylesheet reads rather than fighting each rule. */
    html {
        color-scheme: light !important;
    }

    html[data-bs-theme="dark"] {
        --bs-body-bg: #fff;
        --bs-body-color: #000;
        --bs-emphasis-color: #000;
        --bs-secondary-color: rgba(0, 0, 0, .75);
        --bs-secondary-bg: #e9ecef;
        --bs-tertiary-color: rgba(0, 0, 0, .5);
        --bs-tertiary-bg: #f8f9fa;
        --bs-border-color: #adb5bd;
        --bs-link-color: #000;
    }

    body {
        background: #fff !important;
        color: #000 !important;
    }

    .card {
        border: 0 !important;
        box-shadow: none !important;
    }

    /* A roster longer than a page must not split a learner across the fold. */
    tr {
        page-break-inside: avoid;
    }

    /* A table that runs past the fold repeats its headings on the next sheet, so
       page two of a roster is not forty names under no columns. */
    thead {
        display: table-header-group;
    }

    tfoot {
        display: table-footer-group;
    }

    /* The letterhead, a signature block and the footer each stay whole. */
    .print-header,
    .signature-row,
    .print-footer {
        page-break-inside: avoid;
    }

    .print-sheet {
        max-width: none;
    }

    /* A student number is text on paper too: the pink Bootstrap gives <code> reads
       as a highlight in print, and a monospace face is all the distinction needed. */
    code {
        color: inherit !important;
    }

    /* Links are text on paper. The underline and the brand colour both go. */
    a,
    a:visited {
        color: inherit !important;
        text-decoration: none !important;
    }

    /* A column of controls — Drop, Restore, Edit — has no meaning on paper. */
    .print-hide-column {
        display: none !important;
    }

    /* Bootstrap's screen-only responsive wrapper would otherwise clip a wide
       table to one page-width and hide the rest of the columns. */
    .table-responsive {
        overflow: visible !important;
    }

    /* A sheet of paper is desktop-wide however many CSS pixels it measures: an A4
       page is ~680px inside its margins, under Bootstrap's md breakpoint, so every
       col-md-* on a sheet collapsed to a phone layout in print. Paper gets the
       desktop grid unconditionally. */
    .col-md-1 { flex: 0 0 auto; width: 8.3333%; }
    .col-md-2 { flex: 0 0 auto; width: 16.6667%; }
    .col-md-3 { flex: 0 0 auto; width: 25%; }
    .col-md-4 { flex: 0 0 auto; width: 33.3333%; }
    .col-md-5 { flex: 0 0 auto; width: 41.6667%; }
    .col-md-6 { flex: 0 0 auto; width: 50%; }
    .col-md-7 { flex: 0 0 auto; width: 58.3333%; }
    .col-md-8 { flex: 0 0 auto; width: 66.6667%; }
    .col-md-9 { flex: 0 0 auto; width: 75%; }
    .col-md-10 { flex: 0 0 auto; width: 83.3333%; }
    .col-md-11 { flex: 0 0 auto; width: 91.6667%; }
    .col-md-12 { flex: 0 0 auto; width: 100%; }
    .col-lg-1 { flex: 0 0 auto; width: 8.3333%; }
    .col-lg-2 { flex: 0 0 auto; width: 16.6667%; }
    .col-lg-3 { flex: 0 0 auto; width: 25%; }
    .col-lg-4 { flex: 0 0 auto; width: 33.3333%; }
    .col-lg-5 { flex: 0 0 auto; width: 41.6667%; }
    .col-lg-6 { flex: 0 0 auto; width: 50%; }
    .col-lg-7 { flex: 0 0 auto; width: 58.3333%; }
    .col-lg-8 { flex: 0 0 auto; width: 66.6667%; }
    .col-lg-9 { flex: 0 0 auto; width: 75%; }
    .col-lg-10 { flex: 0 0 auto; width: 83.3333%; }
    .col-lg-11 { flex: 0 0 auto; width: 91.6667%; }
    .col-lg-12 { flex: 0 0 auto; width: 100%; }

    /* A column that has the page to itself once its working-side sibling is hidden. */
    .print-full {
        flex: 0 0 100% !important;
        width: 100% !important;
    }

    /* The watermark stamped across an unofficial copy: one word, diagonal, faint,
       behind the text. Fixed, so it lands on every page. */
    .print-watermark {
        position: fixed;
        top: 40%;
        left: 0;
        right: 0;
        text-align: center;
        font-size: 4.5rem;
        font-weight: 700;
        letter-spacing: .1em;
        white-space: nowrap;
        color: rgba(0, 0, 0, .08);
        transform: rotate(-30deg);
        pointer-events: none;
        z-index: 0;
    }
}

/* Where one sheet ends and the next begins in a batch — a section's report cards
   in one file, one learner per page. Nothing on screen; a page break on paper. */
.page-break-before {
    break-before: page;
    page-break-before: always;
}

/* Signature lines on an issued document, drawn left to right and kept together
   on paper. The name sits above the rule when the record supplies one, and the
   rule waits for a pen when it does not. */
.signature-row {
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem 2.5rem;
    justify-content: space-between;
}

.signature-line {
    flex: 1 1 12rem;
    max-width: 18rem;
    text-align: center;
    font-size: .85rem;
}

.signature-line .signature-name {
    font-weight: 600;
    border-bottom: 1px solid var(--bs-body-color);
    padding-bottom: .15rem;
    min-height: 1.5rem;
    white-space: nowrap;
}

.signature-line .signature-title {
    color: var(--bs-secondary-color);
    font-size: .78rem;
    padding-top: .15rem;
}

.print-watermark {
    display: none;
}

/* Flash messages. Fixed to the bottom-right so a save never reflows the page.
   Above the AdminLTE sidebar and any modal backdrop, below an open modal. */
.toast-container {
    z-index: 1090;
    max-width: min(24rem, calc(100vw - 2rem));
}

.toast-container .toast {
    width: 100%;
    box-shadow: var(--bs-box-shadow);
}

.toast-container .toast-body {
    word-break: break-word;
}
