/* ==========================================================================
   App1 — shared stylesheet
   Theme: a light teal canvas carrying one card per page, and that card is filled
   with the accent itself — #21ffda, the one brand colour. Every page is cut from the
   same cloth: the bank's name across the top of the card as the page's own heading,
   the page's own content under it, the footnote at the foot. No page draws a white
   glass card any more; the only pale surface left is the Y/N overlay, which is a
   panel over the page rather than part of it.
   No page breaks the cloth any more: remove.html's own sketch fills its card with green,
   but that second accent (--remove) was dropped, so the Remove student card wears the same
   teal as every other page and its fields, tag, button and status line are the one accent's.
   Standing on the accent, the accent itself would disappear, so everything resting
   on a card is drawn in the accent's deeper tints instead: --accent-ink for text and
   outlines, --accent-ink-soft for the quiet tier under it, --accent-deep for the 1px
   hairlines, and --danger-ink for a red that still reads up there. Controls are pale
   boxes with an ink outline — .btn--door, .btn--ghost, the fields, the tag — while
   .btn--primary, the one button that takes you further in, is the ink itself.
   Typeface: Comfortaa for the whole UI, pulled from Google Fonts by the <head>
   of every page (Comfortaa ships with no operating system, so the webfont is
   the only way to get it). The system stack behind it in --font is just the
   fallback shown while the webfont swaps in.
   Loaded by every page in the project.
   Every page links this file as styles.css?v=N, and its own scripts the same way, each
   asset carrying its own N — this one is at 23. Bump the N of whichever file has
   changed, the stylesheet for a new look or a script for new behaviour: the host sends
   assets with max-age=14400, so without a new URL a browser can go on painting a
   four-hour-old copy — of this file or of app.js — while the HTML around it is
   already new.
   ========================================================================== */

:root {
    /* accent — the one brand colour */
    --accent:          #21ffda;
    --accent-deep:     #067d6c;   /* darker tint of the accent, for 1px hairlines */
    --accent-ink:      #04302b;   /* text and outlines that stand on top of the accent */
    --accent-ink-soft: rgba(4, 48, 43, 0.74);   /* the quiet tier of text under it */

    /* canvas */
    --bg:         #f2fffc;
    --glow-1:     rgba(33, 255, 218, 0.50);
    --glow-2:     rgba(33, 255, 218, 0.34);
    --glow-3:     rgba(33, 255, 218, 0.24);

    /* surfaces — the pale surface a control borrows when it stands on the accent card:
       the fields and the tag on every page, the doors and the status lines on the hub.
       White enough to carry the deep ink text, which is why the Y/N overlay's panel
       is the same white. */
    --surface:    rgba(255, 255, 255, 0.94);
    --surface-3:  #ffffff;
    --border:     rgba(6, 131, 113, 0.34);
    --border-hi:  rgba(6, 131, 113, 0.80);
    --danger:     #cf3352;   /* for a red drawn on a pale surface */
    --danger-ink: #8a1b34;   /* the same red, deep enough to read on the accent */

    /* text */
    --text:       #052e2a;
    --text-dim:   #2f5c57;
    --text-faint: #4f7873;

    /* effects */
    --gradient:     linear-gradient(135deg, var(--accent) 0%, #1af0d0 52%, #0ec7ab 100%);
    --radius:       18px;
    --radius-sm:    11px;
    --shadow:       0 18px 44px rgba(5, 76, 68, 0.14);
    --shadow-btn:   0 10px 24px rgba(6, 179, 154, 0.30);
    /* The glow a focused field draws. Every field in the app stands on the accent card
       now, where a teal ring would wash out, so the ring is the field's own white. */
    --ring:         0 0 0 4px rgba(255, 255, 255, 0.65);

    --font: "Comfortaa", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    /* the code face is Comfortaa too — <code> keeps its rounded chip so it still
       reads as code. The monospace stack behind it is only a fallback. */
    --mono: "Comfortaa", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

    color-scheme: light;
}

* { box-sizing: border-box; }

/* Every element that carries text — divs, paragraphs, headings, lists, links,
   table cells, form controls, inline code and generated (::before/::after)
   content — draws with Comfortaa. Stated explicitly instead of leaning on
   inheritance from <body>, so no box can drift back to a system font. */
*,
*::before,
*::after {
    font-family: var(--font);
}

body {
    min-height: 100vh;
    margin: 0;
    padding: clamp(1.25rem, 4vw, 3.5rem) clamp(1rem, 4vw, 2.5rem);
    display: flex;
    justify-content: center;
    align-items: flex-start;
    font-family: var(--font);
    line-height: 1.55;
    color: var(--text);
    background-color: var(--bg);
    background-image:
        radial-gradient(58rem 30rem at 10% -10%, var(--glow-1), transparent 68%),
        radial-gradient(48rem 26rem at 92% 4%, var(--glow-2), transparent 66%),
        radial-gradient(42rem 32rem at 50% 118%, var(--glow-3), transparent 70%);
    background-attachment: fixed;
    -webkit-font-smoothing: antialiased;
}

.page { width: 100%; max-width: 46rem; }

/* ----------------------------------------------------------------- card ---- */
/* Every inner page is one card, and that card is filled with the accent — the same
   surface and the same 34px corners index.html and home.html are drawn on. The teal
   hairline the old white card carried along its top edge is gone with the white card:
   the card itself is that colour now.
   The card is a column rather than a plain block for two reasons: it lets the page's
   footnote be pushed to the foot with margin-top: auto, the way the hub pushes Sign
   out there, and its min-height — the hub's — keeps a short page as tall as the
   sketch's card instead of shrinking to its handful of lines. Everything on it
   inherits the accent ink, which is what every control below is drawn against. */
.card {
    position: relative;
    display: flex;
    flex-direction: column;
    min-height: clamp(26rem, 74vh, 38rem);
    padding: clamp(1.6rem, 5vw, 2.75rem);
    color: var(--accent-ink);
    background: var(--accent);
    border: 1.5px solid var(--accent-deep);
    border-radius: 34px;
    box-shadow: var(--shadow);
    overflow: hidden;
}

/* ------------------------------------------------------------------ bar ---- */
.bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1.6rem;
}

/* The bank's name, which opens every card: the page's one h1, standing on the left of
   the top bar with the way back on the right. It wears the card heading's own size
   (.card__title), so the name reads the same on every page, and the bar already carries
   the air of its own under it — so the heading's bottom margin comes off here, which
   also keeps it level with the chip beside it. */
.bar .card__title { margin: 0; }

/* The chip in the top-right corner of every inner page, and the small ones along the
   footnotes: a pale box with an ink outline, the treatment the doors get. */
.tag {
    padding: 0.35rem 0.75rem;
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--accent-ink);
    background: var(--surface);
    border: 1.5px solid var(--accent-ink);
    border-radius: 999px;
    text-decoration: none;
    transition: color 0.16s ease, border-color 0.16s ease, background-color 0.16s ease;
}

a.tag:hover {
    color: var(--accent-ink);
    border-color: var(--accent-deep);
    background: var(--surface-3);
}

