/* ==========================================================================
   DGS Report Card - application screens.

   The printed card has its own stylesheet (card.css). This one dresses the
   rooms a teacher works in: sign-in, class lists, the card editor, admin.

   Cache-busting is by filemtime() in rcAsset(), so there is no ?v= token to
   remember to bump when this file changes.
   ========================================================================== */

:root {
    /*
     * skill-ed.in's own tokens, so the screens a teacher works in and the card
     * they produce come from one palette. The first version of this was deep
     * green and gold, which belonged to a different school entirely.
     */
    --coral:  #f8732e;  --coral-d: #e85c1a;
    --sky:    #2dbeec;  --sky-d:   #179ec9;
    --sun:    #ffc02e;  --sun-d:   #f3a712;
    --leaf:   #8cc63e;  --leaf-d:  #6fae26;
    --berry:  #f0568c;  --berry-d: #d63d73;
    --grape:  #9b6dde;  --grape-d: #7d4fc4;

    --ink:       #3a2f55;   /* friendly deep purple instead of black */
    --ink-soft:  #6c6286;
    /*
     * Darkened from #9a92ad, which measured 2.96:1 against plain white - below
     * the 4.5:1 floor before a single pane of glass was involved. It carries
     * the small print everywhere: table headings, row subtitles, hints,
     * breadcrumbs. This reads at 4.93:1 on white and still sits back from the
     * body text.
     */
    --ink-faint: #706887;
    --paper:     #f7f3fb;
    --card:      #ffffff;
    --cream:     #fff8ec;
    --line:      #e6e0f0;
    --line-soft: #f1edf7;
    /* Secondary text when it sits on translucent glass rather than
       solid white - the normal --ink-soft is too light there. */
    --glass-muted: #55496f;

    /* Pale washes, one per accent, for chips and panels. */
    --coral-pale: #ffe9dc;
    --sky-pale:   #e2f6fd;
    --sun-pale:   #fff4d9;
    --leaf-pale:  #eef8e0;
    --berry-pale: #ffe4ee;
    --grape-pale: #f0e8fd;
    /* The bright palette is for fills. Writing needs darker ink, or it fails
       against the pale tint behind it: leaf was 2.47:1, berry 3.68:1. */
    --sun-ink:    #7d5710;
    --leaf-ink:   #4e7c18;
    --berry-ink:  #c7255f;

    /* The four growth levels, identical to card.css so what a teacher picks on
       screen is the colour that prints. */
    --g1: var(--sky);
    --g2: var(--leaf);
    --g3: var(--sun);
    --g4: var(--coral);

    /*
     * One spacing scale, on a 4px grid. Before this the markup carried ten
     * different top margins by hand - 1rem, 1.5rem, 1.2rem, .9rem, .8rem,
     * .6rem, .5rem - so no two panels breathed the same way. Everything below
     * uses these, and the .gap-* utilities exist so a template never has to
     * write a margin inline again.
     */
    --s-1: .25rem;
    --s-2: .5rem;
    --s-3: .75rem;
    --s-4: 1rem;
    --s-5: 1.25rem;
    --s-6: 1.5rem;
    --s-7: 2rem;

    --radius:   14px;
    --radius-l: 22px;
    --shadow:   0 8px 20px rgba(58, 47, 85, .10);
    --shadow-l: 0 18px 40px rgba(58, 47, 85, .14);
    --font:  'Nunito', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --serif: 'Baloo 2', 'Fredoka', Georgia, "Iowan Old Style", "Palatino Linotype", Palatino, serif;
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    font: 15px/1.6 var(--font);
    color: var(--ink);
    display: flex;
    flex-direction: column;
    min-height: 100vh;

    /*
     * Glass needs something behind it, but a working screen is not a login
     * screen: these washes are at .14 where the sign-in card sits over .55,
     * because this page carries tables and small print rather than four
     * fields. Enough colour for the panels to pick up, not enough to read
     * through them.
     */
    background:
        radial-gradient(42rem 42rem at 6% -6%, rgba(248, 115, 46, .30), transparent 62%),
        radial-gradient(40rem 40rem at 96% 2%, rgba(45, 190, 236, .30), transparent 62%),
        radial-gradient(46rem 46rem at 88% 96%, rgba(240, 86, 140, .26), transparent 62%),
        radial-gradient(36rem 36rem at 4% 92%, rgba(255, 192, 46, .24), transparent 62%),
        var(--paper);
    background-attachment: fixed;
}

a { color: var(--coral-d); }
a:hover { color: var(--coral); }

