
  /* ==========================================================
     LIQUID GLASS — design tokens
     ========================================================== */
  :root{
    color-scheme:light dark;
    --blue:#0A84FF; --indigo:#5E5CE6; --purple:#AF52DE; --green:#34C759; --orange:#FF9F0A; --red:#FF3B30;

    /* Hygiene Quest: the badge's own green, and the silver of its ring. */
    --brand:#0B8A1F; --brand-deep:#05571A; --brand-silver:#C9CDD2;
    --brand-tint:rgba(11,138,31,.13); --brand-fg:#0A6B1B;

    --ink:#16161A; --ink-2:#55555D; --ink-3:#66666F;  /* ink-3 kept ≥4.5:1 on glass */
    --canvas:#E7ECF7;
    --glass:rgba(255,255,255,.55);
    --glass-2:rgba(255,255,255,.74);
    --glass-3:rgba(255,255,255,.92);
    --glass-brd:rgba(255,255,255,.70);
    --hairline:rgba(60,60,67,.13);
    --fill:rgba(120,120,128,.14);
    --fill-2:rgba(120,120,128,.24);
    --field:rgba(255,255,255,.62);
    --field-brd:rgba(60,60,67,.18);
    --inset-hi:inset 0 1px 0 rgba(255,255,255,.85), inset 0 0 0 .5px rgba(255,255,255,.42);

    --shadow-card:0 1px 2px rgba(18,22,40,.05), 0 12px 30px -16px rgba(18,22,40,.32);
    --shadow-pop:0 30px 70px -24px rgba(18,22,40,.45), 0 2px 8px rgba(18,22,40,.10);
    --shadow-btn:0 1px 2px rgba(0,0,0,.16), 0 10px 20px -12px rgba(0,0,0,.55);

    /* --f-* is the ink each tint carries, and the pairing is the point: these
       colours are read almost entirely *on their own tint*, in a notice, not
       on the plain card. Measured that way — tint over glass over canvas —
       blue was 3.89:1, green 3.51 and red 4.39, all under the 4.5 that text
       this size needs; purple and orange already cleared it. Checked against
       the card alone they look better than they are, which is why this went
       unnoticed: the wrong background was the one being imagined.

       Calibrated against the *worst* case the app actually renders, which is
       the tint laid straight on the canvas rather than on a glass card — a
       notice inside a card is the easier one, and calibrating there leaves
       the harder one failing by a fraction while looking checked. On that
       ground the old values measured green 3.23, blue 3.57, red 4.02 and
       purple 4.41 — all four under the 4.5 that text this size needs. Orange
       cleared it by 0.02, which is not a margin, so it moves with them. All
       five are 4.6 or better now.

       Darkened only as far as the line, so the family still reads as the same
       five colours. Light theme only; the dark values are their own set. */
    --t-blue:rgba(10,132,255,.15);   --f-blue:#095EB2;
    --t-purple:rgba(175,82,222,.15); --f-purple:#8739AF;
    --t-green:rgba(52,199,89,.17);   --f-green:#187035;
    --t-orange:rgba(255,159,10,.20); --f-orange:#8D5402;
    --t-red:rgba(255,59,48,.14);     --f-red:#B3271D;

    --blur:saturate(180%) blur(20px);
    --blur-lg:saturate(190%) blur(34px);
    --ease-out-expo:cubic-bezier(.16,1,.3,1);
    --spring:cubic-bezier(.34,1.56,.64,1);
  }
  @media (prefers-color-scheme:dark){
    :root{
      /* ink-3 is the quietest tier — timestamps, "Window closed", the small
         print under a heading. #93939E measured 4.25:1 against the lightest
         card tint in dark mode, where 4.5 is the floor for text this size,
         and the light theme's own token carries a note saying it was kept
         above that line. The dark one had never been checked. #9D9DA9 is
         4.82:1 on the same background and still visibly the quiet tier — it
         has to stay clear of --ink-2 above it or the hierarchy collapses. */
      --ink:#F2F2F7; --ink-2:#A8A8B2; --ink-3:#9D9DA9;
      --canvas:#07070B;
      --glass:rgba(38,38,45,.55);
      --glass-2:rgba(48,48,56,.72);
      --glass-3:rgba(58,58,66,.88);
      --glass-brd:rgba(255,255,255,.14);
      --hairline:rgba(255,255,255,.10);
      --fill:rgba(120,120,128,.26);
      --fill-2:rgba(120,120,128,.40);
      --field:rgba(255,255,255,.07);
      --field-brd:rgba(255,255,255,.15);
      --inset-hi:inset 0 1px 0 rgba(255,255,255,.13);
      --shadow-card:0 1px 2px rgba(0,0,0,.45), 0 14px 34px -16px rgba(0,0,0,.75);
      --shadow-pop:0 30px 70px -24px rgba(0,0,0,.85), 0 2px 8px rgba(0,0,0,.5);
      --t-blue:rgba(10,132,255,.24);   --f-blue:#74BAFF;
      --t-purple:rgba(175,82,222,.24); --f-purple:#DBA6F4;
      --t-green:rgba(52,199,89,.24);   --f-green:#63E282;
      --t-orange:rgba(255,159,10,.24); --f-orange:#FFC456;
      --t-red:rgba(255,59,48,.24);     --f-red:#FF847C;
    }
  }

  html{background:var(--canvas);}
  /* No padding-top: the 44px here used to reserve room for #protoBar, the
     prototype's screen-switcher. That bar was review scaffolding and does not
     exist in the product, so the padding was just a strip of empty backdrop
     above the header. */
  body{font-family:-apple-system,BlinkMacSystemFont,'SF Pro Display','SF Pro Text','Inter','Inter Local',system-ui,sans-serif;color:var(--ink);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;background:transparent!important;}
  .mono{font-family:ui-monospace,'SF Mono','JetBrains Mono',Menlo,Consolas,monospace;}

  /* How wide the page is allowed to get.
     Was max-w-[1400px], which on a 1440px laptop is edge-to-edge: tables
     stretch the full monitor, line lengths get long, and the whole console
     reads as oversized even though every font is the same size it is on a
     phone. Nothing here grows on desktop — there is not one font-size inside
     a min-width media query — so width was the only thing making it feel big.
     Set here rather than by editing tailwind.css, because that build is
     frozen and a class called max-w-[1400px] should mean 1400px. */
  .page-width{max-width:1600px;}

  /* ---- ambient backdrop (the "wallpaper" the glass refracts) ----

     Deliberately faint. At the prototype's opacity this was the most
     saturated thing on screen — a rainbow wash competing with the numbers
     the page exists to show, and reading as decoration rather than as a
     product people file reports in. It should register as a tint you only
     notice if you look for it. */
  body::before,body::after{content:'';position:fixed;z-index:-2;pointer-events:none;will-change:transform;}
  body::before{
    inset:-25%;
    opacity:.30;
    /* The Hygiene Quest palette: the badge's greens, with a cool silver. */
    background:
      radial-gradient(36% 36% at 22% 26%, rgba(11,138,31,.30), transparent 70%),
      radial-gradient(32% 32% at 80% 16%, rgba(160,170,182,.30), transparent 70%),
      radial-gradient(38% 38% at 70% 80%, rgba(52,199,89,.24), transparent 72%);
    filter:blur(46px);
    animation:drift-a 38s var(--ease-out-expo) infinite alternate;
  }
  body::after{
    inset:-25%;
    opacity:.24;
    background:
      radial-gradient(30% 30% at 14% 84%, rgba(5,87,26,.20), transparent 72%),
      radial-gradient(28% 28% at 92% 58%, rgba(201,205,210,.34), transparent 72%),
      radial-gradient(26% 26% at 46% 6%, rgba(64,200,140,.18), transparent 72%);
    filter:blur(50px);
    animation:drift-b 46s var(--ease-out-expo) infinite alternate;
  }
  @keyframes drift-a{from{transform:translate3d(-3%,-2%,0) scale(1);}to{transform:translate3d(4%,3%,0) scale(1.12);}}
  @keyframes drift-b{from{transform:translate3d(3%,2%,0) scale(1.08);}to{transform:translate3d(-4%,-3%,0) scale(1);}}
  /* Dark mode carries it better — the tint reads as depth rather than wash. */
  @media (prefers-color-scheme:dark){ body::before{opacity:.42;} body::after{opacity:.34;} }
  @media (prefers-reduced-motion:reduce){ body::before,body::after{animation:none;} }
  /* Printing a report should never spend ink on wallpaper. */
  @media print{ body::before,body::after{display:none!important;} }

  /* The prototype's #protoBar and .app-screen rules lived here. Both were
     review scaffolding for switching between screens inside one HTML file;
     in the product every screen is its own URL, so they styled nothing and
     .app-screen{display:none} was a trap waiting for anyone who reused the
     class name. */
  .app-screen.active{display:block;}
  .app-screen.active{animation:screenIn .45s var(--ease-out-expo);}
  .section-panel{display:none;animation:fadeIn .42s var(--ease-out-expo);}
  .section-panel.active{display:block;}
  @keyframes fadeIn{from{opacity:0;transform:translateY(10px) scale(.995);}to{opacity:1;transform:none;}}
  @keyframes screenIn{from{opacity:0;transform:scale(.985);}to{opacity:1;transform:none;}}
  @media (prefers-reduced-motion:reduce){.app-screen.active,.section-panel{animation:none;}}

  /* ---- sticky glass header ----
     top:0, not 44px: the offset was clearing the prototype's #protoBar, so
     with that gone the header used to stick 44px down the page with a strip
     of backdrop showing above it. */
  header.bg-white{position:sticky;top:0;z-index:40;background:var(--glass-2)!important;
    -webkit-backdrop-filter:var(--blur-lg);backdrop-filter:var(--blur-lg);
    border-bottom:1px solid var(--glass-brd)!important;
    box-shadow:var(--inset-hi), 0 8px 24px -20px rgba(18,22,40,.5);}

  /* ---- sidebar nav (Settings-app style icon tiles) ---- */
  .sidenav-btn{position:relative;display:flex;align-items:center;gap:11px;width:100%;min-height:44px;text-align:left;padding:8px 12px;border-radius:14px;font-size:13.5px;font-weight:600;color:var(--ink-2);
    transition:background .22s var(--ease-out-expo),color .22s,box-shadow .22s,transform .18s var(--ease-out-expo);}
  .sidenav-btn:hover{background:var(--fill);color:var(--ink);}
  .sidenav-btn svg{flex-shrink:0;width:28px;height:28px;padding:6px;box-sizing:border-box;border-radius:9px;background:var(--fill-2);color:var(--ink-2);
    transition:background .22s var(--ease-out-expo),color .22s,box-shadow .22s;}
  .sidenav-btn.active{background:var(--role-tint);color:var(--role-fg);box-shadow:var(--inset-hi);}
  .sidenav-btn.active svg{background:linear-gradient(180deg, color-mix(in srgb, var(--role) 82%, #fff) 0%, var(--role) 100%);color:#fff;
    box-shadow:0 4px 10px -4px var(--role), inset 0 1px 0 rgba(255,255,255,.45);}

  /* ---- role accent scoping (set per screen wrapper) — iOS system colors ---- */
  .role-admin{--role:#5E5CE6;--role-btn:#4A48C9;--role-tint:rgba(94,92,230,.16);--role-fg:var(--f-indigo,#4340C4);}
  .role-officer{--role:#0A84FF;--role-btn:#0A6ACF;--role-tint:var(--t-blue);--role-fg:var(--f-blue);}
  .role-headteacher{--role:#AF52DE;--role-btn:#8E3CBF;--role-tint:var(--t-purple);--role-fg:var(--f-purple);}
  .role-teacher{--role:#34C759;--role-btn:#1F7A35;--role-tint:var(--t-green);--role-fg:var(--f-green);}
  .role-deo{--role:#FF9F0A;--role-btn:#9A5A00;--role-tint:var(--t-orange);--role-fg:var(--f-orange);}
  .role-ministry{--role:#0A84FF;--role-btn:#0A6ACF;--role-tint:var(--t-blue);--role-fg:var(--f-blue);}
  /* The pages before anybody has a role: sign-in, about, password reset. */
  .role-brand{--role:var(--brand);--role-btn:var(--brand-deep);--role-tint:var(--brand-tint);--role-fg:var(--brand-fg);}
  @media (prefers-color-scheme:dark){:root{--brand-fg:#63E282;--brand-tint:rgba(52,199,89,.22);}}
  :root{--f-indigo:#4340C4;}
  @media (prefers-color-scheme:dark){:root{--f-indigo:#A6A4FF;}}

  /* ---- liquid glass buttons: gradient body + specular top edge ---- */
  .role-btn,.bg-blue-600,.bg-rose-600{position:relative;overflow:hidden;isolation:isolate;}
  .role-btn::after,.bg-blue-600::after,.bg-rose-600::after{content:'';position:absolute;inset:0;z-index:-1;pointer-events:none;
    background:linear-gradient(180deg,rgba(255,255,255,.38) 0%,rgba(255,255,255,.10) 42%,rgba(0,0,0,.06) 100%);}
  .role-btn{background:var(--role-btn, var(--role,#5E5CE6))!important;color:#fff;
    box-shadow:var(--shadow-btn), inset 0 1px 0 rgba(255,255,255,.45), inset 0 0 0 .5px rgba(255,255,255,.25);}
  .role-btn:hover{filter:brightness(1.12) saturate(1.05);box-shadow:var(--shadow-btn), 0 10px 26px -10px var(--role), inset 0 1px 0 rgba(255,255,255,.5);}
  .role-chip{background:var(--role-tint);color:var(--role-fg);box-shadow:inset 0 0 0 .5px color-mix(in srgb, var(--role) 30%, transparent);}
  .role-ring:focus{outline:none;border-color:var(--role,#5E5CE6)!important;
    box-shadow:0 0 0 4px color-mix(in srgb, var(--role,#5E5CE6) 26%, transparent);}

  /* ---- collapsible inline "new record" forms ---- */
  .inline-form{max-height:0;overflow:hidden;opacity:0;transition:max-height .3s var(--ease-out-expo), opacity .25s ease, margin .3s ease;}
  .inline-form.open{max-height:900px;opacity:1;}

  /* ---- modal ---- */
  #modalOverlay{position:fixed;inset:0;background:rgba(12,14,22,.42);backdrop-filter:saturate(140%) blur(14px);-webkit-backdrop-filter:saturate(140%) blur(14px);z-index:200;display:none;align-items:center;justify-content:center;padding:16px;}
  #modalOverlay.open{display:flex;animation:fadeIn .25s ease;}
  #modalOverlay.open > div{animation:sheetIn .42s var(--spring);}
  @keyframes sheetIn{from{opacity:0;transform:scale(.9) translateY(14px);}to{opacity:1;transform:none;}}

  /* ---- misc ---- */
  ::-webkit-scrollbar{height:9px;width:9px;}
  ::-webkit-scrollbar-track{background:transparent;}
  ::-webkit-scrollbar-thumb{background:var(--fill-2);border-radius:8px;border:2px solid transparent;background-clip:padding-box;}
  ::-webkit-scrollbar-thumb:hover{background:var(--ink-3);background-clip:padding-box;}
  table.data-table th{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-3);font-weight:700;text-align:left;padding:2px 12px 10px;}
  table.data-table td{padding:13px 12px;border-top:1px solid var(--hairline);vertical-align:middle;font-size:13.5px;}
  table.data-table tbody tr{transition:background-color .18s ease;}
  table.data-table tbody tr:hover{background-color:var(--fill);}

  /* ---- avatar initials (added for people rows) ---- */
  .avatar-circle{width:28px;height:28px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;flex-shrink:0;letter-spacing:0;}

  @keyframes digitPop{from{opacity:0;transform:translateY(8px) scale(.85);}to{opacity:1;transform:none;}}

  .phone-frame{width:270px;background:#111214;border-radius:38px;padding:14px 10px;box-shadow:0 30px 60px -20px rgba(0,0,0,.45), 0 0 0 2px #2C2C2E, inset 0 1px 1px rgba(255,255,255,.08);}
  .phone-screen{background:#07130C;border-radius:8px;height:340px;padding:14px 12px;display:flex;flex-direction:column;box-shadow:inset 0 0 20px rgba(0,0,0,.5);}
  .phone-screen .ussd-text{color:#6FDE8C;font-size:12.5px;line-height:1.6;white-space:pre-wrap;flex:1;}
  .phone-keys{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:14px;}
  .phone-keys button{background:#2C2C2E;color:#F2F2F2;border-radius:10px;padding:10px 0;font-size:13px;font-weight:600;}
  .phone-keys button:active{background:#3A3A3C;}
  .phone-softkeys{display:flex;justify-content:space-between;padding:0 4px;margin-top:10px;}
  .phone-softkeys span{color:#6FDE8C;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;}

  /* ---- tactile press feedback ---- */
  button:not(:disabled){transition:transform .16s var(--ease-out-expo), filter .18s ease, background-color .18s ease, box-shadow .22s ease, opacity .15s ease, color .18s ease;}
  button:not(:disabled):active{transform:scale(.96);}

  /* ---- GLASS SURFACES: every white panel becomes frosted glass ---- */
  .bg-white{
    background:var(--glass)!important;
    -webkit-backdrop-filter:var(--blur-lg);backdrop-filter:var(--blur-lg);
    box-shadow:var(--shadow-card), var(--inset-hi);
  }
  header.bg-white,#modalOverlay .bg-white{backdrop-filter:var(--blur-lg);-webkit-backdrop-filter:var(--blur-lg);}
  #modalOverlay .bg-white{background:var(--glass-2)!important;box-shadow:var(--shadow-pop), var(--inset-hi)!important;}
  /* nested white panels (e.g. credentials box inside a tinted panel) read brighter */
  .bg-emerald-50 .bg-white,.bg-blue-50 .bg-white{background:var(--glass-2)!important;}

  /* ---- softer, larger radii throughout ---- */
  .rounded{border-radius:.4rem;}
  .rounded-lg{border-radius:.75rem;}
  .rounded-xl{border-radius:1rem;}
  .rounded-2xl{border-radius:1.25rem;}

  /* ================================================================
     PALETTE REPAINT — every Tailwind color utility the markup uses,
     remapped from the old slate/blue/violet/emerald/rose/amber set
     to the iOS-inspired token set above. Placed after the Tailwind
     reset so these values win the cascade without touching markup.
     ================================================================ */
  .bg-slate-50{background-color:transparent!important;}
  .bg-slate-100{background:var(--fill)!important;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);box-shadow:inset 0 0 0 .5px var(--glass-brd);}
  .hover\:bg-slate-200:hover{background:var(--fill-2)!important;}
  .bg-slate-800{background:linear-gradient(160deg, var(--role,#5E5CE6) 0%, color-mix(in srgb, var(--role,#5E5CE6) 55%, #101018) 100%)!important;
    box-shadow:0 10px 24px -12px var(--role,#5E5CE6), inset 0 1px 0 rgba(255,255,255,.45)!important;}
  .border-slate-50,.border-slate-100{border-color:var(--hairline)!important;}
  .border-slate-200{border-color:var(--glass-brd)!important;}
  .border-slate-300{border-color:var(--field-brd)!important;}
  .placeholder-slate-300::-moz-placeholder{color:var(--ink-3)!important;opacity:.6;}
  .placeholder-slate-300::placeholder{color:var(--ink-3)!important;opacity:.6;}
  .placeholder-slate-400::-moz-placeholder{color:var(--ink-3)!important;}
  .placeholder-slate-400::placeholder{color:var(--ink-3)!important;}
  .text-slate-400{color:var(--ink-3)!important;}
  .text-slate-500{color:var(--ink-2)!important;}
  .text-slate-600{color:var(--ink-2)!important;}
  .text-slate-700{color:var(--ink)!important;}
  .text-slate-800{color:var(--ink)!important;}
  .hover\:text-slate-700:hover{color:var(--ink)!important;}
  .hover\:text-slate-800:hover{color:var(--ink)!important;}
  .ring-slate-600,.focus\:ring-slate-600:focus{--tw-ring-color:var(--role,#5E5CE6)!important;}
  .shadow-slate-800\/20{--tw-shadow-color:transparent!important;}

  .bg-blue-50{background:var(--t-blue)!important;}
  .bg-blue-600{background:#0A6ACF!important;color:#fff;box-shadow:var(--shadow-btn), inset 0 1px 0 rgba(255,255,255,.45);}
  .hover\:bg-blue-700:hover{background:var(--blue)!important;filter:brightness(1.08) saturate(1.05);}
  .border-blue-600,.focus\:border-blue-600:focus{border-color:var(--blue)!important;}
  .ring-blue-600,.focus\:ring-blue-600:focus{--tw-ring-color:var(--blue)!important;}
  .text-blue-600,.text-blue-700{color:var(--f-blue)!important;}

  .bg-violet-50{background:var(--t-purple)!important;}
  .bg-violet-600{background:var(--purple)!important;}
  .text-violet-600{color:var(--f-purple)!important;}

  .bg-emerald-50{background:var(--t-green)!important;}
  .bg-emerald-500{background:var(--green)!important;box-shadow:0 0 10px 1px color-mix(in srgb, var(--green) 60%, transparent);}
  .border-emerald-200{border-color:color-mix(in srgb, var(--green) 34%, transparent)!important;}
  .text-emerald-600,.text-emerald-700,.text-emerald-800,.text-emerald-900,.hover\:text-emerald-900:hover{color:var(--f-green)!important;}

  .bg-rose-50{background:var(--t-red)!important;}
  .bg-rose-600{background:#C9251C!important;color:#fff;box-shadow:var(--shadow-btn), inset 0 1px 0 rgba(255,255,255,.4);}
  .hover\:bg-rose-700:hover{background:var(--red)!important;filter:brightness(1.08) saturate(1.05);}
  .border-rose-200{border-color:color-mix(in srgb, var(--red) 30%, transparent)!important;}
  .border-rose-300{border-color:color-mix(in srgb, var(--red) 45%, transparent)!important;}
  .text-rose-500,.text-rose-600,.text-rose-700,.text-rose-800,.hover\:text-rose-600:hover{color:var(--f-red)!important;}

  .bg-amber-50{background:var(--t-orange)!important;}
  .border-amber-200{border-color:color-mix(in srgb, var(--orange) 34%, transparent)!important;}
  .border-amber-300{border-color:color-mix(in srgb, var(--orange) 48%, transparent)!important;}
  .text-amber-600,.text-amber-700,.text-amber-800{color:var(--f-orange)!important;}

  /* ---- the darkest step of each ramp ----

     The remaps above stop at -800 and the templates do not. Nineteen places
     ask for a -900 ink: "Why is this changing?" on the amendment form, "No
     term is running today" on four screens, "No session is open" on the
     scanner, the heading over a form's errors, the banner that says nobody
     has confirmed a school's position. Emerald was the only ramp carried all
     the way, so the rest kept their light values -- #78350F, #881337,
     #0F172A -- and in dark mode that is near-black ink on a translucent
     tinted panel over a #07070B page. Not merely low contrast: the amber one
     measured 4.41:1 where 14px text needs 4.5, and the page-composited figure
     is worse than that.

     Found by measuring, not by reading. The templates and the theme were each
     internally consistent and disagreed with each other, which is a class of
     bug no single file shows you. */
  /* -700 as well, found the same way and for the same reason. "On leave" on
     the Teachers screen measured 2.17:1 in dark mode and the withdrawn-consent
     chip on Class registers 2.13:1 — both keeping a light-theme ink on a dark
     tinted panel. Every step of these two ramps is now accounted for. */
  .text-amber-900{color:var(--f-orange)!important;}
  .text-rose-900{color:var(--f-red)!important;}
  .text-slate-900,.hover\:text-slate-900:hover{color:var(--ink)!important;}
  .hover\:text-rose-800:hover{color:var(--f-red)!important;}

  /* ---- and the variant classes, which the remaps had never reached ----

     `.bg-slate-50` is remapped above; `.hover\:bg-slate-50:hover` and
     `.has-\[\:checked\]\:bg-slate-50:has(:checked)` are different selectors and
     were not. They are the same colour doing the same job, so the omission
     is invisible in the stylesheet and very visible on the screen.

     Worst case measured 1.07:1 -- the selected format in the report export
     dialog, near-white text on a near-white panel, which is not "low
     contrast" but "gone". Hovering any of the three did the same thing on
     the way past. The selected-state border went with it: #1E293B on a
     #07070B page is a border you cannot see, so nothing showed which format
     was chosen either.

     Tinted with the role colour now, which is what the two purpose-built
     pickers in this file (.gov-format, .policy-opt) already do. */
  .hover\:bg-slate-50:hover{background-color:var(--fill)!important;}
  .hover\:bg-slate-700:hover{background-color:var(--glass-3)!important;}
  .has-\[\:checked\]\:bg-slate-50:has(:checked){background-color:var(--role-tint)!important;}
  .has-\[\:checked\]\:border-slate-800:has(:checked){border-color:var(--role)!important;}

  /* ---- and the one sky-tinted thing in the system ----

     The recovered-draft banner on the roll call. It had no dark treatment at
     all, so a page that is otherwise near-black carried a #F0F9FF slab -- the
     brightest thing on the screen, announcing the least urgent news on it.
     Tinted like the other notices now, in the blue it was already using. */
  .bg-sky-50{background:var(--t-blue)!important;}
  .border-sky-200{border-color:color-mix(in srgb, var(--blue) 30%, transparent)!important;}
  .text-sky-900{color:var(--f-blue)!important;}

  /* ==========================================================
     COMPONENTS
     ========================================================== */
  /* ---- form fields: frosted, 44px minimum touch target ---- */
  input:not([type=checkbox]):not([type=radio]):not([type=hidden]),select,textarea{
    background:var(--field)!important;color:var(--ink)!important;
    -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
    border-radius:14px!important;min-height:44px;
    transition:border-color .2s ease, box-shadow .25s var(--ease-out-expo), background .2s ease;
  }
  select{appearance:none;-webkit-appearance:none;padding-right:2.25rem!important;
    background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%)!important;
    background-position:calc(100% - 17px) calc(50% + 1px), calc(100% - 12px) calc(50% + 1px)!important;
    background-size:5px 5px,5px 5px!important;background-repeat:no-repeat!important;}
  input[type=checkbox]{accent-color:var(--role,#5E5CE6);width:19px;height:19px;border-radius:6px;}
  label{color:var(--ink);}

  /* ---- toast: floating glass capsule ---- */
  #toast{background:var(--glass-2)!important;color:var(--ink)!important;
    -webkit-backdrop-filter:var(--blur-lg);backdrop-filter:var(--blur-lg);
    border:1px solid var(--glass-brd);border-radius:999px!important;padding:11px 20px!important;
    box-shadow:var(--shadow-pop), var(--inset-hi)!important;
    display:flex;align-items:center;gap:9px;bottom:24px!important;max-width:calc(100vw - 32px);}
  #toast.hidden{display:none;}
  #toast::before{content:'';width:18px;height:18px;border-radius:50%;flex-shrink:0;
    background:var(--green) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center/12px no-repeat;}
  #toast.show{animation:toastIn .45s var(--spring);}
  @keyframes toastIn{from{opacity:0;transform:translate(-50%,20px) scale(.9);}to{opacity:1;transform:translate(-50%,0) scale(1);}}

  /* ---- segmented control (login role picker) ---- */
  .seg{position:relative;display:grid;grid-template-columns:repeat(2,1fr);gap:0;padding:4px;border-radius:18px;
    background:var(--fill);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);box-shadow:inset 0 0 0 .5px var(--glass-brd);}
  /* the sliding indicator was built for four roles in one row; with six
     it never lined up, so the selected button carries the highlight itself */
  .seg-ind{display:none;}
  .seg button{position:relative;z-index:1;min-height:46px;border-radius:14px;font-size:13px;font-weight:600;
    color:var(--ink-2);padding:8px 4px;line-height:1.25;overflow-wrap:break-word;hyphens:auto;}
  .seg button[aria-selected=true]{color:var(--ink);}


  /* ---- OTP: six auto-advancing digit fields ---- */
  .otp{display:grid;grid-template-columns:repeat(6,1fr);gap:8px;}
  .otp input{width:100%;min-height:58px!important;text-align:center;padding:0!important;
    font-family:ui-monospace,'SF Mono','JetBrains Mono',monospace;font-size:24px;font-weight:800;
    border:1px solid var(--field-brd);border-radius:15px!important;}
  .otp input:focus{outline:none;border-color:var(--role)!important;background:var(--glass-3)!important;
    box-shadow:0 0 0 4px color-mix(in srgb, var(--role) 24%, transparent);transform:translateY(-1px);}
  .otp input.filled{border-color:color-mix(in srgb, var(--role) 55%, transparent)!important;}


  .phone-frame{background:linear-gradient(165deg,#26262C,#0C0C10);}
  .phone-screen{background:#04120A;}
  .phone-keys button{background:rgba(255,255,255,.10);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.16);min-height:40px;}
  .phone-keys button:active{background:rgba(255,255,255,.2);}


  /* ==========================================================
     MISSING UTILITIES

     The Tailwind bundle above is a pre-built, tree-shaken file:
     it only contains the utilities the ORIGINAL markup used, so
     any class introduced afterwards silently does nothing. These
     are the ones this build needs and the bundle does not carry,
     written out by hand with Tailwind's own escaping so the
     markup can keep using the normal class names.
     ========================================================== */
  .gap-2\.5{gap:.625rem;}
  .space-y-4 > :not([hidden]) ~ :not([hidden]){margin-top:1rem;}
  .truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .min-h-\[44px\]{min-height:44px;}
  .h-12{height:3rem;} .w-12{width:3rem;}
  .p-3{padding:.75rem;} .pt-4{padding-top:1rem;} .pb-1{padding-bottom:.25rem;}
  .mt-2{margin-top:.5rem;} .mb-2\.5{margin-bottom:.625rem;}
  .opacity-75{opacity:.75;} .opacity-90{opacity:.9;}
  @media (min-width:640px){
    .sm\:w-36{width:9rem;}
    .sm\:grid-cols-\[1fr_auto\]{grid-template-columns:1fr auto;}
  }
  @media (min-width:1024px){
    .lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr));}
  }
  @media (min-width:1280px){
    .xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr));}
    .xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr));}
  }

  /* ==========================================================
     DATA-VIZ TOKENS
     Chart surface is the glass panel over --canvas:
       light rgba(255,255,255,.55) over #E7ECF7 -> #F4F6FB
       dark  rgba(38,38,45,.55)    over #07070B -> #18181E
     Series steps were validated against those exact surfaces
     with the six computable checks (all-pairs, both modes):
       light #1F6FD0 / #C2690A — CVD dE 26.7, normal dE 31.4
       dark  #2E8BEA / #CE7A22 — CVD dE 27.3, normal dE 30.7
     Pale steps are the composite second channel (hue = gender,
     lightness = present/absent) and clear the 2:1 ordinal floor.
     Green/red is deliberately NOT used for adjacent chart marks
     (CVD dE 4.2 — indistinguishable); it stays on status text
     and icons only, where it always ships with a label.
     ========================================================== */
  :root{
    --viz-surface:#F4F6FB;
    --sr-1:#1F6FD0;  --sr-1-pale:#6FA6E3;
    --sr-2:#C2690A;  --sr-2-pale:#D69B55;
    --viz-grid:rgba(60,60,67,.16);
    --viz-axis:#66666F;
  }
  @media (prefers-color-scheme:dark){
    :root{
      --viz-surface:#18181E;
      --sr-1:#2E8BEA;  --sr-1-pale:#1A4E80;
      --sr-2:#CE7A22;  --sr-2-pale:#7A4712;
      --viz-grid:rgba(255,255,255,.14);
      --viz-axis:#93939E;
    }
  }

  /* ---- KPI / stat tile: label, value, delta, sparkline ---- */
  .kpi{position:relative;display:flex;flex-direction:column;gap:2px;}
  .kpi-label{font-size:12.5px;font-weight:600;color:var(--ink-2);}
  .kpi-value{font-size:30px;font-weight:800;color:var(--ink);line-height:1.1;letter-spacing:-.02em;}
  .kpi-sub{font-size:12px;color:var(--ink-3);}
  .kpi-delta{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:700;margin-top:2px;}
  .kpi-delta[data-dir=up]{color:var(--f-green);}
  .kpi-delta[data-dir=down]{color:var(--f-red);}
  .kpi-delta[data-dir=flat]{color:var(--ink-3);}
  .kpi-delta svg{width:13px;height:13px;flex-shrink:0;}
  .kpi-spark{margin-top:9px;height:30px;width:100%;display:block;}

  /* ---- chart card ---- */
  .chart-card{padding:18px 18px 14px;}
  .chart-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;}
  .chart-title{font-size:14px;font-weight:700;color:var(--ink);}
  .chart-sub{font-size:12px;color:var(--ink-2);margin-top:1px;}
  .chart-body{position:relative;margin-top:10px;}
  .chart-body svg{display:block;width:100%;height:auto;overflow:visible;
    max-width:900px;margin-left:auto;margin-right:auto;}
  /* A donut carries no more information at 600px than at 280px — past that it
     just pushes the legend off the screen. */
  .chart-body.viz-donut svg{max-width:280px;}
  .viz-empty{padding:38px 10px;text-align:center;color:var(--ink-3);font-size:13px;}

  /* legend: identity never rests on colour alone */
  .viz-legend{display:flex;flex-wrap:wrap;gap:6px 14px;margin-top:12px;}
  .viz-key{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--ink-2);font-weight:600;}
  .viz-key i{width:11px;height:11px;border-radius:3px;flex-shrink:0;display:inline-block;}
  .viz-key i.line{height:3px;width:15px;border-radius:2px;}

  /* axis + grid: recessive, hairline, solid */
  .viz-grid-line{stroke:var(--viz-grid);stroke-width:1;shape-rendering:crispEdges;}
  .viz-tick{fill:var(--viz-axis);font-size:10.5px;font-weight:600;font-variant-numeric:tabular-nums;}
  .viz-lbl{fill:var(--ink-2);font-size:11px;font-weight:600;}
  .viz-val{fill:var(--ink);font-size:11.5px;font-weight:700;font-variant-numeric:tabular-nums;}
  .viz-val-in{fill:#fff;font-size:11px;font-weight:700;font-variant-numeric:tabular-nums;}
  .viz-hit{fill:transparent;cursor:pointer;}
  .viz-dot{stroke:var(--viz-surface);stroke-width:2;}

  /* tooltip */
  .viz-tip{position:absolute;z-index:20;pointer-events:none;opacity:0;transform:translate(-50%,-118%);
    background:var(--glass-3);color:var(--ink);border:1px solid var(--glass-brd);border-radius:12px;
    -webkit-backdrop-filter:var(--blur);backdrop-filter:var(--blur);
    box-shadow:var(--shadow-pop);padding:8px 11px;font-size:12px;line-height:1.55;white-space:nowrap;
    transition:opacity .14s ease;}
  .viz-tip.on{opacity:1;}
  .viz-tip b{font-weight:700;}
  .viz-tip .r{display:flex;align-items:center;gap:6px;}
  .viz-tip .r i{width:9px;height:9px;border-radius:2px;flex-shrink:0;}
  .viz-crosshair{stroke:var(--viz-grid);stroke-width:1;}

  /* table view — every chart has a readable fallback */
  .viz-table-toggle{font-size:11.5px;font-weight:700;color:var(--ink-2);background:var(--fill);
    border-radius:999px;padding:0 13px;min-height:36px;flex-shrink:0;
    display:inline-flex;align-items:center;}
  .viz-table-toggle:hover{background:var(--fill-2);color:var(--ink);}
  .viz-table{display:none;margin-top:12px;width:100%;font-size:12px;}
  .viz-table.on{display:table;}
  .viz-table th{text-align:left;font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;
    color:var(--ink-3);font-weight:700;padding:4px 8px;}
  .viz-table td{padding:6px 8px;border-top:1px solid var(--hairline);color:var(--ink-2);
    font-variant-numeric:tabular-nums;}
  .viz-table td:first-child{color:var(--ink);font-weight:600;}

  /* ---- session code cards ---- */
  /* `minmax(0,1fr)`, not `1fr`. A bare `1fr` is `minmax(auto,1fr)`, and that
     `auto` refuses to go below the column's min-content — so one long name in
     the code list ("Nabirye Christine — Kazinga Primary School") pushed all
     three columns out and scrolled the whole page sideways. */
  .sess-grid{display:grid;gap:14px;grid-template-columns:minmax(0,1fr);}
  @media (min-width:900px){.sess-grid{grid-template-columns:repeat(3,minmax(0,1fr));}}
  /* Both halves are needed. `minmax(0,1fr)` lets the *track* be narrower than
     its content; `min-width:0` lets the *item* be. With only the first, the
     card still refuses to shrink below its own min-content and spills out of
     the track it was given. */
  .sess-grid > *{min-width:0;}
  .sess-card{position:relative;padding:18px;border-radius:1.25rem;border:1px solid var(--glass-brd);
    background:var(--glass);-webkit-backdrop-filter:var(--blur);backdrop-filter:var(--blur);
    box-shadow:var(--shadow-card), var(--inset-hi);transition:border-color .35s ease, background .35s ease;}
  .sess-card[data-win=open]{background:var(--t-green);border-color:color-mix(in srgb, var(--green) 34%, transparent);}
  .sess-card[data-win=soon]{background:var(--t-orange);border-color:color-mix(in srgb, var(--orange) 34%, transparent);}
  .sess-ord{font-size:10.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);}
  .sess-name{font-size:15px;font-weight:700;color:var(--ink);margin-top:2px;}
  .sess-win{font-size:12px;color:var(--ink-2);margin-top:1px;font-variant-numeric:tabular-nums;}
  .sess-state{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:700;
    padding:3px 9px;border-radius:999px;margin-top:10px;}
  .sess-state[data-win=open]{background:var(--t-green);color:var(--f-green);}
  .sess-state[data-win=soon]{background:var(--t-orange);color:var(--f-orange);}
  .sess-state[data-win=closed]{background:var(--fill);color:var(--ink-3);}
  .sess-state .dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex-shrink:0;}
  .sess-code{display:flex;gap:5px;justify-content:center;margin:14px 0 8px;}
  .sess-code span{width:34px;height:44px;display:flex;align-items:center;justify-content:center;
    background:linear-gradient(180deg, var(--glass-3), var(--glass));border:1px solid var(--glass-brd);
    border-radius:11px;font-family:ui-monospace,'SF Mono','JetBrains Mono',monospace;
    font-weight:800;font-size:20px;color:var(--ink);box-shadow:var(--inset-hi);
    animation:digitPop .4s var(--spring) both;}
  .sess-code span:nth-child(2){animation-delay:.04s;} .sess-code span:nth-child(3){animation-delay:.08s;}
  .sess-code span:nth-child(4){animation-delay:.12s;} .sess-code span:nth-child(5){animation-delay:.16s;}
  .sess-code span:nth-child(6){animation-delay:.2s;}

  /* ---- per-teacher session codes ---- */
  .code-list{display:flex;flex-direction:column;gap:4px;max-height:260px;overflow-y:auto;
    margin:6px -4px 0;padding:0 4px;}
  .code-row{display:flex;align-items:center;gap:9px;padding:7px 9px;border-radius:12px;
    background:var(--fill);box-shadow:inset 0 0 0 .5px var(--glass-brd);}
  .code-row.is-in{background:var(--t-green);box-shadow:inset 0 0 0 .5px color-mix(in srgb, var(--green) 30%, transparent);}
  /* Truncated rather than wrapped: these rows are scanned down the left edge
     looking for your own name, and a name that wraps to two lines breaks that
     column. The full text stays in the title attribute. */
  .code-name{font-size:12.5px;font-weight:600;color:var(--ink);line-height:1.25;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .code-meta{font-size:10.5px;color:var(--ink-3);
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .code-pill{font-family:ui-monospace,'SF Mono','JetBrains Mono',monospace;font-size:14px;font-weight:800;
    letter-spacing:.06em;color:var(--ink);background:var(--glass-3);border-radius:10px;padding:6px 10px;
    min-height:34px;flex-shrink:0;box-shadow:var(--inset-hi), 0 2px 6px -3px rgba(18,22,40,.4);}
  .code-pill:hover{background:#fff;color:var(--role-fg, var(--role));}

  /* ---- how a check-in was verified ---- */
  .vchip{display:inline-flex;align-items:center;gap:4px;font-size:10.5px;font-weight:700;
    padding:2px 8px;border-radius:999px;white-space:nowrap;}
  .vchip[data-v=good]{background:var(--t-green);color:var(--f-green);}
  .vchip[data-v=weak]{background:var(--t-orange);color:var(--f-orange);}
  .vchip[data-v=bad]{background:var(--t-red);color:var(--f-red);}
  .vchip[data-v=unknown]{background:var(--fill);color:var(--ink-3);}

  /* ---- roll call ---- */
  .roll-row{display:flex;align-items:center;gap:11px;padding:11px 12px;border-radius:14px;
    border:1px solid transparent;transition:background .2s ease, border-color .2s ease;}
  .roll-row + .roll-row{margin-top:5px;}
  .roll-row[data-mark=P]{background:var(--t-green);border-color:color-mix(in srgb, var(--green) 24%, transparent);}
  .roll-row[data-mark=A]{background:var(--t-red);border-color:color-mix(in srgb, var(--red) 24%, transparent);}
  .roll-name{font-size:13.5px;font-weight:600;color:var(--ink);line-height:1.3;overflow-wrap:anywhere;}
  .roll-meta{font-size:11px;color:var(--ink-3);}
  .pa{display:inline-flex;gap:4px;padding:3px;border-radius:12px;background:var(--fill);flex-shrink:0;
    box-shadow:inset 0 0 0 .5px var(--glass-brd);}
  .pa button{min-height:34px;min-width:42px;border-radius:9px;font-size:12px;font-weight:700;color:var(--ink-2);padding:0 8px;}
  .pa button[aria-pressed=true][data-v=P]{background:var(--green);color:#fff;box-shadow:var(--shadow-btn);}
  .pa button[aria-pressed=true][data-v=A]{background:var(--red);color:#fff;box-shadow:var(--shadow-btn);}
  .roll-reason{margin-top:7px;}
  .roll-reason select{font-size:12.5px;min-height:40px!important;width:100%;}
  .roll-reason.missing select{border-color:var(--red)!important;box-shadow:0 0 0 3px color-mix(in srgb, var(--red) 22%, transparent);}
  .roll-reason-note{font-size:11px;color:var(--f-red);font-weight:600;margin-top:4px;display:none;}
  .roll-reason.missing .roll-reason-note{display:block;}

  /* gender pill */
  .g-pill{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;
    border-radius:6px;font-size:10.5px;font-weight:800;letter-spacing:.02em;flex-shrink:0;}
  .g-pill[data-g=M]{background:color-mix(in srgb, var(--sr-1) 20%, transparent);color:var(--sr-1);}
  .g-pill[data-g=F]{background:color-mix(in srgb, var(--sr-2) 22%, transparent);color:var(--sr-2);}

  /* ---- teacher app tabs ---- */
  .ttabs{display:grid;grid-template-columns:repeat(4,1fr);gap:0;padding:4px;border-radius:16px;
    background:var(--fill);box-shadow:inset 0 0 0 .5px var(--glass-brd);position:relative;}
  .ttabs button{position:relative;z-index:1;min-height:44px;border-radius:12px;font-size:11.5px;font-weight:700;
    color:var(--ink-2);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;}
  .ttabs button svg{width:16px;height:16px;}
  .ttabs button[aria-selected=true]{background:var(--glass-3);color:var(--role-fg, var(--role));
    box-shadow:0 2px 8px -2px rgba(18,22,40,.28), var(--inset-hi);}
  .tpanel{display:none;animation:fadeIn .38s var(--ease-out-expo);}
  .tpanel.active{display:block;}

  /* ---- geo chip ---- */
  .geo-chip{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;font-weight:600;
    padding:5px 10px;border-radius:999px;background:var(--fill);color:var(--ink-2);
    box-shadow:inset 0 0 0 .5px var(--glass-brd);}
  .geo-chip svg{width:13px;height:13px;flex-shrink:0;}
  .geo-chip[data-geo=ok]{background:var(--t-green);color:var(--f-green);}
  .geo-chip[data-geo=off]{background:var(--t-orange);color:var(--f-orange);}
  .geo-chip .mono{font-variant-numeric:tabular-nums;}

  /* ---- masked phone ---- */
  .phone-mask{display:inline-flex;align-items:center;gap:6px;}
  .phone-mask .val{font-family:ui-monospace,'SF Mono','JetBrains Mono',monospace;font-size:12.5px;letter-spacing:-.01em;}
  .phone-mask button{color:var(--ink-3);flex-shrink:0;line-height:0;padding:2px;border-radius:6px;}
  .phone-mask button:hover{color:var(--role-fg, var(--role));background:var(--fill);}
  .phone-mask svg{width:14px;height:14px;}

  /* ---- sortable table headers ---- */
  table.data-table th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap;
    transition:color .18s ease;}
  table.data-table th.sortable:hover{color:var(--ink);}
  table.data-table th.sortable::after{content:'';display:inline-block;width:0;height:0;margin-left:5px;
    vertical-align:middle;border-left:3.5px solid transparent;border-right:3.5px solid transparent;
    border-bottom:4.5px solid currentColor;opacity:.22;transition:opacity .18s ease;}
  table.data-table th.sortable[data-dir=asc]::after{opacity:1;}
  table.data-table th.sortable[data-dir=desc]::after{opacity:1;border-bottom:none;
    border-top:4.5px solid currentColor;}

  /* ---- filter bar ---- */
  .filter-bar{display:flex;flex-wrap:wrap;align-items:flex-end;gap:10px;}
  .filter-bar label{display:block;font-size:11px;font-weight:600;color:var(--ink-2);margin-bottom:4px;}
  .filter-bar select,.filter-bar input{font-size:12.5px;padding:7px 11px;min-height:40px!important;
    border:1px solid var(--field-brd);border-radius:12px!important;}
  .filter-field{min-width:0;}
  .filter-chipbar{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px;}

  /* Apply, and the quick ranges.
     Both are scoped to .filter-bar deliberately. The generic touch-target
     rules further down this file — `a[href]{min-height:24px}` and
     `main button{min-height:32px}` — are an element-plus-attribute and a
     descendant selector, so a bare class here loses to them and these came out
     24px and 32px tall, the quick ranges ending up smaller than the 27px they
     were meant to replace. Two classes outrank both. Matched to the 40px the
     inputs beside them already use. */
  .filter-bar .filter-apply{min-height:40px;padding:0 18px;border-radius:12px;
    font-size:12.5px;font-weight:700;color:#fff;}

  /* "See this point on a map", on the boundary screen. Two classes for the
     same reason the filter bar needs them: `a[href]{min-height:24px}` further
     down is an element-plus-attribute selector and beats a lone class, so the
     first attempt at this rendered 24px tall — a link about the one setting
     every attendance record depends on, sized like a footnote. */
  .set-location .map-check{display:inline-flex;align-items:center;gap:6px;
    margin-top:12px;min-height:44px;font-size:13.5px;font-weight:650;
    color:var(--role-fg,var(--role));}
  .set-location .map-check svg{width:16px;height:16px;flex-shrink:0;}

  .filter-presets{margin-left:auto;}
  .range-presets{display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
  .filter-bar .range-preset{display:inline-flex;align-items:center;justify-content:center;
    min-height:40px;padding:0 14px;border-radius:999px;flex-shrink:0;
    font-size:12.5px;font-weight:700;color:var(--ink-2);background:var(--fill);
    text-decoration:none;transition:background .18s,color .18s;}
  .filter-bar .range-preset:hover{background:var(--fill-2);color:var(--ink);}
  .fchip{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;font-weight:600;
    padding:4px 6px 4px 10px;border-radius:999px;background:var(--role-tint);color:var(--role-fg);}
  .fchip button{line-height:0;color:currentColor;opacity:.65;padding:2px;border-radius:50%;}
  .fchip button:hover{opacity:1;background:rgba(0,0,0,.08);}
  .fchip svg{width:11px;height:11px;}

  /* ---- print / PDF export ----

     `#printRoot` is where a sheet is built before `window.print()` — the
     posters and the code slips both. Hidden on screen at all times: it is a
     staging area, not content, and a head teacher who saw a second copy of
     their posters appear under the page would reasonably think something had
     broken.

     Printing used to open a popup and write a document into it, which is the
     wrong mechanism on the devices these screens are opened from: a popup is
     blocked outright in the installed app and swallowed silently by several
     mobile browsers, so the button did nothing at all.

     `#printRoot` is hidden on screen by the rule in the printed-slips block
     below, which is also where the slip design lives. */
  @media print{
    nav,header,.no-print{display:none!important;}
    /* printing slips replaces the page entirely */
    body.printing-slips > *{display:none!important;}
    body.printing-slips #printRoot{display:block!important;}
    #printRoot .slip-sheet{color:#000;}
    body::before,body::after{display:none!important;}
    body{background:#fff!important;}
    .app-screen{display:none!important;}
    .app-screen.active{display:block!important;}
    .section-panel{display:none!important;}
    .section-panel.active{display:block!important;}
    .bg-white{background:#fff!important;box-shadow:none!important;border:1px solid #D8DCE6!important;
      -webkit-backdrop-filter:none!important;backdrop-filter:none!important;break-inside:avoid;}
    .chart-card{break-inside:avoid;}
    .viz-table{display:table!important;}
  }

  /* ---- five roles no longer fit a sliding pill; select the button itself ---- */
  .seg{grid-template-columns:repeat(2,1fr);}
  .seg-ind{display:none;}
  .seg button[aria-selected=true]{color:var(--ink);background:var(--glass-3);
    box-shadow:0 2px 8px -2px rgba(18,22,40,.28), var(--inset-hi);}
  @media (min-width:400px){ .seg{grid-template-columns:repeat(3,1fr);} }

  /* ---- any row that opens a record ---- */
  .row-link{cursor:pointer;}
  .row-link:hover{background-color:var(--role-tint)!important;}
  .row-link:active{transform:scale(.998);}
  .feed-link{cursor:pointer;border-radius:12px;transition:background-color .18s ease;}
  .feed-link:hover{background-color:var(--fill);}

  /* more utilities the pre-built Tailwind bundle does not carry */
  .ml-1{margin-left:.25rem;}
  .space-y-2 > :not([hidden]) ~ :not([hidden]){margin-top:.5rem;}
  .text-slate-300{color:var(--ink-3)!important;opacity:.55;}

  /* ---- record drawer ---- */
  #sheetOverlay{position:fixed;inset:0;z-index:250;display:none;
    background:rgba(12,14,22,.42);-webkit-backdrop-filter:saturate(140%) blur(14px);backdrop-filter:saturate(140%) blur(14px);}
  #sheetOverlay.open{display:block;animation:fadeIn .22s ease;}
  .sheet{position:absolute;top:0;right:0;bottom:0;width:min(560px,100%);display:flex;flex-direction:column;
    background:var(--glass-2);-webkit-backdrop-filter:var(--blur-lg);backdrop-filter:var(--blur-lg);
    border-left:1px solid var(--glass-brd);box-shadow:var(--shadow-pop);
    animation:sheetSlide .4s var(--ease-out-expo);}
  @keyframes sheetSlide{from{transform:translateX(28px);opacity:0;}to{transform:none;opacity:1;}}
  @media (prefers-reduced-motion:reduce){.sheet{animation:none;}}
  .sheet-bar{display:flex;align-items:center;gap:8px;padding:12px 14px;flex-shrink:0;
    border-bottom:1px solid var(--hairline);}
  .sheet-bar button{display:inline-flex;align-items:center;gap:6px;font-size:13px;font-weight:700;
    color:var(--ink-2);padding:7px 12px;border-radius:999px;min-height:38px;}
  .sheet-bar button:hover{background:var(--fill);color:var(--ink);}
  .sheet-bar svg{width:15px;height:15px;}
  .sheet-body{overflow-y:auto;padding:18px 20px 28px;flex:1;}
  .sheet-title{margin-bottom:16px;}
  .sheet-h1{font-size:20px;font-weight:800;color:var(--ink);letter-spacing:-.01em;line-height:1.2;}
  .sheet-sub{font-size:13px;color:var(--ink-2);margin-top:3px;}
  .sheet-title .vchip,.sheet-title .g-pill{margin-top:9px;}
  .sheet-h2{font-size:11px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;
    color:var(--ink-3);margin-bottom:8px;}
  .sheet-note{font-size:12px;color:var(--ink-3);margin:-4px 0 8px;}
  .sheet-section{margin-top:22px;}
  .sheet-facts{display:grid;grid-template-columns:1fr;gap:0;border-radius:16px;overflow:hidden;
    background:var(--glass);box-shadow:inset 0 0 0 .5px var(--glass-brd);}
  .sheet-facts > div{display:flex;align-items:baseline;justify-content:space-between;gap:14px;
    padding:10px 14px;border-bottom:1px solid var(--hairline);}
  .sheet-facts > div:last-child{border-bottom:none;}
  .sheet-facts dt{font-size:12.5px;color:var(--ink-2);flex-shrink:0;}
  .sheet-facts dd{font-size:13px;font-weight:600;color:var(--ink);text-align:right;min-width:0;}
  .sheet-link{font-size:13px;font-weight:700;color:var(--role-fg);text-align:right;}
  .sheet-link:hover{text-decoration:underline;}
  .sheet-note-box{margin-top:16px;padding:11px 14px;border-radius:14px;background:var(--fill);
    font-size:12.5px;line-height:1.55;color:var(--ink-2);box-shadow:inset 0 0 0 .5px var(--glass-brd);}

  /* ---- QR poster: a full-bleed sheet meant to be seen across a room ---- */
  #qrPosterOverlay{position:fixed;inset:0;z-index:260;display:none;align-items:center;justify-content:center;padding:16px;
    background:rgba(12,14,22,.55);-webkit-backdrop-filter:saturate(140%) blur(16px);backdrop-filter:saturate(140%) blur(16px);}
  #qrPosterOverlay.open{display:flex;animation:fadeIn .25s ease;}
  .qr-poster{position:relative;background:var(--glass-2);border:1px solid var(--glass-brd);border-radius:26px;
    padding:26px 24px 22px;text-align:center;max-width:340px;width:100%;
    -webkit-backdrop-filter:var(--blur-lg);backdrop-filter:var(--blur-lg);
    box-shadow:var(--shadow-pop), var(--inset-hi);animation:sheetIn .42s var(--spring);}
  .qr-close{position:absolute;top:10px;right:14px;font-size:26px;line-height:1;color:var(--ink-3);padding:4px 8px;}
  .qr-school{font-weight:800;font-size:15px;color:var(--ink);}
  .qr-sess{font-size:12px;color:var(--ink-2);margin-top:2px;}
  .qr-code{margin:16px auto 12px;width:260px;height:260px;max-width:100%;border-radius:16px;overflow:hidden;
    background:#fff;padding:10px;box-shadow:0 8px 22px -12px rgba(18,22,40,.5);}
  .qr-code svg{display:block;width:100%;height:100%;}
  .qr-track{height:6px;border-radius:999px;background:var(--fill-2);overflow:hidden;max-width:240px;margin:0 auto;}
  .qr-fill{height:100%;border-radius:999px;background:linear-gradient(90deg,var(--role),color-mix(in srgb,var(--role) 60%,#fff));
    transition:width 1s linear;}
  .qr-meta{font-size:12px;color:var(--ink-2);margin-top:9px;font-weight:600;}
  .qr-help{font-size:11.5px;color:var(--ink-3);margin-top:8px;line-height:1.5;}
  .qr-try{margin-top:12px;min-height:40px;width:100%;border-radius:12px;font-size:13px;font-weight:700;
    background:var(--fill);color:var(--ink);box-shadow:inset 0 0 0 .5px var(--glass-brd);}

  /* ---- timetable grid ----

     One grid rather than a head row plus five body rows: the columns have to
     line up across all six, and two grids agreeing about their widths only
     by arithmetic is how a column drifts the first time a session is renamed
     to something longer. */
  /* Three columns was hard-coded here, and stopped being right the day a
     head teacher could add morning preps. The count comes from the page as
     --tt-cols; the wrapper scrolls sideways so a five-session day does not
     squeeze five dropdowns into a phone's width. */
  .tt-scroll{overflow-x:auto;}
  .tt-grid{display:grid;
    grid-template-columns:38px repeat(var(--tt-cols,3),minmax(72px,1fr));
    gap:7px;align-items:stretch;min-width:100%;}
  /* A session this school does not run on this day. Not a free period —
     nothing can be put in it — so it reads as absent rather than empty. */
  .tt-slot.is-off{background:transparent;box-shadow:none;opacity:.4;
    align-items:center;justify-content:center;}
  .tt-slot.is-off::after{content:"—";font-size:12px;color:var(--ink-3);}
  .tt-corner{display:block;}
  .tt-col{font-size:11px;font-weight:700;color:var(--ink-3);text-align:center;text-transform:uppercase;
    letter-spacing:.05em;align-self:end;padding-bottom:2px;}
  .tt-col em{display:block;font-style:normal;font-weight:600;font-size:9.5px;opacity:.75;margin-top:1px;}
  .tt-day{font-size:12px;font-weight:700;color:var(--ink-2);align-self:center;}
  .tt-slot{display:flex;flex-direction:column;gap:4px;padding:5px;border-radius:11px;
    background:var(--fill);box-shadow:inset 0 0 0 .5px var(--glass-brd);transition:background .2s,box-shadow .2s;}
  /* A free period is dimmed rather than hidden. The shape of a week is the
     thing being read here, and a hole has to look like a hole. */
  .tt-slot.is-free{opacity:.55;}
  .tt-slot:focus-within{background:var(--fill-2);box-shadow:inset 0 0 0 1px var(--role);}
  /* A session holds one lesson at almost every school and several at a few.
     The lessons stack inside the cell, separated by a hairline so two
     classes in one morning read as two things rather than one muddle. */
  .tt-lesson{display:flex;flex-direction:column;gap:3px;}
  .tt-lesson + .tt-lesson{padding-top:5px;margin-top:2px;border-top:1px solid var(--glass-brd);}
  .tt-period{font-size:9px;font-weight:800;color:var(--ink-3);text-align:center;letter-spacing:.06em;}
  .tt-class{width:100%;min-height:32px;border:0;border-radius:8px;background:var(--bg);color:var(--ink);
    font-size:13px;font-weight:700;text-align:center;box-shadow:inset 0 0 0 .5px var(--glass-brd);}
  .tt-subject{width:100%;min-height:28px;border:0;border-radius:8px;background:transparent;color:var(--ink-2);
    font-size:11.5px;text-align:center;padding:0 4px;}
  .tt-subject::placeholder{color:var(--ink-3);opacity:.7;}
  /* It is a <select> now rather than a text box. Stripped of the native
     chrome so the grid still reads as a grid: an arrow in every one of
     forty-five cells is a page of arrows. */
  select.tt-subject{appearance:none;-webkit-appearance:none;cursor:pointer;
    padding-right:2px;text-overflow:ellipsis;}
  .tt-subject:focus{background:var(--bg);box-shadow:inset 0 0 0 .5px var(--glass-brd);}
  .tt-class-read{display:block;text-align:center;font-size:13px;font-weight:700;color:var(--ink);padding:6px 0 2px;}
  .tt-subject-read{display:block;text-align:center;font-size:11px;color:var(--ink-3);padding-bottom:5px;}
  /* One class's week: a name per slot instead of a class per slot. */
  .tt-who{display:block;text-align:center;padding:5px 4px;border-radius:8px;text-decoration:none;}
  .tt-who:hover{background:var(--fill-2);}
  .tt-who-name{display:block;font-size:12px;font-weight:700;color:var(--ink);line-height:1.25;}
  .tt-who-sub{display:block;font-size:10.5px;color:var(--ink-3);margin-top:1px;}
  .tt-slot.is-clash{box-shadow:inset 0 0 0 1.5px var(--f-orange);}
  @media (max-width:520px){
    .tt-grid{grid-template-columns:30px repeat(var(--tt-cols,3),minmax(64px,1fr));gap:5px;}
    .tt-class{font-size:12px;}
    .tt-subject{font-size:10.5px;}
  }

  /* ---- finding one teacher in a long list of codes ---- */
  .code-filter{width:100%;min-height:36px;margin-bottom:8px;padding:0 12px;border:0;border-radius:10px;
    background:var(--fill);color:var(--ink);font-size:13px;box-shadow:inset 0 0 0 .5px var(--glass-brd);}
  .code-filter::placeholder{color:var(--ink-3);}
  .code-filter:focus{outline:none;background:var(--bg);box-shadow:inset 0 0 0 1px var(--role);}

  /* ---- a code row for someone who was not issued one ---- */
  .code-row.is-off{opacity:.55;}
  .code-pill.is-muted{background:var(--fill);color:var(--ink-3);box-shadow:none;cursor:default;}

  /* ---- printed slips ---- */
  #printRoot{display:none;}
  .slip-sheet{padding:14px;}
  .slip-h1{font-size:15px;font-weight:800;margin-bottom:2px;color:#000;}
  .slip-sub{font-size:10.5px;color:#444;margin-bottom:12px;}
  .slip{display:flex;gap:12px;align-items:center;border:1.5px dashed #999;border-radius:10px;
    padding:10px 12px;margin-bottom:9px;break-inside:avoid;page-break-inside:avoid;}
  .slip-qr{flex-shrink:0;width:132px;height:132px;}
  .slip-qr svg{display:block;width:100%;height:100%;}
  .slip-name{font-size:14px;font-weight:800;color:#000;}
  .slip-meta{font-size:10.5px;color:#444;margin-bottom:5px;}
  .slip-code{font-family:ui-monospace,'SF Mono',monospace;font-size:27px;font-weight:800;letter-spacing:.16em;color:#000;}
  .slip-hint{font-size:10px;color:#444;margin-top:4px;}
  .slip-foot{font-size:10px;color:#444;margin-top:10px;}

  /* ==========================================================
     UTILITIES THE FROZEN TAILWIND BUILD LEFT OUT

     This file ships a pre-compiled Tailwind sheet, so any class
     added to the markup afterwards has no rule behind it and
     silently does nothing — which is how the dashboards ended up
     one tile per row on a phone. These fill the gaps.
     ========================================================== */
  .grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr));}
  /* Added for the timetable and class-timetable screens, the class picker on
     the register, and the class link on Class registers. Each of these was
     written as an ordinary Tailwind class and did nothing at all: the grid
     spilled past its column, the dropdown ran the full width of the panel,
     and the link had no underline colour. Nothing reported it — which is
     exactly what tools/check_css_classes.py exists to catch. */
  .max-w-3xl{max-width:48rem;}
  .decoration-slate-300{text-decoration-color:#cbd5e1;}
  .hover\:decoration-slate-500:hover{text-decoration-color:#64748b;}
  @media (min-width:640px){
    .sm\:max-w-xs{max-width:20rem;}
    .sm\:p-6{padding:1.5rem;}
  }
  /* containers whose children carry the styling, given a rule so the
     markup and the stylesheet stay in step */
  .slip-body{flex:1;min-width:0;}
  .qr-svg{display:block;width:100%;height:auto;max-width:100%;}
  .tt,.board,.wallqr{display:block;}
  .mb-5{margin-bottom:1.25rem;}
  .mt-2\.5{margin-top:.625rem;}
  .pt-3{padding-top:.75rem;}
  .pt-5{padding-top:1.25rem;}
  .w-32{width:8rem;}
  /* Without this the sign-in card has no height to centre itself within,
     and the roll-call rows square off the corners of their rounded card. */
  .min-h-\[70vh\]{min-height:70vh;}
  .overflow-hidden{overflow:hidden;}
  .font-normal{font-weight:400;}
  .text-amber-700{color:#B45309;}

  /* ---- Added for the scan screen, the class register and the report
     format picker. Same reason as everything above: the frozen build never
     compiled them, so without these the camera stage has no height and the
     register rows have no separators. ---- */
  .aspect-square{aspect-ratio:1/1;}
  .object-cover{object-fit:cover;}
  .inset-0{position:absolute;top:0;right:0;bottom:0;left:0;}
  .pointer-events-none{pointer-events:none;}
  .h-full{height:100%;}
  .w-3\/5{width:60%;}
  .h-3\/5{height:60%;}
  .w-3\.5{width:.875rem;}
  .h-3\.5{height:.875rem;}
  .px-2\.5{padding-left:.625rem;padding-right:.625rem;}
  .px-6{padding-left:1.5rem;padding-right:1.5rem;}
  .text-left{text-align:left;}
  .tracking-wide{letter-spacing:.025em;}
  /* Six digits copied off a phone screen, spaced so they can be checked one
     at a time. There is no Tailwind build here — the utilities are written by
     hand — so an arbitrary-value class only works if it is declared. */
  .tracking-\[0\.3em\]{letter-spacing:.3em;}
  /* Digits that line up in a column — learner counts, dates, references. */
  .tabular-nums{font-variant-numeric:tabular-nums;}

  .bg-slate-900{background-color:#0F172A;}
  /* #059669 carried white text at 3.78:1, in light mode as well as dark --
     this is a shared shim, so it was never a dark-mode bug. It is the button
     that closes a class register for the term, which is not a thing to do by
     accident because the label was hard to read. #047857 is 5.55:1 and is
     still unmistakably the same green. */
  .bg-emerald-600{background-color:#047857;}
  .text-amber-900{color:#78350F;}
  .text-rose-900{color:#881337;}
  .border-white\/70{border-color:rgba(255,255,255,.7);}
  .text-white\/90{color:rgba(255,255,255,.9);}
  .text-white\/70{color:rgba(255,255,255,.7);}
  .text-white\/60{color:rgba(255,255,255,.6);}

  /* ---- Added by the UI work of the last few commits. Every one of these
     was in the markup doing nothing: the draft-recovery banner had no
     background or border at all, the error pages had no vertical rhythm,
     and the console form's error links were not underlined the way they
     read as being. tools/check_css_classes.py is what caught them. ---- */
  .leading-snug{line-height:1.375;}
  .mt-3\.5{margin-top:.875rem;}
  .mt-7{margin-top:1.75rem;}
  .mt-8{margin-top:2rem;}
  .px-1{padding-left:.25rem;padding-right:.25rem;}
  .px-5{padding-left:1.25rem;padding-right:1.25rem;}
  .min-h-\[60vh\]{min-height:60vh;}
  .underline-offset-2{text-underline-offset:2px;}

  /* The recovered-draft banner. Sky rather than the amber the outbox bar
     uses, because the two say different things and can appear together:
     amber is "this is waiting to send", sky is "this was put back, and it
     is not saved yet". */
  .bg-sky-50{background-color:#F0F9FF;}
  .border-sky-200{border-color:#BAE6FD;}
  .text-sky-900{color:#0C4A6E;}

  .hover\:bg-slate-50:hover{background-color:#F8FAFC;}
  .hover\:bg-slate-700:hover{background-color:#334155;}
  /* One step darker again, because the base moved onto what used to be the
     hover colour and a hover that changes nothing is not a hover. 7.50:1. */
  .hover\:bg-emerald-700:hover{background-color:#036249;}
  .hover\:text-slate-900:hover{color:#0F172A;}

  /* The register list. Tailwind's divide-* is a border on every child but
     the first; written out here because the build has neither. */
  .divide-y > * + *{border-top-width:1px;border-top-style:solid;}
  .divide-slate-100 > * + *{border-top-color:#F1F5F9;}

  /* The format picker's selected state, without JavaScript. */
  .has-\[\:checked\]\:border-slate-800:has(:checked){border-color:#1E293B;}
  .has-\[\:checked\]\:bg-slate-50:has(:checked){background-color:#F8FAFC;}

  /* Hooks the QR script binds to; no styling of their own, declared so the
     class checker can see they are deliberate. */
  .qr-target{display:block;max-width:190px;}
  .qr-print{display:inline-flex;}

  /* ---- `hidden` has to win ----

     The HTML attribute is `display:none` in the user-agent stylesheet, which
     is the weakest rule in the building — so any element carrying a display
     utility ignores it entirely. `#placeholder` on the scan screen is
     `class="absolute inset-0 flex ..."`, and setting `hidden` on it did
     nothing at all: measured `display:flex`, `opacity:1`, sitting there under
     a confirmation panel and showing through it.

     Every `hidden` in this project is written expecting it to hide something,
     so the fix belongs here rather than at each call site. This is what the
     HTML specification recommends for exactly this reason. */
  [hidden]{display:none!important;}

  /* ---- A scan that worked, said properly ----

     It used to be `text-xs` at the bottom of the card, followed by a reload.
     A teacher looking up from a poster on a wall had about a second and a
     half to notice twelve-point grey, and the screen they looked at after
     that was the one they started from. This covers the camera: full square,
     one tick, one word, and it stays until it is dismissed.

     Green rather than the role colour on purpose. Every role that can scan
     has a different --role, and "it worked" should not change colour
     depending on who you are. */
  /* `opacity:1` stated outright, and `forwards` rather than `both`.
     `both` fills the from-state backwards as well, so an animation that has
     not started — a throttled background tab is enough — holds the panel at
     opacity 0. Measured exactly that: a confirmation that was there, covered
     the camera, and could not be seen. A fade that fails should fail to a
     visible panel, never to an invisible one. */
  .scan-done{position:absolute;inset:0;z-index:5;display:flex;
    flex-direction:column;align-items:center;justify-content:center;
    gap:10px;padding:24px;text-align:center;opacity:1;
    background:linear-gradient(160deg,#12603A 0%,#0C4A2C 100%);
    animation:scanDoneIn .28s var(--ease-out-expo) forwards;}
  .scan-done[hidden]{display:none;}

  .scan-done-mark{width:84px;height:84px;border-radius:50%;
    background:rgba(255,255,255,.14);display:flex;align-items:center;
    justify-content:center;margin-bottom:2px;}
  .scan-done-mark svg{width:52px;height:52px;fill:none;stroke:#FFFFFF;
    stroke-width:3.4;stroke-linecap:round;stroke-linejoin:round;}
  /* The circle draws, then the tick. Under 600ms in total — long enough to
     read as an event, short enough that nobody waits for it. */
  .scan-done-mark circle{stroke-dasharray:151;stroke-dashoffset:151;
    animation:scanRing .42s var(--ease-out-expo) forwards;}
  .scan-done-mark path{stroke-dasharray:38;stroke-dashoffset:38;
    animation:scanTick .30s var(--ease-out-expo) .30s forwards;}

  .scan-done-title{color:#FFFFFF;font-size:26px;font-weight:800;
    letter-spacing:-.02em;line-height:1.15;margin:0;}
  .scan-done-note{color:rgba(255,255,255,.92);font-size:14px;line-height:1.45;
    margin:0;max-width:30ch;}
  .scan-done-today{color:rgba(255,255,255,.74);font-size:12.5px;margin:0;}
  .scan-done-go{margin-top:8px;min-height:44px;display:inline-flex;
    align-items:center;justify-content:center;padding:0 28px;border-radius:12px;
    background:#FFFFFF;color:#0C4A2C;font-weight:700;font-size:14px;
    text-decoration:none;}
  .scan-done-go:hover{background:#EAF6EF;}

  /* Queued rather than sent: the same panel, in the colour the rest of the
     product uses for "waiting", so it cannot be mistaken for confirmed. */
  .scan-done.is-queued{background:linear-gradient(160deg,#8A5A0B 0%,#6B430A 100%);}
  .scan-done.is-queued .scan-done-go{color:#6B430A;}

  @keyframes scanDoneIn{from{opacity:0;transform:scale(.97);}
                        to{opacity:1;transform:none;}}
  @keyframes scanRing{to{stroke-dashoffset:0;}}
  @keyframes scanTick{to{stroke-dashoffset:0;}}
  @media (prefers-reduced-motion:reduce){
    .scan-done,.scan-done-mark circle,.scan-done-mark path{animation:none;}
    .scan-done-mark circle,.scan-done-mark path{stroke-dashoffset:0;}
  }

  /* ---- The term poster card ---- */
  .hover\:text-rose-800:hover{color:#9F1239;}
  .max-w-prose{max-width:65ch;}
  .min-w-\[12rem\]{min-width:12rem;}

  /* ---- The class registers screen ---- */
  .border-slate-800{border-color:#1E293B;}
  .py-1\.5{padding-top:.375rem;padding-bottom:.375rem;}
  .text-3xl{font-size:1.875rem;line-height:2.25rem;}
  .text-rose-700{color:#BE123C;}

  /* ---- Correcting a saved register ---- */
  /* A segmented present/absent pair. Radio buttons rather than a toggle so
     the current mark is readable without interpreting a switch position. */
  .mark-choice{position:relative;display:inline-flex;}
  .mark-choice input{position:absolute;opacity:0;width:100%;height:100%;
    margin:0;cursor:pointer;}
  .mark-choice span{display:inline-flex;align-items:center;min-height:34px;
    padding:0 12px;border:1px solid var(--hairline);border-radius:10px;
    font-size:12.5px;font-weight:600;color:var(--ink-2);background:var(--glass-3);
    cursor:pointer;}
  /* --role-btn again, and this one mattered most.

     The selected chip was white on the bright role colour: 2.22:1 for a
     teacher, whose role colour is #34C759. That is not a near miss, it is
     white on mid-green, and it is the control that says which way a child's
     attendance mark is about to be changed. #1F7A35 is the same green's
     darker shade and measures 5.39:1.

     The border keeps the bright colour: it is a boundary, not text, and the
     3:1 that non-text contrast asks for it clears easily -- so the chip still
     reads as the role's colour at a glance. */
  .mark-choice input:checked + span{background:var(--role-btn, var(--role));color:#fff;
    border-color:var(--role);}
  .mark-choice input:focus-visible + span{outline:2px solid var(--role);
    outline-offset:2px;}
  .reason-row.is-hidden{display:none;}
  .placeholder-amber-700::placeholder{color:#B45309;}
  .mx-1{margin-left:.25rem;margin-right:.25rem;}
  @media (min-width:640px){
    .sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr));}
    .sm\:col-span-3{grid-column:span 3/span 3;}
  }
  @media (min-width:1024px){
    .lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr));}
  }

  /* ==========================================================
     ACCESSIBILITY FOUNDATIONS
     ========================================================== */
  /* a keyboard user should not have to tab the whole nav first */
  /* --role-btn, not --role. The palette keeps two shades of each role colour
     and the -btn one is the darker: it exists precisely because white text on
     the bright shade does not carry. The button class next to it has always
     used it; this rule was the one place that reached past it, so the skip
     link -- an accessibility feature -- was itself failing contrast on every
     page. White on #0A84FF is 3.65:1 at 14px, where 4.5 is the floor; on
     #0A6ACF it is 5.35, and every role's -btn shade clears it. */
  .skip-link{position:absolute;left:8px;top:-56px;z-index:400;padding:11px 18px;border-radius:0 0 12px 12px;
    background:var(--role-btn, var(--role,#5E5CE6));color:#fff;font-weight:700;font-size:14px;
    transition:top .18s var(--ease-out-expo);}
  .skip-link:focus{top:0;}

  /* text for screen readers only — never a substitute for visible labels,
     only for context a sighted user reads from layout */
  .sr-only{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;}

  /* WCAG 2.2 §2.5.8 — no interactive target smaller than 24px, and
     anything thumb-operated gets the full 44px */
  button,[role=button],a[href]{min-height:24px;}
  /* Scoped to main, not the prototype's .app-screen wrapper — that class does
     not exist in the product, so these rules were matching nothing at all and
     every control on a phone was below the touch-target floor. */
  /* The exclusions are controls that set their own height and mean it. A
     `:not()` counts towards specificity, so this selector scores three
     classes and quietly beat `.filter-bar .filter-apply`, holding Apply at
     32px — still true with `.tt-cell` dropped, which was four: the timetable
     grid has no buttons in it any more, only selects and text boxes. */
  main button:not(.pg-btn):not(.code-pill):not(.linkish):not(.filter-apply){min-height:32px;}
  @media (pointer:coarse){
    main button:not(.code-pill),
    main select,
    main input:not([type=checkbox]):not([type=radio]){min-height:44px;}
    main a.linkish,main .sidenav-btn{min-height:40px;}
  }

  /* The header's own controls. Logout sat at 20px tall; the Settings link
     added beside it came out 24px, because `a[href]{min-height:24px}` further
     down is an element-plus-attribute selector and beats a lone utility class
     — the third time that has caught something in this file. Both are matched
     here so they cannot drift apart, and because on a phone the labels are
     hidden and these are bare icons. */
  header button,header a[href]{min-height:44px;padding:0 8px;border-radius:10px;
    display:inline-flex;align-items:center;}
  header button:hover,header a[href]:hover{background:var(--fill);}

  /* ---- governance dialogs: one-time code, export password, decrypt ----

     There were ID rules here, carried over from the prototype, that hid these
     overlays until an `.open` class was added. Nothing in this codebase ever
     added that class — every dialog is toggled with the `hidden` attribute —
     and an ID selector outranks the `.gov-overlay` class that governs the
     rest. So `#exportOverlay` computed `display:none` no matter what the
     JavaScript did, and the export password dialog could not be opened by
     anybody, on any screen, since the first commit. The buttons looked dead
     because they were.

     `.gov-overlay` and `.gov-overlay[hidden]` below do the same job at a
     specificity the markup can actually beat. `#otpOverlay` and
     `#decryptOverlay` no longer exist in any template; `#revealOverlay`, which
     does, was never named here and has worked all along — which is why the
     one-time code dialog was fine and this one was not.
  */
  .gov-dialog{background:var(--glass-2);border:1px solid var(--glass-brd);border-radius:22px;
    padding:24px 22px 20px;max-width:420px;width:100%;
    -webkit-backdrop-filter:var(--blur-lg);backdrop-filter:var(--blur-lg);
    box-shadow:var(--shadow-pop), var(--inset-hi);animation:sheetIn .42s var(--spring);}
  .gov-title{font-size:17px;font-weight:800;color:var(--ink);}
  .gov-sub{font-size:13px;color:var(--ink-2);line-height:1.55;margin-top:6px;margin-bottom:16px;}
  .gov-dialog label{display:block;font-size:13px;font-weight:600;color:var(--ink);margin-bottom:6px;}
  .gov-dialog label.mt-3{margin-top:12px;}
  .gov-demo{font-size:12px;color:var(--f-orange);background:var(--t-orange);
    border-radius:11px;padding:9px 12px;margin-top:10px;line-height:1.5;}
  .gov-file{font-size:12.5px;color:var(--ink-2);margin-top:11px;}
  .gov-file span{color:var(--ink-3);}
  .gov-actions{display:flex;gap:9px;justify-content:flex-end;margin-top:16px;}
  .gov-cancel{font-size:13.5px;font-weight:600;color:var(--ink-2);padding:0 14px;min-height:44px;border-radius:12px;}
  .gov-cancel:hover{background:var(--fill);color:var(--ink);}
  .gov-go{min-height:44px;padding:0 18px;border-radius:12px;font-size:13.5px;font-weight:700;color:#fff;}
  .gov-fine{font-size:11.5px;color:var(--ink-3);line-height:1.5;margin-top:12px;}

  /* ---- officer lifecycle row controls ---- */
  .row-actions{display:inline-flex;gap:4px;flex-wrap:wrap;justify-content:flex-end;}
  .linkish{font-size:12px;font-weight:700;color:var(--role-fg);padding:6px 9px;border-radius:9px;white-space:nowrap;}
  .linkish:hover{background:var(--role-tint);}
  .linkish.danger{color:var(--f-red);}
  .linkish.danger:hover{background:var(--t-red);}
  .assign-select{font-size:12.5px;padding:6px 26px 6px 10px!important;border-radius:10px!important;
    min-height:36px!important;background:var(--fill)!important;box-shadow:inset 0 0 0 .5px var(--glass-brd);}
  .assign-select:disabled{opacity:.5;}

  /* ---- show the password ----
     The input keeps its own padding; the button sits over the right-hand end
     of it. Scoped past `main button{min-height:32px}` the same way the filter
     bar's controls are — a bare class loses to that selector. */
  .pw-field{position:relative;}
  .pw-field input{padding-right:52px!important;}
  main .pw-field .pw-toggle{
    position:absolute;top:0;right:0;height:100%;width:48px;min-height:48px;
    display:flex;align-items:center;justify-content:center;
    color:var(--ink-3);border-radius:0 12px 12px 0;background:transparent;
  }
  main .pw-field .pw-toggle:hover{color:var(--ink);}
  main .pw-field .pw-toggle svg{width:19px;height:19px;}
  main .pw-field .pw-toggle[aria-pressed=true]{color:var(--role-fg,var(--role));}

  /* ---- a validation message that stays put and is announced ---- */
  .field-error{font-size:12.5px;font-weight:600;color:var(--f-red);margin-top:6px;line-height:1.45;}
  .field-error:empty{display:none;}
  [aria-invalid=true]{border-color:var(--red)!important;
    box-shadow:0 0 0 3px color-mix(in srgb, var(--red) 22%, transparent);}

  /* ---- the section nav below the sidebar breakpoint ----

     This was a horizontal scroller with a fade on its right edge. Measured on
     a 375px screen it held 1124px of items in a 342px box: two of a head
     teacher's eight sections were fully visible, one was clipped, and five —
     Teachers, Class Registers, School boundary, Reports, Settings — were off
     the edge behind a gesture with nothing but a 12% fade to suggest it.

     Worse, it never scrolled to where you were. On Settings the highlighted
     item sat at x=1014 on a 375px screen, so the one element whose whole job
     is to answer "where am I" was the one you could not see.

     It wraps now. Every section is on screen, the active one included, and
     the cost is rows of nav rather than a hidden two-thirds of the product.
     Two columns fits the longest label there is ("Education Officers") on one
     line at this size, so nothing is truncated either. */
  @media (max-width:1023px){
    nav[aria-label=Sections]{
      display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
      gap:4px;padding:6px;overflow:visible;
    }
    /* width:auto is gone with the scroller: in a grid the cell is the width,
       and 100% of the cell is exactly right. */
    nav[aria-label=Sections] .sidenav-btn{
      width:100%;min-height:44px;padding:7px 10px;
      font-size:13px;line-height:1.25;
    }
    nav[aria-label=Sections] .sidenav-btn svg{width:26px;height:26px;padding:5px;}
  }

  /* One column below ~340px, where two would start truncating. */
  @media (max-width:339px){
    nav[aria-label=Sections]{grid-template-columns:1fr;}
  }

  /* ---- the disclosure the grid lives in on a phone ----

     Closed it is one row naming the current section; open it is the grid
     above. On the desktop the <details> steps out of the way entirely with
     display:contents, so the nav is a direct child of the page grid again and
     its lg:sticky has the grid to stick to. */
  .sectionnav{min-width:0;}
  .sectionnav-summary{
    display:flex;align-items:center;gap:8px;cursor:pointer;list-style:none;
    min-height:52px;padding:0 14px;border-radius:16px;
    background:var(--glass-2);box-shadow:inset 0 0 0 .5px var(--glass-brd), var(--inset-hi);
    font-size:14px;font-weight:700;color:var(--ink);
  }
  /* Both spellings: WebKit still uses the pseudo-element, everyone else ::marker. */
  .sectionnav-summary::-webkit-details-marker{display:none;}
  .sectionnav-summary::marker{content:"";}
  .sectionnav-summary:hover{background:var(--glass-3);}
  .sectionnav-eyebrow{font-size:11px;font-weight:700;text-transform:uppercase;
    letter-spacing:.06em;color:var(--ink-3);flex-shrink:0;}
  .sectionnav-current{flex:1 1 auto;min-width:0;overflow:hidden;
    text-overflow:ellipsis;white-space:nowrap;color:var(--role-fg,var(--role));}
  .sectionnav-chevron{width:18px;height:18px;flex-shrink:0;color:var(--ink-3);
    transition:transform .2s var(--ease-out-expo);}
  .sectionnav[open] .sectionnav-chevron{transform:rotate(180deg);}
  .sectionnav[open] nav[aria-label=Sections]{margin-top:8px;}

  @media (min-width:1024px){
    .sectionnav{display:contents;}
    .sectionnav-summary{display:none;}
  }

  /* ---- the page stays put while a layer is open ---- */
  body.scroll-locked{overflow:hidden;}

  /* ---- a focus ring you can actually see, only for keyboard users ---- */
  :focus-visible{outline:3px solid var(--role, #5E5CE6);outline-offset:2px;border-radius:6px;}
  .row-link:focus-visible{outline-offset:-3px;}

  /* ---- compact stat tiles: a count and a word, nothing more ---- */
  .stat-value{font-size:22px;line-height:1.15;}
  @media (max-width:640px){ .stat-value{font-size:24px;} }
  .stat-label{font-size:12.5px;color:var(--ink-2);line-height:1.35;}

  /* On a phone the dashboards are read at arm's length in daylight,
     so the numbers and their labels hold their size rather than
     shrinking to fit more in. */
  @media (max-width:640px){
    .stat-label{font-size:13px;}
    .kpi-value{font-size:26px;}
    .kpi-label{font-size:12px;}
    .sidenav-btn{font-size:14px;min-height:46px;}
    .sidenav-btn svg{width:26px;height:26px;}
    table.data-table td{font-size:13.5px;padding:12px 10px;}
    table.data-table th{font-size:11px;}
    .chart-title{font-size:14.5px;}
    .chart-sub{font-size:12px;}
    .pg-count,.pg-now{font-size:13px;}
    .filter-field label{font-size:12px;}
  }

  /* ---- location policy chooser ---- */
  .policy-opts{display:grid;gap:9px;}
  .policy-opt{display:flex;gap:10px;align-items:flex-start;cursor:pointer;
    background:var(--glass);border:1px solid var(--glass-brd);border-radius:14px;padding:11px 13px;
    transition:border-color .2s, background .2s;}
  .policy-opt:has(input:checked){border-color:var(--role);background:var(--role-tint);}
  .policy-opt input{margin-top:2px;flex-shrink:0;}
  .policy-opt b{display:block;font-size:13px;font-weight:700;color:var(--ink);}
  .policy-opt em{display:block;font-style:normal;font-size:11.5px;color:var(--ink-2);line-height:1.5;margin-top:2px;}

  /* ---- preview-only location simulator ---- */
  .geo-chip[data-geo=bad]{background:var(--t-red);color:var(--f-red);
    box-shadow:inset 0 0 0 .5px color-mix(in srgb, var(--red) 30%, transparent);}
  .geosim{display:flex;align-items:center;gap:6px;flex-wrap:wrap;justify-content:center;
    margin-bottom:14px;padding:8px 10px;border-radius:14px;
    background:var(--fill);box-shadow:inset 0 0 0 .5px var(--glass-brd);}
  .geosim-label{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-3);}
  .geosim button{font-size:11.5px;font-weight:700;padding:5px 10px;border-radius:9px;
    background:transparent;color:var(--ink-2);}
  .geosim button[aria-pressed=true]{background:var(--glass-3);color:var(--ink);
    box-shadow:inset 0 0 0 .5px var(--glass-brd), 0 1px 3px -1px rgba(18,22,40,.3);}

  /* ---- the gate a scan lands on ---- */
  .gate-who{display:flex;align-items:center;gap:10px;text-align:left;padding-bottom:13px;margin-bottom:14px;
    border-bottom:1px solid var(--hairline);}
  .gate-name{font-weight:700;font-size:14px;color:var(--ink);}
  .gate-sub{font-size:11.5px;color:var(--ink-2);}
  .gate-out{margin-left:auto;font-size:12px;font-weight:700;color:var(--ink-3);padding:6px 8px;flex-shrink:0;}
  .gate-out:hover{color:var(--f-red);}
  .gate-label{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.07em;color:var(--ink-3);}
  .gate-note{font-size:13px;line-height:1.55;color:var(--ink-2);background:var(--fill);border-radius:14px;padding:13px 15px;}
  .gate-note.is-far{background:var(--t-orange);color:var(--f-orange);}
  .gate-note.is-ok{background:var(--t-green);color:var(--f-green);}

  /* ---- the printed wall poster ----
     Designed during the port and then left unused for months: what actually
     printed was an inline-styled document.write with a title and one line.
     The print handler in sessions.html builds this markup now and links this
     stylesheet, so there is one copy of the design rather than two.

     340px because that is the size the old inline version printed at and it
     is known to scan; 250 was never tested against a camera on a wall. */
  .wall-card{border:2px solid #111;border-radius:14px;padding:18px 16px;text-align:center;margin:10px 0;}
  .wall-qr{width:340px;height:340px;max-width:100%;margin:0 auto 12px;}
  .wall-qr svg{display:block;width:100%;height:100%;}
  .wall-title{font-size:19px;font-weight:800;color:#000;}
  .wall-school{font-size:12px;color:#444;margin-top:2px;}
  .wall-steps{text-align:left;max-width:280px;margin:13px auto 0;padding-left:20px;
    font-size:12px;color:#222;line-height:1.75;}
  .wall-steps li{list-style:decimal;}
  .wall-foot{font-size:11px;color:#444;margin-top:12px;}
  /* Several posters in one job: one to a page, so they can be cut and hung
     rather than trimmed apart. */
  .wall-card + .wall-card{page-break-before:always;break-before:page;}

  /* The button that starts the job, on the codes screen rather than on
     paper. It only needs to exist as a selector — the layout is Tailwind.

     The slip design itself is not here. It was drawn during the port —
     `.slip`, `.slip-name`, `.slip-code`, `.slip-foot`, a QR slot and a
     `break-inside` rule — and then left unused for months because nothing
     printed. A second copy of it lived here for a while and won by sitting
     later in the file, which is the drift this stylesheet warns about
     everywhere else. There is one again. */
  .slip-print{background:none;}

  /* ---- sheet preview: paper on screen ---- */
  #sheetPreviewOverlay{position:fixed;inset:0;z-index:270;display:none;align-items:center;justify-content:center;padding:16px;
    background:rgba(12,14,22,.55);-webkit-backdrop-filter:saturate(140%) blur(16px);backdrop-filter:saturate(140%) blur(16px);}
  #sheetPreviewOverlay.open{display:flex;animation:fadeIn .25s ease;}
  .sheet-preview{background:var(--glass-2);border:1px solid var(--glass-brd);border-radius:22px;
    width:100%;max-width:640px;max-height:calc(100vh - 32px);display:flex;flex-direction:column;
    -webkit-backdrop-filter:var(--blur-lg);backdrop-filter:var(--blur-lg);
    box-shadow:var(--shadow-pop), var(--inset-hi);animation:sheetIn .42s var(--spring);}
  .sp-head{display:flex;align-items:center;justify-content:space-between;gap:12px;
    padding:14px 16px;border-bottom:1px solid var(--hairline);}
  .sp-title{font-weight:800;font-size:14px;color:var(--ink);min-width:0;overflow:hidden;
    text-overflow:ellipsis;white-space:nowrap;}
  @media (max-width:420px){ .sp-title{font-size:12.5px;} .sp-print{padding:0 11px;font-size:12px;} }
  .sp-actions{display:flex;align-items:center;gap:8px;flex-shrink:0;}
  .sp-print{display:inline-flex;align-items:center;gap:7px;min-height:38px;padding:0 14px;border-radius:12px;
    background:var(--role-btn, var(--role));color:#fff;font-size:13px;font-weight:700;white-space:nowrap;flex-shrink:0;
    box-shadow:var(--shadow-btn), inset 0 1px 0 rgba(255,255,255,.4);}
  .sp-close{font-size:24px;line-height:1;color:var(--ink-3);padding:2px 8px;}
  .sp-scroll{overflow:auto;padding:14px;flex:1;}
  /* the sheet is always white — it is a picture of paper */
  .sp-paper{background:#fff;border-radius:12px;padding:6px 10px;
    box-shadow:0 8px 24px -14px rgba(18,22,40,.5);}
  .sp-paper .slip-h1,.sp-paper .slip-name,.sp-paper .slip-code,.sp-paper .card-cell{color:#000;}
  .sp-paper .slip-sub,.sp-paper .slip-meta,.sp-paper .slip-hint,.sp-paper .slip-foot{color:#444;}
  .sp-foot{font-size:11.5px;color:var(--ink-3);padding:10px 16px 13px;border-top:1px solid var(--hairline);line-height:1.5;}

  /* ---- pager ---- */
  .pager{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;
    padding:12px 16px;border-top:1px solid var(--hairline);}
  .pg-count{font-size:12.5px;color:var(--ink-2);}
  .pg-count b{color:var(--ink);font-weight:700;}
  .pg-controls{display:flex;align-items:center;gap:5px;}
  .pg-now{font-size:12.5px;font-weight:600;color:var(--ink-2);padding:0 8px;white-space:nowrap;}
  .pg-btn{min-width:34px;height:34px;border-radius:10px;background:var(--fill);color:var(--ink-2);
    font-size:13px;font-weight:700;box-shadow:inset 0 0 0 .5px var(--glass-brd);}
  .pg-btn:hover:not(:disabled){background:var(--fill-2);color:var(--ink);}
  .pg-btn:disabled{opacity:.35;cursor:default;}

  /* ---- the teacher's own rotating code ---- */
  .mycode{border-radius:20px;padding:18px 16px;text-align:center;margin-bottom:16px;
    border:1px solid var(--glass-brd);background:var(--glass);
    -webkit-backdrop-filter:var(--blur);backdrop-filter:var(--blur);box-shadow:var(--inset-hi);}
  .mycode.is-live{background:var(--t-green);border-color:color-mix(in srgb,var(--green) 34%, transparent);}
  .mycode.is-far{background:var(--t-orange);border-color:color-mix(in srgb,var(--orange) 38%, transparent);}
  .mycode-label{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.07em;color:var(--ink-3);}
  .mycode-digits{font-size:34px;font-weight:800;letter-spacing:.06em;color:var(--ink);margin:8px 0 10px;}
  .mycode-track{height:5px;border-radius:999px;background:var(--fill-2);overflow:hidden;max-width:200px;margin:0 auto 8px;}
  .mycode-fill{height:100%;border-radius:999px;background:var(--green);transition:width 1s linear;}
  .mycode-note{font-size:12px;color:var(--ink-2);line-height:1.5;}
  .mycode-fine{font-size:11px;color:var(--ink-3);margin-top:10px;line-height:1.5;}
  .mycode-btn,.mycode-go{margin-top:11px;min-height:44px;border-radius:13px;font-weight:700;font-size:14px;
    padding:0 18px;width:100%;background:var(--fill);color:var(--ink);box-shadow:inset 0 0 0 .5px var(--glass-brd);}
  .mycode-go{background:var(--role-btn, var(--role));color:#fff;box-shadow:var(--shadow-btn), inset 0 1px 0 rgba(255,255,255,.4);}

  /* ---- the numbers the head teacher writes on the board ---- */
  .board-row{display:flex;align-items:center;justify-content:space-between;gap:12px;
    padding:9px 0;border-bottom:1px solid var(--hairline);}
  .board-row:last-child{border-bottom:0;}
  .board-sess{font-size:13px;font-weight:600;color:var(--ink-2);}
  .board-line{font-family:ui-monospace,'SF Mono',monospace;font-size:15px;font-weight:800;color:var(--ink);
    background:var(--fill);border-radius:9px;padding:4px 12px;box-shadow:inset 0 0 0 .5px var(--glass-brd);}

  /* ---- printed term card ---- */
  .card-sheet{border:1.5px dashed #999;border-radius:10px;padding:12px 14px;margin-bottom:12px;
    break-inside:avoid;page-break-inside:avoid;}
  .card-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:5px;margin:9px 0;}
  .card-cell{font-family:ui-monospace,'SF Mono',monospace;font-size:12px;font-weight:700;color:#000;
    border:1px solid #CCC;border-radius:6px;padding:4px 3px;text-align:center;}
  .card-cell em{display:block;font-style:normal;font-size:8px;font-weight:600;color:#777;}

  /* ---- empty-state dashed panels ---- */
  .border-dashed{border-color:var(--field-brd)!important;}


/* ============================================================
   FONT FALLBACK — the same size with or without the network

   Inter is fetched from Google. When it does not arrive the
   browser drops to Segoe UI, which sets 8.6% narrower with a
   9.4% shorter x-height, so every screen renders slightly
   smaller. These faces scale the local substitute to Inter's
   own metrics, measured rather than guessed, so the layout is
   identical whether or not the font ever loads.
   ============================================================ */
@font-face{
  font-family:'Inter Local';
  src:local('Segoe UI'), local('Segoe UI Regular');
  font-weight:400 500;
  size-adjust:108.6%;
  ascent-override:89.3%;
  descent-override:22.1%;
  line-gap-override:0%;
}
@font-face{
  font-family:'Inter Local';
  src:local('Segoe UI Semibold'), local('Segoe UI Bold'), local('Segoe UI');
  font-weight:600 800;
  size-adjust:108.6%;
  ascent-override:89.3%;
  descent-override:22.1%;
  line-gap-override:0%;
}
@font-face{
  font-family:'Inter Local';
  src:local('Arial');
  unicode-range:U+0-10FFFF;
  size-adjust:105.9%;
  ascent-override:91.6%;
  descent-override:22.7%;
  line-gap-override:0%;
}


/* ============================================================
   ROLL CALL — a row per child, thumb-sized targets

   Marked in a classroom, standing up, on a cheap phone. Every
   control is at least 44px and the two choices sit side by side
   so a whole class can be marked without hunting.
   ============================================================ */
.roll-row{display:flex;align-items:center;gap:12px;padding:12px 16px;
  border-bottom:1px solid var(--hairline);transition:background .18s ease;}
.roll-row:last-of-type{border-bottom:0;}
.roll-row.is-in{background:var(--t-green);}
.roll-row.is-out{background:var(--t-red);}
.roll-row.is-off{opacity:.55;}
.roll-name{font-size:14px;font-weight:600;color:var(--ink);}
.roll-meta{font-size:11.5px;color:var(--ink-2);margin-top:1px;}

.roll-toggle{display:flex;gap:4px;flex-shrink:0;padding:3px;border-radius:13px;
  background:var(--fill);box-shadow:inset 0 0 0 .5px var(--glass-brd);}
.roll-toggle button{min-height:38px;padding:0 13px;border-radius:10px;
  font-size:12.5px;font-weight:700;color:var(--ink-2);white-space:nowrap;}
.roll-toggle button[aria-pressed=true]{color:#fff;}
.roll-toggle .mark-in[aria-pressed=true]{background:#1F7A35;
  box-shadow:0 2px 8px -3px #1F7A35, inset 0 1px 0 rgba(255,255,255,.35);}
.roll-toggle .mark-out[aria-pressed=true]{background:#C4271F;
  box-shadow:0 2px 8px -3px #C4271F, inset 0 1px 0 rgba(255,255,255,.35);}

.roll-reason{padding:0 16px 14px;background:var(--t-red);}
.roll-reason.hidden{display:none;}
/* A row the register is waiting on. Same visual language as .needs-reason
   below it, because it is the same kind of problem: something the teacher has
   to come back to before this can be saved. Colour is not the only signal —
   the save also moves focus to the first one. */
.roll-row.needs-mark{background:var(--t-orange);
  box-shadow:inset 3px 0 0 var(--orange);}

.roll-reason-note{font-size:11.5px;color:var(--ink-2);margin-top:6px;}
.roll-reason.needs-reason select{border-color:var(--red)!important;
  box-shadow:0 0 0 3px color-mix(in srgb, var(--red) 22%, transparent);}
.roll-reason.needs-reason .roll-reason-note{color:var(--f-red);font-weight:600;}

@media (max-width:400px){
  .roll-toggle button{padding:0 10px;font-size:12px;}
  .roll-name{font-size:13.5px;}
}

/* ============================================================
   TEACHER TABS — the prototype's .ttabs, as links

   The prototype switched hidden panels with JavaScript, so its
   tabs were <button>s and the bar was always four columns wide.
   Here each tab is a real page, so they are <a>s, and the count
   is whatever is actually built — hence data-tabs rather than a
   hard-coded four.
   ============================================================ */
/* ============================================================
   STAT TILES

   The count of tiles changes with the role — the Ministry has no
   "field officers" tile, a head teacher has neither that nor
   "districts". A fixed six-column grid therefore left an orphan
   tile on its own row for some roles and not others. Auto-fit
   flows to whatever is actually there.
   ============================================================ */
/* Flex, not grid. A grid with a fixed column count strands the last tile on a
   row of its own with dead space beside it, and the count differs per role —
   five for the Ministry, six for the administrator. Letting the tiles grow
   means the final row always fills the width, whatever the count. */
.stat-grid{display:flex;flex-wrap:wrap;gap:12px;}
/* `max-width` as well as the flex basis, and it is not decoration.
   `flex:1 1 150px` divides whatever row it is given, so a head teacher —
   who has two tiles rather than the administrator's six — got two cards
   652px wide on a 2560px monitor, each holding an icon, a two-digit number
   and one word. The row is left-aligned and the tiles keep their size; the
   space left over is space, which reads better than a billboard. */
.stat-tile{flex:1 1 150px;min-width:150px;
  display:flex;flex-direction:column;gap:2px;padding:16px;border-radius:1rem;
  background:var(--glass-2);box-shadow:inset 0 0 0 .5px var(--glass-brd), var(--inset-hi);
  transition:transform .18s var(--ease-out-expo), box-shadow .18s;}
.stat-tile:hover{transform:translateY(-1px);
  box-shadow:inset 0 0 0 .5px var(--glass-brd), 0 8px 20px -14px rgba(18,22,40,.55);}
.stat-ico{width:32px;height:32px;border-radius:10px;display:flex;align-items:center;
  justify-content:center;margin-bottom:8px;}
.stat-ico svg{width:16px;height:16px;}

/* ============================================================
   DATA TABLES ON A PHONE

   Measured at 375px, every oversight table was wider than the
   screen and scrolled inside its own box: Teachers 513px,
   Reports 470px, Class registers 407px. Scrolling the box
   rather than the page was the right call and stays. What was
   wrong is which columns fell off the end.

   It was always the last ones, and the last ones are the
   answer: "Rate" and "Grant leave" on Teachers, "Inside fence"
   on Reports, "Closed" on Class registers. What stayed on
   screen was Teacher, Class, Phone, Attended, Due — the raw
   inputs. A head teacher could see everything that went into
   the judgement and neither the judgement nor the thing to do
   about it, with no sign the columns existed at all.

   So below 640px a row stops being a row and becomes a card,
   with each cell labelled by its own column heading. Nothing
   is off the edge because there is no edge.

   The labels are copied from <thead> at load by
   static/js/table-cards.js rather than written into eight
   templates by hand, so a renamed column cannot leave a cell
   captioned with the old name.
   ============================================================ */
@media (max-width:640px){
  table.data-table,
  table.data-table tbody,
  table.data-table tr,
  table.data-table td{display:block;width:auto;}

  /* The headings are on every cell now. */
  table.data-table thead{display:none;}

  table.data-table tr{padding:4px 0;border-top:1px solid var(--hairline);}
  table.data-table tbody tr:first-child{border-top:none;}
  table.data-table tbody tr:hover{background-color:transparent;}

  table.data-table td{
    display:flex;align-items:baseline;justify-content:space-between;gap:14px;
    border-top:none;padding:5px 16px;text-align:right;
  }
  table.data-table td::before{
    content:attr(data-label);
    flex:0 0 auto;text-align:left;
    font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;
    color:var(--ink-3);
  }
  /* The first cell is the row's name — it leads, at full width, no caption. */
  table.data-table td:first-child{
    display:block;text-align:left;padding-top:12px;font-size:15px;
  }
  table.data-table td:first-child::before{display:none;}
  /* The actions column has no heading, so it gets the width instead. */
  table.data-table td[data-label=""],
  table.data-table td:not([data-label]){display:block;text-align:left;}
  table.data-table td[data-label=""]::before,
  table.data-table td:not([data-label])::before{display:none;}
  table.data-table td:last-child{padding-bottom:12px;}
  /* An empty state spans the table and is not a labelled field. */
  table.data-table td[colspan]{display:block;text-align:center;padding:0;}
  table.data-table td[colspan]::before{display:none;}
}

/* ---- the one act the overview offers ----
   Sits above today's numbers because it is the reason for them. Amber when
   nothing is open, because that is something to go and do; quiet green when
   one is, because that is only confirmation. */
.session-nudge{display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  padding:14px 16px;border-radius:1.25rem;
  background:var(--t-orange);box-shadow:inset 0 0 0 .5px var(--glass-brd);}
.session-nudge[data-state=open]{background:var(--t-green);}
.session-nudge-body{flex:1 1 260px;min-width:0;}
.session-nudge-title{font-size:14.5px;font-weight:800;color:var(--f-orange);letter-spacing:-.01em;}
.session-nudge[data-state=open] .session-nudge-title{color:var(--f-green);}
.session-nudge-note{font-size:12.5px;color:var(--ink-2);margin-top:3px;line-height:1.5;}
.session-nudge-go{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;padding:0 18px;border-radius:12px;font-size:13.5px;font-weight:700;
  color:#fff;text-decoration:none;}
.session-nudge[data-state=open] .session-nudge-go{background:transparent;
  color:var(--f-green);text-decoration:underline;text-underline-offset:3px;padding:0 4px;}
@media (max-width:520px){
  .session-nudge-go{width:100%;}
}

/* ---- today's strip: what is happening now, not what exists ---- */
.today-card{padding:20px;border-radius:1.25rem;background:var(--glass-2);
  box-shadow:inset 0 0 0 .5px var(--glass-brd), var(--inset-hi);}
.today-grid{display:grid;gap:18px;grid-template-columns:1fr;}
@media (min-width:820px){.today-grid{grid-template-columns:1.4fr 1fr 1fr;}}
.today-figure{font-size:34px;font-weight:800;line-height:1.05;letter-spacing:-.02em;color:var(--ink);}
.today-of{font-size:15px;font-weight:600;color:var(--ink-3);}
.today-label{font-size:12.5px;font-weight:600;color:var(--ink-2);margin-bottom:5px;}
.today-note{font-size:12px;color:var(--ink-3);margin-top:4px;}
.meter{height:7px;border-radius:999px;background:var(--fill-2);overflow:hidden;margin-top:10px;
  box-shadow:inset 0 0 0 .5px var(--glass-brd);}
.meter i{display:block;height:100%;border-radius:999px;background:var(--role);
  transition:width .5s var(--ease-out-expo);}
.meter[data-v=good] i{background:var(--green);}
.meter[data-v=weak] i{background:var(--orange);}
.meter[data-v=bad]  i{background:var(--red);}

/* ============================================================
   MASKED VALUES AND THE DIALOG THAT OPENS THEM

   A masked name or number sits beside a small unlock control.
   The mask is not a CSS trick — the real value is never in the
   page until the server has checked a one-time code — so these
   styles only have to make the state legible.
   ============================================================ */
.masked{display:inline-flex;align-items:center;gap:6px;}
.masked .mask-val{font-variant-numeric:tabular-nums;letter-spacing:.04em;color:var(--ink-2);}
.masked.is-revealed .mask-val{color:var(--ink);letter-spacing:normal;}
.mask-btn{flex-shrink:0;width:24px;height:24px;min-height:24px;border-radius:8px;
  display:inline-flex;align-items:center;justify-content:center;
  color:var(--ink-3);background:var(--fill);
  box-shadow:inset 0 0 0 .5px var(--glass-brd);
  transition:background .18s,color .18s;}
.mask-btn svg{width:13px;height:13px;}
.mask-btn:hover{background:var(--role-tint);color:var(--role-fg);}
.mask-btn:disabled{opacity:.5;cursor:progress;}
.is-revealed{color:var(--ink);}

/* ---- the governance dialogs: reveal code, export password ---- */
.gov-overlay{position:fixed;inset:0;z-index:280;display:flex;align-items:center;
  justify-content:center;padding:20px;background:rgba(12,14,22,.42);
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  animation:fadeIn .22s ease;}
.gov-overlay[hidden]{display:none;}
.gov-dialog{width:100%;max-width:420px;padding:24px;border-radius:1.25rem;
  background:var(--glass-3);box-shadow:inset 0 0 0 .5px var(--glass-brd),
  0 30px 60px -24px rgba(12,14,22,.55);}
.gov-title{font-size:17px;font-weight:800;color:var(--ink);letter-spacing:-.01em;}
.gov-sub{font-size:13.5px;color:var(--ink-2);margin-top:6px;line-height:1.55;}
.gov-dialog label{display:block;font-size:12.5px;font-weight:600;color:var(--ink-2);
  margin:16px 0 6px;}
.gov-demo{margin-top:10px;padding:9px 12px;border-radius:12px;font-size:12.5px;
  background:var(--t-orange);color:var(--f-orange);line-height:1.5;}
.gov-file{margin-top:10px;font-size:12.5px;color:var(--ink-2);}

/* The format picker in the export dialog. Stacked rather than a select, so
   the CSV caveat is readable before the choice rather than after it. */
.gov-formats{display:flex;flex-direction:column;gap:8px;}
.gov-format{display:flex!important;align-items:flex-start;gap:10px;margin:0!important;
  padding:10px 12px;border:1px solid var(--hairline);border-radius:12px;
  cursor:pointer;font-weight:400!important;line-height:1.45;}
.gov-format:hover{background:var(--fill);}
.gov-format:has(:checked){border-color:var(--role);background:var(--t-role,var(--fill));}
.gov-format input{margin-top:2px;flex:none;accent-color:var(--role);}
.gov-format span{font-size:12.5px;color:var(--ink-2);}
.gov-format b{color:var(--ink);font-weight:700;}
.gov-format:focus-within{outline:2px solid var(--role);outline-offset:2px;}
/* "Nothing arrived? Send it again." Quiet, because it is a fallback, but a
   real button — the code is on a colleague's phone and the person reading
   this cannot tell whether the message got there. */
.gov-resend{margin-top:12px;font-size:12.5px;color:var(--ink-3);
  display:flex;align-items:center;flex-wrap:wrap;gap:2px 8px;}
.gov-resend button{font-size:12.5px;font-weight:650;color:var(--role-fg,var(--role));
  text-decoration:underline;text-underline-offset:2px;min-height:32px;}
.gov-resend button:disabled{color:var(--ink-3);text-decoration:none;cursor:default;font-weight:600;}
.gov-actions{display:flex;align-items:center;justify-content:flex-end;gap:10px;margin-top:18px;}
.gov-cancel{font-size:13.5px;font-weight:600;color:var(--ink-2);padding:0 14px;
  min-height:40px;border-radius:12px;}
.gov-cancel:hover{background:var(--fill);color:var(--ink);}
.gov-go{min-height:40px;padding:0 18px;border-radius:12px;font-size:13.5px;font-weight:700;color:#fff;}
.gov-fine{margin-top:16px;padding-top:14px;border-top:1px solid var(--hairline);
  font-size:11.5px;color:var(--ink-3);line-height:1.55;}

/* The location dialog. Same shell as the export one; the pin and the numbered
   steps are the only additions, and the steps carry most of the weight —
   somebody reading them is trying to undo a Block they tapped weeks ago. */
.loc-icon{width:44px;height:44px;border-radius:14px;display:flex;align-items:center;
  justify-content:center;margin-bottom:12px;background:var(--t-role,var(--fill));
  color:var(--role);}
.loc-icon svg{width:22px;height:22px;}
.loc-steps{margin:14px 0 0;padding:0 0 0 20px;list-style:decimal;
  font-size:13px;color:var(--ink-2);line-height:1.6;}
.loc-steps li{margin-bottom:7px;padding-left:2px;}
.loc-steps li:last-child{margin-bottom:0;}
.loc-steps b{color:var(--ink);font-weight:650;}

/* ==========================================================
   INSTALLED AS AN APP

   Same markup, same templates, same Python — the only difference is that
   there is no browser around it any more, which changes two things the
   layout was quietly relying on.

   The notch and the home indicator are now the app's problem: without the
   insets below, a header sits under the notch and the tab bar sits under
   the home bar. Nothing else changes — the teacher's tab bar is pinned to
   the bottom on every phone, installed or not, because that is where it
   belongs either way.

   Leaving a screen is already covered by the tabs and the sidebar, so no
   back control is added here — nothing in the app is a dead end.
   ========================================================== */
@media (display-mode: standalone), (display-mode: fullscreen) {

  /* Nothing may hide under the notch or the home bar. */
  body{
    padding-top:env(safe-area-inset-top);
    padding-bottom:env(safe-area-inset-bottom);
    padding-left:env(safe-area-inset-left);
    padding-right:env(safe-area-inset-right);
  }

  /* Text selection is a desktop idiom; on a phone it is what happens by
     accident when somebody means to scroll. Inputs keep it, obviously. */
  body{-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;}
  input,textarea,select,[contenteditable]{-webkit-user-select:text;user-select:text;}
}

/* ============================================================
   THE TEACHER'S TABS AS A BOTTOM BAR

   This used to apply only when the app was installed, on the
   reasoning that a home-screen app needs navigation of its own
   while a browser tab already has some. That was wrong about
   what the bar is for. It is not a substitute for browser
   chrome — it is the only way between the four screens a
   teacher uses, and in a browser it sat around 40% down the
   page, below a heading and above the day's sessions, where a
   thumb does not go and an eye does not look.

   Pinned, it is reachable one-handed and it stays put while a
   register of forty children scrolls behind it.

   `fixed` rather than `sticky`: sticky keeps the element's
   original space in the flow, which would leave a strip of
   nothing where the bar used to be. Fixed takes it out, and
   the padding below puts the room back at the end where it is
   needed, so the last row of a register clears the bar.

   Phones only. On a tablet or a laptop the column is narrow
   and centred, and a full-width bar welded to the bottom of a
   1200px window looks like a mistake.
   ============================================================ */
@media (max-width:640px){
  main:has(> * > .ttabs), main:has(> .ttabs){
    padding-bottom:calc(72px + env(safe-area-inset-bottom));
  }
  .ttabs{
    position:fixed;left:0;right:0;bottom:0;z-index:30;
    border-radius:0;
    padding:5px max(8px,env(safe-area-inset-left))
            calc(5px + env(safe-area-inset-bottom)) max(8px,env(safe-area-inset-right));
    background:var(--glass-3);
    -webkit-backdrop-filter:var(--blur);backdrop-filter:var(--blur);
    box-shadow:inset 0 .5px 0 var(--hairline), 0 -10px 26px -22px rgba(18,22,40,.6);
  }
  /* A little more room to read and to hit, now that the width is the
     screen's rather than the column's. */
  .ttabs a{min-height:48px;font-size:11px;}
  .ttabs a svg{width:18px;height:18px;}
}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}

/* ============================================================
   EMPTY STATES

   A grey sentence in the middle of a table reads like a failure.
   These say what the screen will hold and how to put something
   in it, which is the difference between "broken" and "new".
   ============================================================ */
.empty-state{padding:44px 24px;text-align:center;}
.empty-state .es-ico{width:44px;height:44px;border-radius:14px;margin:0 auto 12px;
  display:flex;align-items:center;justify-content:center;background:var(--fill-2);color:var(--ink-3);
  box-shadow:inset 0 0 0 .5px var(--glass-brd);}
.empty-state .es-ico svg{width:21px;height:21px;}
.empty-state h3{font-size:14.5px;font-weight:700;color:var(--ink);margin-bottom:4px;}
.empty-state p{font-size:13px;color:var(--ink-2);max-width:42ch;margin:0 auto;line-height:1.55;}
.empty-state .es-action{margin-top:16px;display:inline-flex;}

/* A section that is designed but not built. Shown rather than hidden so the
   shape of the finished product stays visible, but plainly not clickable. */
.sidenav-btn.is-unbuilt{opacity:.38;cursor:not-allowed;}
.sidenav-btn.is-unbuilt:hover{background:transparent;color:var(--ink-2);}

.ttabs[data-tabs="2"]{grid-template-columns:repeat(2,1fr);}
.ttabs[data-tabs="3"]{grid-template-columns:repeat(3,1fr);}
.ttabs a{position:relative;z-index:1;min-height:44px;border-radius:12px;
  font-size:11.5px;font-weight:700;color:var(--ink-2);text-decoration:none;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
  transition:background .2s ease,color .2s ease;}
.ttabs a svg{width:16px;height:16px;}
.ttabs a:hover{color:var(--ink);}
/* --role-fg, not --role.

   The selected tab said which screen you are on in the raw brand hue: for a
   teacher that is #34C759 at 11.5px on a near-white pill, which measured
   2.17:1. The palette carries --role-fg for exactly this — the same hue at a
   weight that can be read as text — and three other rules were reaching past
   it in the same way (the code pill, the masked-number toggle, the tab bar's
   button form). The chip's background and border still use --role, because
   those are shapes and 3:1 is their bar. */
.ttabs a[aria-selected=true]{background:var(--glass-3);color:var(--role-fg, var(--role));
  box-shadow:0 2px 8px -2px rgba(18,22,40,.28), var(--inset-hi);}


/* ============================================================
   DESKTOP DENSITY
   ============================================================

   Everything in this system is sized for a thumb. 44px is the
   accessibility minimum for a touch target and a teacher on a cheap
   Android is the primary user, so that is right — on a phone.

   A mouse pointer is precise to about a pixel. The same controls on a
   1280px screen read as inflated and soft, which is what a head
   teacher or a district officer sees all day. Nothing in the
   stylesheet reduced them: before this block there was not one
   font-size inside any min-width media query.

   Only above 1024px, so the phone layout this was built for is
   untouched.

   On the selectors: the heights come from Tailwind utilities like
   `.min-h-[48px]`, which is one class — specificity (0,1,0). A bare
   `input` is (0,0,1) and would silently lose, which is how this
   project has been caught three times. `input[type=text]` is element
   plus attribute, (0,1,1), so it wins honestly rather than by
   `!important`. Every value below is verified by measuring the
   rendered page, not by reading this file. */

@media (min-width:1024px){

  /* Text-entry controls. 16px is deliberate everywhere else: below it,
     iOS zooms the page when a field takes focus. Past this breakpoint
     there is no iOS to protect, so 14px is safe. */
  /* The selector is copied from the component rule at the top of this file,
     deliberately and exactly.

     That rule is `input:not([type=checkbox]):not([type=radio]):not(
     [type=hidden])`, and each :not() carries an attribute selector, so it
     scores (0,3,1). A sensible-looking `input[type=text]` is (0,1,1) and
     loses silently — no error, no warning, the page simply ignores you.
     Matching the selector exactly makes the two equal, and a tie is settled
     by source order: this block is last in the file, so it wins.

     The radius needs !important only because the rule above uses it. */
  input:not([type=checkbox]):not([type=radio]):not([type=hidden]),select{
    /* `height`, not `min-height`: a minimum can only raise a box, never
       lower one, and the rendered height came from padding anyway. */
    height:36px;
    min-height:36px;
    padding-top:0;
    padding-bottom:0;
    font-size:14px;
    border-radius:10px!important;
  }

  /* A textarea is the one field that must keep growing, so it gets the type
     and the corner but never a fixed height. */
  textarea{
    min-height:72px;
    font-size:14px;
    border-radius:10px!important;
  }

  /* The show-password eye is absolutely positioned to fill the field it sits
     in and carries min-height:48px. Left alone it would stand 48px tall
     inside a 36px input and hang out of the bottom. */
  main .pw-field .pw-toggle{min-height:36px;width:40px;}

  /* One-time code boxes stay big on purpose: they are typed from a phone
     screen held in the other hand, and they are the step where a mistake
     costs another SMS. They are left alone above.

     The sidebar. Every item is a 44px touch target with a 28px icon, which
     is a lot of furniture for a list of ten sections you navigate with a
     mouse. `nav[aria-label=Sections] .sidenav-btn` is (0,2,1) — it has to
     clear the `main .sidenav-btn` rule at (0,1,1) further up, which the
     bare class would not. */
  nav[aria-label=Sections] .sidenav-btn{
    min-height:34px;
    padding:5px 10px;
    border-radius:10px;
    font-size:13px;
  }
  nav[aria-label=Sections] .sidenav-btn svg{
    width:22px;height:22px;padding:4px;border-radius:7px;
  }

  /* Buttons stay a touch taller than the fields they submit, which is
     the proportion the design already had — just smaller. */
  button[type=submit],button[type=button].role-btn,a.role-btn,
  button.role-btn{
    /* Same reasoning: the height is padding, so the padding is what has to
       come down. 9px either side of a 14px line lands at 40px. */
    min-height:40px;
    padding-top:9px;
    padding-bottom:9px;
    font-size:14px;
    border-radius:10px;
  }

  /* Checkboxes are not text fields and must not follow them down: a
     19px box is already at the small end of comfortable with a mouse. */
  input[type=checkbox]{min-height:auto;}

  /* The show-password eye is positioned against the field it sits in,
     so it moves with the height above rather than being set again. */
}


/* ============================================================
   THE SECTION MENU
   ============================================================ */

/* ---- quieter items, louder current one ----

   Every item used to carry a filled icon chip. Ten identical grey chips is a
   lot of furniture that adds no distinction, because being all the same
   colour they distinguish nothing — the Settings-app look this borrows from
   works because each tile is a different colour and there are six of them.

   The chip is the active item's now. Inactive items keep the icon and lose
   the box, so the column reads as a list rather than a grid of buttons, and
   where you are is unmistakable.

   `:not(.active)` is (0,2,1) — it has to clear `.sidenav-btn svg` at (0,1,1),
   which the bare selector would not. */
.sidenav-btn:not(.active) svg{background:transparent;box-shadow:none;}
.sidenav-btn:not(.active):hover svg{background:var(--fill-2);}

/* A bar down the left edge. The tint behind the active item is deliberately
   soft, and soft is hard to find when you glance rather than look — this is
   the cheapest possible "you are here" and it survives being colour-blind. */
.sidenav-btn.active::before{
  content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);
  width:3px;height:60%;border-radius:0 3px 3px 0;background:var(--role);
}

/* ---- collapsing to icons ---- */

.sidenav-toggle{
  display:none;                       /* desktop only; see the media query */
  align-items:center;gap:11px;width:100%;
  padding:5px 10px;border-radius:10px;
  font-size:12px;font-weight:600;color:var(--ink-3);
  background:none;border:0;cursor:pointer;
  transition:background .2s ease,color .2s ease;
}
.sidenav-toggle:hover{background:var(--fill);color:var(--ink-2);}
.sidenav-toggle svg{width:16px;height:16px;flex-shrink:0;
  transition:transform .25s var(--ease-out-expo);}

@media (min-width:1024px){
  .sidenav-toggle{display:flex;}

  /* The grid column. `[data-shell][data-nav=collapsed]` is (0,2,0), which
     clears the `lg:grid-cols-[212px_1fr]` utility at (0,1,0) — a single
     attribute selector would have tied and lost on source order. */
  [data-shell][data-nav="collapsed"]{grid-template-columns:56px 1fr;}

  [data-nav="collapsed"] .sidenav-btn,
  [data-nav="collapsed"] .sidenav-toggle{justify-content:center;padding:5px;}

  /* Clipped, not `display:none`. A hidden label is gone from the
     accessibility tree too, which would leave ten links a screen reader
     announces only as "link" — the menu would be unusable collapsed. This
     keeps every name and hides every glyph. */
  [data-nav="collapsed"] .sidenav-label{
    position:absolute;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;
  }

  /* The arrow points the way it will move the panel. */
  [data-nav="collapsed"] .sidenav-toggle svg{transform:rotate(180deg);}

  /* Collapsed, the chip is the only thing left to read, so inactive items
     get theirs back — without it the column is ten bare glyphs with no
     edges and nothing to aim at. */
  [data-nav="collapsed"] .sidenav-btn svg{background:var(--fill-2);}
  [data-nav="collapsed"] .sidenav-btn.active svg{background:linear-gradient(180deg,
    color-mix(in srgb, var(--role) 82%, #fff) 0%, var(--role) 100%);}

  /* Settings is not a peer of the screens above it. Pushed to the foot of
     the column with a hairline, the way every application people already
     know puts it. */
  nav[aria-label=Sections] .sidenav-btn:last-child{
    margin-top:auto;border-top:1px solid var(--field-brd);
    padding-top:9px;margin-bottom:0;
  }
  /* ---- the menu runs to the bottom and stays there ----

     Two false starts worth recording.

     `lg:sticky` sits on the <nav>, and the obvious fix was to move sticky up
     to the <details> that wraps it — except `.sectionnav` is `display:contents`
     above this breakpoint, so it generates no box at all. Nothing to stick,
     and `getBoundingClientRect()` on it returns zeros, which is a measurement
     that looks like success and means nothing. The <nav> is the grid item
     here; it is the thing to position.

     And `min-height:60vh` left the card ending in mid-air with empty white
     below it. A fixed `height` is what reaches the bottom of the screen.

     The header is sticky and 81px tall, so the menu starts below it and runs
     to a matching gap at the foot. One variable, because if the header ever
     changes height this is the only line that needs to know. */
  :root{--header-h:81px;}

  /* `display:block`, overriding the `display:contents` set further up.
     That was the third false start and the interesting one: a parent with
     `display:contents` generates no box, and a child of it cannot resolve a
     containing block for `position:sticky` — so the nav was sticky in the
     computed styles, reported `top:97px`, and scrolled away regardless.
     Measured at a 400px viewport: it ended at top -264 having moved exactly
     1:1 with the page.

     Giving the wrapper a real box makes it the grid item, and a grid item
     sticks. The summary stays hidden; it is the phone disclosure's control
     and there is nothing to disclose at this width. */
  .sectionnav{
    display:block;
    position:sticky;
    top:calc(var(--header-h) + 1rem);
    height:calc(100vh - var(--header-h) - 2rem);
  }

  /* The nav fills that box and scrolls inside it, so a ten-item menu on a
     short laptop screen keeps Settings reachable instead of hiding it under
     the fold. */
  nav[aria-label=Sections]{
    position:static;
    height:100%;
    min-height:0;
    overflow-y:auto;
  }
}


/* ============================================================
   SIGN-IN: THE ILLUSTRATED PANEL
   ============================================================ */

/* One column by default. The art has no box at all below the breakpoint —
   `display:none` on an element whose background is set inside a min-width
   query means the file is never fetched, which is the whole point. A phone
   pays nothing for it. */
.signin-layout{width:100%;display:flex;justify-content:center;}
.signin-art{display:none;}

@media (min-width:1024px){
  .signin-layout{
    display:grid;
    /* The art column is sized from the artwork, not from taste. Its text was
       drawn at 30px on an 1086px canvas, so at 480px it rendered at 39% —
       11.6px, measured, which is below anything this design uses anywhere.
       At 560px it renders at 52%, putting that line at 15.5px and the
       headings at 23px. */
    /* 360, not 560. The poster is 492px wide — a thumbnail by the standards
       of a hero — so the wider it is shown the softer it gets: at 440 it is
       being enlarged on any 2x screen. 360 keeps it inside its own pixels on
       an ordinary laptop, and it is the width that also keeps the whole card
       on one screen without scrolling. */
    grid-template-columns:minmax(0,360px) minmax(0,25rem);
    gap:0;
    align-items:stretch;
    width:auto;
    /* The panel takes the picture's height, rather than the picture being
       squeezed into a height chosen by the form. 560 wide at 3:4 is 747, and
       `cover` in anything shorter crops about 84px of real content off the
       top and bottom — the first tile and the last. Capped at 82vh so a short
       laptop screen scales it down instead of pushing the button off screen. */
    /* The card has to fit the screen. At 360 wide the poster is 544 tall,
       and on a 768px laptop — with the header, the footer line and the page
       margins above and below it — anything taller meant scrolling down to
       see the bottom of a picture. 68vh is what leaves room for the rest. */
    min-height:min(544px, 68vh);
  }

  .signin-art{
    display:block;
    /* The poster's own 492x744. Letting the column drive the height means the
       art is never squeezed into a box chosen for the form. */
    aspect-ratio:492/744;
    background-image:url("../img/signin-poster.fdc9effc8389.webp");
    /* cover rather than contain, because this picture's edges are not one
       colour — sky at the top, ground at the bottom — so a letterbox would
       show a seam whichever colour was chosen for it. At the column's own
       ratio cover crops nothing; when the form makes the panel taller or
       shorter it takes a few percent off an edge, which a poster can lose
       and a seam cannot be hidden. */
    background-size:cover;
    background-position:center;
    background-repeat:no-repeat;
  }

  /* ---- one panel, not two things next to each other ----

     The card is the grid itself, and the two columns are its halves. The
     art keeps its own pale ground as the left half's fill, which turns what
     was a seam to hide into the thing that separates the halves.

     `background-color` is the picture's own corner colour, sampled rather
     than guessed, so `contain` can letterbox without a visible edge — the
     gap and the art are the same colour. */
  .signin-layout{
    background:var(--field, #fff);
    border:1px solid var(--field-brd, #E2E8F0);
    border-radius:26px;
    overflow:hidden;
    box-shadow:0 18px 40px -28px rgba(18,22,40,.28);
    align-items:stretch;
    gap:0;
    padding:0;
  }

  .signin-art{
    aspect-ratio:auto;
    max-height:none;
    /* The poster's own sky, sampled, for the sliver that can show before the
       image paints. */
    background-color:#A0D2EE;
    background-size:cover;
    margin:0;
    /* No mask any more. Inside the card the picture is a panel with an
       edge it is supposed to have. */
    -webkit-mask-image:none;
    mask-image:none;
  }

  /* The form half. Its own card melts away — a card inside a card is a
     border nobody asked for. */
  .signin-form{
    max-width:none;
    /* The form decides the card's height — the art column stretches to match
       it — so every pixel of padding here is a pixel of picture that has to
       be enlarged to fill the space beside it, and a pixel closer to the
       screen's bottom edge. Tightened until the whole card fits a 768px
       laptop with nothing to scroll to. */
    padding:28px 40px;
    display:flex;
    flex-direction:column;
    justify-content:center;
  }

  /* The mark above the form, smaller inside the panel: the poster beside it
     carries the same logo at ten times the size, and two of them at once is
     the thing that pushed the card off the bottom of the screen. */
  .signin-layout .brand-hero img{width:64px;height:64px;}
  .signin-layout .brand-hero{margin-bottom:14px;}
  .signin-layout .brand-hero h1{margin-top:10px;}
  /* Scoped to the panel, and it has to be.

     Melting the card away is right *inside* the illustrated layout and wrong
     everywhere else. Unscoped, `.signin-card` was a trap for the next page
     that wanted to look like the sign-in screen: the two password screens
     reuse the class and would have rendered as a bare form floating on the
     page above 1024px, with no card at all. The class is a card; the panel is
     the exception. */
  .signin-layout .signin-card{
    /* !important because `.bg-white` further up carries it, and it brings a
       box-shadow with it — without both, the form keeps a raised edge and
       the result is a card sitting inside a card, which is the one thing
       this change was for. Equal specificity and equal importance, so source
       order decides, and this block is later in the file. */
    background:transparent!important;
    box-shadow:none!important;
    -webkit-backdrop-filter:none;
    backdrop-filter:none;
    border:0;
    padding:0;
    border-radius:0;
  }

  /* The footnote belongs to the page, under the panel rather than inside it. */
  .signin-footnote{max-width:38rem;}

  /* ---- dark mode ----

     The artwork is drawn on a pale ground and would be a lit slab against a
     #07070B page, so it steps aside until there is a dark variant.

     Hiding the picture is not enough, and the first version of this got it
     wrong: the card, its 738px height and the widened padding all stayed, so
     dark mode showed an 888x738 box with a 416px form floating inside it.
     Every part of the panel treatment has to come off together, leaving the
     plain centred form this page had before the picture existed. */
  @media (prefers-color-scheme:dark){
    .signin-art{display:none;}

    .signin-layout{
      display:flex;
      justify-content:center;
      background:none;
      border:0;
      box-shadow:none;
      border-radius:0;
      overflow:visible;
      min-height:0;
      width:100%;
    }

    /* Back to the width and rhythm of a single-column sign-in. */
    .signin-form{
      max-width:26rem;
      padding:0;
      display:block;
    }

    /* And the form's own card becomes a card again. Its classes never went
       away — the overrides above are simply not applied here. Scoped to match
       the rule it is undoing, so the two cannot drift apart. */
    .signin-layout .signin-card{
      background:var(--glass)!important;
      box-shadow:var(--shadow-card), var(--inset-hi)!important;
      border:1px solid var(--field-brd);
      border-radius:1rem;
      padding:2rem;
    }
  }
}


/* ============================================================
   CONTRAST FIXES
   ============================================================ */

/* The signed-in person's initial, top right.

   White on --purple (#AF52DE) measures 4.13:1. The minimum for 12px text is
   4.5, so it failed — in both themes, not just dark; the audit that found it
   happened to be running in dark mode.

   #8E3CBF is not a new colour: it is --role-btn for the head teacher role,
   the shade this palette already reserves for white text on purple. It
   measures 5.92:1.

   `.bg-violet-600` is used in exactly one place, the header avatar, so this
   changes nothing else. */
.bg-violet-600{background:#8E3CBF!important;}


/* ============================================================
   THE SCHOOL DAY — one chip per weekday

   A head teacher saying "this session runs Monday to Friday, and
   this one only on Saturday" is ticking fourteen boxes. Native
   checkboxes at 13px, in two rows, on a phone, is not a control
   anybody gets right first time; the box is hidden and its label
   becomes a 44px target that shows its own state.

   :focus-visible is on the hidden input, so the ring has to be
   drawn by the sibling — keyboard users get the same outline the
   rest of the app has.
   ============================================================ */
.day-row + .day-row{margin-top:0;}
.daychip{position:relative;display:inline-flex;}
.daychip input{position:absolute;inset:0;width:100%;height:100%;
  margin:0;opacity:0;cursor:pointer;}
.daychip span{display:inline-flex;align-items:center;justify-content:center;
  min-width:52px;min-height:44px;padding:0 10px;border-radius:11px;
  border:1px solid var(--hairline);background:var(--surface);
  font-size:12.5px;font-weight:700;color:var(--ink-2);
  transition:background .15s ease,color .15s ease,border-color .15s ease;}
.daychip input:checked + span{background:var(--role-fg);border-color:var(--role-fg);
  color:#fff;}
.daychip input:focus-visible + span{outline:2px solid var(--role-fg);
  outline-offset:2px;}


/* ============================================================
   PAGER — where you are, and how to move

   Under every long list. It leads with "26–50 of 312" because
   that is the sentence somebody needs when they are looking for
   a name they cannot find; the bare slices this replaced said
   nothing at all, and the four hundred and first child was just
   absent from the page.

   Steps are 44px targets: these screens are read on phones.
   ============================================================ */
.pager{display:flex;align-items:center;justify-content:space-between;gap:12px;
  flex-wrap:wrap;padding:12px 16px;border-top:1px solid var(--hairline);}
.pager-count{font-size:12.5px;color:var(--ink-2);}
.pager-of{color:var(--ink-3);}
.pager-steps{display:flex;align-items:center;gap:6px;}
.pager-step{display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;min-width:44px;padding:0 12px;border-radius:11px;
  border:1px solid var(--hairline);background:var(--surface);
  font-size:12.5px;font-weight:700;color:var(--ink);}
.pager-step:hover{background:var(--fill);}
.pager-step.is-off{opacity:.4;}

/* The "Fix" toggle on a register row, and the rose tint its Remove button
   wants. Both exist only on My class; the Tailwind build is frozen, so a
   class with no rule behind it does nothing and reports nothing.

   The tint is `var(--t-red)` and not a literal. A fixed #FFF1F2 is a light
   theme's pink on a near-black panel, which `core.tests_palette` catches —
   and rightly: the same rule already repaints `.bg-rose-50`. */
.row-fix{min-height:44px;padding:0 8px;}
.hover\:bg-rose-50:hover{background:var(--t-red)!important;}


/* ============================================================
   VERY WIDE SCREENS

   The console caps at 1600px, which is right for a table and
   wrong for everything that is a short value in a box: three
   summary cards holding one number each, stretched to 430px
   apiece, read as a page that has run out of things to say.

   So rows of single-number cards get a cap of their own, and
   the panels that are prose rather than data borrow the same
   measure the console screens already use. Tables are left
   alone deliberately — a register with seven columns wants
   every pixel it is given.
   ============================================================ */
.stat-row{max-width:1040px;}

/* The tiles stop growing, and only on a screen wide enough for it to matter.
   `flex:1 1 150px` divides whatever row it is given, so a head teacher --
   who has two tiles rather than the administrator's six -- got two cards
   652px wide on a 2560px monitor, each holding an icon, a two-digit number
   and one word. Capped, the row is left-aligned and the space left over is
   space, which reads better than a billboard.

   Above 900px only: on a phone the tiles should still fill the row, and a
   cap there would leave a gap beside the last one for no reason. */
@media (min-width:900px){ .stat-tile{max-width:280px;} }
.reading-column{max-width:56rem;}

/* ==========================================================
   HYGIENE QUEST BRAND
   ========================================================== */

/* The badge beside the product name in every header. An image rather than an
   icon from the sprite: the badge is a picture, with a gradient and a silver
   ring no single-colour icon can carry. */
.brand-mark{width:40px;height:40px;object-fit:contain;flex-shrink:0;
  filter:drop-shadow(0 3px 6px rgba(5,87,26,.28));}

/* The badge, faint and grey, behind every screen. Fixed, so it stays put while
   a long table scrolls over it; greyscale and at a few percent, so it is a
   texture rather than a picture, and never lowers the contrast of anything
   on top. Above the tinted wallpaper, below everything else. */
.brand-watermark{position:fixed;z-index:-1;pointer-events:none;
  right:-4vmin;bottom:-6vmin;width:min(58vmin,540px);aspect-ratio:1/1;
  background:url("../brand/hygiene-quest-watermark.791adda8af31.webp") no-repeat center/contain;
  opacity:.06;}
@media (prefers-color-scheme:dark){ .brand-watermark{opacity:.045;filter:invert(1);} }
@media (max-width:640px){ .brand-watermark{width:78vmin;right:-18vmin;bottom:-10vmin;opacity:.05;} }
@media print{ .brand-watermark{display:none!important;} }

/* The badge at the front door: sign-in, about, password reset. */
.brand-hero{display:flex;flex-direction:column;align-items:center;text-align:center;}
.brand-hero img{width:112px;height:112px;object-fit:contain;
  filter:drop-shadow(0 12px 22px rgba(5,87,26,.30));
  animation:brandIn .7s var(--ease-out-expo) both;}
.brand-hero .brand-kicker{margin-top:10px;font-size:11.5px;font-weight:700;letter-spacing:.14em;
  text-transform:uppercase;color:var(--brand-fg);}
@keyframes brandIn{from{opacity:0;transform:translateY(8px) scale(.94);}to{opacity:1;transform:none;}}
@media (prefers-reduced-motion:reduce){ .brand-hero img{animation:none;} }
@media (max-width:640px){ .brand-hero img{width:88px;height:88px;} }

/* ============================================================
   LIFE IN THE GLASS

   The glass was right and it was still: frosted panels on a faint
   wash, every number simply printed. This gives it a pulse without
   giving it noise — a backdrop that breathes a little more, cards
   that catch the light as they rise into place, rings that fill,
   figures that count up, and a green dot that beats while a session
   is open. All of it stops for anybody who has asked their phone for
   reduced motion, and none of it is needed to read the page.
   ============================================================ */

/* ---- a backdrop with a little more colour in it ---- */
body::before{opacity:.42;}
body::after{opacity:.34;}
@media (prefers-color-scheme:dark){ body::before{opacity:.5;} body::after{opacity:.4;} }

/* ---- the glass card: frost, a lit top edge, and a sheen ---- */
/* `clip`, not only `hidden`: a hidden box can still be scrolled by the
   browser — focusing something inside it will — and the light sweep below
   parks outside the card, which was enough to slide the whole card sideways
   the moment somebody tapped the names open. */
.glass-card{position:relative;isolation:isolate;overflow:hidden;overflow:clip;
  padding:20px;border-radius:1.4rem;
  background:linear-gradient(160deg,var(--glass-3) 0%,var(--glass) 100%);
  -webkit-backdrop-filter:var(--blur-lg);backdrop-filter:var(--blur-lg);
  box-shadow:var(--shadow-card), var(--inset-hi), inset 0 0 0 .5px var(--glass-brd);}
.glass-card::before{content:'';position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:radial-gradient(120% 60% at 0% 0%, rgba(255,255,255,.55), transparent 55%),
             radial-gradient(90% 70% at 100% 100%, color-mix(in srgb, var(--role, var(--brand)) 10%, transparent), transparent 60%);
  opacity:.8;}
@media (prefers-color-scheme:dark){
  .glass-card::before{background:radial-gradient(120% 60% at 0% 0%, rgba(255,255,255,.08), transparent 55%),
             radial-gradient(90% 70% at 100% 100%, color-mix(in srgb, var(--role, var(--brand)) 16%, transparent), transparent 60%);}
}
.card-title{font-size:15px;font-weight:800;color:var(--ink);margin-bottom:10px;}

/* The feature panels catch the light once, as they arrive. */
.today-card{position:relative;overflow:hidden;overflow:clip;isolation:isolate;}
.glass-card::after,.today-card::after{content:'';position:absolute;inset:0;pointer-events:none;z-index:-1;
  border-radius:inherit;
  background:linear-gradient(105deg,transparent 30%,rgba(255,255,255,.30) 45%,transparent 60%);
  transform:translateX(-120%);animation:sheen 1.8s .4s var(--ease-out-expo) 1 backwards;}
@keyframes sheen{from{transform:translateX(-120%);}to{transform:translateX(120%);}}

/* ---- rising into place, one after another ---- */
.rise{animation:rise .55s var(--ease-out-expo) both;animation-delay:calc(var(--i,0) * 70ms);}
@keyframes rise{from{opacity:0;transform:translateY(14px) scale(.985);}to{opacity:1;transform:none;}}

/* ---- a session that is open now ---- */
.live-dot{position:relative;display:inline-block;width:8px;height:8px;border-radius:50%;
  background:var(--green);flex-shrink:0;vertical-align:middle;
  box-shadow:0 0 0 0 rgba(52,199,89,.55);animation:pulse 1.8s ease-out infinite;}
@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(52,199,89,.55);}80%,100%{box-shadow:0 0 0 9px rgba(52,199,89,0);}}

/* ---- buttons: a slow sweep of light on hover ---- */
.role-btn::before{content:'';position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(110deg,transparent 35%,rgba(255,255,255,.35) 50%,transparent 65%);
  transform:translateX(-130%);transition:transform .8s var(--ease-out-expo);}
.role-btn:hover::before,.role-btn:focus-visible::before{transform:translateX(130%);}

/* ---- panels lift a touch under the pointer ---- */
@media (hover:hover){
  .glass-card,.stat-tile,.today-card{transition:transform .25s var(--ease-out-expo), box-shadow .25s ease;}
  .stat-tile:hover,.today-card:hover{transform:translateY(-2px);}
}

/* ---- a row of pills that switches between sessions ---- */
.pillbar{display:flex;gap:4px;padding:4px;border-radius:999px;width:max-content;max-width:100%;
  overflow-x:auto;scrollbar-width:none;
  background:var(--fill);box-shadow:inset 0 0 0 .5px var(--glass-brd);}
.pillbar::-webkit-scrollbar{display:none;}
.pillbar > a,.pillbar > button{display:inline-flex;align-items:center;gap:6px;white-space:nowrap;
  min-height:36px;padding:0 14px;border-radius:999px;font-size:13px;font-weight:700;color:var(--ink-2);
  transition:background .25s var(--ease-out-expo), color .2s, box-shadow .25s;}
.pillbar > a small,.pillbar > button small{font-size:11px;font-weight:700;color:var(--ink-3);}
.pillbar > a[aria-current=page],.pillbar > button[aria-selected=true]{background:var(--glass-3);color:var(--ink);
  box-shadow:0 2px 10px -3px rgba(18,22,40,.3), var(--inset-hi);}

/* ============================================================
   CHECK-IN: the next step, once checked in
   ============================================================ */
.next-step{display:flex;align-items:center;gap:14px;padding:16px 16px 16px 14px;border-radius:1.25rem;
  color:#fff;text-decoration:none;position:relative;overflow:hidden;isolation:isolate;
  background:linear-gradient(135deg,var(--role-btn,#1F7A35) 0%,var(--role,#34C759) 120%);
  box-shadow:var(--shadow-btn), 0 14px 30px -16px var(--role,#34C759), inset 0 1px 0 rgba(255,255,255,.35);}
.next-step::after{content:'';position:absolute;inset:0;z-index:-1;
  background:radial-gradient(80% 120% at 100% 0%, rgba(255,255,255,.28), transparent 60%);}
.next-step-ico{width:42px;height:42px;border-radius:14px;display:flex;align-items:center;justify-content:center;
  background:rgba(255,255,255,.2);box-shadow:inset 0 1px 0 rgba(255,255,255,.35);flex-shrink:0;}
.next-step-ico svg{width:22px;height:22px;}
.next-step-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1;}
.next-step-body b{font-size:15px;line-height:1.25;}
.next-step-body span{font-size:12.5px;opacity:.9;}
.next-step-go{width:22px;height:22px;flex-shrink:0;transition:transform .25s var(--ease-out-expo);}
.next-step:hover .next-step-go{transform:translateX(4px);}

/* ============================================================
   COUNTING A CLASS
   ============================================================ */
.count-card{display:flex;flex-direction:column;gap:18px;}
.count-session{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:700;color:var(--role-fg);}
.count-classes{border:0;padding:0;margin:0;min-width:0;}
.count-label{font-size:12.5px;font-weight:700;color:var(--ink-2);margin-bottom:8px;padding:0;}

.class-chips{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;}
@media (max-width:400px){ .class-chips{grid-template-columns:repeat(4,1fr);} }
.class-chip{position:relative;display:flex;align-items:center;justify-content:center;min-height:46px;
  border-radius:14px;cursor:pointer;font-size:14px;font-weight:800;color:var(--ink-2);
  background:var(--field);box-shadow:inset 0 0 0 1px var(--field-brd);
  transition:transform .18s var(--ease-out-expo), background .2s, color .2s, box-shadow .2s;}
.class-chip input{position:absolute;opacity:0;pointer-events:none;}
.class-chip:active{transform:scale(.95);}
.class-chip:has(input:checked){color:#fff;
  background:linear-gradient(160deg,color-mix(in srgb,var(--role) 70%,#fff) 0%,var(--role-btn,var(--role)) 100%);
  box-shadow:0 8px 18px -10px var(--role), inset 0 1px 0 rgba(255,255,255,.4);}
.class-chip:has(input:focus-visible){outline:3px solid color-mix(in srgb,var(--role) 45%,transparent);outline-offset:2px;}
.class-chip.is-taken{cursor:not-allowed;opacity:.45;background:var(--fill);box-shadow:none;}
.class-chip.is-mine{box-shadow:inset 0 0 0 1.5px color-mix(in srgb,var(--green) 55%,transparent);}
.class-chip-tick{position:absolute;top:4px;right:4px;width:11px;height:11px;color:var(--green);}
.class-chip:has(input:checked) .class-chip-tick{color:#fff;}

.count-groups{display:grid;grid-template-columns:1fr;gap:12px;}
@media (min-width:380px){ .count-groups{grid-template-columns:1fr 1fr;} }
.count-group{display:flex;flex-direction:column;gap:10px;padding:14px;border-radius:1.1rem;
  background:var(--glass-2);box-shadow:inset 0 0 0 .5px var(--glass-brd), var(--inset-hi);}
.count-group[data-group=boys]{background:linear-gradient(160deg,var(--t-blue),var(--glass-2) 70%);}
.count-group[data-group=girls]{background:linear-gradient(160deg,var(--t-purple),var(--glass-2) 70%);}
.count-group-name{font-size:13px;font-weight:800;letter-spacing:.02em;color:var(--ink);}
.count-group[data-group=boys] .count-group-name{color:var(--f-blue);}
.count-group[data-group=girls] .count-group-name{color:var(--f-purple);}

.stepper{display:flex;flex-direction:column;gap:4px;}
.stepper-label{font-size:11.5px;font-weight:700;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em;}
.stepper-row{display:grid;grid-template-columns:40px 1fr 40px;align-items:center;gap:6px;}
.step{width:40px;height:40px;border-radius:12px;display:flex;align-items:center;justify-content:center;
  color:var(--role-fg);background:var(--glass-3);
  box-shadow:0 1px 2px rgba(18,22,40,.12), inset 0 0 0 .5px var(--glass-brd);touch-action:manipulation;
  user-select:none;-webkit-user-select:none;}
.step svg{width:16px;height:16px;}
.step:active{transform:scale(.9);background:var(--role-tint);}
.step-input{width:100%;min-width:0;height:44px;border-radius:12px;text-align:center;
  font-size:22px;font-weight:800;color:var(--ink);font-variant-numeric:tabular-nums;
  background:var(--field);border:1px solid var(--field-brd);-moz-appearance:textfield;appearance:textfield;}
.step-input::-webkit-outer-spin-button,.step-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0;}
.step-input:focus{outline:none;border-color:var(--role);box-shadow:0 0 0 4px color-mix(in srgb,var(--role) 22%,transparent);}
.step-input.bumped{animation:bump .28s var(--spring);}
@keyframes bump{0%{transform:scale(1);}40%{transform:scale(1.12);}100%{transform:scale(1);}}

.count-total{padding:14px 16px;border-radius:1.1rem;background:var(--glass-3);
  box-shadow:inset 0 0 0 .5px var(--glass-brd), var(--inset-hi);}
.count-total-row{display:grid;grid-template-columns:repeat(3,1fr);text-align:center;}
.count-total-row p{display:flex;flex-direction:column;}
.count-total-row b{font-size:26px;font-weight:800;line-height:1.1;color:var(--ink);font-variant-numeric:tabular-nums;}
.count-total-row p:first-child b{color:var(--f-green);}
.count-total-row p:nth-child(2) b{color:var(--f-red);}
.count-total-row span{font-size:11.5px;font-weight:700;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em;}
.count-bar{height:8px;border-radius:999px;margin-top:12px;overflow:hidden;
  background:color-mix(in srgb,var(--red) 22%,var(--fill-2));}
.count-bar i{display:block;height:100%;width:0;border-radius:999px;
  background:linear-gradient(90deg,var(--green),color-mix(in srgb,var(--green) 70%,#fff));
  transition:width .45s var(--ease-out-expo);}

.count-save{width:100%;min-height:52px;border-radius:14px;font-size:16px;font-weight:800;color:#fff;}
.count-save:disabled{opacity:.6;}
.count-note{min-height:1.2em;font-size:13px;font-weight:600;text-align:center;color:var(--ink-2);}
.count-note[data-tone=good]{color:var(--f-green);}
.count-note[data-tone=bad]{color:var(--f-red);}
.count-note[data-tone=warn]{color:var(--f-orange);}
.count-card.is-saved{animation:saved .6s var(--spring);}
@keyframes saved{0%{transform:scale(1);}35%{transform:scale(1.015);box-shadow:0 0 0 6px color-mix(in srgb,var(--green) 25%,transparent), var(--shadow-card);}100%{transform:scale(1);}}

.counted-list{display:flex;flex-direction:column;}
.counted-list li{display:grid;grid-template-columns:auto 1fr;gap:2px 12px;align-items:center;
  padding:10px 0;border-top:1px solid var(--hairline);}
.counted-list li:first-child{border-top:0;}
.counted-form{grid-row:span 2;width:44px;height:44px;border-radius:13px;display:flex;align-items:center;justify-content:center;
  font-weight:800;font-size:14px;color:var(--role-fg);background:var(--role-tint);}
.counted-figs{font-size:14px;color:var(--ink-2);}
.counted-figs b{color:var(--ink);}
.counted-by{font-size:12px;color:var(--ink-3);}

.count-empty{display:flex;flex-direction:column;align-items:center;text-align:center;gap:8px;padding:28px 20px;}
.count-empty-ico{width:56px;height:56px;border-radius:18px;display:flex;align-items:center;justify-content:center;
  color:var(--role-fg);background:var(--role-tint);margin-bottom:4px;}
.count-empty-ico svg{width:28px;height:28px;}
.count-empty-title{font-size:17px;font-weight:800;color:var(--ink);}
.count-empty-note{font-size:13.5px;color:var(--ink-2);max-width:32ch;}
.count-go{display:inline-flex;align-items:center;justify-content:center;min-height:46px;padding:0 22px;
  margin-top:6px;border-radius:14px;font-weight:700;color:#fff;}

/* ============================================================
   WHO IS IN TODAY — the number, and the names behind it
   ============================================================ */
.who{display:flex;flex-direction:column;gap:14px;}
.who-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;}
.who-kicker{display:block;font-size:11.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--role-fg);}
.who-title{font-size:17px;font-weight:800;color:var(--ink);margin-top:2px;}
.who-tabs{align-self:center;}
.who-tabbed .who-panel:not([data-current]){display:none;}
.who-panel{display:flex;flex-direction:column;gap:12px;}
.who-panel + .who-panel{padding-top:14px;border-top:1px solid var(--hairline);}
.who-tabbed .who-panel + .who-panel{padding-top:0;border-top:0;}
.who-panel[data-current]{animation:rise .45s var(--ease-out-expo) both;}
.who-session{display:flex;align-items:center;flex-wrap:wrap;gap:6px 10px;font-size:13px;font-weight:700;color:var(--ink-2);}
.who-session-time{font-weight:600;color:var(--ink-3);font-variant-numeric:tabular-nums;}
.who-note{font-size:13px;color:var(--ink-3);}
.who-note-warn{padding:10px 12px;border-radius:12px;background:var(--t-orange);color:var(--f-orange);font-weight:600;margin-top:10px;}

.who-details > summary{list-style:none;}
.who-details > summary::-webkit-details-marker{display:none;}
.who-summary{display:flex;align-items:center;gap:16px;cursor:pointer;padding:6px;margin:-6px;border-radius:1.1rem;
  transition:background .2s ease;}
.who-summary:hover{background:var(--fill);}
.who-summary:focus-visible{outline:3px solid color-mix(in srgb,var(--role) 40%,transparent);outline-offset:2px;}
.who-summary-text{display:flex;flex-direction:column;gap:6px;min-width:0;flex:1;}
.who-summary-text > b{font-size:16px;font-weight:800;color:var(--ink);line-height:1.25;}
.who-tap{font-size:12px;font-weight:700;color:var(--role-fg);}
.who-details[open] .who-tap{display:none;}
.who-chev{width:20px;height:20px;color:var(--ink-3);flex-shrink:0;transition:transform .3s var(--ease-out-expo);}
.who-details[open] .who-chev{transform:rotate(180deg);}

/* The ring: a conic gradient whose sweep is --p per cent. Registered as a
   number so the browser can animate it filling; where it cannot, the ring
   is drawn full-size at once, which is still right. */
@property --p{syntax:'<number>';inherits:true;initial-value:0;}
.ring{--c:var(--role);position:relative;flex-shrink:0;width:92px;height:92px;border-radius:50%;
  background:conic-gradient(var(--c) calc(var(--p) * 1%), var(--fill-2) 0);
  transition:--p 1.1s var(--ease-out-expo);
  box-shadow:0 10px 24px -14px var(--c), inset 0 0 0 .5px var(--glass-brd);}
.ring[data-tone=good]{--c:var(--green);}
.ring[data-tone=weak]{--c:var(--orange);}
.ring[data-tone=bad]{--c:var(--red);}
.ring[data-tone=none]{--c:var(--fill-2);}
.ring-in{position:absolute;inset:9px;border-radius:50%;display:flex;flex-direction:column;align-items:center;justify-content:center;
  background:var(--glass-3);box-shadow:inset 0 1px 0 rgba(255,255,255,.6), 0 1px 3px rgba(18,22,40,.12);}
.ring-in b{font-size:26px;font-weight:800;line-height:1;color:var(--ink);font-variant-numeric:tabular-nums;}
.ring-in small{font-size:11px;font-weight:700;color:var(--ink-3);margin-top:2px;}
@media (max-width:380px){ .ring{width:78px;height:78px;} .ring-in b{font-size:22px;} }

.who-pills{display:flex;flex-wrap:wrap;gap:6px;}
.pill{display:inline-flex;align-items:center;gap:5px;min-height:24px;padding:0 10px;border-radius:999px;
  font-size:12px;font-weight:700;}
.pill::before{content:'';width:6px;height:6px;border-radius:50%;background:currentColor;}
.pill[data-tone=good]{background:var(--t-green);color:var(--f-green);}
.pill[data-tone=wait]{background:var(--t-orange);color:var(--f-orange);}
.pill[data-tone=bad]{background:var(--t-red);color:var(--f-red);}
.pill[data-tone=muted]{background:var(--fill);color:var(--ink-2);}

.who-lists{display:grid;grid-template-columns:1fr;gap:12px;margin-top:14px;animation:rise .4s var(--ease-out-expo) both;}
@media (min-width:720px){ .who-lists{grid-template-columns:repeat(auto-fit,minmax(220px,1fr));} }
.who-list{padding:12px 14px;border-radius:1.1rem;background:var(--glass-2);
  box-shadow:inset 0 0 0 .5px var(--glass-brd), var(--inset-hi);}
.who-list-head{display:flex;align-items:center;gap:8px;font-size:12.5px;font-weight:800;text-transform:uppercase;
  letter-spacing:.06em;margin-bottom:6px;}
.who-list-head::before{content:'';width:8px;height:8px;border-radius:50%;}
.who-list-head span{margin-left:auto;font-size:12px;padding:1px 8px;border-radius:999px;background:var(--fill);color:var(--ink-2);}
.who-list[data-tone=good] .who-list-head{color:var(--f-green);} .who-list[data-tone=good] .who-list-head::before{background:var(--green);}
.who-list[data-tone=wait] .who-list-head{color:var(--f-orange);} .who-list[data-tone=wait] .who-list-head::before{background:var(--orange);}
.who-list[data-tone=bad] .who-list-head{color:var(--f-red);} .who-list[data-tone=bad] .who-list-head::before{background:var(--red);}
.who-list[data-tone=muted] .who-list-head{color:var(--ink-2);} .who-list[data-tone=muted] .who-list-head::before{background:var(--ink-3);}
.who-list ul{display:flex;flex-direction:column;}
.who-list li{display:grid;grid-template-columns:1fr auto;gap:0 10px;padding:8px 0;border-top:1px solid var(--hairline);font-size:13.5px;}
.who-list li:first-child{border-top:0;}
.who-name{font-weight:700;color:var(--ink);min-width:0;overflow:hidden;text-overflow:ellipsis;}
.who-meta{font-size:12px;font-weight:700;color:var(--ink-3);}
.who-when{grid-column:1 / -1;font-size:12px;color:var(--ink-3);font-variant-numeric:tabular-nums;}
.who-none{font-size:13px;color:var(--ink-3);}

.who-classes{padding:12px 14px;border-radius:1.1rem;background:var(--glass-2);
  box-shadow:inset 0 0 0 .5px var(--glass-brd);}
.who-classes-head{font-size:13px;color:var(--ink-2);margin-bottom:8px;}
.who-classes-head b{font-size:15px;color:var(--f-green);}
.who-class-chips{display:flex;flex-wrap:wrap;gap:6px;}
.who-class-chip{display:inline-flex;align-items:center;gap:6px;min-height:28px;padding:0 10px;border-radius:10px;
  font-size:12.5px;color:var(--ink-2);background:var(--glass-3);box-shadow:inset 0 0 0 .5px var(--glass-brd);
  font-variant-numeric:tabular-nums;}
.who-class-chip b{color:var(--role-fg);}

/* Above one school: each school's figure, weakest first. */
.school-today{display:flex;flex-direction:column;margin-top:14px;}
.school-today li + li{border-top:1px solid var(--hairline);}
.school-today a{display:flex;align-items:center;gap:12px;padding:10px 6px;border-radius:12px;text-decoration:none;color:var(--ink);
  transition:background .2s ease;}
.school-today a:hover{background:var(--fill);}
.mini-ring{--c:var(--role);width:26px;height:26px;border-radius:50%;flex-shrink:0;
  background:radial-gradient(closest-side,var(--glass-3) 62%,transparent 64%),
             conic-gradient(var(--c) calc(var(--p) * 1%), var(--fill-2) 0);
  transition:--p 1.1s var(--ease-out-expo);}
.mini-ring[data-tone=good]{--c:var(--green);}
.mini-ring[data-tone=weak]{--c:var(--orange);}
.mini-ring[data-tone=bad]{--c:var(--red);}
.mini-ring[data-tone=none]{--c:var(--fill-2);}
.school-today-name{flex:1;min-width:0;font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.school-today-fig{font-size:13px;color:var(--ink-3);font-variant-numeric:tabular-nums;}
.school-today-fig b{font-size:15px;color:var(--ink);}
.school-today-go{width:16px;height:16px;color:var(--ink-3);flex-shrink:0;}

/* The overview's rate strip, now two figures wide. */
@media (min-width:820px){ .today-grid.today-grid-2{grid-template-columns:1fr 1fr;} }

/* ---- stillness, for anybody who asked for it ---- */
@media (prefers-reduced-motion:reduce){
  .rise,.who-panel[data-current],.who-lists,.count-card.is-saved,.step-input.bumped{animation:none;}
  .live-dot{animation:none;}
  .glass-card::after,.today-card::after{animation:none;display:none;}
  .ring,.mini-ring{transition:none;}
  .role-btn::before{display:none;}
}
@media print{
  .rise{animation:none;}
  .glass-card::after,.today-card::after,.glass-card::before{display:none;}
  .who-tabbed .who-panel:not([data-current]){display:flex;}
}

/* ============================================================
   ONE ACCENT

   Every role had its own colour — purple for a head teacher,
   orange for a district, blue for an officer — laid over a green
   brand, so a head teacher's screen put a purple button on an
   amber notice beside a green badge. The role is already written
   in the header chip; the colour does not need to say it again.
   Everybody gets the Hygiene Quest green.
   ============================================================ */
.role-admin,.role-officer,.role-headteacher,.role-teacher,.role-deo,.role-ministry,.role-brand{
  --role:var(--brand);--role-btn:#0A7A1C;--role-tint:var(--brand-tint);--role-fg:var(--brand-fg);}
.hdr-avatar{background:var(--role-btn);}

/* ---- the header, on one row on a phone ---- */
@media (max-width:640px){
  .hdr-row{flex-wrap:nowrap;gap:10px;padding-top:10px;padding-bottom:10px;}
  .hdr-row .brand-mark{width:34px;height:34px;}
  .hdr-name{display:none;}
  .hdr-user{padding:3px;background:transparent;}
  .hdr-actions{gap:2px;}
  .hdr-actions a,.hdr-actions button{min-width:38px;min-height:40px;justify-content:center;}
  .hdr-chip{max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;display:inline-block;}
  .hdr-chip svg{display:inline;vertical-align:-2px;}
}

/* ============================================================
   THE OVERVIEW
   ============================================================ */
.ov{display:flex;flex-direction:column;gap:16px;}
.ov-hello{padding:2px 2px 4px;}
.ov-date{font-size:12px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--role-fg);}
.ov-title{font-size:26px;font-weight:800;letter-spacing:-.02em;line-height:1.15;color:var(--ink);margin-top:4px;}
.ov-where{font-size:14px;color:var(--ink-2);margin-top:4px;}
@media (max-width:640px){ .ov-title{font-size:22px;} }

/* One line that says whether anything needs doing. */
.ov-status{display:flex;align-items:center;gap:12px;padding:12px 14px;border-radius:1.1rem;
  text-decoration:none;color:var(--ink);
  background:var(--glass-2);box-shadow:inset 0 0 0 .5px var(--glass-brd), var(--inset-hi), var(--shadow-card);}
.ov-status-mark{width:38px;height:38px;border-radius:12px;flex-shrink:0;display:flex;align-items:center;justify-content:center;}
.ov-status-mark svg{width:19px;height:19px;}
.ov-status[data-state=open] .ov-status-mark{background:var(--t-green);}
.ov-status[data-state=shut] .ov-status-mark{background:var(--t-orange);color:var(--f-orange);}
.ov-status[data-state=warn] .ov-status-mark{background:var(--t-blue);color:var(--f-blue);}
.ov-status-text{display:flex;flex-direction:column;gap:1px;flex:1;min-width:0;}
.ov-status-text b{font-size:14.5px;font-weight:800;}
.ov-status-text span{font-size:12.5px;color:var(--ink-2);}
.ov-status-go{display:inline-flex;align-items:center;gap:4px;font-size:13px;font-weight:700;color:var(--role-fg);white-space:nowrap;}
.ov-status-go svg{width:16px;height:16px;transition:transform .25s var(--ease-out-expo);}
.ov-status[data-state=open]:hover .ov-status-go svg{transform:translateX(3px);}
.ov-status-btn{display:inline-flex;align-items:center;justify-content:center;min-height:40px;padding:0 16px;
  border-radius:12px;font-size:13.5px;font-weight:700;color:#fff;white-space:nowrap;flex-shrink:0;}
.ov-status-btn-quiet{color:var(--role-fg);background:var(--role-tint);}
@media (max-width:520px){
  .ov-status{flex-wrap:wrap;}
  .ov-status-text{flex-basis:calc(100% - 52px);}
  .ov-status-btn{width:100%;}
  .ov-status-go{margin-left:50px;}
}

/* What exists in the scope, as one line of chips. */
.ov-chips{display:flex;flex-wrap:wrap;gap:8px;}
.ov-chips li{padding:7px 12px;border-radius:999px;font-size:13px;color:var(--ink-2);
  background:var(--glass-2);box-shadow:inset 0 0 0 .5px var(--glass-brd);}
.ov-chips b{color:var(--ink);font-weight:800;margin-right:2px;}

.ov-list-note{font-size:12.5px;color:var(--ink-3);margin:-6px 0 8px;}
.ov-list ul{display:flex;flex-direction:column;}
.ov-list li{display:flex;align-items:center;gap:12px;padding:10px 0;border-top:1px solid var(--hairline);}
.ov-list li:first-child{border-top:0;}
.ov-list-name{flex:1;min-width:0;font-size:14px;font-weight:700;color:var(--ink);display:flex;flex-direction:column;}
.ov-list-name small{font-size:12px;font-weight:500;color:var(--ink-3);}
.ov-list-fig{font-size:15px;font-weight:800;color:var(--ink);font-variant-numeric:tabular-nums;}

.ov-more{display:flex;align-items:center;gap:10px;padding:12px 14px;border-radius:1.1rem;text-decoration:none;
  font-size:13.5px;color:var(--ink-2);background:var(--glass);box-shadow:inset 0 0 0 .5px var(--glass-brd);}
.ov-more svg{width:18px;height:18px;color:var(--role-fg);flex-shrink:0;}
.ov-more b{color:var(--ink);}
.ov-more span{flex:1;}
.ov-more-go{transition:transform .25s var(--ease-out-expo);}
.ov-more:hover .ov-more-go{transform:translateX(3px);}

/* ---- the today card: teachers beside learners ---- */
.who-kicker{letter-spacing:.12em;}
.who-session b{color:var(--ink);font-weight:800;}
.who-grid{display:grid;grid-template-columns:1fr;gap:14px;}
@media (min-width:900px){ .who-grid{grid-template-columns:3fr 2fr;align-items:start;} }
.who-block{padding:14px;border-radius:1.1rem;background:var(--glass-2);
  box-shadow:inset 0 0 0 .5px var(--glass-brd), var(--inset-hi);}
.who-block-head{font-size:11.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-bottom:10px;}
.who-block .who-lists{margin-top:14px;}
.who-block .who-list{background:var(--glass-3);}
.who-big{display:flex;align-items:baseline;flex-wrap:wrap;gap:6px;}
.who-big b{font-size:34px;font-weight:800;line-height:1;letter-spacing:-.02em;color:var(--f-green);font-variant-numeric:tabular-nums;}
.who-big span{font-size:13.5px;color:var(--ink-2);}
.who-bar{height:8px;border-radius:999px;margin:12px 0 10px;overflow:hidden;
  background:color-mix(in srgb,var(--red) 22%,var(--fill-2));}
.who-bar i{display:block;height:100%;border-radius:999px;
  background:linear-gradient(90deg,var(--green),color-mix(in srgb,var(--green) 70%,#fff));}
.who-split{display:flex;flex-wrap:wrap;gap:6px 16px;font-size:13px;color:var(--ink-2);margin-bottom:12px;}
.who-split b{color:var(--ink);}
.who-learners .who-note{margin-top:2px;}

/* Session pills: dot, name, figure — in a row that scrolls on a phone. */
.who-tabs > button{white-space:nowrap;}
.who-tabs > button small{margin-left:2px;}
@media (max-width:640px){
  .who-head{flex-direction:column;align-items:stretch;}
  .who-tabs{align-self:stretch;width:auto;scroll-snap-type:x mandatory;}
  .who-tabs > button{scroll-snap-align:start;}
}

/* ============================================================
   ANALYTICS

   The tiles are the overview's, standing still: icon, label, figure,
   and a bar under each rate. Every chart below them is one shape —
   a row per day, class, gender or teacher, each a name, a bar and
   its figure — so nothing needs a legend or a table to be read.
   ============================================================ */
.kpi-tiles{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;}
@media (min-width:1100px){ .kpi-tiles{grid-template-columns:repeat(4,minmax(0,1fr));} }
@media (max-width:640px){
  .kpi-tiles{gap:10px;}
  .kpi-tile{padding:12px 12px 13px;}
  .kpi-top{gap:8px;margin-bottom:6px;}
  .kpi-tile .kpi-label{font-size:11px;letter-spacing:.05em;}
  .kpi-tile .kpi-value{font-size:26px;}
}
@media (max-width:340px){ .kpi-tiles{grid-template-columns:1fr;} }
.kpi-tile{--tile-t:var(--t-green);--tile-f:var(--f-green);
  position:relative;overflow:hidden;isolation:isolate;display:flex;flex-direction:column;gap:2px;
  min-width:0;padding:14px 16px 15px;border-radius:1.25rem;background:var(--glass-2);
  box-shadow:inset 0 0 0 .5px var(--glass-brd), var(--inset-hi), var(--shadow-card);}
.kpi-tile::after{content:"";position:absolute;right:-18px;bottom:-26px;z-index:-1;width:96px;height:96px;
  border-radius:50%;background:var(--tile-t);opacity:.55;pointer-events:none;}
.kpi-tile[data-tone=blue]{--tile-t:var(--t-blue);--tile-f:var(--f-blue);}
.kpi-tile[data-tone=orange]{--tile-t:var(--t-orange);--tile-f:var(--f-orange);}
.kpi-tile[data-tone=violet]{--tile-t:var(--t-purple);--tile-f:var(--f-purple);}
.kpi-top{display:flex;align-items:center;gap:10px;margin-bottom:8px;}
.kpi-tile .kpi-label{font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-2);}
.kpi-tile .kpi-value{font-size:32px;}
.kpi-note{margin-top:4px;font-weight:700;color:var(--role-fg);}
.kpi-meter{display:block;height:6px;margin:8px 0 4px;border-radius:999px;background:var(--fill);overflow:hidden;}
.kpi-meter i{display:block;height:100%;width:0;border-radius:999px;background:var(--green);
  transition:width .9s var(--ease-out-expo);}
.kpi-meter i[data-tone=weak]{background:var(--orange);}
.kpi-meter i[data-tone=bad]{background:var(--red);}

.bars{display:flex;flex-direction:column;gap:12px;margin-top:14px;}
.bar-row{display:grid;grid-template-columns:minmax(84px,170px) minmax(0,1fr) 112px;
  align-items:center;gap:12px;}
.bar-name{display:flex;flex-direction:column;min-width:0;font-size:13.5px;font-weight:700;color:var(--ink);}
.bar-name small{font-size:11.5px;font-weight:500;color:var(--ink-3);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.bar-track{height:12px;border-radius:999px;background:var(--fill);overflow:hidden;}
.bar-track i{display:block;height:100%;border-radius:999px;background:var(--green);
  transition:width .8s var(--ease-out-expo);}
.bar-row[data-tone=weak] .bar-track i{background:var(--orange);}
.bar-row[data-tone=bad] .bar-track i{background:var(--red);}
.bar-row[data-tone=boys] .bar-track i{background:var(--boys);}
.bar-row[data-tone=girls] .bar-track i{background:var(--girls);}
.bar-fig{display:flex;flex-direction:column;align-items:flex-end;text-align:right;}
.bar-fig b{font-size:14px;font-weight:800;color:var(--ink);font-variant-numeric:tabular-nums;}
.bar-fig small{font-size:11.5px;color:var(--ink-3);white-space:nowrap;}
.bar-row[data-tone=none] .bar-fig b{color:var(--ink-3);font-weight:600;}
.bars-empty{font-size:13px;color:var(--ink-3);}
@media (max-width:560px){
  .bar-row{grid-template-columns:1fr auto;grid-template-areas:"name fig" "bar bar";gap:6px 10px;}
  .bar-name{grid-area:name;}
  .bar-track{grid-area:bar;}
  .bar-fig{grid-area:fig;flex-direction:row;align-items:baseline;gap:8px;}
}


/* ============================================================
   THE MENU RUNS THE FULL HEIGHT

   `height:100%` on the nav stopped reaching the bottom of the screen
   when browsers began wrapping the content of a <details> in a box of
   its own (::details-content). That box is only as tall as the menu,
   so the nav's 100% was 100% of itself, and the card ended under
   Settings with empty page below it. A height worked out from the
   window does not depend on what the browser wraps it in.
   ============================================================ */
@media (min-width:1024px){
  nav[aria-label=Sections]{height:calc(100vh - var(--header-h) - 2rem);}
}

/* ============================================================
   PAGE TITLES

   Every screen's heading was 18px, the same weight as a card's.
   The overview greets at 26px; the rest now stand at 24px, so a
   page is headed by its name rather than by its first card.
   ============================================================ */
[data-main] h1.text-lg{font-size:24px;line-height:1.2;font-weight:800;letter-spacing:-.02em;color:var(--ink);}
[data-main] h1.text-lg + p{font-size:14px;margin-top:4px;color:var(--ink-2);max-width:70ch;}
@media (max-width:640px){ [data-main] h1.text-lg{font-size:21px;} }

/* ============================================================
   THE OVERVIEW TILES

   A number per tile — teachers, learners, and above a school the
   schools and districts — each opening the list behind it in a
   panel under the row. The colours tell the tiles apart at a
   glance; the one accent still marks everything you can press.
   ============================================================ */
:root{--boys:#2F7FE0;--girls:#D9468A;}
@media (prefers-color-scheme:dark){ :root{--boys:#4C9BF0;--girls:#F06AA6;} }

.glance{display:flex;flex-direction:column;gap:12px;}
.glance-tiles{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;}
@media (min-width:900px){
  .glance-tiles[data-n="3"]{grid-template-columns:repeat(3,minmax(0,1fr));}
  .glance-tiles[data-n="4"]{grid-template-columns:repeat(4,minmax(0,1fr));}
}
.glance-tile{--tile-t:var(--t-green);--tile-f:var(--f-green);
  position:relative;overflow:hidden;isolation:isolate;display:grid;grid-template-columns:auto 1fr auto;
  grid-template-areas:"icon label chev" "num num num" "sub sub sub";
  align-items:center;gap:2px 10px;min-width:0;padding:14px 14px 13px;text-align:left;
  border:0;border-radius:1.25rem;cursor:pointer;color:var(--ink);font:inherit;
  background:var(--glass-2);
  box-shadow:inset 0 0 0 .5px var(--glass-brd), var(--inset-hi), var(--shadow-card);
  transition:transform .25s var(--ease-out-expo), box-shadow .25s ease, background-color .2s ease;}
.glance-tile::after{content:"";position:absolute;right:-18px;bottom:-26px;z-index:-1;width:96px;height:96px;
  border-radius:50%;background:var(--tile-t);opacity:.55;pointer-events:none;
  transition:transform .4s var(--ease-out-expo);}
.glance-tile:hover{transform:translateY(-2px);}
.glance-tile:hover::after{transform:scale(1.12);}
.glance-tile:active{transform:scale(.985);}
.glance-tile[data-tone=blue]{--tile-t:var(--t-blue);--tile-f:var(--f-blue);}
.glance-tile[data-tone=orange]{--tile-t:var(--t-orange);--tile-f:var(--f-orange);}
.glance-tile[data-tone=violet]{--tile-t:var(--t-purple);--tile-f:var(--f-purple);}
.glance-icon{grid-area:icon;width:34px;height:34px;border-radius:11px;display:flex;
  align-items:center;justify-content:center;background:var(--tile-t);color:var(--tile-f);}
.glance-icon svg{width:18px;height:18px;}
.glance-label{grid-area:label;font-size:12px;font-weight:800;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-2);min-width:0;}
.glance-num{grid-area:num;margin-top:10px;font-size:34px;font-weight:800;
  line-height:1.05;letter-spacing:-.03em;color:var(--ink);font-variant-numeric:tabular-nums;}
.glance-sub{grid-area:sub;font-size:12.5px;color:var(--ink-3);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.glance-chev{grid-area:chev;width:18px;height:18px;color:var(--ink-3);
  transition:transform .3s var(--ease-out-expo), color .2s ease;}
.glance-tile[aria-expanded=true]{background:var(--glass-3);
  box-shadow:inset 0 0 0 2px var(--tile-f), var(--shadow-card);}
.glance-tile[aria-expanded=true] .glance-chev{transform:rotate(180deg);color:var(--tile-f);}
@media (max-width:640px){
  .glance-tiles{gap:10px;}
  .glance-tile{padding:12px 12px 11px;}
  .glance-num{font-size:30px;margin-top:8px;}
}

.glance-panel{padding:16px 18px;border-radius:1.4rem;}
.glance-panel.glance-in{animation:glance-in .38s var(--ease-out-expo);}
@keyframes glance-in{from{opacity:0;transform:translateY(-8px);}to{opacity:1;transform:none;}}
.glance-panel-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px;}
.glance-panel-head p{display:flex;flex-direction:column;gap:2px;min-width:0;}
.glance-panel-head b{font-size:16px;font-weight:800;color:var(--ink);}
.glance-panel-head span{font-size:12.5px;color:var(--ink-3);}
.glance-link{flex-shrink:0;display:inline-flex;align-items:center;min-height:34px;padding:0 14px;
  border-radius:999px;font-size:13px;font-weight:700;text-decoration:none;
  color:var(--role-fg);background:var(--role-tint);}
.glance-link:hover{background:color-mix(in srgb,var(--role-tint) 100%,var(--role) 10%);}

/* Initials in a circle: a list of names reads as people. */
.avatar,.glance-avatar{--av-t:var(--t-green);--av-f:var(--f-green);
  width:34px;height:34px;border-radius:50%;flex-shrink:0;display:inline-flex;
  align-items:center;justify-content:center;font-size:14px;font-weight:800;line-height:1;
  text-transform:uppercase;background:var(--av-t);color:var(--av-f);}
.avatar[data-tone=blue],.glance-avatar[data-tone=blue]{--av-t:var(--t-blue);--av-f:var(--f-blue);}
.avatar[data-tone=violet],.glance-avatar[data-tone=violet]{--av-t:var(--t-purple);--av-f:var(--f-purple);}
.avatar[data-tone=orange],.glance-avatar[data-tone=orange]{--av-t:var(--t-orange);--av-f:var(--f-orange);}

.glance-people{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:8px;}
.glance-people li{display:flex;align-items:center;gap:10px;min-width:0;padding:9px 10px;
  border-radius:14px;background:var(--glass-3);box-shadow:inset 0 0 0 .5px var(--glass-brd);}
.glance-person{display:flex;flex-direction:column;min-width:0;}
.glance-person b{font-size:14px;font-weight:700;color:var(--ink);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.glance-person small{font-size:12px;color:var(--ink-3);}
.glance-leave{color:var(--f-orange);font-weight:700;}

.glance-rows{display:flex;flex-direction:column;}
.glance-rows li{display:flex;align-items:center;gap:12px;padding:10px 2px;border-top:1px solid var(--hairline);}
.glance-rows li:first-child{border-top:0;}
.glance-rows li > a{display:flex;align-items:center;gap:12px;flex:1;min-width:0;
  color:inherit;text-decoration:none;border-radius:12px;}
.glance-rows li > a:hover .glance-row-name{color:var(--role-fg);}
.glance-rows li > a:hover .glance-row-go{transform:translateX(3px);color:var(--role-fg);}
.glance-row-name{flex:1;min-width:0;display:flex;flex-direction:column;
  font-size:14px;font-weight:700;color:var(--ink);transition:color .15s ease;}
.glance-row-name small{font-size:12px;font-weight:500;color:var(--ink-3);}
.glance-row-fig{font-size:15px;font-weight:800;color:var(--ink);font-variant-numeric:tabular-nums;}
.glance-row-pair{min-width:96px;text-align:right;font-size:12.5px;color:var(--ink-3);white-space:nowrap;}
.glance-row-pair b{font-size:14px;font-weight:800;color:var(--ink);font-variant-numeric:tabular-nums;}
.glance-row-go{width:16px;height:16px;flex-shrink:0;color:var(--ink-3);
  transition:transform .25s var(--ease-out-expo), color .15s ease;}
@media (max-width:560px){
  .glance-row-pair{min-width:0;display:flex;flex-direction:column;align-items:flex-end;line-height:1.15;}
}

/* Boys and girls: one bar for the school, one per class. */
.glance-split{display:flex;height:12px;border-radius:999px;overflow:hidden;background:var(--fill);}
.glance-split-boys{display:block;height:100%;background:var(--boys);}
.glance-split-girls{display:block;height:100%;flex:1;background:var(--girls);}
.glance-split-key{display:flex;flex-wrap:wrap;gap:6px 18px;margin:8px 0 16px;font-size:13px;color:var(--ink-2);}
.glance-split-key b{color:var(--ink);font-weight:800;}
.glance-split-key i{display:inline-block;width:10px;height:10px;border-radius:3px;margin-right:6px;vertical-align:-1px;}
.glance-key-boys{background:var(--boys);}
.glance-key-girls{background:var(--girls);}
.glance-classes{display:flex;flex-direction:column;gap:12px;}
.glance-classes li{display:grid;grid-template-columns:44px minmax(0,1fr) minmax(150px,auto);align-items:center;gap:12px;}
.glance-class{font-size:14px;font-weight:800;color:var(--ink);}
.glance-classes li[data-empty] .glance-class{color:var(--ink-3);}
.glance-class-bar{height:14px;border-radius:999px;background:var(--fill);overflow:hidden;}
.glance-class-fill{display:flex;height:100%;min-width:8px;border-radius:999px;overflow:hidden;}
.glance-class-fig{display:flex;flex-direction:column;align-items:flex-end;}
.glance-class-fig b{font-size:15px;font-weight:800;color:var(--ink);font-variant-numeric:tabular-nums;}
.glance-class-fig small{font-size:11.5px;color:var(--ink-3);white-space:nowrap;}
.glance-class-none{grid-column:2 / -1;font-size:13px;color:var(--ink-3);}
@media (max-width:560px){
  .glance-classes li{grid-template-columns:auto 1fr;grid-template-areas:"cls fig" "bar bar";gap:6px 10px;}
  .glance-class{grid-area:cls;}
  .glance-class-bar{grid-area:bar;}
  .glance-class-fig{grid-area:fig;flex-direction:row;align-items:baseline;justify-content:flex-end;gap:8px;}
  .glance-class-none{grid-area:fig;grid-column:auto;text-align:right;}
}
.glance > .ov-chips a{color:inherit;text-decoration:none;}
.glance > .ov-chips li:hover{background:var(--glass-3);}

/* ============================================================
   LISTS: people, not rows

   A name cell carries its initials; a rate is a short bar with its
   figure, so "which of these is weak" is read before any number is.
   ============================================================ */
.cell-person{display:flex;align-items:center;gap:10px;min-width:0;}
.cell-person .avatar{width:32px;height:32px;font-size:13px;}
.cell-person-text{display:flex;flex-direction:column;min-width:0;}
.rate{display:inline-flex;align-items:center;gap:8px;white-space:nowrap;}
.rate-bar{width:80px;height:6px;border-radius:999px;background:var(--fill);overflow:hidden;flex-shrink:0;}
.rate-bar i{display:block;height:100%;border-radius:999px;background:var(--green);}
.rate b{font-size:13px;font-weight:800;color:var(--ink);font-variant-numeric:tabular-nums;min-width:34px;}
.rate[data-v=weak] .rate-bar i{background:var(--orange);}
.rate[data-v=bad] .rate-bar i{background:var(--red);}
.rate[data-v=bad] b{color:var(--f-red);}
@media (max-width:760px){ .rate-bar{width:44px;} }

/* A glimpse of what is behind a tile, on a screen wide enough for it:
   the first few faces on the staff, and the classes as seven columns. */
.glance-tile{grid-template-areas:"icon label chev" "num num extra" "sub sub extra";}
.glance-extra{grid-area:extra;align-self:end;justify-self:end;}
.glance-faces{display:flex;align-items:center;padding-left:8px;}
.glance-faces .glance-avatar,.glance-more{width:30px;height:30px;margin-left:-8px;font-size:12px;
  box-shadow:0 0 0 2px var(--glass-3);}
.glance-more{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;
  font-weight:800;color:var(--ink-2);background:var(--fill);}
.glance-cols{display:flex;align-items:flex-end;gap:4px;height:42px;}
.glance-cols i{display:block;width:8px;min-height:4px;border-radius:3px 3px 2px 2px;
  background:linear-gradient(180deg,var(--orange),color-mix(in srgb,var(--orange) 55%,transparent));}
.glance-cols i[data-empty]{height:4px;background:var(--fill-2);}
@media (max-width:640px){ .glance-extra{display:none;} }
.glance-chev{justify-self:end;}
@media (max-width:640px){
  .glance-sub{white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;}
}

/* ============================================================
   THE TODAY CARD, LIGHTER

   It was a card of boxes: a box for the teachers and one for the
   learners inside the card, pill chips inside those, a "tap" line
   and a chevron. Now the two halves are sections of one card with a
   hairline between them, the chips are coloured words, and there is
   one clear thing to press.
   ============================================================ */
.who-block{padding:0;background:none;box-shadow:none;border-radius:0;}
.who-grid{gap:18px;}
.who-learners{padding-top:16px;border-top:1px solid var(--hairline);}
@media (min-width:900px){
  .who-grid{column-gap:24px;}
  .who-learners{padding-top:0;border-top:0;padding-left:24px;border-left:1px solid var(--hairline);min-height:100%;}
}
.who-pills{gap:4px 14px;}
.who-pills .pill[data-tone]{min-height:0;padding:0;background:none;font-size:12.5px;font-weight:700;}
.who-summary .who-chev{display:none;}
.who-tap{display:inline-flex;align-items:center;align-self:flex-start;gap:4px;min-height:30px;padding:0 12px;
  border-radius:999px;font-size:12.5px;background:var(--role-tint);color:var(--role-fg);}
.who-tap::after{content:"";width:6px;height:6px;margin-left:2px;border-right:2px solid currentColor;
  border-bottom:2px solid currentColor;transform:rotate(45deg) translate(-1px,-2px);transition:transform .25s var(--ease-out-expo);}
.who-tap-shut{display:none;}
.who-details[open] .who-tap{display:inline-flex;}
.who-details[open] .who-tap-open{display:none;}
.who-details[open] .who-tap-shut{display:inline;}
.who-details[open] .who-tap::after{transform:rotate(-135deg) translate(-1px,-1px);}
.who-summary:hover .who-tap{background:color-mix(in srgb,var(--role-tint) 100%,var(--role) 12%);}
.who-summary:hover{background:transparent;}
.who-block .who-list{background:var(--glass-2);}

/* ---- a list of things done now and then (Sessions: closed days,
   class counts, printing) — one card, a row each ---- */
.glance-icon[data-tone=green]{--tile-t:var(--t-green);--tile-f:var(--f-green);}
.glance-icon[data-tone=blue]{--tile-t:var(--t-blue);--tile-f:var(--f-blue);}
.glance-icon[data-tone=orange]{--tile-t:var(--t-orange);--tile-f:var(--f-orange);}
.glance-icon[data-tone=violet]{--tile-t:var(--t-purple);--tile-f:var(--f-purple);}
button.glance-link{border:0;cursor:pointer;font-family:inherit;}
.tool-list{display:flex;flex-direction:column;padding:6px 16px;border-radius:1.4rem;}
.tool-row{display:flex;align-items:center;gap:12px;padding:12px 0;border-top:1px solid var(--hairline);}
.tool-row:first-child{border-top:0;}
.tool-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px;}
.tool-text b{font-size:14px;font-weight:800;color:var(--ink);}
.tool-text span{font-size:12.5px;color:var(--ink-3);}
.tool-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px;}
@media (max-width:560px){
  .tool-row{flex-wrap:wrap;}
  .tool-text{flex-basis:calc(100% - 50px);}
  .tool-row > .glance-link,.tool-actions{margin-left:46px;}
  .tool-actions{justify-content:flex-start;}
}

/* The session picker: one slim row rather than a card with a label on
   top. The padding is zeroed because the field has a fixed height at
   desktop size and the utility padding on it pushed the text out of it. */
.sess-pick{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;}
.sess-pick label{margin:0!important;font-weight:700;}
.sess-pick select{width:auto;min-width:240px;max-width:100%;padding-top:0!important;padding-bottom:0!important;}
@media (max-width:560px){ .sess-pick select{width:100%;min-width:0;} }

/* ============================================================
   PUPILS ON THE ROLL

   The screen where a head teacher types how many children each
   class has, and the "500 of 3,000 in today" line that figure
   makes possible on the overview.

   The seven classes are rows of two small number fields rather
   than a table: a table of 14 cells on a phone is a horizontal
   scroll, and these are typed once a term by somebody holding a
   register.
   ============================================================ */
.roll-total{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;
  padding:14px 18px;border-radius:1.2rem;background:var(--role-tint);}
.roll-total-num{font-size:30px;font-weight:800;letter-spacing:-.02em;color:var(--role-fg);}
.roll-total-say{flex:1;min-width:200px;font-size:13px;line-height:1.5;color:var(--ink-2);}
.roll-row{display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  padding:12px 16px;border-top:1px solid var(--hairline);}
.roll-row:first-child{border-top:0;}
.roll-form{width:44px;font-size:14px;font-weight:800;color:var(--ink);}
.roll-fields{display:flex;gap:10px;}
.roll-fields label{display:flex;flex-direction:column;gap:2px;font-size:11px;
  font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--ink-3);}
.roll-fields input{width:88px;height:40px;padding:0 10px;font-size:15px;font-weight:700;
  color:var(--ink);background:var(--glass-2);border:1px solid var(--hairline);border-radius:.6rem;}
.roll-fields input:focus{outline:2px solid var(--role);outline-offset:1px;}
.roll-note{flex:1;min-width:150px;text-align:right;font-size:12px;color:var(--ink-3);}
.roll-note b{color:var(--ink-2);font-weight:700;}
@media (max-width:560px){
  .roll-note{flex-basis:100%;text-align:left;padding-left:58px;}
}

/* The learners tile's own line: how many of the roll were in today. */
.glance-today{display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  margin-top:2px;padding:10px 14px;border-radius:.9rem;background:var(--glass-2);}
.glance-today b{font-size:17px;font-weight:800;color:var(--ink);}
.glance-today span{font-size:12.5px;color:var(--ink-3);}
.glance-today-bar{flex-basis:100%;height:7px;border-radius:999px;background:var(--hairline);overflow:hidden;}
.glance-today-bar i{display:block;height:100%;border-radius:999px;background:var(--role);}
.glance-class-today{font-weight:700;color:var(--role-fg);}

/* ── Why children were away (console/why_away.html) ───────────────────────
   Two groups, Illness and Not illness, each a card of rows: the reason and
   a one-line hint on the left, one number box on the right. The roll
   screen's rows were built for "P.5"; "Diarrhoea" ran under its box on a
   phone. */
.why-group{border:0;margin:0;padding:0;min-width:0;}
.why-legend{padding:0;margin-bottom:6px;font-size:14px;font-weight:700;color:var(--ink);}
.why-row{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:12px 16px;border-bottom:1px solid var(--hairline);}
.why-row:last-child{border-bottom:0;}
.why-name{font-size:15px;font-weight:600;color:var(--ink);}
.why-hint{display:block;font-size:12px;font-weight:400;color:var(--ink-3);margin-top:1px;}
.why-row input{width:88px;height:44px;padding:0 10px;font-size:17px;font-weight:700;
  text-align:center;color:var(--ink);background:var(--glass-2);
  border:1px solid var(--hairline);border-radius:.6rem;flex-shrink:0;}
.why-row input:focus{outline:2px solid var(--role);outline-offset:1px;}

/* ── Why children were away, everywhere else ──────────────────────────────
   Cholera on the overview is an .ov-status line like any other warning, in
   red; the rest is an .ov-more row and a chart card on Analytics, drawn with
   the same bar rows as the charts beside it. */
.ov-status[data-state=alert] .ov-status-mark{background:var(--t-red);color:var(--f-red);}
.ov-status[data-state=alert]:hover .ov-status-go svg{transform:translateX(3px);}
.bar-row[data-tone=quiet] .bar-track i{background:var(--ink-3);}
.why-alert{display:flex;align-items:center;gap:8px;margin-top:12px;padding:10px 12px;border-radius:12px;
  font-size:13px;font-weight:700;color:var(--f-red);background:var(--t-red);}
.why-alert svg{width:18px;height:18px;flex-shrink:0;}

/* ── A link inside a sentence ─────────────────────────────────────────────
   `.linkish` is a small button-shaped link for a row of actions; inside a
   paragraph it reads as a chip with a gap before the full stop. This is the
   plain one, for prose: the privacy policy and the lines that point to it. */
.text-link{color:var(--role-fg);font-weight:600;text-decoration:underline;
  text-underline-offset:2px;text-decoration-thickness:1px;}
.text-link:hover{text-decoration-thickness:2px;}

/* ============================================================
   THE NEW LOOK — the Figma screens of September 2026

   Flat white instead of frosted glass: a sidebar down the left with the
   badge at its head and the signed-in person at its foot, over a wash of
   amber into green; a bar across the top with search, the bell and the
   person; big plain headings; tables under a solid green header row; and
   one green for everything you can press.

   Scoped to `.app-body`, the class role_shell.html puts on the page, so the
   screens the designs do not cover yet -- sign-in, the password pages,
   the printed posters -- keep the look they had until they are drawn.
   Everything above this block still applies underneath; this layer only
   says where the new look differs, which is why it is at the end.
   ============================================================ */
:root{
  --nl-green:#16A34A; --nl-green-deep:#15803D; --nl-green-tint:#E8F7EE;
  --nl-amber:#FDB532; --nl-amber-ink:#2B1B00;
  --nl-ink:#111827; --nl-ink-2:#374151; --nl-muted:#6B7280;
  --nl-line:#E5E7EB; --nl-bg:#FFFFFF; --nl-soft:#F7F8FA;
  --nl-red:#EF4444; --nl-blue:#2563EB; --nl-orange:#F59E0B;
  --nl-me:#B3372B;
}
@media (prefers-color-scheme:dark){
  :root{
    --nl-green:#22C55E; --nl-green-deep:#16A34A; --nl-green-tint:rgba(34,197,94,.16);
    --nl-ink:#F3F4F6; --nl-ink-2:#D1D5DB; --nl-muted:#9CA3AF;
    --nl-line:#2A2D34; --nl-bg:#101216; --nl-soft:#181B21;
  }
}

html:has(body.app-body){background:var(--nl-bg);}
body.app-body{background:var(--nl-bg)!important;color:var(--nl-ink);}
body.app-body::before,body.app-body::after,.app-body .brand-watermark{display:none;}

/* ---- the frame: sidebar and main column ---- */
.app-shell{display:grid;grid-template-columns:232px minmax(0,1fr);min-height:100vh;}
.app-side{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;
  background:var(--nl-bg);border-right:1px solid var(--nl-line);overflow-y:auto;overscroll-behavior:contain;}
.app-side-head{position:relative;display:flex;justify-content:center;padding:22px 16px 6px;}
.app-side-brand{display:block;border-radius:50%;}
.app-side-brand img{width:84px;height:84px;border-radius:50%;display:block;}
.app-drawer-close{display:none;}
.app-side-eyebrow{margin:10px 22px 8px;font-size:11px;font-weight:500;letter-spacing:.16em;
  text-transform:uppercase;color:var(--nl-muted);}

.app-nav{display:flex;flex-direction:column;gap:4px;padding:0 12px;}
.app-nav-item{display:flex;align-items:center;gap:12px;min-height:44px;padding:9px 14px;border-radius:8px;
  font-size:14px;font-weight:500;color:var(--nl-ink-2);text-decoration:none;
  transition:background-color .15s ease,color .15s ease;}
.app-nav-item svg{width:20px;height:20px;flex:none;}
.app-nav-item:hover{background:var(--nl-soft);color:var(--nl-ink);}
.app-nav-item.is-active{background:var(--nl-amber);color:var(--nl-amber-ink);font-weight:600;}
.app-nav-item.is-unbuilt{opacity:.45;cursor:not-allowed;}

/* The person, at the foot, over the amber-to-green wash of the designs. */
.app-side-foot{margin-top:auto;padding:130px 12px 14px;
  background:linear-gradient(180deg,rgba(255,255,255,0) 0%,#FFF1CC 16%,#FDBA3B 40%,#A7B12C 66%,#3C9230 86%,#2E8A2D 100%);}
@media (prefers-color-scheme:dark){
  .app-side-foot{background:linear-gradient(180deg,rgba(16,18,22,0) 0%,rgba(253,186,59,.35) 30%,#8A8F26 62%,#2E7A2A 100%);}
}
.app-me{position:relative;}
.app-me-card{list-style:none;display:flex;align-items:center;gap:12px;padding:10px 8px;border-radius:10px;
  cursor:pointer;color:#fff;}
.app-me-card::-webkit-details-marker{display:none;}
.app-me-card::marker{content:'';}
.app-me-card:hover{background:rgba(255,255,255,.12);}
.app-me-avatar{width:42px;height:42px;border-radius:50%;flex:none;display:flex;align-items:center;justify-content:center;
  background:var(--nl-me);color:#fff;font-size:18px;font-weight:600;box-shadow:0 0 0 2px rgba(255,255,255,.55);}
.app-me-text{display:flex;flex-direction:column;min-width:0;flex:1;line-height:1.25;}
.app-me-name{font-size:16px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.app-me-role{font-size:12.5px;opacity:.92;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.app-me-chevron{width:18px;height:18px;flex:none;transition:transform .2s ease;}
.app-me[open] .app-me-chevron{transform:rotate(180deg);}
.app-me-menu{position:absolute;left:0;right:0;bottom:calc(100% + 6px);padding:6px;z-index:5;
  background:var(--nl-bg);border:1px solid var(--nl-line);border-radius:10px;
  box-shadow:0 18px 36px -16px rgba(0,0,0,.35);}
.app-me-menu a,.app-me-menu button{display:flex;align-items:center;gap:10px;width:100%;min-height:42px;padding:8px 12px;
  border-radius:8px;font-size:14px;font-weight:500;color:var(--nl-ink-2);text-align:left;text-decoration:none;}
.app-me-menu svg{width:17px;height:17px;flex:none;}
.app-me-menu a:hover,.app-me-menu button:hover{background:var(--nl-soft);color:var(--nl-ink);}
.app-me-menu button:hover{color:#B91C1C;}

/* ---- the top bar ---- */
.app-main{min-width:0;display:flex;flex-direction:column;}
.app-top{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:16px;
  padding:14px 32px;background:var(--nl-bg);border-bottom:1px solid var(--nl-line);}
.app-burger,.app-top-brand{display:none;}
.app-search{position:relative;flex:1;max-width:420px;margin:0;}
.app-search svg{position:absolute;left:14px;top:50%;transform:translateY(-50%);width:18px;height:18px;
  color:var(--nl-muted);pointer-events:none;}
.app-body .app-search input{width:100%;min-height:44px;padding:0 14px 0 42px!important;font-size:14px;color:var(--nl-ink);
  background:var(--nl-bg)!important;border:1px solid var(--nl-line)!important;border-radius:10px!important;
  box-shadow:none!important;-webkit-backdrop-filter:none;backdrop-filter:none;}
.app-body .app-search input::placeholder{color:var(--nl-muted);}
.app-body .app-search input:focus{outline:none;border-color:var(--nl-green)!important;
  box-shadow:0 0 0 3px color-mix(in srgb,var(--nl-green) 22%,transparent)!important;}

.app-top-right{margin-left:auto;display:flex;align-items:center;gap:14px;}
.app-online{display:inline-flex;align-items:center;gap:7px;padding:5px 12px;border-radius:999px;
  background:var(--nl-green-tint);color:var(--nl-green-deep);font-size:12.5px;font-weight:500;white-space:nowrap;}
.app-online-dot{width:7px;height:7px;border-radius:50%;background:var(--nl-green);}
.app-online.is-offline{background:#FEF3C7;color:#92400E;}
.app-online.is-offline .app-online-dot{background:#D97706;}

.app-bell{position:relative;}
.app-bell-btn{position:relative;width:42px;height:42px;display:inline-flex;align-items:center;justify-content:center;
  border-radius:10px;color:var(--nl-ink-2);}
.app-bell-btn:hover{background:var(--nl-soft);color:var(--nl-ink);}
.app-bell-btn svg{width:22px;height:22px;}
.app-bell-count{position:absolute;top:3px;right:3px;min-width:18px;height:18px;padding:0 4px;border-radius:999px;
  display:flex;align-items:center;justify-content:center;background:var(--nl-red);color:#fff;
  font-size:10.5px;font-weight:700;box-shadow:0 0 0 2px var(--nl-bg);}
.app-bell-panel{position:absolute;right:0;top:calc(100% + 8px);z-index:50;width:min(340px,calc(100vw - 24px));
  padding:12px;background:var(--nl-bg);border:1px solid var(--nl-line);border-radius:12px;
  box-shadow:0 22px 44px -18px rgba(0,0,0,.35);}
.app-bell-title{margin:2px 6px 8px;font-size:13px;font-weight:600;color:var(--nl-ink);}
.app-bell-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px;}
.app-bell-item{display:flex;align-items:flex-start;gap:10px;padding:10px;border-radius:8px;
  font-size:13.5px;line-height:1.45;color:var(--nl-ink-2);text-decoration:none;}
.app-bell-item:hover{background:var(--nl-soft);}
.app-bell-dot{width:8px;height:8px;margin-top:6px;border-radius:50%;flex:none;background:var(--nl-blue);}
.app-bell-item[data-tone=warn] .app-bell-dot{background:var(--nl-orange);}
.app-bell-item[data-tone=alert]{color:#B91C1C;font-weight:600;}
.app-bell-item[data-tone=alert] .app-bell-dot{background:var(--nl-red);}
.app-bell-empty{margin:4px 6px;font-size:13.5px;color:var(--nl-muted);}

.app-top-me{display:flex;align-items:center;gap:12px;padding-left:16px;border-left:1px solid var(--nl-line);
  text-decoration:none;min-height:44px;}
.app-top-me-text{display:flex;flex-direction:column;text-align:left;line-height:1.3;}
.app-top-name{font-size:14px;font-weight:600;color:var(--nl-ink);}
.app-top-sub{max-width:210px;font-size:12.5px;color:var(--nl-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.app-top-avatar{width:44px;height:44px;border-radius:50%;flex:none;display:flex;align-items:center;justify-content:center;
  background:#FDE3B5;color:#7C2D12;font-size:17px;font-weight:600;}

.app-content{display:flex;flex-direction:column;gap:22px;width:100%;max-width:1320px;padding:30px 32px 56px;}
.app-content:focus{outline:none;}

/* ---- below the sidebar breakpoint: a drawer, and a two-row top bar ---- */
@media (max-width:1023px){
  .app-shell{display:block;}
  .app-side{position:fixed;inset:0 auto 0 0;z-index:60;width:min(300px,86vw);height:100%;
    transform:translateX(-102%);transition:transform .28s cubic-bezier(.16,1,.3,1);}
  .app-shell[data-drawer] .app-side{transform:none;box-shadow:0 0 60px rgba(0,0,0,.35);}
  .app-scrim{position:fixed;inset:0;z-index:55;background:rgba(15,17,21,.38);}
  .app-drawer-close{display:inline-flex;position:absolute;right:10px;top:10px;width:44px;height:44px;
    align-items:center;justify-content:center;border-radius:10px;color:var(--nl-ink);}
  .app-drawer-close svg{width:22px;height:22px;}
  .app-drawer-close:hover{background:var(--nl-soft);}

  .app-top{flex-wrap:wrap;gap:10px 8px;padding:10px 14px 12px;}
  .app-burger{display:inline-flex;width:44px;height:44px;align-items:center;justify-content:center;
    border-radius:10px;color:var(--nl-ink);}
  .app-burger svg{width:24px;height:24px;}
  .app-top-brand{display:block;position:absolute;left:50%;top:6px;transform:translateX(-50%);}
  .app-top-brand img{width:48px;height:48px;border-radius:50%;display:block;}
  .app-search{order:10;flex:1 1 100%;max-width:none;}
  .app-top-right{gap:6px;}
  .app-online,.app-top-me-text{display:none;}
  .app-top-me{padding-left:4px;border-left:0;}
  .app-top-avatar{width:40px;height:40px;font-size:16px;}
  .app-content{padding:20px 16px 44px;gap:18px;}
}
@media (prefers-reduced-motion:reduce){ .app-side{transition:none;} }

/* ---- headings: big and plain ---- */
.app-content h1{font-size:34px;line-height:1.15;font-weight:500;letter-spacing:-.015em;color:var(--nl-ink);}
.app-content h1 + p{margin-top:8px;font-size:15.5px;color:var(--nl-ink-2);}
@media (max-width:640px){ .app-content h1{font-size:26px;} .app-content h1 + p{font-size:14px;} }
.page-head{display:flex;flex-direction:column;}
.page-title{font-size:34px;line-height:1.15;font-weight:500;letter-spacing:-.015em;color:var(--nl-ink);}
.page-sub{margin-top:8px;font-size:15.5px;color:var(--nl-ink-2);}

/* ---- cards: white, a hairline, no frost ---- */
.app-body .bg-white,.app-body .glass-card,.app-body .today-card,.app-body .chart-card,.app-body .stat-tile{
  background:var(--nl-bg)!important;-webkit-backdrop-filter:none;backdrop-filter:none;
  border:1px solid var(--nl-line)!important;box-shadow:none!important;}
.app-body .glass-card,.app-body .today-card,.app-body .chart-card,.app-body .rounded-2xl,.app-body .rounded-xl{border-radius:12px;}
.app-body .glass-card::before,.app-body .glass-card::after,.app-body .today-card::after{display:none;}
.app-body .rise{animation:none;}
@media (hover:hover){ .app-body .stat-tile:hover,.app-body .today-card:hover{transform:none;} }
.nl-card{background:var(--nl-bg);border:1px solid var(--nl-line);border-radius:12px;padding:20px;}
.nl-empty p{font-size:14.5px;color:var(--nl-ink-2);}
.nl-empty .nl-muted,.nl-muted{margin-top:6px;font-size:13.5px;color:var(--nl-muted);}

/* ---- one green for everything you can press ---- */
.app-body .role-btn{background:var(--nl-green)!important;color:#fff;border-radius:8px;
  box-shadow:none!important;filter:none;}
.app-body .role-btn::before,.app-body .role-btn::after{display:none;}
.app-body .role-btn:hover{background:var(--nl-green-deep)!important;filter:none;box-shadow:none!important;}
.app-body .linkish{color:var(--nl-green-deep);}
.app-body .linkish:hover{background:var(--nl-green-tint);}
.app-body .role-chip{background:var(--nl-green-tint);color:var(--nl-green-deep);box-shadow:none;}

/* ---- tables: a solid green header row, dotted lines between ---- */
.app-body .bg-white:has(> table.data-table){border:0!important;background:transparent!important;}
.app-body table.data-table{border-collapse:separate;border-spacing:0;}
.app-body table.data-table thead th{background:var(--nl-green);color:#fff;padding:17px 16px;
  font-size:14px;font-weight:500;text-transform:none;letter-spacing:0;white-space:nowrap;}
.app-body table.data-table thead th:first-child{border-radius:8px 0 0 8px;padding-left:22px;}
.app-body table.data-table thead th:last-child{border-radius:0 8px 8px 0;padding-right:22px;}
.app-body table.data-table thead th a,.app-body table.data-table thead th button{color:#fff;}
.app-body table.data-table td{padding:16px;border-top:0;border-bottom:1px dashed var(--nl-line);
  font-size:14px;color:var(--nl-ink);}
.app-body table.data-table td:first-child{padding-left:22px;}
.app-body table.data-table td:last-child{padding-right:22px;}
.app-body table.data-table tbody tr:hover{background-color:var(--nl-soft);}
.app-body table.data-table tbody tr:last-child td{border-bottom:0;}

/* ---- a rate is a coloured pill, as in the designs ---- */
.app-body .rate{display:inline-flex;}
.app-body .rate .rate-bar{display:none;}
.app-body .rate b{display:inline-flex;align-items:center;justify-content:center;min-width:48px;padding:3px 10px;
  border-radius:999px;font-size:12px;font-weight:600;color:#fff;background:var(--nl-green);}
.app-body .rate[data-v=weak] b{background:var(--nl-blue);}
.app-body .rate[data-v=bad] b{background:var(--nl-red);}

/* ---- the filter row: a bordered card, labels over fields, green Apply ---- */
.app-body .filter-field label{font-size:13px;font-weight:500;color:var(--nl-ink-2);text-transform:none;letter-spacing:0;}
.app-body .filter-field input,.app-body .filter-field select{background:var(--nl-bg)!important;
  border:1px solid var(--nl-line)!important;border-radius:8px!important;box-shadow:none!important;
  -webkit-backdrop-filter:none;backdrop-filter:none;color:var(--nl-ink);}

/* ---- the pager: Previous and Next as outlined buttons ---- */
.app-body .pager-step{border:1px solid var(--nl-line);border-radius:8px;color:var(--nl-green-deep);
  background:var(--nl-bg);font-weight:500;box-shadow:none;}
.app-body .pager-step:hover{border-color:var(--nl-green);background:var(--nl-green-tint);}
.app-body .pager-step.is-off{color:var(--nl-muted);opacity:.6;}

/* ---- the search results ---- */
.search-group{padding:8px 8px 10px;}
.search-group-title{display:flex;align-items:center;gap:10px;margin:8px 12px 6px;font-size:13px;font-weight:600;
  text-transform:uppercase;letter-spacing:.08em;color:var(--nl-muted);}
.search-group-title svg{width:18px;height:18px;}
.search-hits{list-style:none;margin:0;padding:0;}
.search-hit{display:flex;align-items:center;gap:14px;min-height:52px;padding:10px 12px;border-radius:8px;
  text-decoration:none;color:var(--nl-ink);}
.search-hit + .search-hit,.search-hits li + li .search-hit{border-top:1px dashed var(--nl-line);}
.search-hit:hover{background:var(--nl-soft);}
.search-hit-title{font-size:15px;font-weight:500;}
.search-hit-detail{font-size:13px;color:var(--nl-muted);}
.search-hit-go{width:18px;height:18px;margin-left:auto;color:var(--nl-muted);flex:none;}

/* ---- fixes to the frame ---- */
@media (min-width:1024px){ .app-body .app-top-brand{display:none!important;} }
.app-search svg{z-index:1;}

/* ---- the overview, as the designs draw it ---- */
.app-body .ov{display:flex;flex-direction:column;gap:20px;}
.app-body .ov-date{font-size:12px;letter-spacing:.12em;color:var(--nl-muted);}
.app-body .ov-title{font-size:38px;line-height:1.12;font-weight:500;letter-spacing:-.02em;color:var(--nl-ink);}
.app-body .ov-where{margin-top:6px;font-size:16px;color:var(--nl-ink-2);}
@media (max-width:640px){ .app-body .ov-title{font-size:27px;} .app-body .ov-where{font-size:14.5px;} }

.app-body .ov-status{gap:16px;padding:18px 20px;border-radius:12px;background:var(--nl-bg);
  border:1px solid var(--nl-line);box-shadow:none;}
.app-body .ov-status-mark{width:48px;height:48px;border-radius:10px;background:var(--nl-green-tint);color:var(--nl-green-deep);}
.app-body .ov-status-text b{font-size:17px;font-weight:500;}
.app-body .ov-status-text span{font-size:13.5px;color:var(--nl-ink-2);}
.app-body .ov-status-go,.app-body .ov-status-btn{min-height:46px;padding:0 24px;border-radius:8px;
  background:var(--nl-green);color:#fff;font-size:14.5px;font-weight:500;}
.app-body .ov-status-go svg{display:none;}
.app-body .ov-status[data-state=open]:hover .ov-status-go{background:var(--nl-green-deep);}
.app-body .ov-status-btn-quiet{background:var(--nl-bg);color:var(--nl-green-deep);border:1px solid var(--nl-green);}
.app-body .ov-status[data-state=alert]{border-color:#FCA5A5;background:#FEF2F2;}
.app-body .ov-status[data-state=alert] .ov-status-go{background:#DC2626;}
@media (max-width:520px){ .app-body .ov-status-go{width:100%;justify-content:center;} }

/* The school's two numbers beside today's card. */
.ov-pair{display:grid;gap:20px;align-items:start;}
@media (min-width:1100px){
  .ov-pair{grid-template-columns:minmax(0,1fr) 300px;}
  .ov-pair .glance-tiles{grid-template-columns:1fr!important;}
}

.app-body .glance-tile{grid-template-areas:"label label chev" "num num extra" "sub sub extra";
  padding:18px 20px;border-radius:12px;background:var(--nl-bg);border:1px solid var(--nl-line);box-shadow:none;}
.app-body .glance-tile::after,.app-body .glance-icon,.app-body .glance-cols{display:none;}
.app-body .glance-tile:hover{transform:none;border-color:#D1D5DB;}
.app-body .glance-tile[aria-expanded=true]{background:var(--nl-bg);box-shadow:inset 0 0 0 2px var(--nl-green);}
.app-body .glance-tile[aria-expanded=true] .glance-chev{color:var(--nl-green-deep);}
.app-body .glance-label{font-size:15.5px;font-weight:500;letter-spacing:0;text-transform:none;color:var(--nl-ink);}
.app-body .glance-num{margin-top:8px;font-size:36px;font-weight:500;letter-spacing:-.01em;}
.app-body .glance-sub{font-size:12.5px;color:var(--nl-ink-2);}
.app-body .glance-panel{border-radius:12px;}

/* Today: the session buttons, the ring, and "see who". */
.app-body .who-kicker{font-size:19px;font-weight:500;letter-spacing:0;text-transform:none;color:var(--nl-ink);}
.app-body .pillbar{gap:10px;padding:0;background:none;box-shadow:none;border-radius:0;}
.app-body .pillbar > a,.app-body .pillbar > button{min-height:40px;padding:0 18px;border-radius:6px;
  border:1px solid var(--nl-line);background:var(--nl-bg);color:var(--nl-ink);font-weight:500;box-shadow:none;}
.app-body .pillbar > a small,.app-body .pillbar > button small{color:inherit;font-weight:500;}
.app-body .pillbar > a[aria-current=page],.app-body .pillbar > button[aria-selected=true]{
  background:var(--nl-green);border-color:var(--nl-green);color:#fff;box-shadow:none;}
.app-body .pillbar .live-dot{background:#fff;box-shadow:none;animation:none;}
.app-body .ring{box-shadow:none;}
.app-body .ring[data-tone=weak]{--c:#F87171;}
.app-body .ring-in{background:var(--nl-bg);box-shadow:none;}
.app-body .ring-in b{font-weight:500;}
.app-body .who-tap{min-height:34px;padding:0 14px;border:1px solid #F7C667;background:#FFFBEB;color:#B45309;font-weight:500;}
.app-body .who-summary:hover .who-tap{background:#FEF3C7;}
.app-body .who-block .who-list{background:var(--nl-bg);border:1px solid var(--nl-line);box-shadow:none;}
.app-body .ov-more{background:var(--nl-bg);border:1px solid var(--nl-line);box-shadow:none;border-radius:12px;}

/* ---- page headings win over the older, more specific rule ---- */
.app-body [data-main] h1.text-lg,.app-body [data-main] h1{font-size:34px;line-height:1.15;font-weight:500;
  letter-spacing:-.015em;color:var(--nl-ink);}
.app-body [data-main] h1.text-lg + p,.app-body [data-main] h1 + p{margin-top:8px;font-size:15.5px;color:var(--nl-ink-2);max-width:80ch;}
@media (max-width:640px){
  .app-body [data-main] h1.text-lg,.app-body [data-main] h1{font-size:26px;}
  .app-body [data-main] h1.text-lg + p,.app-body [data-main] h1 + p{font-size:14px;}
}
.app-body [data-main] h1.ov-title{font-size:38px;}
@media (max-width:640px){ .app-body [data-main] h1.ov-title{font-size:27px;} }

/* ---- a list's own line above its table: quiet ---- */
.app-body .list-count{font-size:13px;color:var(--nl-muted);}
.app-body td.cell-num{font-variant-numeric:tabular-nums;white-space:nowrap;}
.app-body td .cell-note{margin-top:3px;font-size:12px;}

/* ---- Settings: two columns of cards, as the designs lay them out ---- */
.app-body .reading-column.settings-grid{max-width:none;}
@media (min-width:900px){
  .settings-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px;align-items:stretch;}
  .settings-grid > *{margin-top:0!important;}
  .settings-grid > .settings-wide{grid-column:1 / -1;}
}

/* ---- on a phone, each row of a table is its own card ---- */
@media (max-width:640px){
  .app-body .bg-white:has(> table.data-table){overflow:visible;}
  .app-body table.data-table tbody tr{margin:0 0 12px;padding:4px 0;border:1px solid var(--nl-line);border-radius:12px;}
  .app-body table.data-table tbody tr:first-child{border-top:1px solid var(--nl-line);}
  .app-body table.data-table td,.app-body table.data-table td:first-child,.app-body table.data-table td:last-child{
    padding:7px 16px;border-bottom:0;}
  .app-body table.data-table td:first-child{padding-top:12px;padding-bottom:10px;margin-bottom:2px;
    border-bottom:1px dashed var(--nl-line);}
  .app-body table.data-table td:last-child{padding-bottom:12px;}
  .app-body table.data-table td::before{font-size:12.5px;font-weight:500;letter-spacing:0;text-transform:none;
    color:var(--nl-muted);font-family:inherit;}
}

/* ---- your own email address, in Settings ---- */
.own-email-optional{margin-left:6px;font-weight:500;text-transform:none;letter-spacing:0;color:var(--nl-muted,#6B7280);}
.own-email-form{display:flex;gap:10px;margin-top:6px;max-width:520px;}
.own-email-form input{flex:1;min-width:0;min-height:44px;padding:0 12px;font-size:14px;}
.own-email-form button{min-height:44px;flex:none;}
.own-email-note{margin-top:6px;font-size:12.5px;color:var(--nl-muted,#6B7280);}

/* ---- Analytics tiles: flat, like every other card ---- */
.app-body .kpi-tile{background:var(--nl-bg);border:1px solid var(--nl-line);box-shadow:none;border-radius:12px;}
.app-body .kpi-tile::after{display:none;}
.app-body .kpi-tile .kpi-label{font-size:14px;font-weight:500;letter-spacing:0;text-transform:none;color:var(--nl-ink);}
.app-body .kpi-value{font-weight:500;}

/* ---- the School boundary page: left-aligned, and a select that fits its text ---- */
.app-body .set-location{max-width:760px;margin-left:0;margin-right:0;}
.app-body .set-location select{min-height:46px;height:auto;line-height:1.35;padding-top:10px;padding-bottom:10px;}

/* ---- the old sidebar's rules reach the new one by its label ----
   `nav[aria-label=Sections]` was the old sidebar's hook, and on a phone it
   laid the sections out as a two-column grid. The new sidebar keeps the
   label (screen readers announce it) but is always one column. */
.app-body nav.app-nav[aria-label=Sections]{display:flex!important;flex-direction:column;grid-template-columns:none;
  gap:4px;margin:0;padding:0 12px!important;background:none!important;border:0!important;box-shadow:none!important;
  position:static;height:auto;overflow:visible;max-height:none;}

/* ---- on a phone the bell's panel spans the screen under the top bar ---- */
@media (max-width:1023px){
  .app-bell-panel{position:fixed;top:62px;left:12px;right:12px;width:auto;max-height:calc(100vh - 80px);overflow-y:auto;}
}

/* ---- dropdowns fit their own text ----
   An older rule fixes every select at 36px tall with no padding, and the
   forms add their own padding on top, so the chosen option was cut through
   the middle ("Kazinga Primary School" showing only its top half). Taller
   and self-sizing, which is also the 44px a thumb needs. */
.app-body select{height:auto;min-height:44px;padding-top:10px!important;padding-bottom:10px!important;line-height:1.35;}

/* ============================================================
   THE NEW LOOK, SCREEN BY SCREEN — every Figma screen as drawn

   The second pass over the September 2026 screens. The block
   above built the frame; this one matches each screen to its
   drawing: the face, the filter row, the pager, and each page's
   own layout. Still scoped to `.app-body`, so a teacher's own
   screens and sign-in keep their look until they are designed.
   ============================================================ */

/* ---- Poppins, self-hosted ----
   The designs' face. Served from our own origin (the CSP allows no font
   host) and precached by the worker, so a phone offline draws the same
   letters. Latin only: 8 KB a weight. Licence: static/fonts/Poppins-OFL.txt */
@font-face{font-family:'Poppins';font-style:normal;font-weight:400;font-display:swap;
  src:url("../fonts/poppins-latin-400-normal.9212f6f9860f.woff2") format('woff2');}
@font-face{font-family:'Poppins';font-style:normal;font-weight:500;font-display:swap;
  src:url("../fonts/poppins-latin-500-normal.a09f2fccfee3.woff2") format('woff2');}
@font-face{font-family:'Poppins';font-style:normal;font-weight:600;font-display:swap;
  src:url("../fonts/poppins-latin-600-normal.72993dddf88a.woff2") format('woff2');}
@font-face{font-family:'Poppins';font-style:normal;font-weight:700;font-display:swap;
  src:url("../fonts/poppins-latin-700-normal.25b0e113ca7c.woff2") format('woff2');}
body.app-body,.app-body button,.app-body input,.app-body select,.app-body textarea{
  font-family:'Poppins','Inter Local',system-ui,sans-serif;}
/* Poppins sets wide and heavy: what was bold in the old face reads as
   semibold here, which is where the designs draw it. */
.app-body b,.app-body strong{font-weight:600;}

/* ---- the filter row, as drawn ----
   Labels over fields, a calendar at the left of each date, a chevron in
   each dropdown, and the quick ranges folded into one "Filter" dropdown.
   The older rule painted `background` over the select's own arrow; this
   one sets only the colour and draws the chevron back. */
.app-body .filter-bar{gap:14px 22px;align-items:flex-end;}
.app-body .filter-field label{margin-bottom:8px;font-size:13px;font-weight:400;color:var(--nl-ink-2);}
.app-body .filter-bar input,.app-body .filter-bar select{min-height:40px!important;font-size:13px;
  border-radius:6px!important;background-color:var(--nl-bg)!important;}
.app-body .filter-bar select{min-width:118px;padding:0 34px 0 14px!important;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23374151' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E")!important;
  background-size:16px 16px!important;background-position:right 11px center!important;background-repeat:no-repeat!important;}
.date-field{position:relative;display:block;}
.date-field svg{position:absolute;left:12px;top:50%;width:18px;height:18px;transform:translateY(-50%);
  color:var(--nl-ink-2);pointer-events:none;z-index:1;}
.app-body .date-field input{padding-left:40px!important;padding-right:12px!important;min-width:150px;position:relative;}
/* The whole field opens the picker, as the drawn one does; the browser's
   own little calendar at the right would be a second icon. */
.app-body .date-field input::-webkit-calendar-picker-indicator{position:absolute;inset:0;width:auto;height:auto;
  opacity:0;cursor:pointer;}
.app-body .filter-bar .filter-apply{min-height:40px;min-width:92px;padding:0 24px;border-radius:6px;
  font-size:13.5px;font-weight:500;}
@media (prefers-color-scheme:dark){
  .app-body .filter-bar select{
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23D1D5DB' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E")!important;}
  .app-body .date-field input{color-scheme:dark;}
}
@media (max-width:640px){
  .app-body .filter-bar{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;}
  .app-body .filter-bar select,.app-body .date-field input{width:100%;min-width:0;}
  .app-body .filter-bar .filter-apply{width:100%;}
}

/* ---- the pager, as drawn: ‹ Previous  1 (2) 3 … 11  Next › ---- */
.app-body .pager{padding:18px 4px 4px;border-top:0;}
.app-body .pager-count{font-size:13px;color:var(--nl-muted);}
.app-body .pager-steps{gap:10px;}
.app-body .pager-step{gap:6px;min-height:40px;padding:0 14px;border-radius:6px;font-size:13px;font-weight:500;
  color:var(--nl-green-deep);border-color:#CDEBD8;}
.app-body .pager-step svg{width:15px;height:15px;}
.pager-numbers{display:flex;align-items:center;gap:4px;margin:0 14px;}
.pager-num,.pager-gap{display:inline-flex;align-items:center;justify-content:center;min-width:36px;height:36px;
  border-radius:50%;font-size:13.5px;font-weight:500;color:var(--nl-ink,#111827);text-decoration:none;}
.pager-num:hover{background:var(--nl-soft,#F3F4F6);}
.pager-num.is-current{background:var(--nl-green,#16A34A);color:#fff;}
@media (max-width:640px){
  .pager-numbers{margin:0 4px;}
  .app-body .pager-step{padding:0 10px;}
}

/* ---- the navigation, lighter, as drawn ---- */
.app-body .app-nav-item{font-size:13.5px;font-weight:400;}
.app-body .app-nav-item.is-active{font-weight:500;}

/* ---- Overview: the Sessions card ----
   Its icon in a green tile, the sentence under the title, a pill saying
   whether the codes are live, and one green button. */
.ov-sessions{display:flex;align-items:center;}
.ov-sessions .ov-status-text{flex:1;}
.ov-pill{display:inline-flex;align-items:center;gap:7px;padding:4px 12px;border-radius:999px;flex:none;
  font-size:12.5px;font-weight:500;white-space:nowrap;background:var(--nl-soft);color:var(--nl-muted);}
.ov-pill i{width:7px;height:7px;border-radius:50%;background:currentColor;}
.ov-pill[data-tone=good]{background:var(--nl-green-tint);color:var(--nl-green-deep);}
.app-body .ov-sessions .ov-status-btn{min-width:170px;justify-content:center;}
@media (max-width:640px){
  .ov-sessions{display:grid;grid-template-columns:auto 1fr auto;grid-template-areas:"mark text pill" "go go go";gap:12px;}
  .ov-sessions .ov-status-mark{grid-area:mark;}
  .ov-sessions .ov-status-text{grid-area:text;}
  .ov-sessions .ov-pill{grid-area:pill;align-self:start;}
  .app-body .ov-sessions .ov-status-btn{grid-area:go;width:100%;}
}

/* ---- Overview: the tiles ----
   Boys and girls as small figures beside their numbers, the way the
   designs print "127 Boys  126 Girls"; officers as a tile of their own. */
.app-body .glance-sub{display:flex;flex-wrap:wrap;align-items:center;gap:4px 12px;}
.glance-bg{display:inline-flex;align-items:center;gap:3px;white-space:nowrap;}
.glance-bg svg{width:15px;height:15px;color:var(--nl-ink-2);}
.glance-today-line{flex-basis:100%;color:var(--nl-muted);}
.app-body .glance-tile-link{text-decoration:none;color:inherit;grid-template-areas:"label" "num" "sub";}
.app-body .glance-tile-link:hover{border-color:var(--nl-green);}
@media (min-width:1100px){
  .app-body .glance-tiles[data-n="5"]{grid-template-columns:repeat(5,minmax(0,1fr));}
  .app-body .glance-tiles[data-n="6"]{grid-template-columns:repeat(3,minmax(0,1fr));}
}
.app-body .glance-num{font-size:34px;}

/* ---- Overview: Today ----
   The session's state as a pill with a dot, the teachers ring reading
   "0 of 6" in one line, the counts as coloured words with a dot, and the
   names in bordered columns under the ring. */
.app-body .who-session{font-size:13.5px;font-weight:400;color:var(--nl-ink);}
.app-body .who-session b{font-weight:400;color:var(--nl-ink);}
.app-body .who-session-time{font-weight:400;color:var(--nl-ink);}
.app-body .who-session .vchip{display:inline-flex;align-items:center;gap:6px;margin-left:12px;padding:3px 12px;
  border-radius:999px;font-size:12px;font-weight:500;}
.app-body .who-session .vchip::before{content:'';width:6px;height:6px;border-radius:50%;background:currentColor;}
.app-body .vchip[data-v=good]{background:var(--nl-green-tint);color:var(--nl-green-deep);}
.app-body .vchip[data-v=unknown]{background:#FDECEC;color:#DC2626;}
.app-body .vchip[data-v=weak]{background:#FEF3C7;color:#B45309;}
.app-body .who-block-head{font-size:12.5px;font-weight:500;letter-spacing:.02em;text-transform:uppercase;color:var(--nl-ink);}
.app-body .ring{width:92px;height:92px;}
.app-body .ring-in{flex-direction:row;gap:4px;align-items:baseline;justify-content:center;padding-top:28px;}
.app-body .ring-in b{font-size:16px;font-weight:400;}
.app-body .ring-in small{font-size:16px;font-weight:400;color:var(--nl-ink);margin:0;}
.app-body .who-summary-text > b{font-size:13.5px;font-weight:400;}
.app-body .who-pills .pill[data-tone]{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:400;}
.app-body .who-pills .pill[data-tone]::before{content:'';width:6px;height:6px;border-radius:50%;background:currentColor;}
.app-body .who-pills .pill[data-tone=bad]{color:#DC2626;}
.app-body .who-pills .pill[data-tone=wait]{color:#D97706;}
.app-body .who-pills .pill[data-tone=good]{color:var(--nl-green-deep);}
.app-body .who-pills .pill[data-tone=school]{color:var(--nl-green-deep);}
.app-body .who-pills .pill[data-tone=school]::before{display:none;}
.app-body .who-pills .pill svg{width:16px;height:16px;color:var(--nl-ink);}
.app-body .who-tap{min-height:30px;font-size:12.5px;}

.app-body .who-block .who-lists{gap:0;padding:14px 16px;border:1px solid var(--nl-line);border-radius:10px;
  background:var(--nl-bg);grid-template-columns:repeat(auto-fit,minmax(150px,1fr));}
.app-body .who-block .who-list{padding:0 8px;border:0;border-radius:0;background:none;box-shadow:none;}
.app-body .who-list-head{font-size:13px;font-weight:400;letter-spacing:0;text-transform:none;color:var(--nl-ink)!important;}
.app-body .who-list-head::before{width:7px;height:7px;}
.app-body .who-list-head span{margin-left:4px;min-width:20px;padding:0 6px;border-radius:999px;font-size:11px;
  background:none;border:1px solid currentColor;text-align:center;}
.app-body .who-list[data-tone=good] .who-list-head span{color:var(--nl-green);}
.app-body .who-list[data-tone=wait] .who-list-head span{color:#D97706;}
.app-body .who-list[data-tone=bad] .who-list-head span{color:#DC2626;}
.app-body .who-list li{padding:9px 0;border-top:0;border-bottom:1px dashed var(--nl-line);font-size:12.5px;color:var(--nl-ink-2);}
.app-body .who-list li:last-child{border-bottom:0;}
.app-body .who-list .who-meta{color:var(--nl-ink-2);}
.app-body .who-none{font-size:12.5px;color:var(--nl-ink-2);}
@media (min-width:900px){ .app-body .who-grid{grid-template-columns:1fr 1fr;} }
.app-body .who-learners{border-left:1px solid var(--nl-line);padding-left:20px;}
@media (max-width:899px){ .app-body .who-learners{border-left:0;padding-left:0;border-top:1px solid var(--nl-line);padding-top:16px;} }
.app-body .who-note{font-size:12.5px;color:var(--nl-ink-2);}
.app-body .who-note-warn{color:#DC2626;}

/* ---- Overview above a school: "Teachers today" and each school ---- */
.who-card-title{margin-bottom:14px;}
.app-body .school-today-table{margin-top:18px;max-width:560px;}
.app-body .school-today-head{display:flex;justify-content:space-between;padding:14px 22px;border-radius:8px;
  background:var(--nl-green);color:#fff;font-size:14px;}
.app-body .school-today-head span:last-child{padding-right:30px;}
.app-body .school-today{margin-top:4px;}
.app-body .school-today li + li{border-top:1px dashed var(--nl-line);}
.app-body .school-today a{padding:11px 22px;border-radius:0;}
.app-body .school-today a:hover{background:var(--nl-soft);}
.app-body .school-today-name{font-size:13.5px;font-weight:400;}
.app-body .school-today-fig{min-width:60px;text-align:right;font-size:13.5px;}
.app-body .school-today-fig[data-tone=bad]{color:#DC2626;}
.app-body .school-today-fig[data-tone=good]{color:var(--nl-green-deep);}
.app-body .school-today-go{color:#DC2626;}
.app-body .ov-sessions .ov-status-mark{background:var(--nl-green-tint);color:var(--nl-green-deep);}
.app-body .ov-sessions .ov-status-text b{font-size:18px;}
.app-body .pillbar > a,.app-body .pillbar > button{font-weight:400;font-size:13.5px;}
.app-body .pillbar > a small,.app-body .pillbar > button small{font-size:13.5px;font-weight:400;}
.app-body .glance-sub{gap:2px 10px;font-size:11.5px;}
.app-body .glance-label{font-weight:400;}
.who-where{display:flex;align-items:center;gap:6px;margin-top:6px;font-size:12px;color:var(--nl-ink-2,#374151);}
.who-where svg{width:14px;height:14px;}
.app-body .who-list .who-name{font-weight:400;color:var(--nl-ink-2);}
.app-body .who-list .who-meta{font-weight:400;}
.app-body .who-note-warn{display:flex;align-items:flex-start;gap:6px;margin:12px 0 0;padding:0;background:none;
  border:0;font-size:12px;font-weight:400;color:#DC2626;}
.app-body .who-note-warn::before{content:'!';flex:none;display:inline-flex;align-items:center;justify-content:center;
  width:14px;height:14px;margin-top:1px;border:1.4px solid currentColor;border-radius:50%;font-size:9px;font-weight:700;}
/* The session tabs share the width on a phone rather than push past it:
   Poppins sets wider than the old face, and three tabs with their counts
   were 410px on a 390px screen. */
@media (max-width:640px){
  .app-body .pillbar{flex-wrap:wrap;gap:8px;width:100%;}
  .app-body .pillbar > a,.app-body .pillbar > button{flex:1 1 0;min-width:0;padding:0 8px;justify-content:center;
    font-size:12.5px;white-space:nowrap;}
  .app-body .pillbar > a small,.app-body .pillbar > button small{font-size:12.5px;}
  .app-body .glance-num{font-size:30px;}
}

/* ---- Analytics, as drawn ----
   Two rings side by side -- learners and teachers -- then the trend line
   beside the boys-and-girls ring, then the two tables. Rings are a conic
   gradient under a white disc; the line is an SVG the server draws. */
.an-grid{display:grid;grid-template-columns:1fr;gap:20px;}
@media (min-width:1100px){
  .an-grid{grid-template-columns:repeat(2,minmax(0,1fr));}
  .an-grid.an-grid-wide{grid-template-columns:minmax(0,1.25fr) minmax(0,1fr);}
}
.an-card{min-width:0;padding:22px 22px 18px;background:var(--nl-bg,#fff);border:1px solid var(--nl-line,#E5E7EB);border-radius:12px;}
.an-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:14px;}
.an-title{font-size:19px;font-weight:500;color:var(--nl-ink,#111827);line-height:1.3;}
.an-sub{margin-top:2px;font-size:12px;color:var(--nl-muted,#6B7280);}
.an-chip{flex:none;padding:4px 10px;border:1px solid var(--nl-line,#E5E7EB);border-radius:6px;font-size:11.5px;
  color:var(--nl-ink-2,#374151);white-space:nowrap;}
.an-ring-row{display:flex;align-items:center;gap:28px;flex-wrap:wrap;}
.an-ring{--a:var(--nl-green,#16A34A);--b:#DADDE1;position:relative;flex:none;width:170px;height:170px;border-radius:50%;
  background:conic-gradient(var(--a) calc(var(--p) * 1%),var(--b) 0);}
.an-ring::after{content:'';position:absolute;inset:22px;border-radius:50%;background:var(--nl-bg,#fff);}
.an-ring[data-tone=teachers]{--a:var(--nl-blue,#2563EB);}
.an-ring-split{--a:var(--nl-blue,#2563EB);--b:#E83FD2;}
.an-ring-in{position:absolute;inset:0;z-index:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;}
.an-ring-in b{font-size:26px;font-weight:500;color:var(--nl-ink,#111827);}
.an-ring-in small{font-size:12px;color:var(--nl-muted,#6B7280);}
.an-delta{font-size:11.5px;font-weight:500;}
.an-delta[data-dir=up]{color:var(--nl-green-deep,#15803D);}
.an-delta[data-dir=down]{color:#DC2626;}
.an-delta[data-dir=flat]{color:var(--nl-muted,#6B7280);}
.an-key{flex:1;min-width:170px;list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:18px;}
.an-key li{display:flex;align-items:center;gap:12px;font-size:13px;color:var(--nl-ink,#111827);}
.an-key li b{margin-left:auto;font-weight:500;}
.an-key i,.an-legend i{width:12px;height:12px;border-radius:50%;flex:none;background:#D1D5DB;}
.an-key i[data-tone=present-l]{background:var(--nl-green,#16A34A);}
.an-key i[data-tone=present-t],.an-legend i[data-tone=present-t]{background:var(--nl-blue,#2563EB);}
.an-key i[data-tone=absent]{background:var(--nl-orange,#F59E0B);}
.an-key i[data-tone=boys]{background:var(--nl-blue,#2563EB);}
.an-key i[data-tone=girls]{background:#E83FD2;}
.an-key-fig{margin-left:auto;display:flex;flex-direction:column;align-items:flex-end;}
.an-key-fig b{font-weight:500;}
.an-key-fig small{font-size:11px;color:var(--nl-muted,#6B7280);}
.an-foot{display:flex;align-items:center;gap:12px;margin-top:18px;}
.an-foot-icon{width:36px;height:36px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex:none;
  background:var(--nl-green-tint,#E8F7EE);color:var(--nl-green-deep,#15803D);}
.an-foot-icon svg{width:20px;height:20px;}
.an-foot-text{display:flex;flex-direction:column;min-width:0;line-height:1.3;}
.an-foot-text b{font-size:18px;font-weight:500;color:var(--nl-ink,#111827);}
.an-foot-text small{font-size:11.5px;color:var(--nl-muted,#6B7280);}
.an-foot-link{margin-left:auto;font-size:12.5px;color:var(--nl-green-deep,#15803D);text-decoration:none;white-space:nowrap;}
.an-foot-link:hover{text-decoration:underline;}
.an-line svg{display:block;width:100%;height:auto;overflow:visible;}
.an-grid-line{stroke:var(--nl-line,#E5E7EB);stroke-width:1;}
.an-axis{font-size:11px;fill:var(--nl-muted,#6B7280);font-family:inherit;}
.an-trend-line{fill:none;stroke:#5B9CF6;stroke-width:2;stroke-linejoin:round;stroke-linecap:round;}
.an-trend-dot{fill:#5B9CF6;}
.an-legend{display:flex;align-items:center;gap:8px;margin-top:8px;font-size:12px;color:var(--nl-ink-2,#374151);}
.an-ring-split::after{inset:26px;}
.an-key-wide{gap:26px;}
.an-table-wrap{overflow-x:auto;}
.an-table{width:100%;border-collapse:separate;border-spacing:0;font-size:12.5px;}
.an-table th{padding:10px 12px;text-align:left;font-weight:500;color:#fff;background:var(--nl-green,#16A34A);white-space:nowrap;}
.an-table th:first-child{border-radius:6px 0 0 6px;}
.an-table th:last-child{border-radius:0 6px 6px 0;text-align:right;}
.an-table td{padding:10px 12px;border-bottom:1px dashed var(--nl-line,#E5E7EB);color:var(--nl-ink,#111827);}
.an-table td:last-child{text-align:right;}
.an-table tr:last-child td{border-bottom:0;}
.an-fig{font-variant-numeric:tabular-nums;}
.an-fig[data-tone=good]{color:var(--nl-green-deep,#15803D);}
.an-fig[data-tone=weak]{color:#D97706;}
.an-fig[data-tone=bad]{color:#DC2626;}
.an-fig[data-tone=learners]{color:#7C3AED;}
@media (prefers-color-scheme:dark){
  .an-ring{--b:#2A2D34;}
  .an-trend-line{stroke:#7FB2FA;} .an-trend-dot{fill:#7FB2FA;}
}
@media (max-width:640px){
  .an-card{padding:18px 16px;}
  .an-ring{width:140px;height:140px;}
  .an-ring::after{inset:18px;}
  .an-ring-row{gap:18px;}
  .an-head{flex-wrap:wrap;}
}
.app-body .chart-card .chart-title{font-size:19px;font-weight:500;}
.app-body .chart-card .chart-sub{font-size:12px;color:var(--nl-muted);}
.an-fig{white-space:nowrap;}
.app-body .filter-bar select{max-width:240px;text-overflow:ellipsis;}
@media (prefers-color-scheme:dark){ .an-ring-split{--b:#E83FD2;} }
/* The trend's SVG scales with its card; its labels must not shrink with it. */
@media (max-width:640px){ .an-axis{font-size:19px;} .an-trend-line{stroke-width:3;} }

/* ---- Session codes, as drawn ----
   The picker on the left and the session the screen is about as a green
   card on the right; the rest of the day as green cards beneath, "Later
   today" tagged on their corner; the tools as a table under a green
   header with small blue buttons; the term poster with its square at the
   right. */
.sess-top{display:flex;align-items:center;justify-content:space-between;gap:16px 24px;flex-wrap:wrap;}
.sess-pick{display:flex;align-items:center;gap:12px 16px;flex-wrap:wrap;}
.app-body .sess-pick-label{font-weight:400;font-size:14px;color:var(--nl-ink,#111827);}
.app-body .sess-pick select{min-width:230px;min-height:48px;padding:0 40px 0 16px!important;border:1px solid var(--nl-line)!important;
  border-radius:8px!important;background-color:var(--nl-bg)!important;font-size:14px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23374151' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E")!important;
  background-size:16px 16px!important;background-position:right 14px center!important;background-repeat:no-repeat!important;}
.sess-chips{display:flex;gap:28px;flex-wrap:wrap;}
.sess-chip{position:relative;display:flex;align-items:center;justify-content:space-between;gap:16px;width:min(100%,330px);
  min-height:56px;padding:10px 12px 10px 20px;border-radius:6px;background:var(--nl-green,#16A34A);color:#fff;}
.sess-chip-name{font-size:14.5px;font-weight:500;line-height:1.3;}
.sess-chip-time{font-size:11.5px;opacity:.92;}
.sess-chip form{margin:0;}
.sess-chip-go{display:inline-flex;align-items:center;gap:6px;min-height:28px;padding:0 12px;border-radius:999px;border:0;
  background:#FDB532;color:#fff;font-size:12px;font-weight:500;white-space:nowrap;cursor:pointer;}
.sess-chip-go i{width:6px;height:6px;border-radius:50%;background:#fff;}
.sess-chip-go:hover{background:#F59E0B;}
.sess-chip-go.is-open{background:#fff;color:var(--nl-green-deep,#15803D);cursor:default;}
.sess-chip-go.is-open i{background:var(--nl-green,#16A34A);}
.sess-chip-tag{position:absolute;right:10px;top:-10px;padding:1px 8px;border-radius:4px;font-size:10.5px;
  background:#fff;color:#DC2626;border:1px solid #FCA5A5;}
.sess-chip-tag[data-tone=quiet]{color:var(--nl-muted,#6B7280);border-color:var(--nl-line,#E5E7EB);}
.sess-note{display:flex;align-items:flex-start;gap:12px;font-size:12.5px;line-height:1.6;color:var(--nl-muted,#6B7280);}
.sess-note svg{width:20px;height:20px;flex:none;margin-top:2px;color:#DC2626;}
.app-body .sess-tools table.data-table thead th{padding:13px 16px;}
.app-body .sess-tools table.data-table td{padding:10px 16px;font-size:13px;}
.app-body .sess-tools td.sess-how{color:var(--nl-muted);}
.app-body .sess-tools .sess-mark{text-align:right;white-space:nowrap;}
.sess-tag{display:inline-flex;align-items:center;min-height:24px;padding:0 10px;margin-left:6px;border-radius:999px;border:0;
  background:#2336E8;color:#fff;font-size:11.5px;font-weight:400;text-decoration:none;cursor:pointer;}
.sess-tag:hover{background:#1D2BC0;}
.app-body a.sess-tag{min-height:24px;}
.app-body .sess-card{background:var(--nl-bg);border:1px solid var(--nl-line);box-shadow:none;border-radius:12px;}

.term-card{padding:24px;border:1px dashed var(--nl-line,#E5E7EB);border-radius:12px;background:var(--nl-bg,#fff);}
.term-row{display:flex;align-items:flex-start;gap:24px;flex-wrap:wrap;}
.term-icon{width:54px;height:54px;border-radius:8px;flex:none;display:flex;align-items:center;justify-content:center;
  background:var(--nl-green-tint,#E8F7EE);color:var(--nl-green-deep,#15803D);}
.term-icon svg{width:30px;height:30px;}
.term-text{flex:1;min-width:240px;}
.term-title{font-size:19px;font-weight:500;color:var(--nl-ink,#111827);}
.term-which{font-size:14px;color:var(--nl-muted,#6B7280);font-weight:400;}
.term-body{margin-top:12px;max-width:52ch;font-size:13.5px;line-height:1.7;color:var(--nl-ink,#111827);}
.term-runs{display:flex;align-items:center;gap:8px;margin-top:12px;font-size:12px;color:var(--nl-ink-2,#374151);}
.term-runs svg{width:16px;height:16px;}
.term-actions{display:flex;gap:24px;flex-wrap:wrap;margin-top:24px;}
.app-body .term-btn{min-width:170px;min-height:44px;padding:0 24px;border-radius:6px;font-size:14px;font-weight:400;justify-content:center;display:inline-flex;align-items:center;}
.term-btn-quiet{background:var(--nl-bg,#fff);color:var(--nl-green-deep,#15803D);border:1px solid var(--nl-green,#16A34A);cursor:pointer;}
.term-btn-quiet:hover{background:var(--nl-green-tint,#E8F7EE);}
.term-qr{flex:none;padding:8px;background:#fff;border-radius:8px;}
.term-reissue{margin-top:18px;padding-top:18px;border-top:1px solid var(--nl-line,#E5E7EB);}
@media (max-width:640px){
  .sess-chip{width:100%;}
  .term-qr{margin:0 auto;}
  .app-body .term-btn{flex:1;}
  .app-body .sess-tools .sess-mark{text-align:left;white-space:normal;}
  .sess-tag{margin:4px 6px 0 0;}
}
/* One session's codes at a time: its card takes the width, and the codes
   sit in columns rather than one long list down the left. */
.app-body .sess-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:20px;}
@media (min-width:900px){ .app-body .sess-card .code-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:8px;} }

/* ---- Teachers: "Grant leave ▾", one menu per row ---- */
.row-menu{position:relative;display:inline-block;text-align:left;}
.row-menu summary{list-style:none;display:inline-flex;align-items:center;gap:6px;min-height:36px;padding:0 6px;
  font-size:13.5px;color:var(--nl-ink,#111827);cursor:pointer;border-radius:6px;}
.row-menu summary::-webkit-details-marker{display:none;}
.row-menu summary::marker{content:'';}
.row-menu summary:hover{background:var(--nl-soft,#F7F8FA);}
.row-menu summary svg{width:16px;height:16px;transition:transform .15s ease;}
.row-menu[open] summary svg{transform:rotate(180deg);}
.row-menu-list{position:absolute;right:0;top:calc(100% + 4px);z-index:20;min-width:170px;padding:6px;
  background:var(--nl-bg,#fff);border:1px solid var(--nl-line,#E5E7EB);border-radius:10px;
  box-shadow:0 16px 32px -14px rgba(0,0,0,.3);}
.row-menu-list a,.row-menu-list button{display:flex;align-items:center;width:100%;min-height:40px;padding:0 12px;border-radius:6px;
  font-size:13.5px;color:var(--nl-ink-2,#374151);text-decoration:none;text-align:left;background:none;border:0;cursor:pointer;}
.row-menu-list a:hover,.row-menu-list button:hover{background:var(--nl-soft,#F7F8FA);color:var(--nl-ink,#111827);}
.row-menu-list form{margin:0;}
.app-body .data-table th.text-right{text-align:right;}
/* The row menus open below their row, so the Teachers table lets them out
   where it fits the screen; every other table keeps its own scroll. */
@media (min-width:1024px){ .app-body .table-menus{overflow:visible;} }
.app-body table.data-table td .font-medium{font-weight:400;}

/* ---- Reports: the fence as a small outlined pill, as drawn ---- */
.fence-pill{display:inline-flex;align-items:center;justify-content:center;min-width:50px;padding:1px 10px;border-radius:999px;
  font-size:11px;border:1px solid #FCD9A0;color:#F59E0B;background:var(--nl-bg,#fff);}
.fence-pill[data-v=none]{border-color:#D1D5DB;color:var(--nl-ink-2,#374151);}
.fence-pill[data-v=out]{border-color:#FCA5A5;color:#DC2626;}
.app-body #exportOpen svg{order:2;}
.app-body .date-field input{min-width:0;width:152px;}
.app-body .filter-bar select{max-width:220px;}
.app-body table.data-table td .mono,.app-body table.data-table td.mono{font-family:inherit;}
@media (max-width:640px){ .app-body .date-field input{width:100%;} }

/* ---- The registers, as drawn: no letter badges, the code beside the
   name, a pill with a dot for the status, and History in purple ---- */
.app-body .cell-person .avatar{display:none;}
.cell-code{font-size:inherit;color:inherit;white-space:nowrap;}
.staff-pill{display:inline-flex;align-items:center;gap:6px;padding:2px 10px;border-radius:999px;font-size:12px;white-space:nowrap;
  background:#E8F7EE;color:#15803D;}
.staff-pill::before{content:'';width:6px;height:6px;border-radius:50%;background:currentColor;}
.staff-pill[data-state=frozen]{background:#FEF3C7;color:#92400E;}
.staff-pill[data-state=former]{background:#F1F5F9;color:#475569;}
.history-link{font-size:13px;color:#A855F7;text-decoration:none;}
.history-link:hover{text-decoration:underline;}
@media (prefers-color-scheme:dark){
  .staff-pill{background:rgba(34,197,94,.16);color:#4ADE80;}
  .staff-pill[data-state=frozen]{background:rgba(245,158,11,.16);color:#FBBF24;}
  .staff-pill[data-state=former]{background:rgba(148,163,184,.16);color:#CBD5E1;}
  .history-link{color:#C084FC;}
}
.sess-chip-text{min-width:0;}

/* ---- School boundary, as drawn: two columns ----
   The current point and the radius on the left, the map on the right. The
   map is OpenStreetMap's tiles under the page's own pin and circle
   (registry/maps.py places them), so the boundary is drawn to scale even
   when the tiles never arrive. */
.app-body .set-location.bnd{max-width:none;display:flex;flex-direction:column;gap:22px;}
.bnd-note{display:flex;align-items:flex-start;gap:12px;max-width:110ch;font-size:12.5px;line-height:1.7;color:var(--nl-muted,#6B7280);}
.bnd-note svg{width:20px;height:20px;flex:none;margin-top:2px;color:#DC2626;}
.bnd-cols{display:grid;grid-template-columns:1fr;gap:20px;align-items:start;}
@media (min-width:1100px){ .bnd-cols{grid-template-columns:minmax(0,1fr) minmax(0,1.15fr);} }
.bnd-left{display:flex;flex-direction:column;gap:20px;min-width:0;}
.bnd-card{padding:20px;border:1px solid var(--nl-line,#E5E7EB);border-radius:10px;background:var(--nl-bg,#fff);}
.bnd-here{display:flex;gap:18px;align-items:flex-start;}
.bnd-thumb{width:62px;height:62px;flex:none;border-radius:6px;display:flex;align-items:center;justify-content:center;
  background:#D9D9D9;color:#fff;}
.bnd-thumb svg{width:28px;height:28px;}
.bnd-here-text{min-width:0;flex:1;}
.bnd-label{font-size:13.5px;color:var(--nl-ink,#111827);}
.bnd-point{margin-top:6px;font-size:21px;font-weight:600;color:var(--nl-ink,#111827);font-variant-numeric:tabular-nums;word-break:break-word;}
.bnd-when{margin-top:4px;font-size:12px;color:var(--nl-muted,#6B7280);}
.bnd-guess{margin-top:10px;padding:10px 12px;border-radius:8px;border:1px solid #FDE68A;background:#FFFBEB;font-size:12px;
  line-height:1.6;color:#92400E;}
.bnd-guess-title{font-weight:600;font-size:13px;margin-bottom:4px;}
.app-body .set-location .map-check.role-btn{display:inline-flex;align-items:center;gap:8px;margin-top:14px;min-height:40px;
  padding:0 20px;border-radius:4px;color:#fff;font-size:13px;font-weight:400;text-decoration:none;}
.bnd-fine{display:flex;gap:8px;margin-top:10px;font-size:10.5px;line-height:1.6;color:var(--nl-muted,#6B7280);}
.bnd-fine svg{width:14px;height:14px;flex:none;margin-top:1px;color:#DC2626;}
.bnd-radius-title{display:block;font-size:14.5px;}
.bnd-fine-plain{margin-top:2px;font-size:11px;color:var(--nl-muted,#6B7280);}
.bnd-radius-row{display:flex;gap:12px;margin-top:14px;flex-wrap:wrap;}
.app-body .bnd-radius-row select{flex:1 1 220px;min-width:0;border:1px solid var(--nl-line)!important;border-radius:6px!important;
  background-color:var(--nl-bg)!important;font-size:13px;}
.app-body .bnd-take{flex:1 1 170px;min-height:44px;border-radius:4px;font-size:13px;font-weight:400;color:#fff;}
.bnd-custom{margin-top:14px;}
.bnd-map{position:relative;height:320px;overflow:hidden;border:1px solid var(--nl-line,#E5E7EB);border-radius:10px;
  background:#EEF0EB;}
@media (min-width:1100px){ .bnd-map{height:100%;min-height:330px;} }
.bnd-tiles img{position:absolute;width:256px;height:256px;max-width:none;user-select:none;}
.bnd-ring{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);overflow:visible;pointer-events:none;}
.bnd-ring circle{fill:rgba(37,99,235,.10);stroke:#2336E8;stroke-width:2;stroke-dasharray:7 5;}
.bnd-pin{position:absolute;left:50%;top:50%;width:26px;height:34px;transform:translate(-50%,-100%);pointer-events:none;
  filter:drop-shadow(0 2px 2px rgba(0,0,0,.3));}
.bnd-pin path{fill:#2F80ED;} .bnd-pin circle{fill:#fff;}
.bnd-zoom{position:absolute;right:12px;top:12px;display:flex;flex-direction:column;gap:6px;}
.bnd-zoom a,.bnd-zoom span{width:26px;height:26px;display:flex;align-items:center;justify-content:center;border-radius:4px;
  background:#fff;color:#111827;font-size:16px;line-height:1;text-decoration:none;box-shadow:0 1px 3px rgba(0,0,0,.25);}
.bnd-zoom span{opacity:.4;}
.bnd-zoom svg{width:15px;height:15px;}
.bnd-key{position:absolute;left:10px;bottom:10px;padding:6px 10px;border-radius:4px;background:rgba(255,255,255,.95);
  font-size:10px;color:#111827;box-shadow:0 1px 3px rgba(0,0,0,.2);}
.bnd-key p{display:flex;align-items:center;gap:6px;}
.bnd-key p + p{margin-top:3px;}
.bnd-key-pin{width:8px;height:8px;border-radius:50% 50% 50% 0;transform:rotate(-45deg);background:#2F80ED;}
.bnd-key-ring{width:9px;height:9px;border-radius:50%;border:1.5px dashed #2336E8;}
.bnd-credit{position:absolute;right:0;bottom:0;padding:1px 6px;font-size:10px;color:#333;background:rgba(255,255,255,.8);}
.bnd-credit a{color:#1D4ED8;}
.bnd-offline{display:none;position:absolute;left:50%;top:14px;transform:translateX(-50%);max-width:calc(100% - 80px);
  padding:6px 12px;border-radius:6px;background:rgba(255,255,255,.95);font-size:11.5px;color:#374151;text-align:center;
  box-shadow:0 1px 3px rgba(0,0,0,.2);}
.bnd-map[data-offline] .bnd-offline{display:block;}
.bnd-map[data-offline] .bnd-tiles{display:none;}
@media (max-width:640px){
  .bnd-here{flex-direction:column;}
  .bnd-point{font-size:18px;}
}

/* ---- Settings, as drawn ----
   The school's two cards side by side, then your account (your face on the
   left, the registry's facts beside it) beside what this role may see, then
   the password with its lock, then a quiet note under an (i). */
.set-card{padding:22px 24px!important;border-radius:10px!important;}
.set-title{font-size:18px;font-weight:500;color:var(--nl-ink,#111827);}
.app-body .set-card > p,.set-password-text p{margin-top:8px;font-size:12.5px;line-height:1.7;color:var(--nl-muted,#6B7280);}
.app-body .set-btn{display:inline-flex;align-items:center;justify-content:center;min-height:38px;margin-top:14px;padding:0 18px;
  border-radius:4px;font-size:13px;font-weight:400;color:#fff;text-decoration:none;}
.set-account{display:flex;gap:26px;align-items:flex-start;}
.set-face{display:flex;flex-direction:column;align-items:center;gap:8px;flex:none;}
.set-avatar{width:96px;height:96px;border-radius:50%;overflow:hidden;display:flex;align-items:center;justify-content:center;
  background:linear-gradient(160deg,#43C57A,#16A34A 60%,#15803D);}
.set-avatar img{width:100%;height:100%;object-fit:cover;}
.set-avatar svg{width:100%;height:100%;fill:#fff;}
.set-photo-form{display:flex;flex-direction:column;align-items:center;gap:2px;margin:0;}
.set-photo-pick{font-size:12px;color:var(--nl-green-deep,#15803D);cursor:pointer;background:none;border:0;padding:4px 6px;}
.set-photo-pick:hover{text-decoration:underline;}
.set-photo-pick:focus-within{outline:2px solid var(--nl-green,#16A34A);outline-offset:2px;border-radius:4px;}
.set-photo-remove{font-size:11.5px;color:var(--nl-muted,#6B7280);background:none;border:0;cursor:pointer;padding:2px 6px;}
.set-photo-remove:hover{color:#B91C1C;}
.set-fields{flex:1;min-width:0;display:grid;grid-template-columns:1fr auto;gap:16px 20px;}
.set-field-label{font-size:10.5px;color:var(--nl-ink,#111827);text-transform:none;letter-spacing:0;font-weight:400;}
.set-field{display:flex;align-items:center;gap:6px;margin-top:4px;font-size:13px;color:var(--nl-ink,#111827);}
.set-field svg{width:15px;height:15px;flex:none;}
.set-field-end{text-align:right;}
.set-field-end .set-field{justify-content:flex-end;}
.set-field-wide{grid-column:1 / -1;}
.set-field-big{font-size:15px;}
.set-ticks{list-style:none;margin:16px 0 0;padding:0;display:flex;flex-direction:column;gap:14px;}
.set-ticks li{display:flex;align-items:flex-start;gap:12px;font-size:12.5px;line-height:1.5;color:var(--nl-muted,#6B7280);}
.set-ticks svg{width:17px;height:17px;flex:none;margin-top:1px;}
.set-ticks svg rect{fill:var(--nl-green,#16A34A);}
.set-ticks svg path{fill:none;stroke:#fff;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round;}
.set-password{display:flex;align-items:center;gap:22px;flex-wrap:wrap;}
.set-lock{width:48px;height:48px;border-radius:8px;flex:none;display:flex;align-items:center;justify-content:center;
  background:var(--nl-green-tint,#E8F7EE);color:var(--nl-green,#16A34A);}
.set-lock svg{width:24px;height:24px;fill:currentColor;}
.set-password-text{flex:1;min-width:240px;}
.set-password-text p{color:var(--nl-ink,#111827);}
.set-password-text .set-privacy{color:var(--nl-muted,#6B7280);}
.app-body .set-password-btn{display:inline-flex;align-items:center;justify-content:center;min-height:40px;padding:0 18px;
  border-radius:4px;font-size:13px;font-weight:400;color:#fff;text-decoration:none;}
.set-note{display:flex;align-items:flex-start;gap:14px;font-size:12.5px;line-height:1.7;color:var(--nl-muted,#6B7280);}
.set-note svg{width:20px;height:20px;flex:none;margin-top:2px;color:#DC2626;}
@media (max-width:640px){
  .set-account{flex-direction:column;align-items:stretch;}
  .set-face{align-self:center;}
  .set-fields{grid-template-columns:1fr;}
  .set-field-end{text-align:left;}
  .set-field-end .set-field{justify-content:flex-start;}
}

/* ---- your photo, where your initial was ---- */
.app-me-avatar img,.app-top-avatar img,.glance-avatar img{width:100%;height:100%;border-radius:50%;object-fit:cover;display:block;}
.app-me-avatar:has(img),.app-top-avatar:has(img){overflow:hidden;}
.glance-avatar:has(img){overflow:hidden;padding:0;}
.set-fields > *{min-width:0;}
@media (max-width:640px){ .set-fields{grid-template-columns:minmax(0,1fr);} }
.bnd-picture{position:absolute;inset:0;}


/* ============================================================
   CORRECTIONS, OCTOBER 2026 — docs/design/CORRECTIONS.md
   Asked for by the owner screen by screen on the live site.
   Numbered as they are there.
   ============================================================ */

/* ---- 2. Your school day: sessions two to a row ----
   The Add button is the grid's last cell, so it always takes the next
   free one. One column on a phone. */
.day-page{max-width:64rem;}
.day-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:16px;}
@media (min-width:820px){ .day-grid{grid-template-columns:repeat(2,minmax(0,1fr));} }
.day-add{display:flex;align-items:center;justify-content:center;gap:8px;min-height:120px;
  padding:16px;border:1px dashed var(--nl-line-strong,#CBD5E1);border-radius:16px;background:var(--nl-bg,#fff);
  font-size:14px;font-weight:600;color:var(--nl-ink-2,#374151);cursor:pointer;}
.day-add:hover{background:var(--nl-green-tint,#E8F7EE);border-color:var(--nl-green,#16A34A);color:var(--nl-green-deep,#15803D);}
.day-add svg{width:18px;height:18px;}

/* ---- 5. One Cancel: a bordered button the height and corners of the
   green one beside it. ---- */
.btn-cancel{display:inline-flex;align-items:center;justify-content:center;min-height:40px;padding:9px 18px;
  border:1px solid var(--nl-line-strong,#CBD5E1);border-radius:10px;background:var(--nl-bg,#fff);
  font-size:14px;font-weight:600;line-height:1.2;color:var(--nl-ink,#111827);text-decoration:none;cursor:pointer;}
.btn-cancel:hover{background:var(--nl-surface-2,#F8FAFC);border-color:var(--nl-muted,#6B7280);}
.app-body .btn-cancel,.app-body .gov-cancel{display:inline-flex;align-items:center;justify-content:center;
  min-height:40px;padding:9px 18px;border:1px solid var(--nl-line-strong,#CBD5E1);border-radius:8px;
  background:var(--nl-bg,#fff);font-size:14px;font-weight:600;color:var(--nl-ink,#111827);}
.app-body .btn-cancel:hover,.app-body .gov-cancel:hover{background:var(--nl-surface-2,#F8FAFC);border-color:var(--nl-muted,#6B7280);}

/* ---- 6. Session codes: the table runs the full width, lined up with the
   note above it and the Term poster below. ---- */
.app-body .sess-tools table.data-table{width:100%;}

/* ---- 8. Attendance by class: three columns, evenly spaced. ---- */
.an-table-even{table-layout:fixed;}
.an-table-even th:nth-child(2),.an-table-even td:nth-child(2){text-align:center;}

/* ---- 10. Sign-in: the card and its footnote centred in the window,
   with nothing crossing the page beneath them. ---- */
.signin-body #main{min-height:100vh;min-height:100dvh;display:flex;flex-direction:column;justify-content:center;}
.signin-page{display:flex;flex-direction:column;align-items:center;justify-content:center;}
.signin-body .brand-watermark{display:none;}

/* ---- 3 and 4. Register a teacher, and a person's history, over the list.
   The page's own panel, shown in the export dialog's overlay; see
   static/js/page-dialog.js. Tall forms scroll inside the box, not the page. ---- */
.page-dialog{position:relative;width:min(44rem,100%);max-height:calc(100vh - 32px);max-height:calc(100dvh - 32px);
  overflow-y:auto;padding:28px 24px 24px;border-radius:16px;background:var(--nl-bg,#fff);
  box-shadow:0 24px 60px -12px rgba(15,23,42,.35);}
.page-dialog > [data-dialog-content]{max-width:none;}
.page-dialog-close{position:absolute;top:12px;right:12px;width:36px;height:36px;border-radius:50%;border:0;
  display:inline-flex;align-items:center;justify-content:center;background:transparent;
  font-size:24px;line-height:1;color:var(--nl-muted,#6B7280);cursor:pointer;}
.page-dialog-close:hover{background:var(--nl-green-tint,#E8F7EE);color:var(--nl-ink,#111827);}
.page-dialog-open{overflow:hidden;}
@media (max-width:640px){ .page-dialog{padding:24px 16px 16px;border-radius:12px;} }

/* ---- 7. Analytics: the card labels open a list of ranges. ---- */
.an-range{position:relative;flex:none;}
.an-range > summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:6px;}
.an-range > summary::-webkit-details-marker{display:none;}
.an-range > summary svg{width:14px;height:14px;transition:transform .15s ease;}
.an-range[open] > summary svg{transform:rotate(180deg);}
.an-range-menu{position:absolute;right:0;top:calc(100% + 6px);z-index:30;min-width:170px;margin:0;padding:6px;list-style:none;
  background:var(--nl-bg,#fff);border:1px solid var(--nl-line,#E5E7EB);border-radius:8px;
  box-shadow:0 12px 30px -10px rgba(15,23,42,.25);}
.an-range-menu a{display:block;padding:8px 12px;border-radius:6px;font-size:13px;color:var(--nl-ink,#111827);text-decoration:none;}
.an-range-menu a:hover{background:var(--nl-green-tint,#E8F7EE);}
.an-range-menu a[aria-current]{font-weight:600;color:var(--nl-green-deep,#15803D);}
.an-range-menu a[aria-current]::after{content:" \2713";}


/* ============================================================
   THE NEW LOOK, A TEACHER'S OWN SCREENS — as the designer drew them

   docs/design/figma/teacher/: Check in, Scan and Count, in the frame every
   dashboard shares (templates/teacher_base.html). The greeting is the head
   teacher Overview's (.ov-hello); under it three buttons, the one you are
   on solid green. Then each screen's own cards: white, a hairline, and
   notes as a line of text beside a red-ringed "i" rather than a grey box.
   ============================================================ */
.app-body .teach-col{width:100%;display:flex;flex-direction:column;gap:22px;}
.app-body .teach-col > .ov-hello{margin-bottom:-4px;}

/* Check in · Scan · Count */
.app-body .teach-pills{display:flex;flex-wrap:wrap;gap:12px;}
.app-body .teach-pills a{display:inline-flex;align-items:center;justify-content:center;min-width:88px;min-height:40px;
  padding:0 20px;border-radius:8px;border:1px solid var(--nl-line);background:var(--nl-bg);
  font-size:14px;font-weight:500;color:var(--nl-ink);text-decoration:none;
  transition:background-color .15s ease,border-color .15s ease;}
.app-body .teach-pills a:hover{border-color:var(--nl-green);}
.app-body .teach-pills a[aria-current]{background:var(--nl-green);border-color:var(--nl-green);color:#fff;}

/* Two cards side by side, sessions on the left; one column on a phone,
   where what you can do comes first. */
.app-body .teach-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:24px;align-items:start;}
.app-body .teach-side{display:flex;flex-direction:column;gap:16px;}
@media (max-width:767px){
  .app-body .teach-grid{grid-template-columns:minmax(0,1fr);gap:16px;}
  .app-body .teach-grid > .teach-side{order:-1;}
}
.app-body .teach-col .rounded-2xl{border-radius:10px;}
.app-body .teach-card-title{font-size:18px;font-weight:500;color:var(--nl-ink);}
.app-body .teach-col .text-xs{font-size:13px;line-height:1.6;}

/* The session rows: a dotted rule between them, and a filled slate pill. */
.app-body .teach-grid .border-b{border-bottom:1px dashed var(--nl-line);padding:12px 0;}
.app-body .teach-grid .border-b:last-child{border-bottom:0;}
.app-body .teach-col .text-sm.font-semibold.text-slate-800{font-weight:500;}
.app-body .teach-col .mono{font-family:inherit;}
.app-body .teach-col .vchip{display:inline-flex;align-items:center;padding:3px 12px;border-radius:999px;
  font-size:12px;font-weight:500;white-space:nowrap;}
.app-body .teach-col .vchip[data-v=unknown]{background:#5B6B85;color:#fff;}
.app-body .teach-col .vchip[data-v=good]{background:var(--nl-green);color:#fff;}
.app-body .teach-col .vchip[data-v=weak]{background:var(--nl-orange);color:#fff;}

/* The buttons: the drawings' green, as wide as their words. "Show my code"
   keeps the full width -- it is the one thing to press. */
.app-body .teach-btn{display:inline-flex;align-items:center;justify-content:center;min-height:42px;padding:0 22px;
  border:0;border-radius:6px;background:var(--nl-green);color:#fff;font-size:14px;font-weight:500;cursor:pointer;}
.app-body .teach-btn:hover{background:var(--nl-green-deep);}
.app-body .teach-btn.is-full{width:100%;min-height:48px;font-size:15px;}

/* A note, as a line of text beside a red-ringed "i". */
.app-body .teach-info{display:flex;align-items:flex-start;gap:14px;font-size:13px;line-height:1.6;color:var(--nl-muted);}
.app-body .teach-info svg{width:20px;height:20px;flex:none;margin-top:1px;color:var(--nl-red);}
.app-body .teach-info b{font-weight:500;color:var(--nl-ink);}
.app-body .teach-info a{color:var(--nl-green-deep);text-decoration:underline;}

/* Checked in, now count: the next thing, in the green tint. */
.app-body .teach-col .next-step{background:var(--nl-green-tint);border:1px solid color-mix(in srgb,var(--nl-green) 30%,transparent);
  border-radius:10px;box-shadow:none;color:var(--nl-ink);}
.app-body .teach-col .next-step::after{display:none;}
.app-body .teach-col .next-step-ico{background:var(--nl-green);color:#fff;box-shadow:none;}

/* Scan: "No session is open" in an amber outline, as wide as its words. */
.app-body .teach-warn{align-self:flex-start;max-width:100%;padding:10px 14px;border:1px solid #FCD9A0;border-radius:6px;background:var(--nl-bg);}
.app-body .teach-warn .text-sm{font-weight:500;color:#B45309;}
.app-body .teach-warn .text-xs{color:#D97706;}

/* Scan: the camera's square, white with a camera in it until it starts. */
.app-body .teach-camera{border-radius:10px;box-shadow:none;padding:16px 16px 0;}
.app-body .teach-camera #stage{width:min(100%,270px);margin:0 auto;border-radius:10px;overflow:hidden;
  background:var(--nl-bg)!important;border:1px solid var(--nl-line);}
.app-body .teach-camera #stage:has(#placeholder[hidden]){background:#111827!important;border-color:#111827;}
.app-body .teach-camera #placeholder svg{width:56px;height:56px;color:#334155;}
.app-body .teach-camera #placeholder .text-sm{font-weight:400;color:var(--nl-ink);}
.app-body .teach-camera #placeholder .text-xs{color:var(--nl-muted);}
.app-body .teach-camera #stage:not(:has(#placeholder[hidden])){aspect-ratio:8/5;}
.app-body .teach-camera #stage:not(:has(#placeholder[hidden])) #reticle{display:none;}
.app-body .teach-camera #stage:has(#placeholder[hidden]) #reticle > div{border-color:rgba(255,255,255,.7);}
.app-body .teach-camera .p-4{display:flex;flex-direction:column;align-items:center;}
.app-body .teach-camera #start,.app-body .teach-camera #stop{width:auto;min-width:200px;padding:0 28px;border-radius:6px;font-weight:500;}
.app-body .teach-camera #start{background:var(--nl-green)!important;color:#fff;border:0;box-shadow:none!important;}
.app-body .teach-camera #start:hover{background:var(--nl-green-deep)!important;}

/* Count, before checking in: the clock in its green square, the words, and
   the button at the right. */
.app-body .teach-first{display:flex;flex-direction:row;align-items:center;gap:16px;padding:20px;text-align:left;
  background:var(--nl-bg);border:1px solid var(--nl-line);border-radius:10px;box-shadow:none;}
.app-body .teach-first .count-empty-ico{flex:none;margin:0;width:36px;height:36px;border-radius:6px;
  background:var(--nl-green-tint);color:var(--nl-green-deep);box-shadow:none;}
.app-body .teach-first-text{flex:1;min-width:0;}
.app-body .teach-first .count-empty-ico svg{width:20px;height:20px;}
.app-body .teach-first .count-empty-title{font-size:16px;font-weight:500;color:var(--nl-ink);margin:0;}
.app-body .teach-first .count-empty-note{font-size:13px;color:var(--nl-ink-2);margin:2px 0 0;max-width:52ch;}
.app-body .teach-first .count-go{flex:none;margin:0;width:auto;min-height:42px;padding:0 22px;border-radius:6px;
  background:var(--nl-green);color:#fff;font-size:14px;font-weight:500;box-shadow:none;}
@media (max-width:640px){
  .app-body .teach-first{flex-wrap:wrap;}
  .app-body .teach-first .count-go{width:100%;}
}

/* Count your class, flat: the classes as the session buttons (white, a
   hairline, the chosen one solid green), boys and girls as two plain panels
   with a coloured name, the boxes as the drawn inputs. */
.app-body .teach-col .count-card{background:var(--nl-bg);border:1px solid var(--nl-line);border-radius:10px;box-shadow:none;}
.app-body .teach-col .class-chip{border-radius:8px;font-weight:500;color:var(--nl-ink-2);
  background:var(--nl-bg);box-shadow:inset 0 0 0 1px var(--nl-line);}
.app-body .teach-col .class-chip:has(input:checked){color:#fff;background:var(--nl-green);box-shadow:none;}
.app-body .teach-col .class-chip.is-taken{background:var(--nl-soft);}
.app-body .teach-col .class-chip.is-mine{box-shadow:inset 0 0 0 1.5px var(--nl-green);}
.app-body .teach-col .count-group{border-radius:10px;background:var(--nl-soft);box-shadow:none;border:1px solid var(--nl-line);}
.app-body .teach-col .count-group-name{font-size:15px;font-weight:600;}
.app-body .teach-col .count-group[data-group=boys] .count-group-name{color:var(--nl-blue);}
.app-body .teach-col .count-group[data-group=girls] .count-group-name{color:#9333EA;}
.app-body .teach-col .stepper-label,.app-body .teach-col .count-total-row span{font-weight:500;color:var(--nl-muted);}
.app-body .teach-col .step{border-radius:8px;color:var(--nl-green-deep);background:var(--nl-bg);
  box-shadow:inset 0 0 0 1px var(--nl-line);}
.app-body .teach-col .step-input{border-radius:8px;font-weight:600;color:var(--nl-ink);
  background:var(--nl-bg);border:1px solid var(--nl-line);box-shadow:none;}
.app-body .teach-col .step-input:focus{border-color:var(--nl-green);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--nl-green) 22%,transparent);}
.app-body .teach-col .count-total{border-radius:10px;background:var(--nl-bg);box-shadow:none;border:1px solid var(--nl-line);}
.app-body .teach-col .count-total-row b{font-weight:600;}
.app-body .teach-col .count-total-row p:first-child b{color:var(--nl-green-deep);}
.app-body .teach-col .count-total-row p:nth-child(2) b{color:var(--nl-red);}
.app-body .teach-col .count-bar{background:var(--nl-soft);}
.app-body .teach-col .count-bar i{background:var(--nl-green);}
.app-body .teach-col .count-save{border-radius:6px;font-weight:500;}

/* A head teacher's Scan, inside their own frame: a way back, and a heading. */
.app-body .teach-back{display:inline-flex;align-items:center;gap:6px;font-size:14px;font-weight:500;
  color:var(--nl-muted);text-decoration:none;}
.app-body .teach-back:hover{color:var(--nl-ink);}
.app-body .teach-back svg{width:16px;height:16px;}
.app-body .teach-title{overflow-wrap:anywhere;}
.app-body .teach-note{max-width:60ch;}


/* ============================================================
   THE OVERVIEW'S PANELS AS TABLES

   What opens under Districts, Schools, Teachers and Learners on an
   Overview (templates/oversight/_glance.html): a table under a green
   header, the same as the "Teachers today" table each school is listed
   in (docs/design/figma/ministry/02-overview-each-school.png). Wide
   tables scroll sideways inside the card on a phone, never the page.
   ============================================================ */
.glance-table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
.glance-table{width:100%;border-collapse:separate;border-spacing:0;font-size:13.5px;}
.glance-table thead th{padding:13px 18px;background:var(--nl-green,#16A34A);color:#fff;
  font-weight:500;font-size:13.5px;text-align:left;white-space:nowrap;}
.glance-table thead th:first-child{border-radius:8px 0 0 8px;}
.glance-table thead th:last-child{border-radius:0 8px 8px 0;}
.glance-table tbody th,.glance-table tbody td{padding:11px 18px;text-align:left;vertical-align:middle;
  color:var(--nl-ink,#111827);border-top:1px dashed var(--nl-line,#E5E7EB);}
.glance-table tbody tr:first-child th,.glance-table tbody tr:first-child td{border-top:0;}
.glance-table tbody th{font-weight:500;}
.glance-table tbody td{color:var(--nl-ink-2,#374151);}
.glance-table .num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap;}
.glance-table tbody tr:hover th,.glance-table tbody tr:hover td{background:var(--nl-soft,#F7F8FA);}
.glance-table a{color:inherit;text-decoration:none;}
.glance-table a:hover{color:var(--nl-green-deep,#15803D);text-decoration:underline;}
.glance-table tr[data-empty] th{color:var(--nl-muted,#6B7280);}
.glance-table-none{color:var(--nl-muted,#6B7280);font-style:italic;}
.glance-rate[data-tone=good]{color:var(--nl-green-deep,#15803D);font-weight:600;}
.glance-rate[data-tone=weak]{color:#D97706;font-weight:600;}
.glance-rate[data-tone=bad]{color:#DC2626;font-weight:600;}
.glance-who{display:inline-flex;align-items:center;gap:10px;}
.glance-who .glance-avatar{width:30px;height:30px;font-size:12px;}
.glance-panel .glance-table-wrap + .who-note{margin-top:10px;}
.glance-panel .glance-split-key + .glance-table-wrap{margin-top:14px;}

/* The Schools panel as the owner drew it (figma/ministry/14): the label
   and its number on one line, the link a pale green pill, the district in
   blue and the teachers centred. */
.glance-table-head{align-items:center;margin-bottom:18px;}
.glance-panel-head .glance-table-title{flex-direction:row;align-items:center;gap:10px;}
.glance-table-title svg{width:20px;height:20px;color:var(--nl-ink,#111827);}
.glance-panel-head .glance-table-title span{font-size:14px;color:var(--nl-ink,#111827);}
.glance-panel-head .glance-table-title b{font-size:22px;font-weight:500;margin-left:12px;}
.glance-table-head .glance-link{min-height:28px;padding:0 18px;border-radius:999px;font-size:13.5px;font-weight:500;
  background:var(--nl-green-tint,#E8F7EE);color:var(--nl-green-deep,#15803D);}
.glance-table-head .glance-link:hover{background:var(--nl-green-tint,#E8F7EE);filter:brightness(.96);}
.glance-table thead th{padding:17px 24px;font-size:15px;font-weight:400;}
.glance-table tbody th,.glance-table tbody td{padding:13px 24px;font-size:15px;font-weight:400;
  border-top:1px dotted var(--nl-line,#E5E7EB);}
.glance-table .mid{text-align:center;font-variant-numeric:tabular-nums;}
.glance-table td.glance-district{color:var(--nl-blue,#2563EB);}
.glance-table-head .glance-table-note{order:3;flex-basis:100%;margin:0;}
.glance-table-head:has(.glance-table-note){flex-wrap:wrap;}

/* ============================================================
   ANALYTICS BY GENDER, AND THE SCHOOLS TO VISIT — October 2026
   Two lines a chart (male and female teachers; boys and girls)
   and the best and lowest schools. The two line colours are the
   palette's --sr-1 and --sr-2, checked for colour-blind
   separation in light and dark; the second line is also dashed,
   so the pair never rests on colour alone. The Boys and Girls
   ring takes the same pair, so a group is one colour across the
   page (its old magenta was too close to blue for protanopes).
   ============================================================ */
.an-two-line{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round;}
.an-two-line[data-tone=male]{stroke:var(--sr-1,#1F6FD0);}
.an-two-line[data-tone=female]{stroke:var(--sr-2,#C2690A);stroke-dasharray:6 4;}
.an-two-dot{stroke:var(--nl-bg,#fff);stroke-width:2;}
.an-two-dot[data-tone=male]{fill:var(--sr-1,#1F6FD0);}
.an-two-dot[data-tone=female]{fill:var(--sr-2,#C2690A);}
.an-two-hit{fill:transparent;cursor:default;}
.an-two-hit:hover{fill:var(--viz-grid,rgba(60,60,67,.16));}
.an-two-end{font-size:11px;font-weight:600;fill:var(--nl-ink,#111827);font-family:inherit;}
.an-two-legend{display:flex;flex-wrap:wrap;gap:8px 20px;margin:8px 0 0;padding:0;list-style:none;
  font-size:12px;color:var(--nl-ink-2,#374151);}
.an-two-legend li{display:inline-flex;align-items:center;gap:8px;}
.an-two-legend b{font-weight:600;color:var(--nl-ink,#111827);}
.an-two-legend i{width:18px;height:0;border-top:2px solid var(--sr-1,#1F6FD0);flex:none;}
.an-two-legend i[data-tone=female]{border-top:2px dashed var(--sr-2,#C2690A);}
.an-ring-split{--a:var(--sr-1,#1F6FD0);--b:var(--sr-2,#C2690A);}
.an-key i[data-tone=boys]{background:var(--sr-1,#1F6FD0);}
.an-key i[data-tone=girls]{background:var(--sr-2,#C2690A);}
@media (prefers-color-scheme:dark){ .an-ring-split{--b:var(--sr-2,#CE7A22);} }
@media (max-width:640px){ .an-two-end{font-size:19px;} .an-two-line{stroke-width:3;} }
.an-rank-head{margin:4px 0 8px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;
  color:var(--nl-muted,#6B7280);}
.an-rank-head[data-end=worst]{margin-top:18px;}
.an-table-rank{table-layout:fixed;}
.an-table-rank th:first-child,.an-table-rank td:first-child{width:38%;}

/* ---- A head teacher's Learners and Teachers panels, full width ----
   Beside Today's card the tiles sit in a 300px column, and the panels used
   to open inside it: the tables were squeezed and half their columns fell
   off the side. On a wide screen the glance box steps out of the way
   (display:contents), so its tiles keep their place beside Today's card and
   an open panel runs the full width beneath both, as the Ministry's does. */
@media (min-width:1100px){
  .ov-pair > .glance{display:contents;}
  .ov-pair > .glance > .glance-panel{grid-column:1 / -1;}
  .ov-pair > [role=status]{grid-column:1 / -1;order:-1;}
}

/* ---- A head teacher's Overview on a phone ----
   Today's card and the school's tiles share one column on a narrow screen,
   and a grid column with no minimum grows to fit its widest child: opening
   Learners pushed the whole page to 661px on a 390px phone, so everything
   slid off the right edge. The column may now be narrower than its content,
   and a wide table scrolls sideways inside its own box, as the Ministry's do. */
@media (max-width:1099px){ .ov-pair{grid-template-columns:minmax(0,1fr);} }
.ov-pair > *{min-width:0;}

/* ============================================================
   BARS AND LINES, NOT RINGS — October 2026

   The owner: "line graphs, bar graphs — no pie charts", and in
   colours that are not boring. On Analytics the Learners and
   Teachers rings are a headline over a bar a day, the trend is
   teachers against learners as two lines, and the Boys and Girls
   ring is a bar each; on an Overview the "6 of 9" ring is a bar
   under its figure. The charts are drawn on the server
   (oversight._columns_chart, _lines_chart); the old ring rules
   above are left in place, matched by nothing.

   One hue per group, the same on every card, checked with the
   six-checks validator (OKLCH band, chroma floor, colour-blind
   separation, contrast) against the card itself, white / #101216:
     teachers  teal    #0D9488 light, #0FA898 dark
     learners  violet  #7C3AED light, #8B5CF6 dark  (the class
               table already writes learners in this violet)
     boys, girls  --sr-1 blue, --sr-2 orange, as the lines above
   Teal and violet, the pair drawn together: colour-blind dE 21.0
   light / 20.2 dark, normal dE 30.2 / 28.5, both over 3:1. The
   learners' line is dashed as well, so the two part on paper in
   black and white. The pale steps are "absent": the same hue,
   lighter in light and darker in dark, each over 2:1 on the card
   and checked as an ordered pair with its solid step.
   ============================================================ */
.app-body{
  --an-teachers:#0D9488; --an-teachers-pale:#5FC4B8;
  --an-learners:#7C3AED; --an-learners-pale:#B49DFA;
}
@media (prefers-color-scheme:dark){
  .app-body{
    --an-teachers:#0FA898; --an-teachers-pale:#0B5E55;
    --an-learners:#8B5CF6; --an-learners-pale:#5B3FB0;
  }
}

/* ---- Learners and Teachers: the headline, its key, a bar a day ---- */
.app-body .an-stat{display:flex;align-items:center;justify-content:space-between;gap:14px 28px;flex-wrap:wrap;}
.app-body .an-hero{display:flex;flex-direction:column;align-items:flex-start;gap:3px;margin:0;}
.app-body .an-hero b{font-size:34px;font-weight:500;line-height:1.1;letter-spacing:-.01em;color:var(--nl-ink);}
.app-body .an-hero small{font-size:12px;color:var(--nl-muted);}
.app-body .an-stat .an-key{flex:0 1 230px;min-width:190px;gap:10px;}
.app-body .an-stat .an-key i{border-radius:3px;}
.app-body .an-key i[data-tone=learners]{background:var(--an-learners);}
.app-body .an-key i[data-tone=learners-pale]{background:var(--an-learners-pale);}
.app-body .an-key i[data-tone=teachers]{background:var(--an-teachers);}
.app-body .an-key i[data-tone=teachers-pale]{background:var(--an-teachers-pale);}
.app-body .an-cols{margin-top:16px;}
.app-body .an-cols svg{display:block;width:100%;height:auto;overflow:visible;}
.app-body .an-cols[data-tone=learners]{--an-on:var(--an-learners);--an-off:var(--an-learners-pale);}
.app-body .an-cols[data-tone=teachers]{--an-on:var(--an-teachers);--an-off:var(--an-teachers-pale);}
.app-body .an-col-present{fill:var(--an-on);}
.app-body .an-col-absent{fill:var(--an-off);}
.app-body .an-col-hit{fill:transparent;}
.app-body .an-col-hit:hover{fill:var(--nl-ink);fill-opacity:.06;}
@media (max-width:640px){ .app-body .an-stat .an-key{flex:1 1 100%;} }

/* ---- Teachers and learners: two lines ---- */
.app-body .an-vs-line{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round;}
.app-body .an-vs-line[data-tone=teachers]{stroke:var(--an-teachers);}
.app-body .an-vs-line[data-tone=learners]{stroke:var(--an-learners);stroke-dasharray:6 4;}
.app-body .an-vs-dot{stroke:var(--nl-bg);stroke-width:2;}
.app-body .an-vs-dot[data-tone=teachers]{fill:var(--an-teachers);}
.app-body .an-vs-dot[data-tone=learners]{fill:var(--an-learners);}
.app-body .an-two-legend i[data-tone=teachers]{border-top:2px solid var(--an-teachers);}
.app-body .an-two-legend i[data-tone=learners]{border-top:2px dashed var(--an-learners);}
@media (max-width:640px){ .app-body .an-vs-line{stroke-width:3;} }

/* ---- Boys and Girls: a bar each, present against absent ---- */
.app-body .an-split{min-width:0;}
.app-body .an-split-rows{display:flex;flex-direction:column;gap:20px;margin:8px 0 0;padding:0;list-style:none;}
.app-body .an-split-row{display:grid;grid-template-columns:52px minmax(0,1fr) auto;align-items:center;gap:14px;}
.app-body .an-split-name{font-size:13px;color:var(--nl-ink);}
.app-body .an-split-bar{display:block;width:100%;height:22px;}
.app-body .an-split-bar[data-tone=boys]{--an-on:var(--sr-1);--an-off:var(--sr-1-pale);}
.app-body .an-split-bar[data-tone=girls]{--an-on:var(--sr-2);--an-off:var(--sr-2-pale);}
.app-body .an-split-present{fill:var(--an-on);}
.app-body .an-split-absent{fill:var(--an-off);}
.app-body .an-split-gap{fill:var(--nl-bg);}
.app-body .an-split-none{font-size:12px;color:var(--nl-muted);}
.app-body .an-split-fig{display:flex;flex-direction:column;align-items:flex-end;min-width:76px;line-height:1.25;}
.app-body .an-split-fig b{font-size:18px;font-weight:500;color:var(--nl-ink);}
.app-body .an-split-fig small{font-size:11px;color:var(--nl-muted);white-space:nowrap;}
.app-body .an-split-key{display:flex;flex-wrap:wrap;gap:8px 22px;margin:20px 0 0;padding:0;list-style:none;
  font-size:12px;color:var(--nl-ink-2);}
.app-body .an-split-key li{display:inline-flex;align-items:center;gap:3px;}
.app-body .an-split-key i{width:12px;height:12px;border-radius:3px;flex:none;}
.app-body .an-split-key i + i{margin-right:6px;}
.app-body .an-split-key i[data-tone=boys]{background:var(--sr-1);}
.app-body .an-split-key i[data-tone=girls]{background:var(--sr-2);}
.app-body .an-split-key i[data-tone=boys-pale]{background:var(--sr-1-pale);}
.app-body .an-split-key i[data-tone=girls-pale]{background:var(--sr-2-pale);}
.app-body .an-split-note{margin-top:12px;font-size:12px;color:var(--nl-muted);}

/* ---- An Overview's "6 of 9": the figure over a bar ----
   It fills to the share checked in, in the colour of the band the
   figure falls in, as the ring did, and fills as it arrives
   (static/js/life.js) where motion is welcome. */
.app-body .who-meter{--c:var(--nl-green);display:flex;flex-direction:column;gap:9px;flex:none;width:132px;
  transition:--p 1.1s var(--ease-out-expo);}
.app-body .who-meter[data-tone=weak]{--c:#F87171;}
.app-body .who-meter[data-tone=bad]{--c:#DC2626;}
.app-body .who-meter[data-tone=none]{--c:transparent;}
.app-body .who-meter-fig{display:flex;align-items:baseline;gap:6px;}
.app-body .who-meter-fig b{font-size:30px;font-weight:500;line-height:1;color:var(--nl-ink);}
.app-body .who-meter-fig small{font-size:14px;color:var(--nl-ink-2);}
.app-body .who-meter-bar{display:block;height:10px;border-radius:999px;background:var(--nl-line);overflow:hidden;}
.app-body .who-meter-bar i{display:block;height:100%;width:calc(var(--p) * 1%);border-radius:999px;background:var(--c);}
@media (max-width:380px){ .app-body .who-meter{width:104px;} .app-body .who-meter-fig b{font-size:24px;} }
@media (prefers-reduced-motion:reduce){ .app-body .who-meter{transition:none;} }

/* Keys and the overview's bar are coloured boxes, which a browser leaves
   out of a printed page unless told; the charts themselves are SVG and
   print as they are. */
@media print{
  .app-body .an-key i,.app-body .an-split-key i,.app-body .an-two-legend i,
  .app-body .who-meter-bar,.app-body .who-meter-bar i{-webkit-print-color-adjust:exact;print-color-adjust:exact;}
}

/* ---- Why children were away: how many were counted absent ----
   Above the boxes on console/why_away.html, the number the reasons are
   about: the children the class counts found absent that day, class by
   class. The reasons cannot add up to more. In the new look's tokens, like
   the cards around it; amber when the saved reasons are more than the
   count now says. */
.app-body .away-absent{padding:14px 16px;border-radius:12px;border:1px solid var(--nl-line);
  background:var(--nl-soft);}
.app-body .away-absent[data-state=over]{border-color:var(--orange);background:var(--t-orange);}
.app-body .away-absent-head{font-size:15px;font-weight:600;color:var(--nl-ink);}
.app-body .away-absent-head b{font-size:20px;font-weight:700;}
.app-body .away-absent-sub{margin-top:4px;font-size:13px;color:var(--nl-muted);}
.app-body .away-absent-warn{margin-top:8px;font-size:13px;font-weight:600;color:var(--f-orange);}
.app-body .away-absent[data-state=over] .away-absent-sub{color:var(--nl-ink-2);}

/* ---- The toast, where it can be seen ----
   "Checked in.", "Nothing to print yet": the short messages the pages raise
   with toast() in base.html. The capsule above was styled but never placed,
   so it drew in the page flow under the last card -- below the fold on a
   phone, where nobody saw it. Fixed to the foot of the screen, centred (its
   toastIn keyframes already slide it in at translate(-50%, ...)), above the
   dialogs, and clear of an iPhone's home bar. */
#toast{position:fixed;left:50%;transform:translateX(-50%);z-index:500;
  bottom:calc(24px + env(safe-area-inset-bottom, 0px))!important;width:max-content;}
/* Over the new look's cards the glass let the figures behind show through
   the words; a solid capsule in the page's own card colour reads cleanly
   in both themes. */
.app-body #toast{background:var(--nl-bg)!important;border-color:var(--nl-line);
  color:var(--nl-ink)!important;font-weight:500;}