/* ---------------------------------------------------------- card headings ---- */
.card__title {
    margin: 0 0 0.35rem;
    font-size: clamp(1.6rem, 4.5vw, 2.05rem);
    font-weight: 700;
    letter-spacing: -0.01em;
}

.card__hint {
    margin: 0 0 1.85rem;
    font-size: 0.95rem;
    color: var(--accent-ink-soft);
}

/* The page's own name, which used to be the card's heading: it opens the hint in the ink
   rather than in the quiet tier the rest of the line is written in, so a page still says
   which page it is without a second heading over the card. */
.card__hint strong {
    color: var(--accent-ink);
    font-weight: 700;
}

/* ------------------------------------------------------------- buttons ---- */
.actions {
    display: grid;
    gap: 0.75rem;
    grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr));
}

.btn {
    appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.85rem 1.5rem;
    font-family: inherit;
    font-size: 1rem;
    font-weight: 650;
    letter-spacing: 0.01em;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    cursor: pointer;
    text-decoration: none;
    transition: transform 0.16s ease, box-shadow 0.16s ease,
                background-color 0.16s ease, border-color 0.16s ease, color 0.16s ease;
}

.btn--primary {
    color: var(--accent-ink);
    background-image: var(--gradient);
    box-shadow: var(--shadow-btn);
}

.btn--primary:hover {
    transform: translateY(-2px);
    box-shadow: 0 14px 30px rgba(6, 179, 154, 0.38);
}

.btn--primary:active { transform: translateY(0); }

.btn--ghost {
    color: var(--text-dim);
    background: transparent;
    border-color: var(--border);
}

.btn--ghost:hover {
    color: var(--text);
    border-color: var(--border-hi);
    background: rgba(33, 255, 218, 0.18);
}

/* The dark filled button: the landing page's two doors. That card is filled with the
   accent itself, so the ordinary accent button would disappear into it; this one is
   the accent ink instead — the darkest tint of the same teal — and lightens to the
   accent deep on hover, like every other button here. */
.btn--ink {
    color: #f2fffc;
    background: var(--accent-ink);
    border-color: var(--accent-ink);
    box-shadow: 0 10px 22px rgba(4, 48, 43, 0.26);
}

.btn--ink:hover {
    background: var(--accent-deep);
    border-color: var(--accent-deep);
    transform: translateY(-2px);
    box-shadow: 0 14px 28px rgba(4, 48, 43, 0.32);
}

.btn--ink:active { transform: translateY(0); }

/* The one filled red button in the app: the Yes that answers the remove page's question.
   Taking a student away cannot be undone, so the answer that does it is the colour the app
   already draws a refusal in, filled rather than outlined, and the quieter answer beside it
   keeps the ordinary outline — the heavier-looking of the two is then also the heavier one
   to have pressed by mistake. It stands on the Y/N overlay's white panel, so it is the pale
   surface's red (--danger) and not the deep one a card draws — the history table's delete
   button is that other red, under .card .btn--danger below. */
.btn--danger {
    color: #fff5f7;
    background: var(--danger);
    border-color: var(--danger);
    box-shadow: 0 10px 22px rgba(207, 51, 82, 0.28);
}

.btn--danger:hover {
    background: #b62a46;
    border-color: #b62a46;
    transform: translateY(-2px);
    box-shadow: 0 14px 30px rgba(207, 51, 82, 0.34);
}

.btn--danger:active { transform: translateY(0); }

.btn:focus-visible {
    outline: 2px solid var(--accent-deep);
    outline-offset: 2px;
}

/* Next stays disabled while the backend is being asked for permission */
.btn[aria-disabled="true"] {
    opacity: 0.55;
    box-shadow: none;
    transform: none;
    pointer-events: none;
}

/* The buttons that stand on a card, where the accent they used to be filled with is
   the card itself.
   .btn--primary — the one button that takes you further in — becomes the accent ink,
   the same dark filled button the sign-in card's doors are, and lightens to the accent
   deep on hover; .btn--ghost (Clear) keeps its outline but draws it in the ink, since
   the accent border would disappear into the card. The pale filled treatment, for a
   set of choices that are all equal, is .btn--door — the hub's five doors and the
   transaction-type menu both wear it, so nothing here repeats it.
   The buttons that stand on a pale surface instead, the Y/N overlay's Y and N, are
   untouched by all this and keep the rules above. */
.card .btn--primary {
    color: #f2fffc;
    background-image: none;
    background-color: var(--accent-ink);
    border-color: var(--accent-ink);
    box-shadow: 0 10px 22px rgba(4, 48, 43, 0.26);
}

.card .btn--primary:hover {
    background-color: var(--accent-deep);
    border-color: var(--accent-deep);
    box-shadow: 0 14px 28px rgba(4, 48, 43, 0.32);
}

.card .btn--primary:active { transform: translateY(0); }

.card .btn--ghost {
    color: var(--accent-ink);
    border: 1.5px solid rgba(4, 48, 43, 0.45);
}

.card .btn--ghost:hover {
    color: var(--accent-ink);
    background: rgba(255, 255, 255, 0.45);
    border-color: var(--accent-ink);
}

/* The same red on a card is the deep one. The history table's delete button stands on the
   accent, where --danger — mixed for a pale surface — would glare, so a red drawn here is
   --danger-ink, for the reason every other control up here changes palette: the colour the
   card does not supply has to be brought to it. Filled rather than outlined, for the reason
   the pale one is filled: the button that takes a transaction away with no undo is the
   heaviest thing in its row. This is also what keeps its label readable where the row's
   other buttons sit: at this size --danger carries to about the letter the ink does. */
.card .btn--danger {
    color: #fff5f7;
    background: var(--danger-ink);
    border-color: var(--danger-ink);
    box-shadow: 0 10px 22px rgba(138, 27, 52, 0.26);
}

.card .btn--danger:hover {
    background: #6e1229;
    border-color: #6e1229;
    box-shadow: 0 14px 28px rgba(138, 27, 52, 0.32);
}

.card .btn--danger:active { transform: translateY(0); }

/* the ring the .btn rule draws is the deeper teal, which would wash out against the
   card — on the card it is the accent ink, like the outline of the button it rings */
.card .btn:focus-visible { outline-color: var(--accent-ink); }

/* ---------------------------------------------------------- acc. panel ---- */
/* The reason pages' panel — the title, the dropdown and Next — was a rounded box
   filled with the accent, standing on a white card. The card is that colour now, so
   the box would only repeat it. What is left is the column itself, which keeps the
   airy spacing the sketch leaves between the dropdown and the button. */
.panel {
    display: grid;
    align-content: start;
    justify-items: center;
    gap: clamp(2.5rem, 7vw, 4.5rem);
    min-height: clamp(17rem, 42vw, 25rem);
}

.panel__title {
    margin: 0;
    font-size: clamp(1.1rem, 3.4vw, 1.4rem);
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    text-align: center;
    color: var(--accent-ink);
}