h1, h2, h3 { line-height: 1.25; margin: 0 0 .4em; font-weight: 800; font-family: var(--serif); }
h1 { font-size: 1.5rem; letter-spacing: -.01em; }
h2 { font-size: 1.15rem; }
h3 { font-size: 1rem; }

/* --------------------------------------------------------------------------
   Top bar
   -------------------------------------------------------------------------- */

/*
 * Dark glass, and sticky, because the two go together: a translucent bar over
 * a page that never moves is just a bar with the wrong opacity. The point is
 * watching the class tiles slide under it.
 */
.topbar {
    position: sticky;
    top: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    gap: 1.5rem;
    padding: var(--s-3) var(--s-5);
    color: #fff;
    flex-wrap: wrap;

    background: rgba(48, 39, 72, .78);
    -webkit-backdrop-filter: blur(22px) saturate(180%);
    backdrop-filter: blur(22px) saturate(180%);
    border-bottom: 1px solid rgba(255, 255, 255, .10);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .12),
        0 6px 24px rgba(35, 26, 58, .18);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .topbar { background: var(--ink); }
}

.brand { display: flex; flex-direction: column; gap: .15rem; text-decoration: none; color: #fff; line-height: 1.15; }
/* The logo is multicolour on transparent, so it sits happily on the purple
   bar without needing a reversed version. */
.brand-logo { display: block; height: 26px; width: auto; }
.brand-sub { font-size: .68rem; text-transform: uppercase; letter-spacing: .12em; opacity: .75; }

.mainnav { display: flex; gap: .25rem; flex: 1; flex-wrap: wrap; }
.mainnav a {
    color: rgba(255, 255, 255, .82);
    text-decoration: none;
    padding: .35rem .7rem;
    border-radius: 999px;
    font-size: .92rem;
    white-space: nowrap;
}
.mainnav a:hover { background: rgba(255, 255, 255, .12); color: #fff; }
.mainnav a.on { background: rgba(255, 255, 255, .18); color: #fff; font-weight: 600; }

.whoami { display: flex; align-items: baseline; gap: .55rem; font-size: .85rem; }
.who-name { font-weight: 600; }
.who-role { opacity: .68; font-size: .76rem; text-transform: uppercase; letter-spacing: .07em; }
.signout { color: rgba(255, 255, 255, .82); text-decoration: none; border-bottom: 1px solid rgba(255, 255, 255, .35); }
.signout:hover { color: #fff; }

/* --------------------------------------------------------------------------
   Page frame
   -------------------------------------------------------------------------- */

/*
 * min-width:0 is doing real work here. body is a flex column and .wrap is a
 * flex item, and a flex item defaults to min-width:auto - it refuses to shrink
 * below its content. The rating table has a 30rem minimum, so .wrap grew to
 * fit it and dragged the whole page sideways on a phone, even though the table
 * had its own scroll box. Zero lets it shrink and lets the box do its job.
 */
.wrap { flex: 1; min-width: 0; width: min(1120px, 100% - 2.5rem); margin: 1.75rem auto; }
.wrap.narrow { width: min(640px, 100% - 2.5rem); }

.pagefoot {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    padding: 1.1rem 1.5rem;
    border-top: 1px solid var(--line);
    color: var(--ink-faint);
    font-size: .82rem;
}
.foot-note { font-style: italic; }

.pagehead {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--s-4);
    flex-wrap: wrap;
    margin-bottom: var(--s-5);
}
.pagehead p { margin: .15rem 0 0; color: var(--ink-soft); font-size: .92rem; }

.crumb { font-size: .82rem; color: var(--ink-faint); margin-bottom: .3rem; }
.crumb a { color: var(--ink-faint); }

/* --------------------------------------------------------------------------
   Panels, tables
   -------------------------------------------------------------------------- */

/*
 * Light glass. .82 rather than the sign-in card's .80 because a panel here can
 * be a table of forty children, and every extra point of opacity is contrast
 * the small print keeps. The inset top line is the lit rim of the pane.
 */
.panel {
    background: rgba(255, 255, 255, .82);
    -webkit-backdrop-filter: blur(20px) saturate(170%);
    backdrop-filter: blur(20px) saturate(170%);
    border: 1px solid rgba(255, 255, 255, .70);
    border-radius: var(--radius-l);
    box-shadow:
        0 10px 30px rgba(58, 47, 85, .10),
        inset 0 1px 0 rgba(255, 255, 255, .90);
    padding: var(--s-5);
    margin-bottom: var(--s-5);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .panel { background: rgba(255, 255, 255, .97); }
}
.panel > :last-child { margin-bottom: 0; }
.panel-head { display: flex; justify-content: space-between; align-items: center; gap: var(--s-4); flex-wrap: wrap; margin-bottom: var(--s-4); }
.panel-head h2 { margin: 0; }
.panel-note { color: var(--ink-soft); font-size: .88rem; margin: calc(var(--s-1) * -1) 0 var(--s-4); }

table.grid { width: 100%; border-collapse: collapse; font-size: .93rem; }
table.grid th, table.grid td { padding: var(--s-2) var(--s-2); text-align: left; border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
table.grid th {
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--ink-faint);
    font-weight: 700;
    border-bottom: 1px solid var(--line);
}
table.grid tbody tr:hover { background: var(--grape-pale); }
table.grid td.num { text-align: right; font-variant-numeric: tabular-nums; }
table.grid .rowname { font-weight: 600; }
table.grid .rowsub { display: block; color: var(--ink-faint); font-size: .82rem; font-weight: 400; }

.empty { padding: var(--s-7) var(--s-4); text-align: center; color: var(--ink-faint); }
.empty strong { display: block; color: var(--ink-soft); font-size: 1.02rem; margin-bottom: .25rem; }

/* Class tiles on the dashboard */
.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: var(--s-4); }
.tile {
    display: block;
    background: rgba(255, 255, 255, .78);
    -webkit-backdrop-filter: blur(18px) saturate(170%);
    backdrop-filter: blur(18px) saturate(170%);
    border: 1px solid rgba(255, 255, 255, .70);
    border-radius: var(--radius-l);
    box-shadow:
        0 10px 28px rgba(58, 47, 85, .10),
        inset 0 1px 0 rgba(255, 255, 255, .90);
    padding: 1.1rem 1.2rem;
    text-decoration: none;
    color: inherit;
    transition: transform .16s ease, box-shadow .16s ease, background .16s ease;
}
.tile:hover {
    transform: translateY(-3px);
    background: rgba(255, 255, 255, .90);
    box-shadow:
        0 18px 40px rgba(58, 47, 85, .16),
        inset 0 1px 0 rgba(255, 255, 255, .95);
    color: inherit;
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .tile { background: rgba(255, 255, 255, .97); }
}
@media (prefers-reduced-motion: reduce) {
    .tile, .tile:hover { transform: none; transition: none; }
}
.tile-name { font-size: 1.12rem; font-weight: 650; }
.tile-house { color: var(--berry); font-size: .84rem; font-weight: 700; letter-spacing: .02em; }
.tile-meta { margin-top: .7rem; color: var(--ink-soft); font-size: .86rem; display: flex; gap: .8rem; flex-wrap: wrap; }

/* --------------------------------------------------------------------------
   Buttons, chips, progress
   -------------------------------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .5rem .95rem;
    border-radius: 999px;
    border: 2px solid var(--coral);
    background: var(--coral);
    color: #fff;
    font: inherit;
    font-size: .9rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
}
.btn:hover { background: var(--coral-d); border-color: var(--coral-d); color: #fff; }
.btn-ghost { background: transparent; color: var(--coral-d); }
.btn-ghost:hover { background: var(--coral-pale); color: var(--coral-d); }
/* Secondary buttons are glass too, or they sit on a glass panel looking like
   stickers. */
.btn-plain {
    background: rgba(255, 255, 255, .72);
    -webkit-backdrop-filter: blur(12px) saturate(150%);
    backdrop-filter: blur(12px) saturate(150%);
    color: var(--ink-soft);
    border-color: rgba(58, 47, 85, .14);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .70);
}
.btn-plain:hover { background: var(--grape-pale); color: var(--ink); border-color: var(--grape); }
.btn-danger { background: var(--berry-d); border-color: var(--berry-d); }
.btn-danger:hover { background: #b8305f; border-color: #b8305f; }
.btn-sm { padding: .3rem .7rem; font-size: .82rem; }
.btn[disabled], .btn:disabled { opacity: .5; cursor: not-allowed; }

.btnrow { display: flex; gap: var(--s-2); flex-wrap: wrap; align-items: center; }

.chip {
    display: inline-block;
    padding: .18rem .65rem;
    border-radius: 999px;
    font-size: .74rem;
    font-weight: 800;
    letter-spacing: .03em;
    text-transform: uppercase;
    white-space: nowrap;
    /* A hairline of its own colour and a lit top edge, so a chip reads as a
       tinted pane on the panel rather than a sticker pressed onto it. */
    border: 1px solid rgba(58, 47, 85, .08);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .60);
}
.chip-draft     { background: var(--line-soft); color: var(--ink-soft); }
.chip-submitted { background: var(--sun-pale);   color: var(--sun-ink); }
.chip-published { background: var(--leaf-pale);  color: var(--leaf-ink); }

.prog { display: inline-flex; align-items: center; gap: .5rem; }
.prog-bar { display: block; width: 84px; height: 6px; border-radius: 999px; background: var(--line); overflow: hidden; }
.prog-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--g3); }
.prog-full .prog-bar > span { background: var(--leaf); }
.prog-half .prog-bar > span { background: var(--sun); }
.prog-low  .prog-bar > span { background: var(--coral); }
.prog-num { font-size: .8rem; color: var(--ink-faint); font-variant-numeric: tabular-nums; min-width: 2.6em; }

