:root {
    --sjsd-blue: #00529b;
    --sjsd-blue-dark: #003f78;
    --success: #198754;
    --success-dark: #157347;
    --ink: #1f2933;
    --muted: #6b7280;
    --line: #d8dde3;
    --danger: #b42318;
    --danger-bg: #fef3f2;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    color: var(--ink);
    background: #f7f8fa;
    line-height: 1.5;
}

/* ---- top bar ----
   Three columns: back | brand | actions. The outer columns are equal, so the brand stays optically
   centred whether or not a page has back-navigation — which is what lets the bar carry navigation
   without the title jumping between pages. Empty slots are rendered as placeholders by _Layout. */
.topbar {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 0.5rem;
    background: var(--sjsd-blue);
    padding: 0.5rem 0.75rem;
    min-height: 44px;
}

.topbar .brand {
    grid-column: 2;
    justify-self: center;
    color: #fff;
    text-decoration: none;
    font-weight: 600;
    white-space: nowrap;
}

.tb-slot { display: block; }

.tb-back {
    justify-self: start;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    color: #dbe6f1;
    text-decoration: none;
    font-size: 0.9rem;
    font-weight: 600;
    padding: 0.25rem 0.4rem 0.25rem 0.2rem;
    border-radius: 5px;
}
.tb-back:hover { color: #fff; background: rgba(255, 255, 255, 0.1); }

.tb-actions {
    grid-column: 3;
    justify-self: end;
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    color: #dbe6f1;
    font-size: 0.88rem;
}

.who-name { color: #fff; }

/* Phones: shorten the brand and drop the back label to the chevron, so all three
   slots fit without the bar wrapping. */
@media (max-width: 560px) {
    .brand-full { display: none; }
    .tb-back-text { display: none; }
    .who-name { display: none; }
}

.inline { display: inline; margin: 0; }

.linkbtn {
    background: none;
    border: none;
    color: #cfe0f1;
    cursor: pointer;
    font: inherit;
    text-decoration: underline;
    padding: 0;
}
.linkbtn:hover { color: #fff; }

/* ---- layout ---- */
.container {
    max-width: 960px;
    margin: 1.5rem auto;
    padding: 0 1.25rem;
}

@media (max-width: 560px) {
    .container {
        margin: 1rem auto;
        padding: 0 0.85rem;
    }
}

.muted { color: var(--muted); }

/* ---- auth card ---- */
.auth-card {
    max-width: 380px;
    margin: 3rem auto;
    padding: 2rem;
    background: #fff;
    border: 1px solid var(--line);
    border-radius: 10px;
    box-shadow: 0 1px 3px rgba(16, 24, 40, 0.06);
}

.auth-card h1 {
    margin: 0 0 1.25rem;
    font-size: 1.4rem;
}

.auth-card label {
    display: block;
    margin: 0.85rem 0 0.3rem;
    font-weight: 600;
    font-size: 0.9rem;
}

.auth-card input {
    width: 100%;
    padding: 0.55rem 0.65rem;
    border: 1px solid var(--line);
    border-radius: 6px;
    font-size: 1rem;
}
.auth-card input:focus {
    outline: none;
    border-color: var(--sjsd-blue);
    box-shadow: 0 0 0 3px rgba(0, 82, 155, 0.15);
}

.btn-primary {
    margin-top: 1.25rem;
    width: 100%;
    padding: 0.6rem 1rem;
    background: var(--sjsd-blue);
    color: #fff;
    border: none;
    border-radius: 6px;
    font-size: 1rem;
    font-weight: 600;
    cursor: pointer;
}
.btn-primary:hover { background: var(--sjsd-blue-dark); }

/* Sending state. A phone photo over school wi-fi takes seconds, and the browser's hairline progress bar
   is easy to miss on a small screen — long enough that a parent reasonably concludes nothing happened
   and presses Send again. The button says so itself instead. */
.btn-primary[aria-busy="true"],
.btn-primary[aria-busy="true"]:hover {
    background: var(--sjsd-blue);
    opacity: 0.7;
    cursor: default;
}
.btn-primary[aria-busy="true"]::before {
    content: "";
    display: inline-block;
    width: 0.85em;
    height: 0.85em;
    margin-right: 0.5em;
    vertical-align: -0.1em;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    animation: pp-spin 0.7s linear infinite;
}
@keyframes pp-spin { to { transform: rotate(360deg); } }

/* Slowed rather than stopped: a motionless ring says nothing, and the point of this element is to say
   that something is still happening. The "Sending…" text carries the message either way. */
@media (prefers-reduced-motion: reduce) {
    .btn-primary[aria-busy="true"]::before { animation-duration: 2.4s; }
}

.form-error {
    color: var(--danger);
    background: var(--danger-bg);
    border: 1px solid #fda29b;
    border-radius: 6px;
    padding: 0.6rem 0.75rem;
    font-size: 0.9rem;
}
/* The validation-summary tag helper always renders a <ul> skeleton, so :empty never matches it — an
   error-less form would otherwise show an empty red bar. validation-summary-valid is the helper's own
   "nothing to say" class; :empty still covers summaries written by hand. */
.form-error:empty,
.form-error.validation-summary-valid { display: none; }
.form-error ul { margin: 0; padding-left: 1.1rem; }

.staff-link {
    margin-top: 1.5rem;
    padding-top: 1rem;
    border-top: 1px solid var(--line);
    text-align: center;
    font-size: 0.9rem;
}
.staff-link a { color: var(--sjsd-blue); }

/* ---- dashboard ---- */
.dash-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.75rem 1rem;
    justify-content: space-between;
    margin-bottom: 1.25rem;
}
.dash-head h1 { margin: 0; font-size: 1.6rem; }

/* A "Welcome, First Last" heading is easy to wrap to two lines on a phone at full desktop size —
   which reads as shouting, not a greeting. Smaller and still bold keeps it scannable in one line for
   the common case without losing the weight that makes it read as a heading at all. */
@media (max-width: 560px) {
    .dash-head h1 { font-size: 1.2rem; }
}

.yearchip {
    display: inline-block;
    background: #e8f0f8;
    color: var(--sjsd-blue-dark);
    border: 1px solid #cfe0f1;
    border-radius: 999px;
    padding: 0.2rem 0.8rem;
    font-weight: 600;
    font-size: 0.9rem;
    white-space: nowrap;
}

.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 1rem;
}

.student-card {
    background: #fff;
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 1.1rem 1.2rem;
    box-shadow: 0 1px 3px rgba(16, 24, 40, 0.05);
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.sc-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.6rem;
}
.sc-name { margin: 0; font-size: 1.15rem; }
.sc-meta { margin: 0; color: var(--ink); font-size: 0.92rem; }
.sc-action { margin-top: auto; padding-top: 0.4rem; font-size: 0.92rem; }

/* Graduated students: a fact, not a task. The card keeps its grid column so graduates stay aligned with
   their siblings, but shrinks to the one line it actually has to say. align-self keeps it from being
   stretched to the height of a full card sharing its row — without it, a lone graduate beside an active
   student would be a tall box with a name floating at the top, which is worse than the card it replaces. */
.student-card.sc-compact {
    align-self: start;
    padding: 0.7rem 0.9rem;
    gap: 0;
}
/* Name and badge sit together rather than being thrown to opposite edges: with no other content to
   balance, space-between just leaves a gap in the middle of an otherwise tiny card. */
.student-card.sc-compact .sc-head {
    align-items: center;
    justify-content: flex-start;
    gap: 0.5rem;
}
.student-card.sc-compact .sc-name { font-size: 1.05rem; }
/* Only present when a graduate has documents already sent. */
.student-card.sc-compact .sc-action { margin-top: 0.45rem; padding-top: 0; }

.badge {
    display: inline-block;
    padding: 0.18rem 0.55rem;
    border-radius: 999px;
    font-size: 0.76rem;
    font-weight: 600;
    white-space: nowrap;
    line-height: 1.4;
}
.badge-ok    { background: #e7f6ec; color: #137333; }
.badge-warn  { background: #fef3e2; color: #b25e09; }
.badge-info  { background: #e8f0f8; color: #1a4f87; }
.badge-grad  { background: #efe9fb; color: #5b3aa8; }
.badge-muted { background: #f0f1f3; color: #5f6b7a; }

.btn-outline {
    display: inline-block;
    padding: 0.45rem 0.9rem;
    border: 1px solid var(--sjsd-blue);
    color: var(--sjsd-blue);
    background: #fff;
    border-radius: 6px;
    font-weight: 600;
    text-decoration: none;
}
.btn-outline:hover { background: #f2f7fc; }

.btn-primary.inline-btn,
.sc-action .btn-primary {
    display: inline-block;
    width: auto;
    margin-top: 0;
    text-decoration: none;
    padding: 0.45rem 0.9rem;
}

/* Registering is the card's headline action, so it gets its own color (green reads as "go", distinct
   from the blue used for sending a document) and sits centered rather than pinned to the left edge —
   the one button on the card worth making the eye stop on. */
.sc-action .btn-register {
    display: block;
    width: fit-content;
    margin: 0 auto;
    background: var(--success);
}
.sc-action .btn-register:hover { background: var(--success-dark); }

/* The outline Aspire buttons — "Finish registration in Aspire", "View in Aspire" — center on the same
   axis as .btn-register above. They are the same kind of thing (the card's one trip out to the SIS), and
   left-pinning them meant a card's primary action sat at a different alignment depending on which state
   the student happened to be in. */
.sc-action .btn-outline {
    display: block;
    width: fit-content;
    margin: 0 auto;
}

/* Eased back a hair from the plain .btn-primary size — still a real, easy-to-tap button, just not
   quite as large as the registration CTA it sometimes shares a card with. */
.btn-send {
    padding: 0.5rem 1rem;
    font-size: 0.95rem;
}

.empty-state {
    background: #fff;
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 1.75rem;
    max-width: 560px;
}
.empty-state h2 { margin-top: 0; font-size: 1.25rem; }
.empty-state .inline-btn { margin-top: 0.5rem; }

/* ---- documents ----
   The page is an invitation, not a checklist: one send panel, then receipts. Nothing here badges a
   document as missing or overdue, because the office still collects most of this on paper and the portal
   is the convenient option, not the demand. */
.doc-head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.15rem 0.7rem;
}
.doc-head h1 { margin: 0; font-size: 1.4rem; }
.doc-head .sub { color: var(--muted); font-size: 0.93rem; }

.sec-label {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    margin: 1.4rem 0 0;
    padding-bottom: 0.35rem;
    border-bottom: 1px solid var(--line);
}
.sec-label h2 {
    margin: 0;
    font-size: 0.82rem;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--sjsd-blue-dark);
}

.flash {
    margin: 1rem 0 0;
    padding: 0.65rem 0.85rem;
    border-radius: 8px;
    font-size: 0.93rem;
}
.flash-ok {
    background: #e7f6ec;
    border: 1px solid #a6dcb8;
    color: #10632c;
}
.flash-bad {
    background: var(--danger-bg);
    border: 1px solid #fda29b;
    color: var(--danger);
}

.send-panel {
    background: #fff;
    border: 1px solid var(--line);
    border-radius: 10px;
    box-shadow: 0 1px 3px rgba(16, 24, 40, 0.05);
    padding: 1rem 1.1rem;
    margin-top: 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.sp-lead { margin: 0; color: var(--muted); font-size: 0.9rem; }

.sp-field {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    margin-top: 0.35rem;
    max-width: 24rem;
}
.sp-field label { font-size: 0.84rem; font-weight: 600; }
.sp-field select,
.sp-field .labelfield {
    padding: 0.45rem 0.55rem;
    border: 1px solid var(--line);
    border-radius: 6px;
    font: inherit;
    font-size: 0.92rem;
    background: #fff;
    color: var(--ink);
}
.sp-field select:focus,
.sp-field .labelfield:focus {
    outline: none;
    border-color: var(--sjsd-blue);
    box-shadow: 0 0 0 3px rgba(0, 82, 155, 0.15);
}

.sp-actions { position: relative; margin-top: 0.6rem; }
.sp-submit { margin-top: 0.35rem; }

.addfile {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.45rem 0.8rem;
    border: 1px solid var(--sjsd-blue);
    border-radius: 6px;
    background: #fff;
    color: var(--sjsd-blue);
    font-weight: 600;
    font-size: 0.91rem;
    cursor: pointer;
}
.addfile:hover { background: #f2f7fc; }

/* Visually hidden rather than display:none — a hidden-but-required input makes Chrome refuse to submit
   ("not focusable") with no message anyone can see. Clipped at 1px it stays focusable and reachable. */
.filefield {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}
.sp-actions:focus-within .addfile {
    box-shadow: 0 0 0 3px rgba(0, 82, 155, 0.2);
}

.chosen {
    display: inline-block;
    margin-left: 0.6rem;
    font-size: 0.88rem;
    color: var(--muted);
}
.chosen:empty { display: none; }

/* An over-sized file, said at the moment it is picked. Full width rather than trailing the button:
   this is a sentence to read, not a filename to glance at. */
.chosen-bad {
    display: block;
    margin: 0.5rem 0 0;
    color: var(--danger);
    font-weight: 600;
}

.hint { color: var(--muted); font-size: 0.81rem; margin: 0.4rem 0 0; }

/* "X documents sent" sits just left of the button that sends the next one — both pinned to the card's
   lower-right corner, consistent whether the button reads "Send a document" or "Send another". */
.hint-row { display: flex; align-items: center; justify-content: flex-end; gap: 0.7rem; }

/* Same family as .btn-outline (so it reads as a real, tappable button, not a footnote) but a size up:
   a thumb-sized target and text large enough to read without leaning in, since this is often the only
   button on the card and needs to be found at a glance. */
.btn-send {
    padding: 0.6rem 1.15rem;
    font-size: 1rem;
    border-width: 1.5px;
}

.sent-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.75rem;
}
.sent-row {
    background: #fff;
    border: 1px solid var(--line);
    border-radius: 8px;
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.6rem 0.85rem;
    font-size: 0.92rem;
}
.file-ic { flex: 0 0 auto; color: var(--muted); display: flex; }
.file-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.05rem;
}
.file-name {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-weight: 500;
}
.file-meta {
    color: var(--muted);
    font-size: 0.81rem;
    font-variant-numeric: tabular-nums;
}
.sent-note { color: #8a4b0a; font-size: 0.84rem; margin-top: 0.15rem; }
.sent-row a { color: var(--sjsd-blue); flex: 0 0 auto; }

.nothing-yet {
    color: var(--muted);
    font-size: 0.9rem;
    padding: 0.9rem 0 0.2rem;
    margin: 0;
}

@media (max-width: 560px) {
    .doc-head h1 { font-size: 1.2rem; }
    .addfile { width: 100%; justify-content: center; padding: 0.6rem 0.8rem; }
    .sp-field { max-width: none; }
    .chosen { display: block; margin: 0.4rem 0 0; }
    /* The badge and View link wrap under the filename rather than squeezing it to nothing. */
    .sent-row { flex-wrap: wrap; }
    .file-main { flex-basis: 100%; }
}

/* ================= staff console =================
   Landscape, unlike the parent pages: staff work this at a desk, and the queue is a table because
   comparing rows down a column is the whole job. */

.staff-head {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    margin: 1rem 0 0.75rem;
}
.staff-head h1 { font-size: 1.25rem; margin: 0; }

/* One tab per school, always -- but scrolling sideways rather than wrapping. 51 staff are posted to all
   thirteen schools, and a bar that wrapped to three rows would push the queue itself off the screen.
   The container is what scrolls, so the page body never does. */
.school-tabs {
    display: flex;
    gap: 0.25rem;
    overflow-x: auto;
    border-bottom: 1px solid var(--line);
    margin-bottom: 0.75rem;
    /* Momentum scrolling on touch, and no bounce past the ends on desktop trackpads. */
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
}

.school-tab {
    flex: 0 0 auto;
    padding: 0.45rem 0.8rem;
    color: var(--muted);
    text-decoration: none;
    font-size: 0.9rem;
    font-weight: 600;
    white-space: nowrap;
    border-bottom: 3px solid transparent;
    border-radius: 5px 5px 0 0;
}
.school-tab:hover { color: var(--ink); background: #eef1f5; }
.school-tab.is-open {
    color: var(--sjsd-blue);
    border-bottom-color: var(--sjsd-blue);
}

.queue-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.6rem;
}
.queue-where { color: var(--muted); font-size: 0.9rem; }

.filter-toggle {
    font-size: 0.85rem;
    text-decoration: none;
    color: var(--sjsd-blue);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 0.2rem 0.7rem;
    white-space: nowrap;
}
.filter-toggle:hover { background: #eef1f5; }
.filter-toggle.is-on {
    background: var(--sjsd-blue);
    border-color: var(--sjsd-blue);
    color: #fff;
}

/* The table scrolls inside its own box; the page never scrolls sideways. */
.queue-scroll {
    overflow-x: auto;
    background: #fff;
    border: 1px solid var(--line);
    border-radius: 8px;
}

.queue {
    border-collapse: collapse;
    width: 100%;
    font-size: 0.9rem;
}
.queue th,
.queue td {
    text-align: left;
    padding: 0.5rem 0.7rem;
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}
.queue thead th {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--muted);
    background: #f7f8fa;
    position: sticky;
    top: 0;
}
.queue tbody tr:last-child td { border-bottom: 0; }
.queue tbody tr:hover { background: #f7f9fc; }
.queue a { color: var(--sjsd-blue); }
.q-student { font-weight: 600; }
.q-from { color: var(--muted); }

/* Present to screen readers, absent from the layout -- for a column whose header is only an action. */
.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* Diagnostics: sysadmin-only, so it can be denser and plainer than the staff queue. */
.staff-head .diag-link { margin-left: auto; font-size: 0.85rem; color: var(--sjsd-blue); }
.sec-label {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
    margin: 1.4rem 0 0.35rem;
}
.sec-note { font-size: 0.85rem; margin: 0 0 0.5rem; }
/* Label/value tables: the row header is the label, so it should not shout like a column head. */
.queue tbody th[scope="row"] {
    text-align: left;
    font-weight: 600;
    color: var(--muted);
    white-space: nowrap;
    width: 1%;
}
.queue td code, .queue th code {
    font-size: 0.85em;
    background: #eef1f5;
    padding: 0.05rem 0.3rem;
    border-radius: 4px;
}
/* Diagnostics values wrap; the queue's nowrap is wrong for prose and long paths. */
.queue tbody td .muted { white-space: normal; font-size: 0.83rem; }