/* the dropdown: pale rectangle, dark outline and hollow triangle (the sketch) */
.panel select {
    width: min(100%, 26rem);
    padding: 0.65rem 2.5rem 0.65rem 1rem;
    font-family: inherit;
    font-size: 1rem;
    color: var(--accent-ink);
    background-color: rgba(255, 255, 255, 0.92);
    border: 1.5px solid var(--accent-ink);
    border-radius: 8px;
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2.5 5.5h11L8 12z' fill='none' stroke='%2304302b' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 1rem center;
    background-size: 0.8rem;
    cursor: pointer;
}

.panel select:hover { background-color: #ffffff; }

/* stays disabled while the backend list is on its way */
.panel select[aria-disabled="true"] {
    opacity: 0.55;
    pointer-events: none;
}

.panel select:focus-visible {
    outline: 2px solid var(--accent-ink);
    outline-offset: 3px;
}

/* The date box the four transaction-type pages carry — the three reason lists below their
   dropdown, and the Other page's in its form — wears the .field treatment the rest of the
   app's boxes wear, and on a reason page that box is held to the dropdown's own width so the
   two of them line up down the centre of the panel. */
.panel .field { width: min(100%, 26rem); }

/* --------------------------------------------------------- home page hub ---- */
/* home.html, the page behind the front door: one card filled with the accent, like
   the sign-in card below it. The bank's name sits at its top-left, the greeting and
   the question under it, the five doors in two columns, and Sign out on its own at
   the foot — the sketch, door for door. Because the card is the accent itself,
   everything standing on it is drawn in the accent ink, and each door is a pale box
   with a dark outline, the treatment .panel select gets.
   The card is a column rather than a grid, which is what lets Sign out be pushed to
   the foot with margin-top: auto without the doors stretching to fill the height.
   The student's own hub (student-home.html) wears the same card, built the same way:
   its brand, greeting and one door, then the balance box and Sign out at the foot. */
.hub {
    display: flex;
    flex-direction: column;
    gap: clamp(1.1rem, 3.2vw, 1.75rem);
    min-height: clamp(26rem, 74vh, 38rem);
    padding: clamp(1.6rem, 5vw, 2.75rem);
    background: var(--accent);
    border: 1.5px solid var(--accent-deep);
    border-radius: 34px;
    box-shadow: var(--shadow);
}

.hub__brand {
    margin: 0;
    font-size: clamp(1.05rem, 3.2vw, 1.3rem);
    font-weight: 700;
    letter-spacing: 0.02em;
    color: var(--accent-ink);
}

/* the greeting and the question under it read as one block */
.hub__hello { display: grid; gap: 0.2rem; }

.hub__greeting,
.hub__question {
    margin: 0;
    color: var(--accent-ink);
}

.hub__greeting { font-size: clamp(1.15rem, 3.6vw, 1.45rem); font-weight: 600; }

.hub__question { font-size: clamp(1rem, 3vw, 1.15rem); }

/* the doors, two by two: six of them, three full rows, with Rotate jobs closing the last */
.hub__actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: clamp(0.85rem, 3vw, 1.6rem);
    margin-top: clamp(0.5rem, 2vw, 1rem);
}

/* One door alone on the row: the student's own hub, where the balance stands on the card
   itself now rather than behind a View balance button, so View transaction history is the
   only door left on it. A single column lets that door take the whole width instead of
   being drawn at half of it against an empty cell. */
.hub__actions--single { grid-template-columns: 1fr; }

/* The balance box on the student's own hub, under the door: the pale surface the doors are
   drawn on, so the figure reads as an answer standing on the card rather than one more way
   further in. It stands there with the dots in it from the moment the page opens, while
   studenthome.js reads the figure, and the states that have no figure to draw — no student
   signed in, no transaction recorded, a read that failed — take it off the card with the
   hidden attribute, which has to win over the display rule below — the same reason
   .roster__frame[hidden] and .combo__list[hidden] exist. margin-top: auto is what leaves it
   low on the card, where the sketch draws it; on the student's card the free height is
   shared with the foot below, which carries an auto top margin of its own. */
.hub__balance {
    /* The line inside stands in a span of its own so the box can centre it: a flex
       container takes the text run and the figure as two items, which would drop the
       space between them — "Balance:235" — and set the figure on a line of its own. */
    display: flex;
    align-items: center;
    min-height: clamp(3.25rem, 9vw, 4.25rem);
    margin-top: auto;
    padding: clamp(0.8rem, 2.6vw, 1.05rem) 1.15rem;
    font-size: clamp(1rem, 3vw, 1.15rem);
    color: var(--accent-ink);
    background: rgba(255, 255, 255, 0.94);
    border: 1.5px solid var(--accent-ink);
    border-radius: 10px;
    box-shadow: 0 10px 22px rgba(4, 48, 43, 0.20);
}

.hub__balance[hidden] { display: none; }

.hub__balance strong { font-weight: 700; }

/* A balance below zero, on the box's pale surface: the same red the students table draws
   one in, since that is the same figure on the same kind of surface. */
.hub__balance strong.hub__balance--negative { color: var(--danger); }

/* A door: the pale box the panels use, standing on the accent with a dark outline
   and the accent ink for its word. */
.btn--door {
    padding: clamp(1.05rem, 3.2vw, 1.45rem) 1.1rem;
    /* The page's line-height, said out loud. The doors are mostly links, which inherit
       it, but Sign out is a <button> — the one door left in the app that is, now that
       Remove student opens remove.html like the others — and a button takes the user
       agent's font shorthand instead, which pins it to line-height: normal. Left alone, a
       button door stands a line-box shorter than the link doors, and shows it wherever it
       has no neighbour to be stretched level with. */
    line-height: 1.55;
    color: var(--accent-ink);
    background: rgba(255, 255, 255, 0.94);
    border-width: 1.5px;
    border-color: var(--accent-ink);
    border-radius: 10px;
    text-align: center;
    box-shadow: 0 10px 22px rgba(4, 48, 43, 0.20);
}

.btn--door:hover {
    background: #ffffff;
    border-color: var(--accent-deep);
    transform: translateY(-2px);
    box-shadow: 0 14px 28px rgba(4, 48, 43, 0.26);
}

.btn--door:active { transform: translateY(0); }

/* the ring the .btn rule draws is the deeper teal, which would wash out against the
   accent card — on the doors it is the accent ink, like their outline */
.btn--door:focus-visible {
    outline: 2px solid var(--accent-ink);
    outline-offset: 3px;
}

/* The foot of the card: Sign out on a row of its own, pushed to the right as the sketch
   draws it. It keeps a row separate from the doors, so it can never be mistaken for a
   way further into the bank. Both hubs wear it — the admin's six doors and the student's
   one — and margin-top: auto is what lands it at the very foot. On the student's card the
   balance box above it carries the same auto margin, so the free height is split between
   the two and Sign out is still the last thing on the card. */
.hub__foot {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    margin-top: auto;
}

.hub__foot .btn { align-self: flex-end; }

/* A status line standing on an accent card — the hub's, or an inner page's — borrows
   the white surface the doors borrow so that it stays readable there. On the hub an
   empty one takes no room at all, the page as it opens being the sketch and nothing
   else; on an inner page the empty slot keeps the dashed placeholder above, which is
   the line that says what the page is waiting for. */