.flash {
    padding: var(--s-3) var(--s-4);
    border-radius: var(--radius);
    margin-bottom: var(--s-4);
    font-size: .93rem;
    border: 1px solid transparent;
    -webkit-backdrop-filter: blur(14px) saturate(160%);
    backdrop-filter: blur(14px) saturate(160%);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .55);
}
.flash-ok   { background: var(--leaf-pale);  border-color: #cfe6ad; color: var(--leaf-ink); }
.flash-warn { background: var(--sun-pale);   border-color: #f4dda2; color: var(--sun-ink); }
.flash-err  { background: var(--berry-pale); border-color: #f8c4d8; color: var(--berry-ink); }

/* --------------------------------------------------------------------------
   Forms
   -------------------------------------------------------------------------- */

label { display: block; font-weight: 600; font-size: .88rem; margin-bottom: .28rem; }
.hint { font-weight: 400; color: var(--ink-faint); font-size: .82rem; display: block; margin-top: .18rem; }

/*
 * Wells cut into the panel rather than tiles laid on it: a touch translucent,
 * with a hairline inset shadow at the top so the edge catches light.
 *
 * .72 sounds thin, but translucency compounds here and it lands almost white:
 * this sits inside a .82 panel which itself sits on a pale wash, so what a
 * teacher types comes out at better than 11:1. Measured, not assumed.
 */
input[type=text], input[type=email], input[type=password], input[type=date],
input[type=number], input[type=url], select, textarea {
    width: 100%;
    padding: .55rem .7rem;
    border: 1px solid rgba(58, 47, 85, .13);
    border-radius: var(--radius);
    background: rgba(255, 255, 255, .72);
    box-shadow: inset 0 1px 2px rgba(58, 47, 85, .05);
    font: inherit;
    color: var(--ink);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    input[type=text], input[type=email], input[type=password], input[type=date],
    input[type=number], input[type=url], select, textarea { background: #fff; }
}
/*
 * One height for every single-line control, stated rather than inherited.
 *
 * A date input carries a picker icon and renders about 2px taller than a text
 * input. Invisible on its own; obvious the moment the two sit in a row that is
 * meant to line up - which is exactly what happened when Date of birth became
 * a calendar and stopped matching Name and My class teacher beside it.
 * min-height did not fix it, because 2px taller was still above the minimum.
 */
input[type=text], input[type=email], input[type=password], input[type=date],
input[type=number], input[type=url], select { height: 2.7rem; }

textarea { line-height: 1.6; resize: vertical; min-height: 6.5rem; font-family: var(--font); }
input:focus, select:focus, textarea:focus {
    outline: 2px solid var(--grape);
    outline-offset: 1px;
    border-color: var(--grape);
    background: rgba(255, 255, 255, .97);
    box-shadow: inset 0 1px 2px rgba(58, 47, 85, .04);
}
input[readonly], input:disabled, textarea:disabled { background: var(--line-soft); color: var(--ink-soft); }
input:disabled, textarea:disabled { cursor: not-allowed; opacity: .8; }

.field { margin-bottom: var(--s-4); }
.fields { display: grid; gap: var(--s-4); grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
.fields.two { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }

/*
 * Line the controls up across a row, whatever height the labels above them
 * happen to be.
 *
 * "Name" carries a hint - "From the school roll" - and "Date of birth" and
 * "My class teacher" do not. Content flowing top-down inside each cell put the
 * Name box a whole line lower than the two beside it, so a row that should
 * read as one row read as a staircase.
 *
 * The grid already stretches every cell to the tallest, so making the cell a
 * flex column and giving the control margin-top:auto drops all three onto the
 * same baseline. Scoped to controls inside .fields: a lone .field has no spare
 * height, so the auto margin does nothing there.
 */
.fields > .field { display: flex; flex-direction: column; margin-bottom: 0; }
.fields > .field > input,
.fields > .field > select,
.fields > .field > .inline-pair { margin-top: auto; }

.check { display: flex; align-items: flex-start; gap: var(--s-2); font-weight: 400; }
.check input { width: auto; margin-top: .28rem; }

/* --------------------------------------------------------------------------
   Spacing utilities

   Deliberately few. Three sizes covered every gap the templates had been
   setting by hand - ten different top margins across nine files, so no two
   panels breathed the same way. A short list is what stops the next person
   inventing an eleventh.
   -------------------------------------------------------------------------- */

.gap-sm   { margin-top: var(--s-2); }
.gap-md   { margin-top: var(--s-4); }
.gap-lg   { margin-top: var(--s-6); }
.gap-none { margin: 0; }
.flush    { margin-bottom: 0; }

/* The table rule below is more specific than a bare class, so the utility has
   to say table cell out loud or a right-aligned action column stays left. */
.align-right, table.grid th.align-right, table.grid td.align-right { text-align: right; }
/* Only bites outside a flex row: a flex child is blockified whatever this
   says, which is why the inline style it replaced did nothing in .btnrow
   either. It is the hint spans that need it. */
.inline-form { display: inline; }
.row-end     { justify-content: flex-end; }
/* Rows that are off the roll, or template rows that have been retired.
   These were dimmed with opacity, which dropped their text to 2.79:1 - and
   they are exactly the rows an admin has to read in order to put them back.
   The state is carried by a tint and by the wording beside the name. */
tr.is-off > td            { background: var(--line-soft); }
tr.is-off > td,
tr.is-off .rowname        { color: var(--ink-soft); font-weight: 500; }

/* A label sitting beside its control on one line, rather than above it. */
.label-inline { margin: 0 var(--s-2) 0 0; }
/* Prose that belongs to the thing directly beneath it. */
.gap-tight    { margin-bottom: var(--s-2); }
/* A field that should not stretch the width of a panel: a short select, a
   code, a name. */
.narrow-field { max-width: 22rem; }
/* A column of panels with no outer frame of its own - the change-password
   page is one panel and does not want the dashboard grid around it. */
.panel-stack  { width: 100%; }

/* Text that needs to catch the eye without being an error banner: a date the
   importer could not read, a locked account. */
.warn-text { color: var(--berry-ink); font-weight: 600; }

/* A panel that is announcing something rather than just holding it. The bar of
   colour along the top is the whole signal. */
.accent-sun   { border-color: var(--sun);   border-top: 4px solid var(--sun); }
.accent-berry { border-color: var(--berry); border-top: 4px solid var(--berry); }

/* A list inside a panel, kept close to the prose above it. */
.tight-list { margin: 0; padding-left: var(--s-5); color: var(--ink-soft); }
.tight-list li + li { margin-top: var(--s-2); }

/* --------------------------------------------------------------------------
   Sign in
   -------------------------------------------------------------------------- */

/* ==========================================================================
   Sign in - frosted glass
   ==========================================================================
   Glass is only glass if there is something behind it worth blurring. A
   translucent panel over a flat colour just looks like a panel with the wrong
   opacity, so the backdrop is built first: four soft brand-coloured lights on
   a deep purple ground, which the card then refracts.

   Everything is self-contained CSS. The Content-Security-Policy allows no
   external images or scripts, and none are needed for this.
   ========================================================================== */

body.signin {
    display: grid;
    place-items: center;
    color: var(--ink);
    background:
        radial-gradient(58rem 58rem at 12% 16%, rgba(248, 115, 46, .55), transparent 62%),
        radial-gradient(50rem 50rem at 88% 10%, rgba(45, 190, 236, .50), transparent 62%),
        radial-gradient(54rem 54rem at 82% 90%, rgba(240, 86, 140, .48), transparent 62%),
        radial-gradient(46rem 46rem at 16% 94%, rgba(255, 192, 46, .42), transparent 62%),
        #2a2140;
    background-attachment: fixed;
    overflow-x: hidden;
}

/* One slow-drifting light, so the glass has something that moves under it.
   Purely decorative: it sits behind everything and is switched off entirely
   for anyone who has asked for less motion. */
body.signin::before {
    content: "";
    position: fixed;
    inset: -30vmax;
    z-index: 0;
    background: radial-gradient(38rem 38rem at 50% 50%, rgba(155, 109, 222, .45), transparent 60%);
    animation: signin-drift 26s ease-in-out infinite alternate;
    pointer-events: none;
}
@keyframes signin-drift {
    from { transform: translate3d(-8%, -6%, 0) scale(1); }
    to   { transform: translate3d(10%,  8%, 0) scale(1.15); }
}
@media (prefers-reduced-motion: reduce) {
    body.signin::before { animation: none; }
}

/* The sign-in page borrows the standard shell, which brings a footer with it.
   On a page that is one centred box, that footer is a stray grey line. */
body.signin .pagefoot { display: none; }
body.signin .wrap { margin: 0; position: relative; z-index: 1; }

.signin-box {
    width: min(440px, 100% - 2.5rem);
    margin: 2rem auto;
    padding: 2.25rem 2rem;
    border-radius: 28px;

    /*
     * The glass itself.
     *
     * saturate() alongside the blur is what stops frosted glass looking grey:
     * it pulls the colour back up in what has just been smeared out, which is
     * the trick Apple's vibrancy materials use.
     *
     * The opacity is .80, and the secondary text on the card is darkened to
     * --glass-muted, because this is a form people sign into every morning and
     * the text has to stay readable where the deep backdrop shows through.
     *
     * Those two numbers are measured, not chosen. At .74 with the normal muted
     * grey the small print came out at 3.30:1 against the darkest part of the
     * background - below the 4.5:1 floor for body text, which is the point
     * where frosted glass stops being a style and starts being a squint.
     */
    background: rgba(255, 255, 255, .80);
    -webkit-backdrop-filter: blur(34px) saturate(180%);
    backdrop-filter: blur(34px) saturate(180%);
    border: 1px solid rgba(255, 255, 255, .55);

    /*
     * Three shadows doing three jobs: the cast shadow that lifts the card off
     * the page, a bright inset top edge which is the specular highlight along
     * the rim of a real pane, and a faint dark inset at the foot so the glass
     * has thickness rather than being a sticker.
     */
    box-shadow:
        0 30px 70px rgba(20, 12, 40, .38),
        inset 0 1px 0 rgba(255, 255, 255, .85),
        inset 0 -1px 0 rgba(58, 47, 85, .06);
}

/*
 * Where the browser cannot blur, translucency is just washed-out text. Go
 * nearly opaque instead and keep the rounded, lifted shape.
 */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .signin-box { background: rgba(255, 255, 255, .97); }
}

/* The masthead is one centred block. The logo was centring on its own while
   the title and the school name sat left, which read as a mistake rather than
   a choice. Form labels below stay left, where a form wants them. */
.signin-head { text-align: center; margin-bottom: 1.6rem; }
.signin-logo { display: block; width: 152px; height: auto; margin: 0 auto 1.1rem; }
.signin-box h1 { font-size: 1.4rem; margin: 0 0 .3rem; letter-spacing: -.01em; }
.signin-meta { color: var(--glass-muted); font-size: .82rem; margin: 0; }

/* The fields are glass too, a shade clearer than the card so they read as
   wells cut into it rather than tiles sitting on top. */
.signin-box input[type=email],
.signin-box input[type=password] {
    background: rgba(255, 255, 255, .55);
    border: 1px solid rgba(58, 47, 85, .14);
    box-shadow: inset 0 1px 2px rgba(58, 47, 85, .06);
}
.signin-box input[type=email]:focus,
.signin-box input[type=password]:focus {
    background: rgba(255, 255, 255, .92);
    border-color: var(--grape);
}

.signin-box .btn {
    width: 100%;
    justify-content: center;
    margin-top: .6rem;
    height: 2.9rem;
    border-radius: 999px;
    /* A lit top edge and a shadow in the brand colour, so the button looks like
       it belongs to the same material as the card. */
    background: linear-gradient(180deg, #ff8a4d 0%, var(--coral) 55%, var(--coral-d) 100%);
    border-color: transparent;
    box-shadow:
        0 8px 20px rgba(232, 92, 26, .40),
        inset 0 1px 0 rgba(255, 255, 255, .45);
}
.signin-box .btn:hover {
    background: linear-gradient(180deg, #ff7f3c 0%, var(--coral-d) 60%, #d24f12 100%);
    box-shadow:
        0 10px 26px rgba(232, 92, 26, .48),
        inset 0 1px 0 rgba(255, 255, 255, .45);
}
.signin-box .btn:active { transform: translateY(1px); }

.signin-box .hint { color: var(--glass-muted); }

/* --------------------------------------------------------------------------
   The card editor
   -------------------------------------------------------------------------- */

/*
 * The one bar a teacher keeps in view for half an hour: the child's name, how
 * much is written, and the way out to the print preview. Glass, and blurred a
 * little harder than a panel, because the card content scrolls right under it.
 */
.editor-head {
    display: flex;
    gap: 1.1rem;
    align-items: center;
    flex-wrap: wrap;
    background: rgba(255, 255, 255, .80);
    -webkit-backdrop-filter: blur(24px) saturate(175%);
    backdrop-filter: blur(24px) saturate(175%);
    border: 1px solid rgba(255, 255, 255, .70);
    border-radius: var(--radius-l);
    box-shadow:
        0 10px 30px rgba(58, 47, 85, .12),
        inset 0 1px 0 rgba(255, 255, 255, .90);
    padding: 1rem 1.25rem;
    margin-bottom: 1.25rem;
    position: sticky;
    /* Below the top bar, which now owns top:0. Both at zero and the child's
       name would slide underneath the nav. */
    top: 3.75rem;
    z-index: 20;
}
.editor-who { flex: 1; min-width: 200px; }
.editor-who strong { font-size: 1.15rem; display: block; }
.editor-who span { color: var(--ink-soft); font-size: .87rem; }

/* Section headings inside the editor mirror the printed domains, numbered the
   same way, so a teacher checking their work against the paper is looking at
   the same landmarks. */
.domain { margin-bottom: var(--s-5); }
.domain-head { display: flex; gap: .8rem; align-items: baseline; margin-bottom: .2rem; }
.domain-no {
    font-family: var(--serif);
    font-size: 1.6rem;
    color: var(--berry);
    line-height: 1;
}
.domain-title { font-size: 1.15rem; font-weight: 650; }
.domain-sub { color: var(--ink-soft); font-size: .89rem; margin: 0 0 .9rem; }

/*
 * On a phone the rating table cannot shrink below about 415px - four columns
 * of dots plus a readable skill name - so it used to push the whole page
 * sideways and every panel with it. It scrolls inside its own box instead:
 * the page stays put and only the table moves, which is the one place a
 * sideways drag is expected.
 */
.skills-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table.skills { width: 100%; min-width: 30rem; border-collapse: collapse; }
table.skills th {
    font-size: .68rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--ink-faint);
    font-weight: 700;
    padding: .4rem .3rem;
    text-align: center;
    border-bottom: 1px solid var(--line);
}
table.skills th:first-child { text-align: left; }
table.skills th small { display: block; font-weight: 400; text-transform: none; letter-spacing: 0; font-size: .72rem; opacity: .8; }
table.skills td { padding: .5rem .3rem; border-bottom: 1px solid var(--line-soft); text-align: center; }
table.skills td:first-child { text-align: left; width: 46%; }
/* Share what is left evenly, so the four dots sit under their own headings
   instead of bunching against the right edge. */
table.skills th:not(:first-child), table.skills td:not(:first-child) { width: 13.5%; }
.skill-label { font-weight: 600; font-size: .93rem; }
.skill-desc { color: var(--ink-faint); font-size: .81rem; }

/* The rating control is a radio with the input itself hidden - the label is
   the target, which makes the tap area the whole cell rather than a 13px
   circle. Keyboard focus is drawn on the label via :focus-visible on the
   input, so it stays reachable without a mouse. */
/*
 * position:relative is not decoration. The radio inside is absolutely
 * positioned to hide it, and without a positioned ancestor it escapes to the
 * initial containing block - so once the rating table scrolled sideways, those
 * invisible 1px inputs sat at x=480 and stretched the whole document to match.
 * A page that scrolls sideways on a phone because of an input nobody can see.
 */
.rate { position: relative; display: inline-grid; place-items: center; cursor: pointer; }
.rate input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.rate span {
    width: 22px; height: 22px;
    border-radius: 50%;
    border: 2px solid rgba(58, 47, 85, .16);
    background: rgba(255, 255, 255, .70);
    box-shadow: inset 0 1px 1px rgba(58, 47, 85, .05);
    display: block;
    transition: background .12s ease, border-color .12s ease, transform .12s ease;
}
.rate:hover span { border-color: var(--grape); }
.rate input:checked + span {
    border-color: transparent;
    transform: scale(1.12);
    box-shadow: 0 2px 6px rgba(58, 47, 85, .22), inset 0 1px 0 rgba(255, 255, 255, .40);
}
.rate input:focus-visible + span { outline: 2px solid var(--grape); outline-offset: 3px; }
.rate-1 input:checked + span { background: var(--g1); }
.rate-2 input:checked + span { background: var(--g2); }
.rate-3 input:checked + span { background: var(--g3); }
.rate-4 input:checked + span { background: var(--g4); }

.prompt { margin-top: var(--s-4); }
.prompt label { font-size: .9rem; }

/* Characters remaining, sat under the box it belongs to. Quiet until the room
   is nearly gone - a counter that shouts from the first keystroke is a counter
   people stop seeing. */
.counter {
    display: block;
    text-align: right;
    font-size: .76rem;
    color: var(--ink-faint);
    margin-top: .15rem;
    font-variant-numeric: tabular-nums;
}
.counter-low { color: var(--sun-d); }
.counter-out { color: var(--berry-ink); font-weight: 700; }

.legend { display: flex; gap: var(--s-4); flex-wrap: wrap; margin: 0 0 var(--s-4); padding: 0; list-style: none; }
.legend li { display: flex; gap: .45rem; align-items: center; font-size: .82rem; color: var(--ink-soft); }
.legend i { width: 13px; height: 13px; border-radius: 50%; display: block; }
.legend .l1 { background: var(--g1); }
.legend .l2 { background: var(--g2); }
.legend .l3 { background: var(--g3); }
.legend .l4 { background: var(--g4); }

/* The three "Where I'm Growing Next" rows */
/*
 * Three columns, and each one a cell rather than a bare textarea.
 *
 * The textareas used to be direct children of this grid. Then the character
 * counter arrived, and app.js inserts its little "60 left" span immediately
 * after each box - which made six children per row, not three. The grid dutifully
 * flowed them 1-2-3 then 1-2-3, so every counter landed in the next row's first
 * column and the whole section stepped sideways down the page.
 *
 * A wrapper per column keeps the counter inside its own cell where it belongs.
 */
.growth-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--s-3) var(--s-4); margin-bottom: var(--s-3); align-items: start; }
.growth-row > div { min-width: 0; }
.growth-row textarea { min-height: 4.5rem; }
.growth-head { font-size: .72rem; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-faint); font-weight: 800; }

/*
 * "182  of  198  days" reads as one sentence and should sit as one. As two
 * fields in the auto-fitting grid they were flung to opposite ends of a
 * 1100px panel with no visible relationship.
 */
.inline-pair { display: flex; align-items: center; gap: .5rem; }
.inline-pair input { width: 6rem; text-align: center; }
.inline-pair span { color: var(--ink-soft); font-size: .92rem; white-space: nowrap; }

/* An attribution line under its quote, not beside it. */
.field.half { max-width: 24rem; }

/* Save bar pinned to the bottom, because these forms are long and a Save
   button at the end of four screens of typing is a Save button nobody finds
   until they have already navigated away. */
.savebar {
    position: sticky;
    bottom: 0;
    display: flex;
    gap: var(--s-3);
    align-items: center;
    flex-wrap: wrap;
    background: rgba(255, 255, 255, .86);
    -webkit-backdrop-filter: blur(22px) saturate(170%);
    backdrop-filter: blur(22px) saturate(170%);
    border: 1px solid rgba(255, 255, 255, .70);
    border-radius: var(--radius-l);
    /* Shadow upward, since the page passes underneath it, and tinted with the
       ink rather than the old brown. */
    box-shadow:
        0 -8px 26px rgba(58, 47, 85, .12),
        inset 0 1px 0 rgba(255, 255, 255, .90);
    padding: var(--s-3) var(--s-4);
    margin-top: var(--s-5);
    z-index: 20;
}
.savebar .spacer { flex: 1; }
.savebar .saved-note { color: var(--ink-faint); font-size: .84rem; }

.photo-slot { display: flex; gap: 1.25rem; align-items: flex-start; }
.photo-slot > div:last-child { flex: 1; min-width: 0; }
/* A file input will not shrink below the width of its own button and label,
   so on a narrow screen it pokes out of the panel. Cap it, and let the slot
   wrap so the control gets the full width instead of a squeezed column. */
.photo-slot input[type=file] { max-width: min(22rem, 100%); }
.photo-slot img { width: 76px; height: 92px; object-fit: cover; border-radius: 8px; border: 1px solid var(--line); background: var(--grape-pale); }
.photo-none { width: 76px; height: 92px; border-radius: 8px; border: 1px dashed var(--line); display: grid; place-items: center; color: var(--ink-faint); font-size: .7rem; text-align: center; padding: .3rem; }

@media (max-width: 720px) {
    .wrap { width: calc(100% - 1.6rem); margin: 1.1rem auto; }
    .growth-row { grid-template-columns: 1fr; }
    .photo-slot { flex-wrap: wrap; }

    /*
     * The data tables cannot shrink below about 460px - four columns of
     * headings and a child's name - so on a phone they used to push the whole
     * page sideways, every panel with it. display:block turns the table into
     * its own scroll box: the page stays put and only the table moves. Rows
     * and cells keep their table layout inside it.
     *
     * This is not a glass problem. It was there before any of this and shows
     * up on the dashboard, the class list, Students, Teachers and Settings.
     */
    table.grid { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
    table.skills td:first-child { width: auto; }
    .whoami { width: 100%; }
    .editor-head { position: static; }
}

/* Anything marked no-print is chrome: it exists to operate the app, not to
   describe a child. view.php is the page meant for paper. */
@media print {
    .topbar, .pagefoot, .savebar, .no-print { display: none !important; }
    body { background: #fff; }
}