.hub .results { margin-top: 0; }

.hub .results:empty { display: none; }

.hub .results > p,
.card .results > p {
    color: var(--accent-ink);
    background: rgba(255, 255, 255, 0.94);
    border-color: var(--accent-ink);
    border-radius: var(--radius-sm);
}

.hub .results > p.results__error,
.card .results > p.results__error {
    background: rgba(255, 255, 255, 0.94);
    border-left-color: var(--danger);
}

/* on the white surface the line borrows, the deeper accent tint is what reads as a
   link — the accent itself would disappear into it */
.hub .results > p a,
.card .results > p a {
    color: var(--accent-deep);
    font-weight: 700;
    text-decoration-thickness: 1.5px;
    text-underline-offset: 3px;
}

/* -------------------------------------------------- landing sign-in card ---- */
/* index.html is the card the bank opens on, and that card is the whole page: the
   bank's name at its top-left, the username and password boxes across its middle and
   the two sign-in doors along its foot. It is filled with the accent, so everything
   standing on it — brand, boxes, buttons, status line — is drawn in the accent ink,
   the darkest tint of the same colour, the way the content of every other card is. The
   card is as tall as the window allows, which is what leaves the empty teal between the
   fields and the buttons the sketch shows. */
.signin {
    display: grid;
    gap: clamp(1.5rem, 4.5vw, 2.5rem);
    min-height: clamp(24rem, 72vh, 36rem);
    padding: clamp(1.6rem, 5vw, 2.75rem);
    background: var(--accent);
    border: 1.5px solid var(--accent-deep);
    border-radius: 34px;
    box-shadow: var(--shadow);
}

.signin__brand {
    margin: 0;
    font-size: clamp(1.05rem, 3.2vw, 1.3rem);
    font-weight: 700;
    letter-spacing: 0.02em;
    color: var(--accent-ink);
}

.signin__form {
    display: grid;
    align-content: start;
    gap: clamp(1rem, 3.5vw, 1.6rem);
}

/* the box, the label that only the screen reader reads, and nothing else — the label
   is a .sr-only box below, so this one is positioned to hold it */
.signin__field { position: relative; }

.signin__field input {
    width: 100%;
    padding: clamp(0.9rem, 2.6vw, 1.15rem) 1.15rem;
    font-family: inherit;
    font-size: clamp(1rem, 3vw, 1.15rem);
    text-align: center;   /* the sketch centres the word inside the box */
    color: var(--accent-ink);
    background: rgba(255, 255, 255, 0.94);
    border: 1.5px solid var(--accent-ink);
    border-radius: 10px;
    transition: border-color 0.16s ease, box-shadow 0.16s ease, background-color 0.16s ease;
}

.signin__field input::placeholder {
    color: var(--text-faint);
    opacity: 1;   /* Firefox dims placeholders of its own accord */
}

.signin__field input:hover { background: #ffffff; }

/* The ring a focused box draws is --ring: a ring of white, because a teal glow would
   wash out against the accent card. The inner pages' fields draw the same ring. */
.signin__field input:focus {
    outline: none;
    background: #ffffff;
    border-color: var(--accent-ink);
    box-shadow: var(--ring);
}

/* the same rule as the form pages: a box colours up once it has the user's attention
   and is still wrong, never before */
.signin__field input:user-invalid { border-color: var(--danger); }

/* The two doors: one on each side of the card's foot, with the empty teal between
   the fields and them left to grow (margin-top: auto). */
.signin__actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: clamp(0.9rem, 6vw, 3.5rem);
    margin-top: auto;
    padding-top: clamp(1rem, 4vw, 2rem);
}

/* The status line under the buttons, on the teal: it borrows the white surface of the
   fields so that it stays readable there, and an empty one shows nothing at all rather
   than the dashed placeholder the other pages keep out. */
.signin .results { margin-top: 0; }

.signin .results > p {
    color: var(--accent-ink);
    background: rgba(255, 255, 255, 0.94);
    border-color: var(--accent-ink);
    border-radius: var(--radius-sm);
}

.signin .results > p.results__error {
    background: rgba(255, 255, 255, 0.94);
    border-left-color: var(--danger);
}

/* The status line also carries the link on to the home page once the sign-in has gone
   through. On the white surface the line borrows, the deeper accent tint is what reads
   as a link — the accent itself would disappear into it. */
.signin .results > p a {
    color: var(--accent-deep);
    font-weight: 700;
    text-decoration-thickness: 1.5px;
    text-underline-offset: 3px;
}

.signin .results:empty::before { display: none; }

/* A label nobody sees: the sign-in card names its boxes the way the sketch does, with
   the word inside the box, so the real <label> is clipped to a point and left to the
   screen reader instead of being read twice on the page. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* non-interactive aside (home page easter egg) */
.chip {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 1.4rem;
    padding: 0.5rem 0.85rem;
    font-size: 0.85rem;
    color: var(--text-faint);
    background: rgba(207, 51, 82, 0.05);
    border: 1px dashed rgba(207, 51, 82, 0.45);
    border-radius: 999px;
    cursor: help;
}

.chip__icon { color: var(--danger); }

/* --------------------------------------------------------------- forms ---- */
.form { display: grid; gap: 1.15rem; }

/* job_rotation.html: one .field per student in this grid — the student's name is the
   label and their job's text box sits under it — so the page is a column of little
   forms, one per student, in the order the students are listed. */
.jobs { display: grid; gap: 1.15rem; }

.field { display: grid; gap: 0.45rem; }

.field label {
    font-size: 0.82rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--accent-ink-soft);
}

.field .hint {
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    color: var(--accent-ink-soft);
}

/* The one-line fields and the memo box share one surface, so the memo on
   transaction-other.html cannot drift away from the fields above it. The second rule
   is the only thing a <textarea> needs on top of that: a height to start from and the
   browser's own grab handle, downwards only, so the page below it is never covered.
   The boxes stand on the accent card, so each one is a pale box with an ink outline —
   the treatment the sign-in card's two boxes and the doors get. */
.field input,
.field textarea {
    width: 100%;
    padding: 0.8rem 0.95rem;
    font-family: inherit;
    font-size: 1rem;
    color: var(--accent-ink);
    background: var(--surface);
    border: 1.5px solid var(--accent-ink);
    border-radius: var(--radius-sm);
    transition: border-color 0.16s ease, box-shadow 0.16s ease, background-color 0.16s ease;
}

.field textarea {
    min-height: 7rem;
    line-height: 1.5;
    resize: vertical;
}

.field input:hover,
.field textarea:hover {
    background: var(--surface-3);
    border-color: var(--accent-deep);
}

.field input:focus,
.field textarea:focus {
    outline: none;
    background: var(--surface-3);
    border-color: var(--accent-ink);
    box-shadow: var(--ring);
}

/* A required field colours up only once it has the user's attention and is still
   wrong. :user-invalid is the state that says just that — the old
   ":not(:placeholder-shown)" trick needed a placeholder to test against, and the
   fields no longer carry one. A browser too old to know the state simply leaves the
   border alone, and its own validation bubble still explains what is missing. */
.field input:user-invalid,
.field textarea:user-invalid {
    border-color: rgba(207, 51, 82, 0.55);
}

/* ------------------------------------------------- typing dropdown ---- */
/* The username field on transaction-view-middle.html and the student name field on
   remove.html suggest account names while they are typed. The list is a plain <ul> the
   script fills, laid over what is below it. studentpicker.js finds each field by id
   (transaction-view-middle.html) or by its data-student-picker attribute (remove.html),
   and the list and hint off the aria-controls and aria-describedby the field carries.
   transaction1.html's box of students, which asks the same question without a typed field,
   is the .picker block below. */
.combo { position: relative; }

/* room on the right edge for the same hollow triangle as .panel select */
.combo input {
    padding-right: 2.6rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2.5 5.5h11L8 12z' fill='none' stroke='%23067d6c' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.95rem center;
    background-size: 0.8rem;
}

.combo__list {
    position: absolute;
    z-index: 5;
    top: calc(100% + 0.35rem);
    right: 0;
    left: 0;
    max-height: 15rem;
    margin: 0;
    padding: 0.3rem;
    overflow-y: auto;
    list-style: none;
    background: var(--surface-3);
    border: 1px solid var(--border-hi);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
}

/* the script toggles the hidden attribute, so it has to win over any display rule
   that lands on these two elements */
.combo__list[hidden],
.combo__hint[hidden] { display: none; }

.combo__option {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.5rem 0.7rem;
    border-radius: 8px;
    cursor: pointer;
}

.combo__option--active,
.combo__option:hover { background: rgba(33, 255, 218, 0.26); }

/* the letters the typed text lined up with */
.combo__option mark {
    font-weight: 650;
    color: inherit;
    background: transparent;
    text-decoration: underline;
    text-decoration-color: var(--accent-deep);
    text-decoration-thickness: 2px;
    text-underline-offset: 2px;
}

/* The muted chip that used to sit at the far end of the option is gone with it: it held
   the account's supervisor, and the list only ever offers the signed-in admin's own
   students, so it restated the admin already at the top of the page and read as a label
   on the student. The row is the name alone now, and studentpicker.js no longer sets
   aria-label on the option because there is no second span to spell out. */

.combo__hint {
    margin: 0;
    font-size: 0.82rem;
    color: var(--accent-ink-soft);
}

/* the hint is the one red that is written straight on the card rather than on a pale
   box, where --danger would only reach about 3.9:1 against the accent — this is the
   same red, deep enough to read up there */
.combo__hint--error { color: var(--danger-ink); }

/* ------------------------------------------------- student picker ---- */
/* transaction1.html's box: the sketch draws it rounded, with the title across the top, one row
   per student inside it — the student's name, the word "[choose]" and the empty box a click
   ticks — and "continue" at the foot. The sketch fills the box pale blue, which is the sketch's
   own colour and not one of this app's: the card the box stands on is the accent, so the box is
   the pale surface every other control borrows (--surface), outlined in the accent ink the fields
   and the tag wear, and the box the eye reads as the tick is drawn in that same ink. The rows are
   drawn by sessionstorage.js; the tick and the picked row's tint are the row's own aria-checked
   state, so no part of a pick is styled from the script. */
.picker {
    display: grid;
    gap: 1rem;
    padding: clamp(1rem, 3vw, 1.5rem);
    background: var(--surface);
    border: 1.5px solid var(--accent-ink);
    border-radius: var(--radius);
}

/* the sketch's title, across the top of the box */
.picker__title {
    margin: 0;
    text-align: center;
    font-weight: 600;
    color: var(--accent-ink);
}

.picker__list {
    display: grid;
    gap: 0.15rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* One row, and the whole of it is the button: the name, "[choose]" and the box are its three
   spans, so the click, the hover tint and the focus ring are the row's rather than a small
   target's inside it — clicking a name picks the student exactly as clicking the box does. */
.picker__option {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0.5rem 0.55rem;
    font-family: inherit;
    font-size: 1rem;
    color: var(--accent-ink);
    text-align: left;
    background: none;
    border: 1.5px solid transparent;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.picker__option:hover { background: rgba(33, 255, 218, 0.26); }

/* keyboard focus has to be as plain to see as the tint a pointer gets */
.picker__option:focus-visible {
    outline: 2px solid var(--accent-ink);
    outline-offset: 2px;
}

/* the name takes the room, so "[choose]" and the box stand at the end of the row whatever the
   name is — the sketch's line, wrapping under itself when the row is narrow */
.picker__name { flex: 1 1 auto; overflow-wrap: anywhere; }

.picker__choose { flex: none; color: var(--accent-ink-soft); }

/* the box at the end of the row: empty while the student is not picked, ticked while they are,
   both read off the row's own aria-checked */
.picker__box {
    flex: none;
    display: grid;
    place-items: center;
    width: 1.35rem;
    height: 1.35rem;
    background: var(--surface-3);
    border: 1.5px solid var(--accent-ink);
    border-radius: 5px;
}

.picker__option[aria-checked="true"] .picker__box {
    color: var(--surface-3);
    background: var(--accent-ink);
}

.picker__option[aria-checked="true"] .picker__box::before { content: "\2713"; }

/* a picked row keeps the tint a hovered row has, so the tick is not the only thing saying it */
.picker__option[aria-checked="true"] { background: rgba(33, 255, 218, 0.20); }

/* the one line the box says while the backend is being read, or when there is no list to draw */
.picker__note {
    padding: 0.5rem 0.55rem;
    font-size: 0.96rem;
    color: var(--accent-ink-soft);
}

/* "continue" at the foot of the box, where the sketch centres it */
.picker__actions {
    display: flex;
    justify-content: center;
    margin-top: 0.4rem;
}

.picker__actions .btn { min-width: 12rem; }

/* ----------------------------------- pay salaries / pay rent (salary.html, rent.html) ---- */
/* The hub's two money doors stand the same box transaction1.html picks students in, drawn the way
   those two pages are sketched: the box tall, so it fills the card rather than hugging its rows, one
   row per student along it — the student's name, "[uncheck]" beside it and the box a click ticks —
   and the page's button at the foot of the box, on the left where the sketch puts it rather than
   centred the way transaction1.html's "continue" is. The students run along the box and wrap when
   they run out of room, which is how the sketch lays them out; everything else about each row is the
   row styles above, unchanged. Only these two pages carry .picker--payroll, so none of this is drawn
   on the transaction page, whose box is untouched. */
.picker--payroll {
    grid-template-rows: auto 1fr auto;   /* title, box, button — the button at the foot */
    min-height: clamp(15rem, 42vh, 24rem);
}

/* the students along the box, rather than one per line: the box is wide and the rows are short */
.picker__list--flow {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    gap: 0.15rem 1rem;
}

/* a flowed row is as wide as its student and "[uncheck]", not the whole box */
.picker__list--flow .picker__option { width: auto; }

.picker--payroll .picker__actions {
    justify-content: flex-start;
    margin-top: 0;
}

.form__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-top: 0.4rem;
}

.form__actions .btn--primary { flex: 1 1 11rem; }

/* ------------------------------------------------------------- results ---- */
.results {
    display: grid;
    gap: 0.6rem;
    margin-top: 1.75rem;
}

.results > p {
    margin: 0;
    padding: 0.8rem 1rem;
    font-size: 0.96rem;
    background: linear-gradient(90deg, rgba(33, 255, 218, 0.30), rgba(33, 255, 218, 0.06));
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent-deep);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.results > p.results__error {
    background: linear-gradient(90deg, rgba(207, 51, 82, 0.18), rgba(207, 51, 82, 0.05));
    border-left-color: var(--danger);
}

/* getusers.html: one card per account, and inside it one line per field — the query
   lists every field the backend keeps on an account, so each line is that field's
   own name in the accent ink and its value after it. The name is the quiet half of
   the pair, which is why it is the smaller, deeper text. */
.results__field {
    display: block;
    font-size: 0.92rem;
}

.results__field + .results__field {
    margin-top: 0.3rem;
}

.results__field-label {
    font-weight: 700;
    color: var(--accent-deep);
}

/* the line a page shows while it waits reads straight on the card, so it is drawn in
   the card's ink rather than in the light page's faintest grey */
.results:empty::before {
    content: attr(data-empty);
    padding: 1rem 1.1rem;
    font-size: 0.92rem;
    color: var(--accent-ink-soft);
    border: 1px dashed rgba(4, 48, 43, 0.45);
    border-radius: var(--radius-sm);
}

/* -------------------------------------------------------- Y/N confirm ---- */
/* The reason pages ask "confirm transaction, Y/N" before they write anything: a
   transaction moves a student's balance and this app has no undo, so the question
   is put in front of the admin once more. app.js builds the overlay and toggles its
   hidden attribute; it sits over the whole window until it is answered.
   remove.html borrows the same overlay for its own question — "confirm remove student,
   yes/no" — so the one dialog in the app is the one place anything irreversible is
   asked about, and the panel's white stays the only pale surface a control stands on. */
.confirm {
    position: fixed;
    inset: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: clamp(1rem, 4vw, 2.5rem);
    background: rgba(5, 46, 42, 0.42);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}

/* the script toggles the hidden attribute, so it has to win over the display rule
   above — the same reason .combo__list[hidden] exists */
.confirm[hidden] { display: none; }

.confirm__panel {
    width: min(100%, 26rem);
    padding: clamp(1.4rem, 4.5vw, 2rem);
    text-align: center;
    background: var(--surface-3);
    border: 1px solid var(--border-hi);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

.confirm__question {
    margin: 0 0 0.6rem;
    font-size: clamp(1.05rem, 3.6vw, 1.3rem);
    font-weight: 700;
    letter-spacing: 0.02em;
}

.confirm__text {
    margin: 0 0 1.4rem;
    font-size: 0.94rem;
    color: var(--text-dim);
}

.confirm__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
}

/* Y and N side by side, each half the panel — the two answers are equals, only one
   of them writes. */
.confirm__actions .btn { flex: 1 1 6rem; }

/* --------------------------------------------------------- remove page ---- */
/* remove.html, the page behind the hub's Remove student door. Its own sketch fills the card
   with green, but that second accent has been dropped: the card is cut from the bank's teal
   like every other page, so the fields, the tag, the remove button, the status line and the
   <code> chip all follow the one accent without a rule of their own. The card's own fill,
   hairline and ink come from those same variables, so nothing else about .card has to be
   repeated here; only the field and the button keep the placement the sketch draws. */

/* The form under the page's own line: the box at the top of it and the remove button at the
   foot of the card, which is where the sketch puts the two of them. flex: 1 1 auto stretches
   it down the card's free height, and the min-height is the sketch's own air between the box
   and the button — the page's own line is long enough that the card is taller than its
   content, so without it the button would stand right under the box with no gap at all. */
.remove {
    flex: 1 1 auto;
    display: grid;
    grid-template-rows: auto 1fr;
    gap: 0.9rem;
    min-height: clamp(11rem, 30vh, 18rem);
}

/* The label over the box, and the box with it, centred in the card: the sketch draws the
   pair narrower than the card and away from its left edge, and the name typed into it is
   centred under its own label. */
.card--remove .field {
    justify-items: center;
    text-align: center;
}

.card--remove .field input {
    width: min(100%, 22rem);
    text-align: center;
}

/* The typing dropdown's wrapper has to hold the width the plain box had: the field is
   centred here rather than stretched, so the wrapper would otherwise shrink to fit the
   empty box and the box would shrink with it. It takes the box's own width instead, and
   the list laid over it (left: 0; right: 0) then follows that width. */
.card--remove .combo { width: min(100%, 22rem); }

/* The sketch's wording over the box, in sentence case: every other page's fields are
   labelled in the small tracked caps, which would spell this one "ENTER STUDENT NAME". */
.card--remove .field label {
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: 0;
    text-transform: none;
    color: var(--accent-ink-soft);
}

/* The button's own row at the foot of the form — one button, centred, as the sketch draws
   it, and the doors' pale box with an ink outline, which on this card is the accent ink. It
   is given a width rather than the whole row, so it reads as the one action it is. */
.remove__actions {
    align-self: end;
    display: flex;
    justify-content: center;
}

.remove__actions .btn { flex: 0 1 14rem; }

/* ------------------------------------------------------------- roster ---- */
/* students.html, the page behind the hub's "View students" door: the students of the
   admin behind the session cookie, one row each, as the four-column table the sketch
   draws — Student, balance, job and job-salary — and the last student's row closing
   the table. app.js fills the rows and unhides the frame they stand in.
   The table is the pale surface the doors and the fields borrow, so the card's teal
   shows around it, and everything inside keeps the palette a pale surface gets: the head
   row is the four names in the accent ink, in the case the page writes them, the
   hairlines between the cells are the deeper teal, and a balance below zero is the red
   that reads on white. */
.roster {
    display: grid;
    gap: 1.15rem;
}

/* the status line above the table keeps the white panel the .results block gives it —
   the same line every other page waits and complains on */
.roster .results { margin-top: 0; }

/* The frame is that pale surface: one box the table is drawn inside. The students page
   draws it as its sketch does — square at the corners under a single ink hairline, no
   shadow, the card's teal around it — and, like the history page's box, it scrolls
   sideways when the table asks for more width than the card has, so the last column is
   never silently cut off. The history page keeps the rounded frame it draws; that box's
   own rule, further down, names the frame class it wears. */
.roster__frame {
    overflow-x: auto;
    background: rgba(255, 255, 255, 0.94);
    border: 1px solid var(--accent-ink);
}

/* the class would otherwise win over the browser's own [hidden] rule */
.roster__frame[hidden] { display: none; }

.roster__table {
    width: 100%;
    border-collapse: collapse;
}

.roster__table th,
.roster__table td {
    padding: clamp(0.55rem, 2vw, 0.75rem) clamp(0.7rem, 3vw, 1.1rem);
    text-align: left;
    vertical-align: baseline;
}

/* The students page's head row is drawn as that page's sketch draws it: the four names
   in the case the page writes them — Student, balance, job, job-salary — in the card's
   ink on the pale surface, one line each, with the sketch's upright bar standing between
   them. */
.roster__table:not(.history__table) thead th {
    white-space: nowrap;
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: 0;
    text-transform: none;
    color: var(--accent-ink);
}

/* One of the names has a break inside it — job-salary, at its hyphen — and the browser
   would take that break even from a column wide enough to hold the whole name, which
   would draw the sketch's last column as "job-" over "salary". So the four names are held
   on one line, and the line they hold is the table's own minimum width: a card narrower
   than that has a box that scrolls sideways (see .roster__frame) rather than one that
   clips the column at the end of the row. */

/* The bar the sketch writes between the names — "Student | balance | job | job-salary" —
   is drawn on the column's own edge rather than set down as a character after each name.
   A typed bar stands wherever its name happens to end on the line, so it says nothing about
   where the column it names is cut: at a 1024px window the balance column's own name stopped
   16px short of the figures under it, the bar having taken that 16px, and a column wider
   than its name — "Student", "job" — drew its bar in the middle of itself with the boundary
   under it away to the right, so that a head row read as four narrow columns over four
   wide ones. As the cell's left edge the bar is the very line the column is cut on, and
   being the rows' own hairline (.roster__table tbody td + td, same colour and same width)
   the two halves of it meet as one line drawn down the table — so each name stands over its
   own figures at every width, and no glyph is left for a reader or a copy to pick up. The
   fourth name ends the line, so that column takes no bar. */
.roster__table:not(.history__table) thead th + th { border-left: 1px solid rgba(6, 131, 113, 0.28); }

/* The history table is the one head row drawn some other way: the accent ink filled
   edge to edge, carrying the card's own pale text, with a hairline down between the
   cells — the one place in the app where the ink is the background. Both rules name
   the table class it wears, so they leave the students page's head row alone. */
.roster__table.history__table thead th {
    background: var(--accent-ink);
    font-size: 0.74rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: #f2fffc;
}

.roster__table.history__table thead th + th { border-left: 1px solid rgba(255, 255, 255, 0.35); }

.roster__table tbody td {
    font-size: 0.95rem;
    color: var(--accent-ink);
    border-top: 1px solid rgba(6, 131, 113, 0.28);
}

.roster__table tbody td + td { border-left: 1px solid rgba(6, 131, 113, 0.28); }

/* Every other student in a tint of the accent, so the eye can follow a long name
   across to its balance; the row under the pointer a shade stronger. */
.roster__row:nth-child(even) { background: rgba(33, 255, 218, 0.14); }

.roster__row:hover { background: rgba(33, 255, 218, 0.30); }

/* a name can be a whole sentence: it wraps inside its own column rather than pushing
   the balance out of the table */
.roster__name { overflow-wrap: anywhere; }

/* The balance column takes only as much room as its longest figure, and its digits
   are fixed-width, so 235 and -10 end on the same edge — the right one. The <th> of
   the head row needs the class in the selector to win over the left-aligned rule
   above it. */
.roster__table th.roster__amount,
.roster__table td.roster__amount {
    width: 1%;
    font-weight: 650;
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
}

/* The one figure in the table that is red, on the pale surface the frame behind it
   draws. The class needs the cell in the selector: the colour the rows carry is set on
   .roster__table tbody td, which carries more weight than a class on its own and would
   otherwise keep the minus sign the same ink as every other balance. */
.roster__table td.roster__amount--negative { color: var(--danger); }

/* A job can be a whole sentence, like a name: it wraps inside its own column rather than
   pushing the balance or the salary beside it out of the table. */
.roster__job { overflow-wrap: anywhere; }

/* The salary that job pays: the same figure in its own box the balance gets — only as
   wide as its longest figure, right-aligned with fixed-width digits, so 65 and 100 end on
   the same edge — but not in the balance's weight, the money already on the account being
   what the row is read for. */
.roster__table th.roster__salary,
.roster__table td.roster__salary {
    width: 1%;
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.roster__table td.roster__salary { white-space: nowrap; }

/* The salary column is the one column the head row holds open for itself: its figures
   are narrower than the name above them, so the column is as wide as "job-salary" on its
   one line (the head row is held on one line above) rather than as wide as its widest
   figure. */

/* the foot of the section: when the table was last read on the left, the button that
   reads it again on the right. justify-content keeps the button at that edge even
   while the line beside it has nothing to say. */
.roster__actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.75rem 1rem;
    flex-wrap: wrap;
}

/* the line is outside the live region, so a clock going round is not announced */
.roster__stamp {
    flex: 1 1 12rem;
    margin: 0;
    font-size: 0.78rem;
    line-height: 1.4;
    color: var(--accent-ink-soft);
}

.roster__stamp:empty { display: none; }

/* ------------------------------------------------------------ history ---- */
/* transaction-view.html, the page behind the hub's "View student transaction history"
   door: the transactions of one student, one row each, in the five columns the sketch
   draws. The table is the roster's table, worn by a second page — the same
   .roster__table, so the tinted every-other row and the red for a figure below zero come
   from the rules above and are written once, as does the frame, so what is left to say
   here is the two things this page draws differently: its head row is the ink filled
   band rather than the students page's line of names, and its box is the rounded one
   with the shadow it has always drawn rather than that page's square one. What else is
   new is the width: five columns need a tighter cell than two, the date is not left to
   wrap, a memo or a type can be a whole sentence or a slug with no space in it, and a
   table wider than the phone it is read on scrolls inside the frame rather than pushing
   the card out of the window. The rules that override a roster rule name both classes on
   purpose: they must win whatever order the two blocks end up in.

   The admin's page — transaction-view.html — carries one column more than the sketch's
   five: the controls at the end of each row. The red delete button takes that transaction
   away, and the four boxes that open over a row it is clicked on retype it — that row's
   cell holding save and cancel where the delete button was. The student's own page, which
   is the same table and the same script, carries none of it, so every rule below is written
   against the classes transactionview.js puts on those rows and cells
   (.history__row--changeable, .history__row--open, .history__actions, .history__box) rather
   than against the table — and the head row of the student's page keeps the five words it
   has always had, while the admin's sixth column is given a word nobody sees. */
.roster__frame.history__frame {
    border-width: 1.5px;
    border-radius: var(--radius-sm);
    box-shadow: 0 10px 22px rgba(4, 48, 43, 0.16);
    overflow-x: auto;
}

/* Five columns have to be given somewhere to land: the table keeps a width of its own
   and the frame above scrolls sideways rather than squeezing "bonus" onto two lines.
   The two columns that carry sentences get their own minimum, so the wrap happens
   between words — a memo or a type is read as words, not as letters. Each
   minimum is the width of the longest word that column is likely to hold rather than a
   round number, and the cell's own gutters are tighter here than the roster's for the
   same reason: at this font "BONUS" asks for 5rem and "seconds" 5.5rem.
   The figures are then picked so the five columns *and their headings* come out no
   wider than the frame the card draws them in: measured, that frame is 644px at the
   .page width above and 619px in a 760px window, and a frame clips what it cannot hold
   — the column at the end of the row is the one cut off, and a balance nobody can see
   without dragging a scrollbar is a balance nobody reads. The date column needs no
   minimum of its own either: its heading is left able to break at the space between the
   date and the time, so the column is sized by the stamp under it — 2026/09/29 16:17,
   121px at this font — and never by the heading, which asks for 262px on one line and
   then draws over three lines in the column it leaves. The floors above come to about
   475px together — the reason column's 7.5rem floor went with the column — so only a
   phone, whose frame is narrower than all five columns, scrolls. */
.history__table { min-width: 32rem; }

.roster__table.history__table .history__type { min-width: 5rem; }

.roster__table.history__table .history__memo { min-width: 5.5rem; }

.roster__table.history__table th,
.roster__table.history__table td {
    padding: clamp(0.45rem, 1.4vw, 0.6rem) clamp(0.4rem, 1.2vw, 0.5rem);
}

/* The roster keeps its figures from ever wrapping (.roster__amount, nowrap), which a
   head row must not inherit here: AMOUNT and ENDING BALANCE are words rather than
   figures, so they wrap and stop setting the width of the two columns. The cells below
   the head keep the nowrap, because there it is a number that must stay on one line. */
.roster__table.history__table th.history__amount-head { white-space: normal; }

/* The date is the one column never left to wrap. A stamp is read as one moment, not as a
   date on one line and a time on the next, so the space in the stamp — the only break a
   browser would take in it, since a slash and a colon are neither — is closed here and
   2026/09/29 16:17 stays on one line. The heading above it is left able to break at that
   same space, so the heading is never what sets the width of the column. The stamp is. */
.history__date { white-space: nowrap; }

/* The head of the date column names the shape its stamps are drawn in, and the letters of
   a shape are read as they are written: this page's head row is uppercased, which would
   turn the minutes' mm into the months' MM — in this shape MM is already the month, three
   letters earlier. This one parenthetical is therefore left exactly as it is written. */
.roster__table.history__table thead .history__pattern { text-transform: none; }

.roster__table.history__table .history__type,
.roster__table.history__table .history__memo { overflow-wrap: anywhere; }

/* The sixth column: what may be done with a row. It is as wide as the widest thing that
   ever stands in it — the save and cancel pair, which asks about 5.6rem where the delete
   button alone asks about 3.4rem — because a table column is as wide as its widest cell,
   and it never wraps: the two buttons would otherwise take one line each and make the row
   the height of a paragraph. Only the admin's page gives its rows this class. */
.roster__table.history__table .history__actions { white-space: nowrap; }

/* The controls are the app's own buttons at the size a cell can carry: the letter of a
   table cell rather than the letter of a page, since each stands beside 0.95rem text, and
   the same pair of palettes as the pages' own controls — the button that carries an action
   out is the filled ink one, the one that backs out of it the ink outline. The shadow the
   page's filled buttons carry comes off here: the frame clips it, and two shadows a button
   apart on the same line read as a smudge. */
.roster__table.history__table .history__actions .btn {
    padding: 0.3rem 0.55rem;
    font-size: 0.78rem;
    letter-spacing: 0.01em;
    border-radius: calc(var(--radius-sm) - 3px);
    box-shadow: none;
}

.roster__table.history__table .history__actions .btn + .btn { margin-left: 0.4rem; }

/* A row the backend answered for can be opened by clicking it, and says so under the
   pointer: the cursor turns into the hand and the row takes the tint the roster's rows
   already take, a shade stronger than .roster__row:hover's, because a click here does
   something. A read that was refused, or a backend that would not take a change, leaves the
   rows plain and without the hand. */
.history__row--changeable { cursor: pointer; }

.roster__row.history__row--changeable:hover { background: rgba(33, 255, 218, 0.42); }

/* The open row holds the tint while the pointer is away from it, so the boxes on the page
   are always boxes on the row being worked on. One shade stronger again than the hover,
   since it is the row that stops being the backend's and starts being the typist's. */
.roster__table.history__table tr.history__row--open { background: rgba(33, 255, 218, 0.58); }

/* The boxes a row is retyped in: the page's own fields — a pale box with an ink outline on
   the accent card (.field input) — cut down to a cell. Nothing else moves: the cell keeps
   its own padding and its place in the table, so a row the same width as its neighbours
   opens without the table shifting under the pointer that clicked it. The ink is the cell's
   own rather than the field's, so a figure below zero is retyped in the red the figure was
   read in, and the figure's digits stay fixed-width, so an amount does not step sideways
   when it is opened. */
.roster__table.history__table .history__box {
    width: 100%;
    min-width: 3.5rem;
    padding: 0.3rem 0.4rem;
    font-family: inherit;
    font-size: 0.88rem;
    color: inherit;
    background: var(--surface);
    border: 1.5px solid var(--accent-ink);
    border-radius: calc(var(--radius-sm) - 3px);
}

.roster__table.history__table .history__box:hover { background: var(--surface-3); }

.roster__table.history__table .history__box:focus {
    outline: none;
    background: var(--surface-3);
    box-shadow: var(--ring);
}

/* the two figure columns: the same figures in the same box, so what is typed and the
   balance under it end on the same edge — the right one */
.roster__table.history__table td.roster__amount .history__box {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 650;
}

/* ------------------------------------------------------------ footnote ---- */
/* The foot of the card: the line that says where else you could have gone. margin-top:
   auto pushes it to the bottom of the card the way the hub pushes Sign out to its own
   foot — the card is a column, so a page shorter than the card gets the empty teal
   between its content and its footnote that the sketch draws. */
.footnote {
    margin: auto 0 0;
    padding-top: 1.1rem;
    font-size: 0.85rem;
    color: var(--accent-ink-soft);
    border-top: 1px solid rgba(4, 48, 43, 0.35);
}

/* the one piece of code on the pages — the <code>Lost library book (-25 pts)</code> in the
   other-transaction hint — keeps its chip but takes the card's ink for its word, the
   accent tint it used to be written in being a tint of the card's own colour */
code {
    padding: 0.15rem 0.4rem;
    font-family: var(--mono);
    font-size: 0.9em;
    color: var(--accent-ink);
    background: rgba(255, 255, 255, 0.55);
    border: 1px solid rgba(4, 48, 43, 0.45);
    border-radius: 6px;
}

/* --------------------------------------------------------------- a11y ----- */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
    }
    .btn--primary:hover,
    .btn--ink:hover,
    .btn--door:hover { transform: none; }
}

@media (max-width: 30rem) {
    .form__actions .btn { flex: 1 1 100%; }
    .chip { width: 100%; justify-content: center; }
    /* one door above the other, rather than two squeezed side by side */
    .signin__actions { grid-template-columns: 1fr; }
    /* the same once the doors run out of width */
    .hub__actions { grid-template-columns: 1fr; }
    .hub__foot .btn { align-self: stretch; }
}