  :root {
    --bg: #f8fafc; --card: #ffffff; --border: #e2e8f0; --border-soft: #f1f5f9;
    --ink: #0f172a; --ink-2: #475569;
    /* --ink-3 was #94a3b8, which measured 2.56:1 on a card and 2.45:1 on the page
       against a 4.5:1 requirement. It is not decoration: .sub uses it in 230
       places and carries every child's score and percentage, the class and
       teacher line, the moderation counter, the paragraphs explaining how growth
       is computed, and every chart caption — and .crumb uses it for the way out
       of every drill-down. #64748b is 4.76:1 on white and 4.55:1 on the page. */
    --ink-3: #64748b;
    /* Brand — Reason Education. --brand is the logo teal and is used for marks,
       accents and illustration; --teal is the same hue deepened so it can carry
       text and buttons.
       --teal was #0e857a, documented as "4.5:1 on white" and true at 4.51:1 —
       but main sits on --bg, where it measured 4.31:1, and worse again on the
       amber, green and red banners. #0d7d73 is 4.78:1 on the page ground and
       4.57:1 on the palest banner, and white on it is 5.00:1 so the primary
       button still clears AA (its hover, --teal-dark, is 6.38:1). */
    --brand: #0abfb0; --brand-soft: #e6faf8; --brand-line: #9ee7e0;
    --coral: #fb6956; --gold: #fcc615;
    --teal: #0d7d73; --teal-dark: #0b6b62;
    /* The keyboard focus colour, at root because the ring is now portal-wide.
       Same blue the sign-in screens already use, so a teacher tabbing from the
       login form into the portal does not meet a second focus style. */
    --focus: #1878F0;
    --green-bg:#ecfdf5; --green:#047857; --green-bd:#a7f3d0;
    --amber-bg:#fffbeb; --amber:#b45309; --amber-bd:#fde68a;
    --red-bg:#fef2f2; --red:#b91c1c; --red-bd:#fecaca;
    --grey-bg:#f1f5f9; --grey:#475569;
  }
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body { background: var(--bg); color: var(--ink); font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; font-size: 14px; }
  a { color: var(--teal); text-decoration: none; cursor: pointer; }
  button { font: inherit; cursor: pointer; }

  /* The skip link. Off-screen until it is focused, then the first thing on the
     page — a keyboard user should not have to tab past seven nav tabs, the hands
     chip and Sign out to reach a roster, once per page, all day. */
  .skip-link { position: absolute; left: 8px; top: -60px; z-index: 100;
    background: var(--card); color: var(--teal); border: 1px solid var(--teal);
    border-radius: 6px; padding: 10px 14px; font-size: 14px; font-weight: 500;
    transition: top .12s; }
  .skip-link:focus { top: 8px; }
  main:focus { outline: none; }

  /* header */
  header { background: var(--card); border-bottom: 1px solid var(--border); }
  .header-inner { max-width: 1100px; margin: 0 auto; height: 56px; display: flex; align-items: stretch; gap: 28px; padding: 0 16px; }
  .wordmark { align-self: center; font-weight: 600; font-size: 14px; letter-spacing: -0.01em; cursor: pointer; }
  .wordmark span { color: var(--teal); }
  .wordmark small { color: var(--ink-3); font-size: 10px; letter-spacing: .12em; margin-left: 5px; font-weight: 500; }
  nav { display: flex; gap: 4px; }
  nav button { background: none; border: none; border-bottom: 2px solid transparent; padding: 0 12px; font-size: 14px; font-weight: 500; color: var(--ink-2); }
  nav button.active { border-color: var(--teal); color: var(--teal-dark); }
  .header-right { margin-left: auto; align-self: center; display: flex; align-items: center; gap: 10px; color: var(--ink-2); font-size: 13px; }
  .btn-ghost { background: var(--card); border: 1px solid #cbd5e1; border-radius: 6px; padding: 6px 12px; font-size: 12px; font-weight: 500; color: var(--ink-2); }
  .btn-ghost:hover { background: var(--border-soft); }

  main { max-width: 1100px; margin: 0 auto; padding: 32px 16px 64px; }

  h1 { font-size: 24px; font-weight: 600; }
  .page-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24px; flex-wrap: wrap; gap: 12px; }
  .crumb { color: var(--ink-3); font-size: 14px; }
  .crumb:hover { color: var(--ink-2); }

  .btn { border-radius: 6px; padding: 8px 16px; font-size: 14px; font-weight: 500; border: 1px solid transparent; }
  .btn-primary { background: var(--teal); color: #fff; }
  .btn-primary:hover { background: var(--teal-dark); }
  .btn-sm { padding: 6px 10px; font-size: 12px; border-radius: 6px; font-weight: 500; border: 1px solid #cbd5e1; background: var(--card); color: var(--ink-2); }
  .btn-sm:hover { background: var(--border-soft); }
  .btn-amber { border-color: var(--amber-bd); background: var(--amber-bg); color: var(--amber); }
  .btn-green { border-color: var(--green-bd); background: var(--green-bg); color: var(--green); }
  .btn-red { border-color: var(--red-bd); background: var(--red-bg); color: var(--red); }
  .btn-danger { background: #dc2626; border-color: #dc2626; color: #fff; }
  .btn[disabled], .btn-sm[disabled] { opacity: .5; cursor: not-allowed; }

  .stat-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 24px; }
  @media (max-width: 720px) { .stat-row { grid-template-columns: repeat(2, 1fr); } }
  .stat { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }
  .stat .v { font-size: 24px; font-weight: 600; font-variant-numeric: tabular-nums; }
  .stat .l { font-size: 12px; color: var(--ink-2); margin-top: 4px; }

  .card { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 20px; }
  .card h2 { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; color: var(--ink-2); margin-bottom: 16px; }
  /* gap is load-bearing, not tidying: the h2 is flex:auto and the hint beside it
     wraps, and with no gap the two run into each other the moment the hint is
     long enough to need a second line. .qcard-head below always had it; this one
     was missed. */
  .card-head { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; }
  .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
  @media (max-width: 860px) { .grid-2 { grid-template-columns: 1fr; } }

  table { width: 100%; border-collapse: collapse; font-size: 14px; }
  .tbl-wrap { background: var(--card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
  th { text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-2); font-weight: 500; padding: 12px 16px; border-bottom: 1px solid var(--border); }
  td { padding: 12px 16px; border-bottom: 1px solid var(--border-soft); vertical-align: middle; }
  tr:last-child td { border-bottom: 0; }
  tbody tr.click:hover { background: var(--bg); cursor: pointer; }
  .sub { font-size: 12px; color: var(--ink-3); }
  .mono { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; color: var(--ink-2); }

  .badge { display: inline-block; border-radius: 999px; border: 1px solid; padding: 2px 9px; font-size: 11px; font-weight: 500; white-space: nowrap; }
  .b-green { background: var(--green-bg); color: var(--green); border-color: var(--green-bd); }
  .b-amber { background: var(--amber-bg); color: var(--amber); border-color: var(--amber-bd); }
  .b-red { background: var(--red-bg); color: var(--red); border-color: var(--red-bd); }
  .b-grey { background: var(--grey-bg); color: var(--grey); border-color: var(--border); }
  .b-indigo { background:#eef2ff; color:#4338ca; border-color:#c7d2fe; }
  .b-sky { background:#f0f9ff; color:#0369a1; border-color:#bae6fd; }
  .b-violet { background:#f5f3ff; color:#6d28d9; border-color:#ddd6fe; }

  .bar { height: 8px; border-radius: 999px; background: var(--border-soft); overflow: hidden; }
  .bar > div { height: 100%; border-radius: 999px; background: var(--teal); }
  .bar.warn > div { background: #f59e0b; } .bar.over > div { background: #ef4444; }

  dl.kv { display: grid; grid-template-columns: 140px 1fr; gap: 10px 12px; font-size: 14px; }
  dl.kv dt { color: var(--ink-2); } dl.kv dd { color: var(--ink); }

  label { display: block; font-size: 13px; font-weight: 500; color: var(--ink-2); margin-bottom: 4px; }
  input, select { width: 100%; border: 1px solid #cbd5e1; border-radius: 6px; padding: 8px 10px; font: inherit; background: var(--card); }
  /* 3px, not 1px. At 1px the ring is indistinguishable from the border it sits
     on, so the only thing marking the focused field was a hue change — which is
     nothing at all to a teacher who cannot separate the two teals. */
  input:focus, select:focus { outline: none; border-color: var(--teal); box-shadow: 0 0 0 3px var(--teal); }

  /* ── THE FOCUS RING, EVERYWHERE ─────────────────────────────────────────
     This rule used to exist exactly once and was scoped to .login-wrap, so the
     five sign-in screens had a visible focus indicator and the entire signed-in
     portal had none: a teacher tabbing through a roster, a moderation form or
     the tab bar could not see where they were. A school laptop is a keyboard
     device and this is the screen someone tabs through in a hurry with a class
     waiting. 3px solid with an offset, on 4.2:1 blue against white — comfortably
     over the 3:1 a non-text indicator needs. */
  :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
    outline: 3px solid var(--focus); outline-offset: 3px; border-radius: 6px;
  }
  .field { margin-bottom: 14px; }
  .form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }
  .hint { font-size: 11px; color: var(--ink-3); margin-top: 3px; }
  .radio-row { display: flex; gap: 16px; padding-top: 4px; }
  .radio-row label { display: flex; align-items: center; gap: 6px; font-weight: 400; color: var(--ink); margin: 0; }
  .radio-row input { width: auto; accent-color: var(--teal); }

  .filters { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
  .filters input, .filters select { width: auto; }
  .filters input { width: 240px; }

  .subtabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin-bottom: 22px; }
  .subtabs button { background: none; border: none; border-bottom: 2px solid transparent; margin-bottom: -1px; padding: 9px 16px; font-size: 14px; font-weight: 500; color: var(--ink-2); }
  .subtabs button.active { border-color: var(--teal); color: var(--teal-dark); }

  .banner { border-radius: 8px; padding: 12px 16px; font-size: 13px; margin-bottom: 22px; border: 1px solid; }
  .banner-amber { background: var(--amber-bg); border-color: var(--amber-bd); color: var(--amber); }
  .banner-green { background: var(--green-bg); border-color: var(--green-bd); color: var(--green); }

  .overlay { position: fixed; inset: 0; background: rgba(15,23,42,.45); display: flex; align-items: center; justify-content: center; padding: 16px; z-index: 50; }
  .modal { background: var(--card); border-radius: 10px; padding: 24px; width: 100%; max-width: 440px; max-height: 88vh; overflow-y: auto; }
  .modal h3 { font-size: 16px; margin-bottom: 8px; }
  .modal p { color: var(--ink-2); font-size: 13px; margin-bottom: 10px; }
  .modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
  .temp-pass { background: var(--amber-bg); border: 1px solid var(--amber-bd); border-radius: 8px; padding: 12px; color: var(--amber); font-size: 13px; }
  .temp-pass .code { background: #fff; border-radius: 6px; padding: 6px 10px; font-family: ui-monospace, monospace; font-size: 15px; margin-top: 6px; color: #92400e; }

  .check { display: flex; gap: 10px; margin-bottom: 12px; align-items: flex-start; }
  .dot { width: 10px; height: 10px; border-radius: 999px; margin-top: 4px; flex: none; }
  .dot-pass { background: #10b981; } .dot-warn { background: #f59e0b; } .dot-fail { background: #ef4444; }
  .check b { display: block; font-size: 13px; }
  .check span { color: var(--ink-2); font-size: 13px; }

  .legend { display: flex; gap: 16px; font-size: 12px; color: var(--ink-2); margin-bottom: 8px; }
  .legend i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 5px; }

  .toast { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: #0f172a; color: #fff; padding: 10px 18px; border-radius: 8px; font-size: 13px; z-index: 100; opacity: 0; transition: opacity .2s; pointer-events: none; }
  .toast.show { opacity: 1; }

  .empty { border: 1px dashed #cbd5e1; background: var(--card); border-radius: 8px; padding: 40px; text-align: center; color: var(--ink-2); }
  .mt { margin-top: 20px; } .mb { margin-bottom: 20px; }

  /* login */
  .login-wrap { min-height: calc(100vh - 30px); display: flex; align-items: center; justify-content: center; padding: 16px; }
  .login-box { width: 100%; max-width: 360px; }
  .login-brand { text-align: center; margin-bottom: 28px; }
  .login-brand .wm { font-size: 20px; font-weight: 600; }
  .login-brand .wm span { color: var(--teal); }
  .login-brand .tag { font-size: 11px; letter-spacing: .18em; text-transform: uppercase; color: var(--ink-3); margin-top: 4px; }
  .login-card { background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 24px; }
  .login-card .btn { width: 100%; margin-top: 8px; padding: 10px; }
  .login-hint { text-align: center; font-size: 12px; color: var(--ink-3); margin-top: 14px; }

  /* PHP build: nav/subtab buttons are wrapped in <a> links */
  nav a { display: flex; align-items: stretch; text-decoration: none; }
  nav a button { height: 56px; cursor: pointer; }
  .subtabs a { text-decoration: none; }
  .header-inner { align-items: center; }
  nav { align-self: stretch; }

  /* staff portal additions */
  .role-chip { font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); }
  .pin-hidden { letter-spacing: 2px; }
  .strategy-pill { display:inline-block; background:#f0fdfa; border:1px solid #99f6e4; color:#0f766e; border-radius:999px; padding:2px 10px; font-size:12px; margin:2px 3px 2px 0; }
  .search-list { max-height: 260px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; padding: 6px; }
  .search-list label { display:flex; gap:8px; align-items:center; font-weight:400; color:var(--ink); padding:6px 8px; border-radius:6px; margin:0; cursor:pointer; }
  .search-list label:hover { background: var(--bg); }
  .search-list input[type=checkbox] { width:auto; accent-color: var(--teal); }
  .qa-row { border-top: 1px solid var(--border-soft); padding: 10px 0; font-size: 14px; }
  .qa-row:first-child { border-top: 0; }
  .report-box { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 16px; font-size: 13px; line-height: 1.55; }
  .report-box h4 { font-size: 12px; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-2); margin: 14px 0 6px; }
  .report-box h4:first-child { margin-top: 0; }
  .pending-ai { border-left: 3px solid var(--amber-bd); background: var(--amber-bg); color: var(--amber); padding: 8px 12px; border-radius: 6px; font-size: 12px; }

  /* ── v2: student rows, unit tiles, growth bars, question strips ── */
  .stu-row { display:flex; align-items:center; gap:14px; padding:14px 16px; border:1px solid var(--border);
    border-radius:10px; background:var(--card); margin-bottom:10px; }
  .stu-row:hover { border-color:#cbd5e1; }
  .stu-emoji { font-size:26px; line-height:1; width:44px; height:44px; display:flex; align-items:center;
    justify-content:center; border-radius:10px; background:var(--bg); border:1px solid var(--border); flex:none; }
  .stu-emoji.pick { cursor:pointer; }
  .stu-emoji.pick:hover { border-color:var(--teal); background:#f0fdfa; }
  .stu-name { font-weight:600; font-size:15px; color:var(--ink); }
  /* tabular-nums here, on .stat .v and on .diag-read b: these are the three
     places a teacher reads a COLUMN of figures rather than a figure. With
     proportional digits a 1 is narrower than an 8, so the columns fray and
     comparing two rows becomes reading rather than looking. */
  .stu-meta { font-size:12px; color:var(--ink-3); font-variant-numeric: tabular-nums; }
  .stu-cell { min-width:0; }
  .stu-grade select { width:auto; padding:6px 10px; font-size:13px; }
  .emoji-grid { display:grid; grid-template-columns:repeat(8,1fr); gap:8px; }
  .emoji-grid button { font-size:24px; padding:8px 0; border:1px solid var(--border); background:var(--card);
    border-radius:10px; cursor:pointer; }
  .emoji-grid button:hover { border-color:var(--teal); background:#f0fdfa; }

  .unit-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:14px; }
  .unit-tile { display:block; text-align:left; width:100%; border:1px solid var(--border); background:var(--card);
    border-radius:12px; padding:16px 18px; cursor:pointer; transition:border-color .15s, box-shadow .15s; }
  .unit-tile:hover { border-color:var(--teal); box-shadow:0 2px 10px rgba(15,118,110,.08); }
  .unit-name { font-size:16px; font-weight:600; color:var(--ink); margin-bottom:12px; }
  .pp-row { display:flex; gap:22px; align-items:flex-start; margin-bottom:12px; }
  .pp-item { display:flex; flex-direction:column; gap:5px; }
  .pp-label { font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:var(--ink-3); font-weight:600; }
  .pp-box { width:76px; height:40px; border-radius:8px; border:2px solid var(--border); background:#fff;
    display:flex; align-items:center; justify-content:center; font-size:14px; font-weight:700; color:var(--ink-2); }
  .pp-box.done { border-color:#94a3b8; }
  .pp-box.green { background:#dcfce7; border-color:#86efac; color:#166534; }
  .pp-box.orange { background:#ffedd5; border-color:#fdba74; color:#9a3412; }
  .pp-box.red { background:#fee2e2; border-color:#fca5a5; color:#991b1b; }
  .growth-wrap { border-top:1px solid var(--border-soft); padding-top:10px; }
  .growth-track { height:12px; border-radius:999px; background:#f1f5f9; overflow:hidden; }
  .growth-fill { height:100%; border-radius:999px; }
  .growth-fill.met { background:#16a34a; } .growth-fill.miss { background:#f59e0b; }
  .growth-note { font-size:12px; margin-top:5px; display:flex; justify-content:space-between; gap:8px; }

  /* ── the unit tile's growth bar ────────────────────────────────────────────
     One hue, two shades: the pale segment is where the child was, the deep one
     is the movement to where they are. These two values are EV_PRE_INK and
     EV_POST_INK from inc/evidence_chart.php, on purpose — the tile and the
     Evidence Report chart show the same child's growth and must not be drawn in
     different inks. tests/evidence.test.php asserts they still match.
     The 2px gap between the segments is the surface showing through, so the two
     shades are separated by geometry and not only by colour. */
  .growth-track { position:relative; }
  .growth-track .gr-pre, .growth-track .gr-move { position:absolute; top:0; bottom:0; }
  .growth-track .gr-pre { left:0; background:#3fb8aa; border-radius:999px 0 0 999px; }
  .growth-track .gr-move { border-radius:0 999px 999px 0; box-shadow:-2px 0 0 0 var(--card); }
  .growth-track .gr-move.up { background:#0e857a; }
  .growth-track .gr-move.down { background:var(--amber); }
  .growth-note .gr-move-n { font-weight:600; color:var(--ink-2); font-variant-numeric:tabular-nums; }
  .growth-note .gr-d { font-weight:600; color:var(--ink); font-variant-numeric:tabular-nums; }
  .growth-note .gr-d.is-flat { color:var(--ink-3); font-weight:500; }
  .gr-why { font-size:11.5px; line-height:1.5; margin-top:5px; }
  .gr-why.is-flat { color:var(--ink-3); }
  .gr-why.is-detect { color:var(--teal-dark); }

  .qstrip { display:flex; gap:4px; flex-wrap:wrap; }
  .qbox { width:44px; height:44px; border-radius:8px; display:flex; align-items:center; justify-content:center;
    font-size:12px; font-weight:700; border:2px solid; }
  .qbox.green { background:#dcfce7; border-color:#4ade80; color:#166534; }
  .qbox.yellow { background:#fef9c3; border-color:#facc15; color:#854d0e; }
  .qbox.red { background:#fee2e2; border-color:#f87171; color:#991b1b; }
  .qbox.empty { background:#f8fafc; border-color:#e2e8f0; color:#cbd5e1; }
  .qbox.grey { background:#f1f5f9; border-color:#cbd5e1; color:#64748b; }  /* NOT_ASSESSABLE: answered but unreadable/unmarked — neutral, never red */
  .qlegend { display:flex; gap:16px; flex-wrap:wrap; font-size:12px; color:var(--ink-2); }
  .qlegend span { display:flex; align-items:center; gap:6px; }
  .qkey { width:14px; height:14px; border-radius:4px; border:2px solid; }
  .std-col h4 { font-size:12px; text-transform:uppercase; letter-spacing:.06em; color:var(--ink-2); margin-bottom:8px; }
  .std-item { border-left:3px solid var(--border); padding:6px 0 6px 10px; margin-bottom:8px; font-size:13px; }
  .std-item.yes { border-left-color:#16a34a; } .std-item.no { border-left-color:#f59e0b; }
  .readonly-note { background:#f8fafc; border:1px dashed #cbd5e1; border-radius:8px; padding:10px 14px;
    font-size:13px; color:var(--ink-2); }

  /* ── v2.1: centred results + per-question report blocks ── */
  .results-center { max-width: 760px; margin: 0 auto; }
  .phase-line { display:flex; align-items:baseline; justify-content:center; gap:10px; flex-wrap:wrap; margin-bottom:8px; }
  .phase-line .pname { font-weight:600; }
  .phase-line .pscore { font-weight:600; }
  .results-center .qstrip { justify-content:center; margin-bottom:20px; }
  .results-center .qlegend { justify-content:center; }
  .growth-center { max-width:440px; margin:14px auto 0; }

  .rep-section { margin-bottom:22px; }
  .rep-section-head { display:flex; align-items:baseline; gap:10px; flex-wrap:wrap;
    border-bottom:2px solid var(--border); padding-bottom:8px; margin-bottom:14px; }
  .rep-section-head h3 { font-size:13px; font-weight:600; text-transform:uppercase; letter-spacing:.07em; color:var(--ink); }
  .rep-section-head .sub { margin-left:auto; }
  .qblock { border:1px solid var(--border); border-radius:12px; background:var(--card); padding:16px 18px; margin-bottom:12px; }
  .qblock-head { display:flex; align-items:baseline; gap:10px; margin-bottom:12px; }
  .qblock-head .qt { font-weight:600; font-size:15px; flex:1; }
  .qblock-code { font-family:ui-monospace,Menlo,Consolas,monospace; font-size:11px; color:var(--ink-3); white-space:nowrap; }
  .qblock-grid { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.15fr); gap:22px; }
  @media (max-width:820px){ .qblock-grid { grid-template-columns:1fr; gap:14px; } }
  .ans-line { font-size:14px; margin-bottom:10px; display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
  .ans-line .val { font-weight:700; }
  .tag { display:inline-block; border-radius:999px; padding:3px 10px; font-size:12px; font-weight:500; border:1px solid; }
  .tag-ok { background:#dcfce7; border-color:#86efac; color:#166534; }
  .tag-slip { background:#fef9c3; border-color:#fde047; color:#854d0e; }
  .tag-no { background:#fee2e2; border-color:#fca5a5; color:#991b1b; }
  .tag-neutral { background:#f1f5f9; border-color:#e2e8f0; color:#475569; }
  .work-label2 { font-size:12px; color:var(--ink-2); font-weight:500; margin-bottom:5px; }
  .work-box { border:1px solid var(--border); border-radius:8px; background:var(--bg); padding:10px 12px;
    font-size:13px; line-height:1.7; color:var(--ink); }
  .work-box img { display:block; width:100%; border-radius:6px; background:#fff; }
  .qtags { display:flex; gap:6px; flex-wrap:wrap; margin-bottom:8px; }
  .ai-text { font-size:13px; line-height:1.6; color:var(--ink); }
  .ai-first-wrong { font-size:12px; color:var(--ink-2); margin-top:8px; }
  .ai-first-wrong code { background:#fee2e2; border:1px solid #fca5a5; border-radius:5px; padding:1px 6px; color:#991b1b; }

  /* ── v2.2: effect-size growth ── */
  .d-value { font-size:20px; font-weight:600; line-height:1.1; }
  .d-meaning { font-size:12px; color:var(--ink-2); }
  .d-context { font-size:12px; color:var(--ink-3); }
  .sd-note { font-size:11px; color:var(--amber); background:var(--amber-bg); border:1px solid var(--amber-bd);
    border-radius:6px; padding:2px 8px; display:inline-block; margin-top:6px; }
  .formula { font-family:ui-monospace,Menlo,Consolas,monospace; font-size:12px; color:var(--ink-2);
    background:var(--bg); border:1px solid var(--border); border-radius:6px; padding:6px 10px; display:inline-block; }

/* ── v2.3 · Victorian Curriculum 2.0 ─────────────────────────────── */
.std-item .std-top{display:flex;align-items:center;gap:8px;margin-bottom:4px;flex-wrap:wrap}
.std-item .std-top b{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;letter-spacing:.02em}
.std-pill{font-size:11px;font-weight:600;padding:1px 7px;border-radius:999px;
  background:var(--green-bg);color:var(--green);border:1px solid var(--green-bd)}
.std-pill.warn{background:var(--amber-bg);color:var(--amber);border-color:var(--amber-bd)}
.std-pill.bad{background:var(--red-bg);color:var(--red);border-color:var(--red-bd)}
.std-strand{font-size:11px;color:var(--ink-3)}
.std-item.flat{background:var(--border-soft);border-color:var(--border);color:var(--ink-2)}
.std-rest{margin-top:12px}
.std-rest>summary{cursor:pointer;font-size:12px;color:var(--ink-2);padding:6px 0;user-select:none}
.std-rest>summary:hover{color:var(--ink)}
.std-rest .std-item{margin-top:6px}
.vc-attrib{margin:16px 0 0;padding-top:12px;border-top:1px solid var(--border);
  font-size:11px;line-height:1.5;color:var(--ink-3)}

.vc-filters{display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap}
.vc-filters label{display:block;margin-bottom:4px}
.vc-filters select,.vc-filters input[type=search]{width:100%;min-width:150px}

.vc-row{padding:14px 0;border-bottom:1px solid var(--border)}
.vc-row:last-of-type{border-bottom:0}
.vc-row-head{display:flex;align-items:center;gap:8px;margin-bottom:5px;flex-wrap:wrap}
.vc-code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;font-weight:600;
  color:var(--teal);background:#eef7f6;border:1px solid #99d3ce;
  padding:1px 7px;border-radius:5px}
.vc-tag{font-size:11px;color:var(--ink-3)}
.vc-desc{font-size:14px;line-height:1.55;color:var(--ink)}
.vc-units{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}
.vc-chip{font-size:11px;padding:3px 9px;border-radius:999px;border:1px solid var(--border);
  background:var(--border-soft);color:var(--ink-3);cursor:pointer;transition:.12s}
button.vc-chip:hover{border-color:#99d3ce;color:var(--teal)}
.vc-chip.on{background:#eef7f6;border-color:#99d3ce;color:var(--teal);font-weight:600}
.vc-elabs{margin:8px 0 0;padding-left:18px}
.vc-elabs li{font-size:13px;line-height:1.55;color:var(--ink-2);margin-bottom:6px}
.report-vc{background:#eef7f6;border:1px solid #99d3ce;border-radius:8px;padding:10px 14px;
  font-size:13px;line-height:1.6;color:var(--ink-2);margin-bottom:14px}
.report-vc b{color:var(--teal-dark)}

/* ── v2.4 · Class overview + bell graph ──────────────────────────────── */
.unit-picker{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px}
.unit-picker .chip{font-size:13px;padding:6px 13px;border-radius:999px;border:1px solid var(--border);
  background:var(--card);color:var(--ink-2);display:inline-flex;align-items:center;gap:7px;transition:.12s}
.unit-picker .chip:hover{border-color:#99d3ce;color:var(--teal)}
.unit-picker .chip.active{background:var(--teal);border-color:var(--teal);color:#fff;font-weight:500}
.chip-n{font-size:11px;opacity:.7;font-variant-numeric:tabular-nums}

.bell-wrap{position:relative;margin:4px 0 10px}
#bell{width:100%;height:auto;display:block;overflow:visible}
#bell .band.low{fill:#fef2f2}
#bell .band.mid{fill:#f8fafc}
#bell .band.high{fill:#ecfdf5}
#bell .band-lbl{font-size:10px;font-weight:600;letter-spacing:.1em;fill:var(--ink-3)}
#bell .axis{stroke:#cbd5e1;stroke-width:1}
#bell .tick{stroke:#cbd5e1;stroke-width:1}
#bell .tick-lbl{font-size:10px;fill:var(--ink-3)}
#bell .axis-lbl{font-size:11px;fill:var(--ink-3);letter-spacing:.06em}
#bell .curve-area.pre{fill:#e2e8f0;opacity:.55}
#bell .curve.pre{fill:none;stroke:#94a3b8;stroke-width:1.5}
#bell .curve.post{fill:none;stroke:var(--teal);stroke-width:2;stroke-dasharray:5 4}
#bell .mean{stroke-width:1;stroke-dasharray:3 4}
#bell .mean.pre{stroke:#94a3b8}
#bell .mean.post{stroke:var(--teal)}

#bell .stu{cursor:pointer;outline:none}
#bell .hit{stroke:transparent;stroke-width:16}
#bell .link{stroke-width:2;opacity:.75}
#bell .link.met{stroke:#16a34a}
#bell .link.miss{stroke:#f59e0b}
#bell .dot.pre{fill:#fff;stroke:#64748b;stroke-width:2}
#bell .dot.post{fill:var(--teal);stroke:#fff;stroke-width:2}
#bell .dot.post.miss{fill:#f59e0b}
#bell .dot.post.met{fill:#16a34a}
#bell.dim .stu{opacity:.22;transition:opacity .12s}
#bell.dim .stu.hl{opacity:1}
#bell .stu.hl .link{stroke-width:3.5;opacity:1}
#bell .stu.hl .dot{r:9}
#bell .stu:focus-visible .dot{stroke:var(--teal);stroke-width:3}
#bell .bell-empty{font-size:13px;fill:var(--ink-3)}

.bell-tip{position:absolute;z-index:5;pointer-events:none;background:var(--ink);color:#fff;
  border-radius:8px;padding:9px 12px;font-size:12px;line-height:1.5;max-width:280px;
  box-shadow:0 8px 24px rgba(15,23,42,.28)}
.bell-tip .tip-name{font-weight:600;margin-bottom:3px}
.bell-tip .tip-line{font-size:13px}
.bell-tip .tip-line b{font-size:14px}
.bell-tip .tip-sub{color:#cbd5e1;font-size:11px;margin-top:2px}
.tip-pts{margin-left:6px;font-size:11px;padding:1px 6px;border-radius:999px}
.tip-pts.up{background:#14532d;color:#86efac}
.tip-pts.down{background:#7f1d1d;color:#fecaca}

.bell-legend{display:flex;gap:18px;flex-wrap:wrap;align-items:center;font-size:12px;color:var(--ink-2);
  border-top:1px solid var(--border-soft);padding-top:12px}
.lg-dot{width:11px;height:11px;border-radius:50%;display:inline-block;vertical-align:-1px;margin-right:5px}
.lg-dot.pre{background:#fff;border:2px solid #64748b}
.lg-dot.post{background:var(--teal)}
.lg-line{width:22px;height:3px;border-radius:2px;display:inline-block;vertical-align:2px;margin-right:5px}
.lg-line.met{background:#16a34a}
.lg-line.miss{background:#f59e0b}
.lg-band{width:14px;height:11px;border-radius:3px;display:inline-block;vertical-align:-1px;margin-right:5px;
  border:1px solid var(--border)}
.lg-band.low{background:#fef2f2}
.lg-band.mid{background:#f8fafc}
.lg-band.high{background:#ecfdf5}
.btn-sm-outline{border-radius:6px;padding:8px 16px;font-size:14px;font-weight:500;
  border:1px solid #cbd5e1;background:var(--card);color:var(--ink-2)}
.btn-sm-outline:hover{background:var(--border-soft);border-color:#99d3ce;color:var(--teal)}
.stat-row.stat-5{grid-template-columns:repeat(5,1fr)}
@media (max-width:960px){.stat-row.stat-5{grid-template-columns:repeat(2,1fr)}}
#bell .mean-lbl{font-size:10px;font-weight:600;letter-spacing:.04em}
#bell .mean-lbl.pre{fill:#64748b}
#bell .mean-lbl.post{fill:var(--teal)}

/* ── v2.5 · Assessments: unit tiles, rosters, results, moderation ─────── */
.unit-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;align-items:start}
@media (max-width:900px){.unit-grid{grid-template-columns:repeat(2,1fr)}}
@media (max-width:560px){.unit-grid{grid-template-columns:1fr}}
.unit-sq{aspect-ratio:1/.58;min-height:150px;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:8px;background:var(--card);border:1px solid var(--border);border-radius:14px;padding:18px;
  text-align:center;transition:.14s;color:var(--ink)}
.unit-sq:hover{border-color:#99d3ce;box-shadow:0 4px 16px rgba(15,23,42,.07);transform:translateY(-1px)}
.unit-sq.open{border-color:var(--teal);box-shadow:0 0 0 2px #d5ebe9}
.unit-sq .usym{font-size:38px;line-height:1;color:var(--teal)}
.unit-sq .uname{font-size:17px;font-weight:600}
.unit-sq .ustate{font-size:11px;color:var(--ink-3);display:flex;align-items:center;gap:6px}
.udot{width:8px;height:8px;border-radius:50%;display:inline-block;background:#cbd5e1}
.udot.part{background:#f59e0b}
.udot.done{background:#16a34a}
/* ── the diagnostic tile: one tile, three columns wide ──────────────────────
   Same component as a unit square — same border, hover, open state and toggle —
   laid out as a bar rather than a square, because it is one thing per class
   rather than one of six and a square that wide would be a poster. It keeps the
   unit tiles' aspect ratio off and sets its own height. */
.diag-sq{grid-column:1/-1;aspect-ratio:auto;min-height:0;flex-direction:row;
  align-items:center;justify-content:flex-start;gap:18px;padding:16px 20px;text-align:left}
.diag-sq .dsym{font-size:30px;line-height:1;color:var(--teal);flex:0 0 auto}
.diag-sq .dtext{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1 1 auto}
.diag-sq .dname{font-size:17px;font-weight:600;color:var(--ink)}
.diag-sq .dsub{font-size:12px;color:var(--ink-3);line-height:1.45}
.diag-sq .dstate{display:flex;align-items:center;gap:8px;flex:0 0 auto;font-size:12px;color:var(--ink-3)}
.diag-sq .dcount b{color:var(--ink-2);font-variant-numeric:tabular-nums}
@media (max-width:560px){
  .diag-sq{flex-direction:column;align-items:flex-start;gap:10px}
  .diag-sq .dstate{align-self:flex-start}
}
/* The panel body already draws its own card; inside the grid it must not draw a
   second border around the first one. */
.diag-in-grid{padding:0;border:0;background:none;border-radius:0}
.diag-in-grid .diag-panel{margin:0}
/* The panel's own header repeated the tile word for word — title, pill,
   explanation and the have-a-level count, all within 100px of the tile stating
   them. Hidden here only; the panel is unchanged for any other caller, and
   nothing actionable lives in .diag-head — the progress bar, the roster chips,
   the send button and Open results are all outside it. */
.diag-in-grid .diag-head{display:none}
.diag-in-grid .diag-panel{border-top-left-radius:12px;border-top-right-radius:12px;padding-top:18px}

.unit-panel{grid-column:1/-1;background:var(--card);border:1px solid var(--teal);border-radius:14px;
  padding:20px;margin:-2px 0 6px}

.useq-step{padding:14px 0;border-bottom:1px solid var(--border-soft)}
.useq-step:last-child{border-bottom:0;padding-bottom:0}
.useq-step.locked{opacity:.55}
.useq-line{display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap}
.step-n{display:inline-flex;width:22px;height:22px;border-radius:50%;background:var(--teal);color:#fff;
  align-items:center;justify-content:center;font-size:12px;font-weight:600;margin-right:8px;vertical-align:-4px}

.roster{margin-top:14px;background:var(--border-soft);border:1px solid var(--border);border-radius:10px;padding:16px}
.roster-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px;margin-bottom:8px;flex-wrap:wrap}
.roster-grid{display:grid;grid-template-columns:2.2fr .8fr 1fr .8fr .6fr;gap:10px;align-items:center;
  padding:7px 8px;border-radius:8px}
.roster-hdr{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);padding-bottom:2px}
.roster-row{background:var(--card);border:1px solid var(--border);margin-bottom:6px;transition:.12s}
.roster-row.out{opacity:.45;background:var(--border-soft)}
.rname{display:flex;align-items:center;gap:8px;font-weight:500}
.remoji{font-size:18px}
.rdiag,.rpass{font-variant-numeric:tabular-nums;color:var(--ink-2)}
.rset{width:66px;padding:5px 7px;border:1px solid #cbd5e1;border-radius:6px;font:inherit;text-align:right}
.roster-foot{display:flex;justify-content:flex-end;gap:10px;margin-top:12px}

.inc-switch{display:inline-block;cursor:pointer}
.inc-switch input{position:absolute;opacity:0;width:0;height:0}
.inc-switch .track{display:block;width:42px;height:23px;border-radius:999px;background:#ef4444;
  position:relative;transition:.15s}
/* The knob carries the state as a SHAPE. Red and green measure 1.14:1 against
   each other, so on the one control that decides whether a child sits a paper
   the colour was doing the whole job and doing it for nobody with a red-green
   deficiency. ✓ / ✕ says the same thing in a channel that always survives. */
.inc-switch .knob::before{content:"\2715";font-size:11px;line-height:18px;color:#b91c1c;
  display:block;text-align:center;font-weight:700}
.inc-switch input:checked + .track .knob::before{content:"\2713";color:#15803d}
.inc-switch .knob{position:absolute;top:2.5px;left:2.5px;width:18px;height:18px;border-radius:50%;
  background:#fff;transition:.15s;box-shadow:0 1px 3px rgba(0,0,0,.25)}
.inc-switch input:checked + .track{background:#16a34a}
.inc-switch input:checked + .track .knob{left:21.5px}
/* The input itself is 0×0 and transparent, so the portal-wide focus ring would
   draw against nothing. The track wears it instead. */
.inc-switch input:focus-visible{outline:none}
.inc-switch input:focus-visible + .track{box-shadow:0 0 0 3px #bae6fd}

.paper{border:1px solid var(--border);border-radius:10px;margin-top:10px;overflow:hidden}
.paper-line{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:10px 12px}
.paper-name{background:none;border:0;text-align:left;font:inherit;font-weight:500;color:var(--ink);
  display:flex;align-items:center;gap:8px;flex:1}
.paper-name .caret{color:var(--ink-3);transition:.15s;display:inline-block}
.paper-name:hover{color:var(--teal)}
.paper-body{border-top:1px solid var(--border-soft);padding:10px 12px;background:var(--border-soft)}
.btn-go{border-color:var(--teal);color:var(--teal);font-weight:600}
.btn-go:hover{background:#eef7f6}

.who{display:flex;gap:6px;flex-wrap:wrap;margin-top:10px}
.who-chip{font-size:12px;padding:3px 9px;border-radius:999px;border:1px solid var(--border);
  background:var(--card);color:var(--ink-2);display:inline-flex;gap:5px;align-items:center}
.who-chip.done{border-color:var(--green-bd);background:var(--green-bg);color:var(--green)}
.who-chip.under{border-color:var(--amber-bd);background:var(--amber-bg);color:var(--amber)}
.who-chip.wait{color:var(--ink-3)}

/* class results */
.res-row{display:flex;align-items:center;gap:16px;padding:12px 0;border-bottom:1px solid var(--border-soft);flex-wrap:wrap}
.res-row:last-child{border-bottom:0}
.res-row.waiting{opacity:.6}
.res-who{display:flex;align-items:center;gap:10px;min-width:230px;flex:1}
.res-emoji{font-size:22px}
.res-strip{display:flex;gap:5px;flex-wrap:wrap;flex:2}
.res-go{display:flex;align-items:center;gap:8px;margin-left:auto}
.pass-ok{color:var(--green);font-weight:600}
.pass-no{color:var(--amber);font-weight:600}
.pass-tbl td,.pass-tbl th{padding:5px 4px}
.pass-in{width:52px;padding:4px 5px;border:1px solid #cbd5e1;border-radius:6px;font:inherit;
  font-size:12px;text-align:right}

/* moderation */
.mod-banner{display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;
  background:#eef7f6;border:1px solid #99d3ce;border-radius:10px;padding:14px 16px;margin-bottom:16px;
  font-size:13px;color:var(--ink-2)}
.qcard{background:var(--card);border:1px solid var(--border);border-left:4px solid #cbd5e1;
  border-radius:12px;padding:16px 18px;margin-bottom:14px}
.qcard.green{border-left-color:#4ade80}
.qcard.yellow{border-left-color:#facc15}
.qcard.red{border-left-color:#f87171}
/* A 1% tint against white is not a state, it is a rounding error — a teacher
   part-way down a twenty-four question paper could not see which cards were
   done. The left EDGE is not available: it already carries the mark colour, and
   that is the more important of the two facts. So the tick goes on the heading,
   where it survives greyscale and a projector, and the card is inset a little to
   settle behind the ones still asking for a decision. */
.qcard.decided{background:#fcfdfd;box-shadow:inset 3px 0 0 var(--teal)}
.qcard.decided .qcard-head h3::before{content:"\2713 ";color:var(--teal);font-weight:700}

/* The queue link in a question card's footer. Right-aligned under the decision
   buttons, because it is where the eye lands after pressing one. */
.mod-next{display:inline-block;margin-top:8px;font-weight:500;color:var(--teal)}
.qcard-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px;margin-bottom:12px}
.qcard-head h3{font-size:15px;font-weight:600}
.qcard-code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--teal)}
.qcard-grid{display:grid;grid-template-columns:1fr 1.15fr;gap:22px}
@media (max-width:800px){.qcard-grid{grid-template-columns:1fr}}
.work-img{max-width:100%;border:1px solid var(--border);border-radius:8px;background:#fff}
.work-text{background:var(--border-soft);border:1px solid var(--border);border-radius:8px;padding:10px 12px;
  font-size:13px;line-height:1.7;white-space:pre-wrap;font-family:inherit;margin:0}
.tag-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px}
.verdict{font-size:12px;font-weight:600;padding:3px 10px;border-radius:6px}
.verdict.green{background:var(--green-bg);color:var(--green);border:1px solid var(--green-bd)}
.verdict.yellow{background:var(--amber-bg);color:var(--amber);border:1px solid var(--amber-bd)}
.verdict.red{background:var(--red-bg);color:var(--red);border:1px solid var(--red-bd)}
.verdict.grey{background:#f1f5f9;color:#64748b;border:1px solid #e2e8f0}  /* NOT_ASSESSABLE: neutral, never red */
.tech{font-size:12px;padding:3px 10px;border-radius:6px;background:#eef7f6;border:1px solid #99d3ce;color:var(--teal)}
.ai-read{font-size:13.5px;line-height:1.65;color:var(--ink);margin:0 0 10px}
.ai-read.pending{color:var(--ink-2);background:var(--amber-bg);border:1px solid var(--amber-bd);
  border-radius:8px;padding:10px 12px;font-size:12.5px}
.first-wrong{font-size:12.5px;color:var(--ink-2);margin-bottom:10px}
.first-wrong code{background:var(--red-bg);color:var(--red);padding:2px 7px;border-radius:5px}
.mod-form{margin-top:12px}
.mod-note{width:100%;padding:7px 10px;border:1px solid var(--border);border-radius:7px;font:inherit;
  font-size:13px;margin-bottom:10px}
.mod-note::placeholder{color:var(--teal)}
.mod-actions{display:flex;gap:8px}
.mod-done{margin-top:10px;font-size:12px;color:var(--ink-2)}
.mod-foot{display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;
  border-top:1px solid var(--border);padding-top:14px;margin-top:4px}
.lv-tag{font-size:10.5px;font-weight:600;padding:1px 7px;border-radius:999px;white-space:nowrap;
  border:1px solid transparent}
.lv-tag.lv-on{background:var(--border-soft);color:var(--ink-2);border-color:var(--border)}
.lv-tag.lv-up{background:var(--green-bg);color:var(--green);border-color:var(--green-bd)}
.lv-tag.lv-down{background:var(--amber-bg);color:var(--amber);border-color:var(--amber-bd)}
.rdiag{display:flex;align-items:center;gap:7px}

/* ── v2.6 · Measurement: intervals, gates, next steps, band strip ─────── */
.next-line{font-size:12.5px;line-height:1.55;padding:7px 10px;border-radius:8px;margin-bottom:6px;
  border:1px solid var(--border);background:var(--card)}
.next-line b{display:block;font-size:12.5px}
.next-line.red{background:var(--red-bg);border-color:var(--red-bd);color:var(--red)}
.next-line.red b{color:var(--red)}
.next-line.amber{background:var(--amber-bg);border-color:var(--amber-bd);color:var(--amber)}
.next-line.amber b{color:var(--amber)}
.next-line.green{background:var(--green-bg);border-color:var(--green-bd);color:var(--green)}
.next-line.green b{color:var(--green)}
.next-none{padding:7px 0}

.gate-note{background:var(--amber-bg);border:1px solid var(--amber-bd);border-radius:10px;
  padding:12px 14px;font-size:13px;line-height:1.6;color:var(--amber)}
.growth-headline{font-size:20px;font-weight:600;letter-spacing:-.01em}
.item-change{font-size:26px;font-weight:600}
.item-change.up{color:var(--green)}
.item-change.down{color:var(--amber)}

.ci-wrap{max-width:520px;margin:14px auto 0}
.ci-track{position:relative;height:26px;background:var(--border-soft);border:1px solid var(--border);
  border-radius:7px}
.ci-zero{position:absolute;top:-4px;bottom:-4px;width:1px;background:var(--ink-3)}
.ci-band{position:absolute;top:6px;height:14px;border-radius:7px;opacity:.9}
.ci-band.up{background:#a7f3d0}
.ci-band.down{background:#fecaca}
.ci-band.unclear{background:#e2e8f0}
.ci-point{position:absolute;top:3px;width:4px;height:20px;border-radius:2px;transform:translateX(-2px)}
.ci-point.up{background:#16a34a}
.ci-point.down{background:#b91c1c}
.ci-point.unclear{background:#64748b}
.ci-scale{display:flex;justify-content:space-between;font-size:11px;color:var(--ink-3);margin-top:5px}
.ci-est{font-weight:600;color:var(--ink)}

.band-strip{display:flex;flex-direction:column;gap:6px;margin-top:6px}
.band-row{display:grid;grid-template-columns:82px 1fr;gap:12px;align-items:start;
  padding:9px 10px;border:1px solid var(--border);border-radius:10px;background:var(--card)}
.band-score{font-weight:600;font-variant-numeric:tabular-nums}
.band-score .sub{display:block;font-weight:400}
.band-bar{height:6px;border-radius:3px;background:var(--teal);opacity:.35;margin-bottom:7px}
.band-kids{display:flex;gap:6px;flex-wrap:wrap}
.band-kid{font-size:12px;padding:3px 9px;border-radius:999px;background:var(--border-soft);
  border:1px solid var(--border);color:var(--ink)}
.band-tie{font-size:11px;color:var(--ink-3);margin-top:6px;font-style:italic}
.band-group{border-top:1px dashed var(--border);margin-top:10px;padding-top:10px}
.band-group-hd{font-size:11px;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-3);margin-bottom:6px}
.growth-row{display:grid;grid-template-columns:170px 1fr;gap:16px;align-items:center;
  padding:14px 0;border-bottom:1px solid var(--border-soft)}
.growth-row:last-of-type{border-bottom:0}
.growth-unit{font-weight:600}
.growth-gate{font-size:12.5px;line-height:1.6;color:var(--ink-2)}
/* .gate-code has no markup left: overview.php stopped printing the database
   constant (NO_PAIR, N_TOO_SMALL) beside its own plain-English message. Kept so
   a screen that wants to show a gate code to a developer still has a style for
   it; delete it if nothing has claimed it by the next pass. */
.growth-figure .ci-wrap{margin-left:0}
.tabs-lite{display:flex;gap:6px;margin-bottom:14px}
.tl{font-size:13px;padding:6px 14px;border-radius:999px;border:1px solid var(--border);
  background:var(--card);color:var(--ink-2)}
.tl.active{background:var(--teal);border-color:var(--teal);color:#fff;font-weight:500}
.grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
@media (max-width:800px){.grid-3{grid-template-columns:1fr}}
.grp-card{border:1px solid var(--border);border-radius:10px;padding:12px}
.grp-hd{font-weight:600;font-size:13px;margin-bottom:8px}

/* ── v2.7 · Question diagrams (stimulus) — shared with the student app ── */
.stimulus{max-width:100%;margin:0 0 10px}
.stimulus svg{max-width:100%;height:auto;display:block}
.stimulus.stim-sm{max-width:220px;margin-top:6px}
.stimulus.qcard-stim{max-width:300px}
.stimulus.qblock-stim{max-width:280px}

/* ══════════ Start-of-year diagnostic ══════════
   Sits above the unit tiles because it comes first in the year. The panel is
   loud while anyone is missing a level and quiet once nobody is. */
.diag-panel{border:1px solid var(--border);border-radius:14px;padding:18px 20px;margin-bottom:18px;
  background:var(--card)}
.diag-panel.diag-waiting{border-color:#f0c987;background:linear-gradient(180deg,#fffaf0,var(--card) 70%)}
.diag-panel.diag-done{border-color:#bfe0cd}
.diag-head{display:flex;gap:20px;align-items:flex-start;flex-wrap:wrap}
.diag-head h2{margin:0 0 4px;font-size:17px;font-weight:600;display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.diag-head p{margin:0;max-width:70ch}
.diag-pill{font-size:11px;font-weight:500;letter-spacing:.04em;color:var(--ink-3);
  border:1px solid var(--border);border-radius:999px;padding:3px 9px;white-space:nowrap}
.diag-count{margin-left:auto;text-align:right;flex:none}
.diag-n{font-size:30px;font-weight:600;line-height:1;color:var(--ink)}
.diag-n span{font-size:16px;color:var(--ink-3);font-weight:500}
.diag-bar{height:5px;border-radius:999px;background:var(--border);margin:14px 0 0;overflow:hidden}
.diag-bar-fill{height:100%;background:var(--teal);border-radius:999px;transition:width .3s}

.diag-kids{display:flex;flex-wrap:wrap;gap:6px;margin-top:14px}
.diag-kid{display:inline-flex;align-items:center;gap:6px;text-decoration:none;
  border:1px solid var(--border);border-radius:999px;padding:4px 10px 4px 6px;font-size:12.5px;
  color:var(--ink-2);background:var(--card)}
.diag-kid:hover{border-color:var(--teal)}
.dk-emoji{font-size:14px}
.dk-lv{font-weight:600;font-variant-numeric:tabular-nums}
.diag-kid.k-test{border-color:#bfe0cd;background:#f2fbf6}
.diag-kid.k-test .dk-lv{color:#1c7a4a}
.diag-kid.k-teacher{border-color:#c9d9ef;background:#f4f8fd}
.diag-kid.k-teacher .dk-lv{color:#2b5f9e}
.diag-kid.k-sitting{border-color:#f0d199;background:#fffaf0}
.diag-kid.k-none{border-style:dashed;color:var(--ink-3)}
.diag-kid.k-none .dk-lv{color:var(--ink-3)}

.diag-foot{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:16px}
.diag-ok{font-size:13px;font-weight:500;color:#1c7a4a}

/* A child with no level cannot be sent a unit test. */
.roster-row.row-blocked{opacity:.6;background:var(--border-soft)}
.need-diag{font-size:12px;font-weight:500;color:#9a6b12;background:#fdf3df;
  border:1px solid #f0d199;border-radius:999px;padding:2px 9px;white-space:nowrap}
.need-diag-note{font-size:12.5px;color:var(--ink-2);background:#fffaf0;border:1px solid #f0d199;
  border-radius:8px;padding:9px 11px;margin:4px 0 0;line-height:1.55}

/* The band profile on a diagnostic result. */
.band-strip{display:flex;flex-direction:column;gap:4px;margin:10px 0 0}
.band-row{display:grid;grid-template-columns:74px 1fr 46px;gap:10px;align-items:center;font-size:12.5px}
.band-row .bl{color:var(--ink-3)}
.band-dots{display:flex;gap:3px}
.band-dot{width:15px;height:15px;border-radius:4px;background:var(--border)}
.band-dot.on{background:var(--teal)}
.band-row.b-cleared .bn{color:#1c7a4a;font-weight:600}
.band-row.b-part .bn{color:#9a6b12;font-weight:600}
.band-row.b-not .bn{color:var(--ink-3)}
.band-row.b-ceiling{background:#f2fbf6;border-radius:6px;padding:2px 6px;margin:0 -6px}

/* ══════════════════════════════════════════════════════════════════════
   v3 · Reason Education brand — logo, mascot and icon system
   ----------------------------------------------------------------------
   The mascot is the "R" of the wordmark drawn as a character. It is used
   to carry TONE at the points where a teacher is being told something:
   an empty screen, a verdict, a next step, a report. It is never used as
   decoration inside data — a number never gets a face next to it.
   ══════════════════════════════════════════════════════════════════════ */

/* ── the wordmark in the app header ─────────────────────────────── */
.wordmark img { display: block; height: 34px; width: auto; }
.login-brand .logo { display: block; height: 46px; width: auto; margin: 0 auto 10px; }

/* ── reusable mascot / icon images ──────────────────────────────── */
.mascot, .bicon { display: inline-block; vertical-align: middle; object-fit: contain; }
.mascot-xs { height: 22px; } .mascot-sm { height: 34px; } .mascot-md { height: 56px; }
.mascot-lg { height: 92px; }  .mascot-xl { height: 132px; }
.bicon-xs { height: 15px; } .bicon-sm { height: 20px; } .bicon-md { height: 28px; }

/* A round teal-tinted plate behind a mascot, for avatar-like uses. */
.mascot-plate { background: var(--brand-soft); border: 1px solid var(--brand-line);
  border-radius: 50%; padding: 5px; }

/* ── empty states get a face and a reason to exist ──────────────── */
.empty { padding: 34px 40px; }
.empty::before { content: ''; display: block; height: 86px; margin: 0 auto 12px;
  background: url(brand/m-curious.png) center/contain no-repeat; }

/* ── banners ────────────────────────────────────────────────────── */
.banner-green { background-image: url(brand/i-success.png); background-repeat: no-repeat;
  background-position: 14px center; background-size: 18px; padding-left: 42px; }

/* ── what a child needs next: tone carries an icon ──────────────── */
/* The longhands sit on the compound selectors because the tone rules above use
   the `background` shorthand, which would otherwise reset size/position. */
.next-line { padding-left: 40px; }
.next-line.red    { background-image: url(brand/m-confused.png); background-repeat: no-repeat;
  background-position: 10px 8px; background-size: 21px; }
.next-line.amber  { background-image: url(brand/m-thinking.png); background-repeat: no-repeat;
  background-position: 10px 8px; background-size: 21px; }
.next-line.green  { background-image: url(brand/i-success.png); background-repeat: no-repeat;
  background-position: 11px 10px; background-size: 17px; }
.next-none { background: none; padding-left: 0; }

/* ── the AI's reading of the working out ────────────────────────── */
.ai-read:not(.pending) { padding-left: 44px; background: url(brand/m-thoughtful.png) 0 1px/30px no-repeat; min-height: 34px; }
.ai-read.pending { padding-left: 46px; background-image: url(brand/m-calm.png);
  background-repeat: no-repeat; background-position: 12px 10px; background-size: 24px; }
.ai-text { padding-left: 40px; background: url(brand/m-thoughtful.png) 0 0/28px no-repeat; min-height: 30px; }
.pending-ai { padding-left: 40px; background: url(brand/m-calm.png) 12px center/20px no-repeat; }

/* ── verdict + tags ─────────────────────────────────────────────── */
.verdict.green, .tag-ok { background-image: url(brand/i-success.png); background-repeat: no-repeat;
  background-position: 7px center; background-size: 12px; padding-left: 23px; }

/* ── growth ─────────────────────────────────────────────────────── */
.growth-headline { padding-left: 30px; background: url(brand/i-growth.png) 0 center/21px no-repeat; }
.gate-note { padding-left: 48px; background: url(brand/i-inquiry.png) 16px 14px/20px no-repeat; }

/* ── reports ────────────────────────────────────────────────────── */
.report-box { padding-left: 16px; }
.report-head-mascot { float: right; height: 74px; margin: -6px 0 6px 12px; }

/* ── the start-of-year diagnostic panel ─────────────────────────── */
.diag-mascot { height: 74px; width: auto; flex: none; margin-right: 4px; }
.diag-ok { padding-left: 24px; background: url(brand/i-success.png) 0 center/16px no-repeat; }

/* ── badges a child has earned ──────────────────────────────────── */

/* ── a class you can look at but not change ─────────────────────── */
.readonly-note { padding-left: 52px; background: url(brand/m-calm.png) 14px center/26px no-repeat; }

/* ── needs the diagnostic ───────────────────────────────────────── */
.need-diag { padding-left: 22px; background: url(brand/i-inquiry.png) 6px center/11px no-repeat; }

/* ── page headings that carry a mascot ──────────────────────────── */
.head-mascot { height: 54px; width: auto; margin-right: 2px; }

/* ── sign-in screens ────────────────────────────────────────────── */
.login-wrap { position: relative; }
.login-brand .tag { color: var(--ink-2); }

/* ── teacher moderation: the mascot introduces the AI's reading ─── */
.mod-banner { padding-left: 78px; background-image: url(brand/m-presenting.png);
  background-repeat: no-repeat; background-position: 16px center; background-size: 48px; }

/* ── strategy pills and chips pick up the brand teal ────────────── */
.strategy-pill { background: var(--brand-soft); border-color: var(--brand-line); color: var(--teal-dark); }
.vc-code { background: var(--brand-soft); border-color: var(--brand-line); }
.report-vc { background: var(--brand-soft); border-color: var(--brand-line); }
.mod-banner { background-color: var(--brand-soft); border-color: var(--brand-line); }
.unit-sq:hover, .btn-sm-outline:hover, button.vc-chip:hover { border-color: var(--brand); }
.unit-sq .usym { color: var(--teal); }

/* The sign-in screens use .tag as a caption; the per-question .tag pill styling
   must not leak into it. */
.login-brand .tag { border: 0; background: none; padding: 0; border-radius: 0; display: block;
  font-weight: 500; }
.login-brand .mascot-lg { margin-bottom: 10px; }
.login-brand .logo { height: 40px; }

/* ══════════════════════════════════════════════════════════════════════
   v4 · Page-transition loader — the Reason mark drawing itself
   ----------------------------------------------------------------------
   The overlay covers the CONTENT REGION, not the viewport: it starts below
   the 56px header so the wordmark, tabs and account chip stay on screen
   while the page under them changes. Markup: inc/_loader.php.

   The drawing is one continuous pen stroke split across five elements,
   each with pathLength="1" so every stage can share the same dash values
   whatever its real geometry. Stage timings are the 1000ms master sequence
   scaled by 0.6.
   ══════════════════════════════════════════════════════════════════════ */

:root {
  --reason-loader-color: #000000;
  --reason-loader-background: var(--bg);
  --reason-loader-size: 96px;
  --reason-loader-duration: 600ms;
  --reason-loader-fade: 160ms;
  --header-h: 57px;   /* 56px bar + its 1px border, so the rule stays visible */
}

.reason-loading-screen {
  position: fixed;
  inset: var(--header-h) 0 0 0;      /* content region only — header stays put */
  display: flex; align-items: center; justify-content: center;
  background: var(--reason-loader-background);
  z-index: 40;                        /* under modals (50) and toasts */
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity var(--reason-loader-fade) ease,
              visibility var(--reason-loader-fade) ease;
}
/* Signed-out screens (sign-in, setup, reset) have no header to protect. Kept
   deliberately: the loader is only emitted for signed-in pages today, so this
   rule is unreachable until that changes — and it costs one line to be right
   on the day it does. */
body.no-header .reason-loading-screen { inset: 0; }

.reason-loading-screen.is-active { opacity: 1; visibility: visible; pointer-events: all; }

.reason-loading-symbol { width: var(--reason-loader-size); height: auto; display: block;
  transform: translateZ(0); backface-visibility: hidden; }

.reason-loading-symbol .reason-stroke {
  fill: none; stroke: var(--reason-loader-color); stroke-width: 12;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 1; stroke-dashoffset: 1;
}

/* 0 → 1000ms · top node 0-150 · upper path 130-610 · middle node 580-720
   · shaft 690-910 · head 880-1000. The master sequence, at full length: the
   overlaps are what make it read as one pen stroke rather than five
   animations. Keep LOADER_MS in _loader_controller.php in step with the 1000
   here, or the overlay lifts before the mark finishes drawing. */
.reason-loading-screen.is-drawing .reason-node-top    { animation: reason-draw 150ms linear   0ms forwards; }
.reason-loading-screen.is-drawing .reason-upper-path  { animation: reason-draw 480ms linear 130ms forwards; }
.reason-loading-screen.is-drawing .reason-node-middle { animation: reason-draw 140ms linear 580ms forwards; }
.reason-loading-screen.is-drawing .reason-arrow-shaft { animation: reason-draw 220ms linear 690ms forwards; }
.reason-loading-screen.is-drawing .reason-arrow-head  { animation: reason-draw 120ms linear 880ms forwards; }

@keyframes reason-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }

@media (prefers-reduced-motion: reduce) {
  .reason-loading-screen { transition: none; }
  .reason-loading-symbol .reason-stroke { animation: none !important; stroke-dashoffset: 0; }
}

/* ══════════════════════════════════════════════════════════════════════
   v5 · Denser class roster
   ----------------------------------------------------------------------
   A class is 20–25 children and a teacher scans the whole list — so the row
   is sized to fit a class on a screen rather than to look generous. The
   height was set by the two-line cells (label above control), so the saving
   comes from tightening those, not from shrinking the name: the name and the
   level are what a teacher reads, and they stay legible.
   ══════════════════════════════════════════════════════════════════════ */

.stu-row { gap: 10px; padding: 3px 10px; margin-bottom: 3px; border-radius: 8px; }
.stu-emoji { width: 28px; height: 28px; font-size: 17px; border-radius: 7px; }
.stu-name { font-size: 13.5px; line-height: 1.2; }
.stu-meta { font-size: 10.5px; line-height: 1.25; }

/* controls sit on one line at the height of the text beside them */
.stu-grade select { padding: 2px 6px; font-size: 12px; }
.stu-row .btn-sm { padding: 3px 8px; font-size: 12px; }
.stu-row .lv-tag { font-size: 10px; padding: 0 6px; }
.stu-row .need-diag { font-size: 10.5px; padding: 1px 8px 1px 18px; background-size: 10px; }
.stu-row .mono { font-size: 12px; }

/* The diagnostic column is a reading, not a control: no box, no caret, nothing
   that invites a click. The number a teacher can change is the one beside it. */
.diag-read { display: flex; align-items: center; gap: 6px; font-size: 13px; }
.diag-read b { font-weight: 600; font-variant-numeric: tabular-nums; }

/* The column labels live once, above the list, instead of on every row —
   at 24 children the repeat was three lines of chrome per student. */
/* Sticky, because the two numeric columns it labels are "Diagnostic suggests" —
   read-only — and "Working at", which decides which year's paper a child sits.
   Twenty-eight rows down, with the header scrolled away, they are two unlabelled
   numbers next to each other and the wrong one is one click from being changed. */
.stu-head { position: sticky; top: 0; z-index: 2; background: var(--card);
  display: flex; align-items: center; gap: 10px; padding: 6px 10px 5px;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-3);
  font-weight: 500; }
.stu-head .sh-emoji { width: 28px; flex: none; }
.stu-head .sh-go { width: 96px; flex: none; }

/* Column widths live here rather than in inline styles on every row, so the
   header and the rows cannot drift apart and a media query can actually
   re-lay-out the row. */
.stu-c-name { flex: 2 1 0; min-width: 0; }
.stu-c-id,
.stu-c-diag,
.stu-c-work { flex: 1 1 0; min-width: 0; }
.stu-go { flex: none; }

/* ── Roster below 720px ────────────────────────────────────────────────
   One header row cannot sit over columns this narrow, so the row stops
   pretending to be a table: the child's name takes the first line, and the
   three facts stack underneath carrying their own labels. Density was only
   ever worth it on a wide screen — a bare mono number with nothing to say
   what it is costs a teacher more than a tall row does.
   ────────────────────────────────────────────────────────────────────── */
@media (max-width: 720px) {
  .stu-head { display: none; }

  .stu-row { flex-wrap: wrap; column-gap: 12px; row-gap: 10px; padding: 10px 12px; }
  .stu-emoji { flex: none; }

  .stu-c-name { flex: 1 1 calc(100% - 38px); }   /* first line, beside the emoji */
  .stu-c-id   { flex: 1 1 34%; }
  .stu-c-diag { flex: 1 1 55%; }
  .stu-c-work { flex: 1 1 40%; }
  .stu-go     { flex: 1 1 45%; margin-left: auto; text-align: right; }

  .stu-cell[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: .06em;
    font-weight: 500;
    color: var(--ink-3);
    margin-bottom: 3px;
  }
  /* the name column names itself */
  .stu-cell.stu-c-name[data-label]::before { display: none; }
}

/* ══════════════════════════════════════════════════════════════════════
   v6 · Per-unit levels
   ----------------------------------------------------------------------
   The level lives on the unit tile because that is the only place it can
   be read against the evidence for it. Two lines, in this order: what the
   teacher decided (a control), then what the paper suggested (text). Never
   the other way round — the suggestion is the softer claim and putting it
   first invites a teacher to take it as the answer.
   ══════════════════════════════════════════════════════════════════════ */

/* The tile is no longer one big link: it holds a link (everything above) and a
   form (the level). A <form> inside an <a> is invalid HTML and the select would
   swallow the navigation, so the anchor wraps only the part that navigates. */
/* Tiles stretch to the tallest in the row so every level bar lines up: a
   teacher scans that row across six units, and a ragged baseline makes them
   hunt for it in each tile. */
.unit-grid { align-items: stretch; }
.unit-tile { padding: 0; cursor: default; overflow: hidden;
  display: flex; flex-direction: column; }
.unit-tile-body { display: block; text-align: left; width: 100%; padding: 16px 18px 12px;
  cursor: pointer; color: inherit; text-decoration: none; flex: 1 0 auto; }
.unit-tile-body:hover .unit-name { color: var(--teal); }

.unit-level { border-top: 1px solid var(--border-soft); background: var(--grey-bg);
  padding: 9px 18px 11px; }
.ul-set { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ul-label { font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-3); font-weight: 600; }
.unit-level select { padding: 2px 6px; font-size: 12.5px; border: 1px solid var(--border);
  border-radius: 6px; background: #fff; color: var(--ink); }
.ul-sug { font-size: 11.5px; margin-top: 5px; display: flex; gap: 6px; flex-wrap: wrap; }
/* A teacher who has overruled the paper should see that they have, without it
   reading as an error — this is the expected case, not a warning. */
.ul-diff { color: var(--teal); font-weight: 500; }
.ul-diff::before { content: "→"; margin-right: 4px; color: var(--ink-3); font-weight: 400; }

/* Four tiles across, and the fourth is a link rather than a figure. */
.stat-row.stat-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 960px) { .stat-row.stat-4 { grid-template-columns: repeat(2, 1fr); } }
.stat-link { display: block; color: inherit; text-decoration: none;
  transition: border-color .15s, box-shadow .15s; }
.stat-link:hover { border-color: var(--teal); box-shadow: 0 2px 10px rgba(15,118,110,.08); }
.stat-link .v { color: var(--teal); }
.stat-link.is-empty { pointer-events: none; }
.stat-link.is-empty .v { color: var(--ink-3); }

/* ── Diagnostic results, per unit ───────────────────────────────────────
   Six small panels rather than one long strip: the whole value of the
   per-unit paper is that the units differ, and a layout that stacks them
   vertically hides the comparison a teacher opened the page to make. */
.diag-units { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; align-items: start; }
@media (max-width: 900px) { .diag-units { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .diag-units { grid-template-columns: 1fr; } }

.diag-unit { border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; background: var(--card); }
.du-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.du-name { font-size: 15px; font-weight: 600; color: var(--ink); }
.du-lv { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.du-note { margin: 8px 0 0; font-size: 11.5px; line-height: 1.45; }
.du-foot { margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--border-soft);
  display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.du-state { font-size: 11.5px; }
.du-state.on  { color: #1c7a4a; }
.du-state.off { color: var(--ink-3); }

/* The band row inside a unit panel is narrower than the full-page one. */
.diag-unit .band-row { grid-template-columns: 62px 1fr 34px; gap: 8px; font-size: 11.5px; }
.diag-unit .band-dot { width: 13px; height: 13px; }

/* ══════════════════════════════════════════════════════════════════════
   v7 · Provenance — where a statement came from
   ----------------------------------------------------------------------
   See inc/judgement.php. Three sources, and a teacher (or a principal, or
   a parent reading over their shoulder) has to be able to tell them apart
   without hovering anything. So these are visible chips, not tooltips.

   The AI chip is deliberately the plainest of the three. It is not a
   warning — the AI reading is the product's whole point — it is a
   statement that a human has not looked yet, and it should read as an
   invitation to open the page rather than as an error.
   ══════════════════════════════════════════════════════════════════════ */

.prov { display: inline-block; font-size: 10px; font-weight: 500; line-height: 1.6;
  padding: 0 7px; border-radius: 999px; white-space: nowrap; vertical-align: middle;
  text-transform: none; letter-spacing: 0; margin-left: 6px; }
.prov-ai       { background: var(--brand-soft); border: 1px solid var(--brand-line); color: var(--teal-dark); }
.prov-teacher  { background: #f1f5f9; border: 1px solid var(--border); color: var(--ink-2); }
.prov-measured { background: #f8fafc; border: 1px solid var(--border); color: var(--ink-3); }

/* An unchecked AI line is drawn as an open question: dashed edge, no fill.
   A settled amber line keeps its solid amber, so the two never look alike. */
.next-line.is-ai { background: #fff; border: 1px dashed var(--brand-line); color: var(--ink-2); }
.next-line.is-ai b { color: var(--teal-dark); }

.prov-note { font-size: 11.5px; color: var(--ink-3); margin: 8px 0 0;
  padding-left: 10px; border-left: 2px solid var(--brand-line); }

/* ── The AI disclosure (spec §7.7) ──────────────────────────────────────
   Collapsed by default so it does not shout at a teacher who has read it,
   but present on every visit — a disclosure agreed to once in week one and
   never seen again is not a disclosure.
   ────────────────────────────────────────────────────────────────────── */
.ai-disclose { border: 1px dashed var(--brand-line); background: var(--brand-soft);
  border-radius: 9px; margin: 0 0 14px; }
.ai-disclose > summary { cursor: pointer; padding: 9px 13px; font-size: 12.5px;
  font-weight: 600; color: var(--teal-dark); list-style: none; }
.ai-disclose > summary::-webkit-details-marker { display: none; }
.ai-disclose > summary::before { content: "ⓘ"; margin-right: 7px; font-weight: 400; }
.ai-disclose[open] > summary { border-bottom: 1px dashed var(--brand-line); }
.ai-disclose-body { padding: 11px 15px 4px; }
.ai-disclose-body p { font-size: 12.5px; line-height: 1.55; color: var(--ink-2); margin: 0 0 10px; }
.ai-disclose-body b { color: var(--ink); }

/* A tag from the frozen taxonomy, wherever it is shown. It is a category a
   human wrote, so it is set in the interface's own voice, not quoted. */
.tag-chip { display: inline-block; font-size: 11px; padding: 1px 8px; border-radius: 999px;
  background: #fff; border: 1px solid var(--brand-line); color: var(--teal-dark); }

/* ══════════════════════════════════════════════════════════════════════════
   Monitor — a class sitting a test, live (doc 33)
   ══════════════════════════════════════════════════════════════════════════
   Design rules this section follows, so the next person changing it keeps them:

   1. COLOUR IS A CLOCK, NOT A VERDICT. Amber and red on a tile mean "this many
      seconds on this question" and the tile prints the number right underneath.
      Nothing here colours a child by how well they are doing, because this
      screen does not know how well they are doing and must never imply it.
   2. A HAND UP OUTRANKS EVERYTHING. It is the only state a child chose. It gets
      the strongest treatment on the tile and its own queue above the grid.
   3. THE GRID IS SCANNABLE AT ARM'S LENGTH. A teacher reads this from across a
      desk between helping children — big question number, one line of words.
   ────────────────────────────────────────────────────────────────────────── */

/* the "live" pip beside the title */
.live-pip { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .07em; color: var(--green);
  background: var(--green-bg); border: 1px solid var(--green-bd); border-radius: 999px; padding: 3px 10px; }
.live-pip span { width: 7px; height: 7px; border-radius: 50%; background: var(--green);
  animation: live-pulse 2s ease-in-out infinite; }
.live-pip.stale { color: var(--ink-3); background: var(--border-soft); border-color: var(--border); }
.live-pip.stale span { background: var(--ink-3); animation: none; }
@keyframes live-pulse { 0%,100% { opacity: 1; } 50% { opacity: .3; } }

/* hands up */
.card[hidden] { display: none; }
.hand-count { display: inline-block; min-width: 20px; text-align: center; background: var(--amber);
  color: #fff; border-radius: 999px; padding: 1px 8px; font-size: 12px; font-weight: 700; }
.hand-row { display: flex; align-items: center; gap: 12px; padding: 10px 0;
  border-top: 1px solid var(--border-soft); }
.hand-row:first-child { border-top: none; }
.hand-emoji { font-size: 22px; width: 32px; text-align: center; flex: none; }
.hand-who { flex: 1; min-width: 0; font-size: 14px; }

/* layout: grid, and the dock that appears beside it while watching */
.monitor-layout { display: grid; grid-template-columns: 1fr; gap: 16px; align-items: start; }
.monitor-layout.watching { grid-template-columns: minmax(0,1fr) 400px; }
@media (max-width: 1100px) { .monitor-layout.watching { grid-template-columns: 1fr; } }

/* the tiles */
.mon-grid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(184px, 1fr)); }
.mon-tile { text-align: left; background: var(--card); border: 1px solid var(--border);
  border-left: 4px solid var(--ink-3); border-radius: 10px; padding: 12px 13px; cursor: pointer;
  display: flex; flex-direction: column; gap: 7px; transition: box-shadow 120ms ease, transform 120ms ease; }
.mon-tile:hover:not([disabled]) { box-shadow: 0 3px 12px rgba(15,23,42,.09); transform: translateY(-1px); }
.mon-tile[disabled] { cursor: default; opacity: .72; }
.tile-top { display: flex; align-items: center; gap: 7px; min-width: 0; }
.tile-emoji { font-size: 17px; flex: none; }
.tile-name { font-size: 13px; font-weight: 600; color: var(--ink); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.tile-q { font-size: 27px; font-weight: 700; line-height: 1; color: var(--ink); }
.tile-q small { font-size: 14px; font-weight: 500; color: var(--ink-3); }
.tile-bar { height: 4px; border-radius: 999px; background: var(--border-soft); overflow: hidden; }
.tile-bar div { height: 100%; background: var(--ink-3); border-radius: 999px; transition: width 400ms ease; }
.tile-line { font-size: 12px; color: var(--ink-2); }
.tile-flags { display: flex; flex-wrap: wrap; gap: 4px; }
.fl { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-2); background: var(--border-soft); border-radius: 4px; padding: 2px 6px; }
.fl-help { background: var(--amber-bg); color: var(--amber); }

/* the states — see rule 1 above */
.mon-tile.t-working     { border-left-color: var(--green); }
.mon-tile.t-working  .tile-bar div { background: var(--green); }
.mon-tile.t-review      { border-left-color: #0284c7; }
.mon-tile.t-finishing   { border-left-color: #0284c7; }
.mon-tile.t-slow        { border-left-color: var(--amber); background: var(--amber-bg); }
.mon-tile.t-stalled     { border-left-color: var(--red); background: var(--red-bg); }
.mon-tile.t-away        { border-left-color: var(--ink-3); background: var(--border-soft); }
.mon-tile.t-not_started { border-left-color: var(--border); color: var(--ink-3); }
.mon-tile.t-not_started .tile-q, .mon-tile.t-not_started .tile-name { color: var(--ink-3); }
.mon-tile.t-complete    { border-left-color: var(--green); background: var(--green-bg); }
/* rule 2 — a hand up wins over every clock-derived state */
.mon-tile.t-help { border-left-color: var(--amber); background: var(--amber-bg);
  box-shadow: 0 0 0 2px var(--amber-bd); }
.mon-tile.t-watching { box-shadow: 0 0 0 2px #0284c7; }

.mon-legend { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 14px; padding-top: 12px;
  border-top: 1px solid var(--border-soft); font-size: 11px; color: var(--ink-2); }
.mon-legend span { display: inline-flex; align-items: center; gap: 6px; }
.sw { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
.sw-working { background: var(--green); } .sw-slow { background: var(--amber); }
.sw-stalled { background: var(--red); }   .sw-help { background: var(--amber-bd); border: 2px solid var(--amber); }
.sw-away { background: var(--ink-3); }    .sw-complete { background: var(--green-bd); }

/* the dock — one child's page, live */
.mon-dock { background: var(--card); border: 1px solid var(--border); border-radius: 10px;
  padding: 14px; position: sticky; top: 72px; }
.mon-dock[hidden] { display: none; }
.dock-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.dock-name { font-size: 15px; font-weight: 600; }
.dock-frame { position: relative; border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
  background: #fff; min-height: 180px; display: flex; align-items: center; justify-content: center; }
.dock-frame img { display: block; width: 100%; }
/* An author rule that sets `display` beats the browser's own [hidden] rule, so
   anything this file gives a display value has to say so again for [hidden] —
   otherwise the empty <img> renders as a broken-image icon before the first
   frame arrives, and the dock opens looking like a failure. */
.dock-frame img[hidden], .dock-wait[hidden] { display: none; }
.dock-wait { font-size: 12px; color: var(--ink-3); padding: 40px 0; }
.dock-answer { margin-top: 10px; font-size: 13px; color: var(--ink-2); }
.dock-note { margin-top: 10px; font-size: 11px; line-height: 1.5; color: var(--ink-3); }

/* the header chip — hands up, from any page in the portal */
.hands-chip { display: none; align-items: center; gap: 6px; text-decoration: none;
  background: var(--amber-bg); border: 1px solid var(--amber-bd); color: var(--amber);
  border-radius: 999px; padding: 5px 11px; font-size: 12px; font-weight: 600; }
.hands-chip.on { display: inline-flex; animation: hands-in 260ms ease both; }
.hands-chip .hc-hand { font-size: 14px; line-height: 1; }
@keyframes hands-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
/* A run of this teacher's is open but nobody needs anything — quieter chip, so
   the way back to the Monitor is always one click from wherever they are. */
.hands-chip.idle { background: var(--green-bg); border-color: var(--green-bd); color: var(--green); }

@media (prefers-reduced-motion: reduce) {
  .live-pip span { animation: none; }
  .hands-chip.on { animation: none; }
  .mon-tile:hover:not([disabled]) { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
 * STUDENT EVIDENCE REPORT
 *
 * The one screen in this portal a teacher opens with a school report open
 * beside it, at 9pm, twenty-eight times in a fortnight. So it is built for
 * reading rather than for scanning: one column, larger type than the rest of
 * the portal, and space between things.
 *
 * It borrows the portal's whole visual language rather than inventing one —
 * the same teal, the same card, the same three question colours, the same
 * provenance chip. A teacher must never have to learn a second grammar to read
 * their own evidence.
 *
 * There are no charts, and that is a decision, not an omission. A chart of a
 * child's scores is a shape, and a shape invites a reading — and reading the
 * shape is the teacher's job on this page, not the software's.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ── the head: who, and over what window ── */
.ev-page-head { align-items: flex-start; gap: 16px; }
.ev-controls { margin-left: auto; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.ev-periods { display: inline-flex; align-items: stretch; border: 1px solid var(--border);
  background: var(--card); border-radius: 8px; overflow: visible; }
.ev-period-btn { display: inline-block; padding: 7px 14px; font-size: 13px; font-weight: 500;
  color: var(--ink-2); border-right: 1px solid var(--border-soft); cursor: pointer;
  list-style: none; white-space: nowrap; }
.ev-periods > *:last-child .ev-period-btn, .ev-periods > a:last-child { border-right: 0; }
.ev-period-btn:hover { background: var(--border-soft); color: var(--ink); }
.ev-period-btn.active { background: var(--brand-soft); color: var(--teal-dark); font-weight: 600; }
.ev-custom { position: relative; }
.ev-custom > summary::-webkit-details-marker { display: none; }
.ev-custom-form { position: absolute; right: 0; top: calc(100% + 6px); z-index: 20;
  background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 14px;
  box-shadow: 0 8px 24px rgba(15,23,42,.10); width: 230px; }
.ev-custom-form label { font-size: 12px; margin-bottom: 10px; }
.ev-custom-form input { margin-top: 3px; }

.ev-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px;
  flex-wrap: wrap; background: var(--card); border: 1px solid var(--border); border-radius: 12px;
  padding: 26px 28px; margin-bottom: 16px; }
.ev-kicker { font-size: 11px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
  color: var(--teal); margin-bottom: 6px; }
.ev-name { font-size: 30px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.15; }
.ev-meta { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px;
  font-size: 13px; color: var(--ink-2); }
.ev-meta span + span::before { content: "·"; color: var(--ink-3); margin-right: 14px; }
.ev-head-period { text-align: right; min-width: 190px; }
.ev-period-label { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--ink-3); }
.ev-period-value { font-size: 17px; font-weight: 600; margin-top: 3px; }
.ev-period-dates { font-size: 12px; color: var(--ink-3); margin-top: 2px; }

/* ── evidence captured: the counts, and nothing that looks like a verdict ── */
.ev-summary { background: linear-gradient(180deg, var(--brand-soft), var(--card) 70%);
  border: 1px solid var(--brand-line); border-radius: 12px; padding: 22px 26px; margin-bottom: 28px; }
.ev-summary-head { display: flex; justify-content: space-between; align-items: baseline;
  gap: 16px; flex-wrap: wrap; margin-bottom: 16px; }
.ev-summary-head h2 { font-size: 12px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .08em; color: var(--teal-dark); }
.ev-counts { display: grid; grid-template-columns: repeat(6, 1fr); gap: 14px; }
@media (max-width: 900px) { .ev-counts { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 520px) { .ev-counts { grid-template-columns: repeat(2, 1fr); } }
.ev-count { background: var(--card); border: 1px solid var(--border); border-radius: 10px;
  padding: 14px 15px; }
.ev-count b { display: block; font-size: 26px; font-weight: 600; letter-spacing: -0.02em;
  line-height: 1.1; }
.ev-count span { display: block; font-size: 11.5px; color: var(--ink-2); margin-top: 5px; line-height: 1.35; }

/* ── section rhythm ── */
.ev-jump { display: flex; gap: 4px; flex-wrap: wrap; position: sticky; top: 0; z-index: 15;
  background: rgba(248,250,252,.92); backdrop-filter: blur(6px);
  padding: 10px 0 12px; margin-bottom: 8px; border-bottom: 1px solid var(--border); }
.ev-jump a { font-size: 12.5px; font-weight: 500; color: var(--ink-2); padding: 5px 11px;
  border-radius: 999px; }
.ev-jump a:hover { background: var(--card); color: var(--teal-dark); box-shadow: 0 0 0 1px var(--border); }

.ev-section { margin: 34px 0; scroll-margin-top: 62px; }
.ev-section-head { display: flex; justify-content: space-between; align-items: baseline;
  gap: 16px; flex-wrap: wrap; padding-bottom: 10px; margin-bottom: 16px;
  border-bottom: 2px solid var(--ink); }
.ev-section-head h2 { font-size: 17px; font-weight: 600; letter-spacing: -0.01em; color: var(--ink); }
.ev-section-head .sub { max-width: 620px; }
.ev-src { font-size: 12px; font-weight: 500; white-space: nowrap; }

.ev-card { background: var(--card); border: 1px solid var(--border); border-radius: 12px;
  padding: 20px 22px; }
.ev-sub-head { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink-2); margin: 20px 0 10px; }
.ev-row { display: flex; flex-wrap: wrap; gap: 12px 34px; }
.ev-fact span { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--ink-3); margin-bottom: 3px; }
.ev-fact b { font-size: 17px; font-weight: 600; }
.ev-note { font-size: 12px; line-height: 1.6; color: var(--ink-2); margin-top: 12px;
  padding-left: 11px; border-left: 2px solid var(--border); }
.ev-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }

/* Empty states. Stated plainly and given room, because "no post-test completed"
   is a fact a teacher writing a report needs to read, not an error to hide. */
.ev-none { font-size: 13px; color: var(--ink-2); background: var(--bg);
  border: 1px dashed var(--border); border-radius: 8px; padding: 13px 15px; }
.ev-none-inline { font-size: 12px; color: var(--ink-3); margin-top: 12px; }
.ev-empty-card { text-align: center; padding: 44px 28px; }
.ev-empty-card h3 { font-size: 17px; font-weight: 600; margin: 12px 0 8px; }
.ev-empty-card p { max-width: 520px; margin: 0 auto 8px; line-height: 1.65; }

/* ── diagnostic ── */
.ev-diag-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
@media (max-width: 900px) { .ev-diag-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .ev-diag-grid { grid-template-columns: 1fr; } }
.ev-diag-unit { border: 1px solid var(--border); border-radius: 10px; padding: 13px 15px;
  background: var(--bg); }
.ev-diag-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  margin-bottom: 9px; }
.ev-diag-name { font-weight: 600; font-size: 14px; }
.ev-diag-lv { font-size: 12px; font-weight: 600; color: var(--teal-dark); white-space: nowrap; }
.ev-diag-unit .band-row { grid-template-columns: 62px 1fr 34px; gap: 8px; font-size: 11.5px; }
.ev-diag-unit .band-dot { width: 13px; height: 13px; }
.ev-diag-note { font-size: 11.5px; line-height: 1.5; color: var(--ink-3); margin-top: 8px; }

/* ── unit evidence: the core of the report ── */
.ev-unit { background: var(--card); border: 1px solid var(--border); border-radius: 12px;
  padding: 22px 24px; margin-bottom: 16px; }
.ev-unit.is-empty { background: var(--bg); }
.ev-unit-head { display: flex; justify-content: space-between; align-items: baseline; gap: 16px;
  flex-wrap: wrap; padding-bottom: 12px; margin-bottom: 16px; border-bottom: 1px solid var(--border-soft); }
.ev-unit-head h3 { font-size: 19px; font-weight: 600; letter-spacing: -0.01em; }
.ev-unit-meta { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.ev-lv { font-size: 12px; font-weight: 600; color: var(--teal-dark); background: var(--brand-soft);
  border: 1px solid var(--brand-line); border-radius: 999px; padding: 2px 10px; }

/* align-items:start, so a unit with only one end of the pair does not stretch the
   missing side into a column of empty box. "No pre-test completed." is one line
   of evidence and should occupy one line. */
.ev-phases { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: start; }
@media (max-width: 860px) { .ev-phases { grid-template-columns: 1fr; } }
.ev-phase { border: 1px solid var(--border); border-radius: 10px; padding: 15px 17px; background: var(--bg); }
.ev-phase-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
  padding-bottom: 10px; margin-bottom: 12px; border-bottom: 1px solid var(--border); }
.ev-phase-name { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink-2); }
.ev-phase-score { font-size: 16px; font-weight: 600; }
.ev-fact-line { display: flex; justify-content: space-between; gap: 12px; font-size: 13px;
  padding: 5px 0; border-bottom: 1px solid var(--border-soft); }
.ev-fact-line:last-of-type { border-bottom: 0; }
.ev-fact-line span { color: var(--ink-2); }
.ev-fact-line b { font-weight: 500; text-align: right; }

.ev-qstrip { display: flex; gap: 3px; flex-wrap: wrap; margin: 14px 0 4px; }
.ev-qbox { width: 30px; height: 26px; border-radius: 5px; display: inline-flex; align-items: center;
  justify-content: center; font-size: 10px; font-weight: 600; border: 1px solid var(--border);
  background: var(--card); color: var(--ink-2); }
.ev-qbox.green { background: #dcfce7; border-color: #4ade80; color: #166534; }
.ev-qbox.yellow { background: #fef9c3; border-color: #facc15; color: #854d0e; }
.ev-qbox.red { background: #fee2e2; border-color: #f87171; color: #991b1b; }
.ev-qbox.grey { background: #f1f5f9; border-color: #cbd5e1; color: #64748b; }
.ev-qbox:not(.is-static):hover { box-shadow: 0 0 0 2px var(--teal); }

/* The pre → post comparison. A subtraction, set like a subtraction. The delta
   is coloured by DIRECTION, which is arithmetic — it is never labelled with a
   word, because a word about a child is a judgement and this page makes none. */
.ev-change { margin-top: 18px; padding: 16px 18px; border-radius: 10px;
  background: var(--bg); border: 1px solid var(--border); }
.ev-change-line { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.ev-change-from, .ev-change-to { font-size: 22px; font-weight: 600; letter-spacing: -0.02em; }
.ev-change-from { color: var(--ink-2); }
.ev-change-arrow { font-size: 18px; color: var(--ink-3); }
.ev-change-delta { font-size: 13px; font-weight: 600; border-radius: 999px; padding: 4px 12px;
  border: 1px solid; }
.ev-change-delta.up { background: var(--green-bg); border-color: var(--green-bd); color: var(--green); }
.ev-change-delta.down { background: var(--red-bg); border-color: var(--red-bd); color: var(--red); }
.ev-change-delta.flat { background: var(--grey-bg); border-color: var(--border); color: var(--grey); }
.ev-change-pct { font-size: 12.5px; color: var(--ink-2); }

/* ── expandable evidence ── */
.ev-details { margin-top: 14px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--card); }
.ev-details > summary { cursor: pointer; font-size: 12.5px; font-weight: 500; color: var(--ink-2);
  padding: 11px 14px; list-style: none; }
.ev-details > summary::-webkit-details-marker { display: none; }
.ev-details > summary::before { content: "▸ "; color: var(--ink-3); }
.ev-details[open] > summary::before { content: "▾ "; }
.ev-details > summary:hover { color: var(--teal-dark); }
.ev-details[open] > summary { border-bottom: 1px solid var(--border-soft); }
.ev-fb { padding: 13px 15px; border-bottom: 1px solid var(--border-soft); }
.ev-fb:last-child { border-bottom: 0; }
.ev-fb-q { font-size: 13px; font-weight: 600; margin-bottom: 6px; }
.ev-fb-text { font-size: 13px; line-height: 1.6; color: var(--ink-2); margin-bottom: 6px; }
.ev-fb-note { font-size: 13px; line-height: 1.6; color: var(--ink); background: var(--brand-soft);
  border-left: 3px solid var(--brand-line); border-radius: 0 6px 6px 0; padding: 8px 12px; margin: 8px 0; }
.ev-fb-group { margin-bottom: 10px; }

/* ── the working-out portfolio ── */
.ev-mini-head { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .07em;
  color: var(--ink-3); margin: 16px 0 8px; }
.ev-mini-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.ev-mini { position: relative; display: block; border: 1px solid var(--border); border-radius: 8px;
  background: #fff; overflow: hidden; aspect-ratio: 4 / 3; }
.ev-mini:hover { border-color: var(--teal); box-shadow: 0 2px 10px rgba(15,118,110,.10); }
.ev-mini img { width: 100%; height: 100%; object-fit: cover; object-position: top center; display: block; }
.ev-mini-text { display: block; padding: 8px; font-size: 10.5px; line-height: 1.4; color: var(--ink-2);
  font-family: ui-monospace, Menlo, Consolas, monospace; overflow: hidden; }
.ev-mini-tag { position: absolute; left: 4px; bottom: 4px; font-size: 9.5px; font-weight: 600;
  background: rgba(15,23,42,.72); color: #fff; border-radius: 4px; padding: 1px 5px; }

.ev-work-group { border: 1px solid var(--border); border-radius: 12px; background: var(--card);
  margin-bottom: 12px; }
.ev-work-group > summary { cursor: pointer; font-size: 15px; font-weight: 600; padding: 15px 18px;
  list-style: none; display: flex; align-items: baseline; gap: 10px; }
.ev-work-group > summary::-webkit-details-marker { display: none; }
.ev-work-group > summary::before { content: "▸"; color: var(--ink-3); font-weight: 400; }
.ev-work-group[open] > summary::before { content: "▾"; }
.ev-work-group[open] > summary { border-bottom: 1px solid var(--border-soft); }
.ev-work-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; padding: 18px; }
@media (max-width: 900px) { .ev-work-grid, .ev-mini-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .ev-work-grid { grid-template-columns: 1fr; } }
.ev-work { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--bg); }
.ev-work-img { display: block; background: #fff; }
.ev-work-img img { display: block; width: 100%; height: 190px; object-fit: contain;
  object-position: center; background: #fff; }
.ev-work-img:hover { box-shadow: inset 0 0 0 2px var(--teal); }
.ev-work-text { padding: 14px; min-height: 190px; background: #fff; font-size: 12.5px; line-height: 1.6;
  font-family: ui-monospace, Menlo, Consolas, monospace; color: var(--ink-2); }
.ev-work figcaption { padding: 11px 13px; border-top: 1px solid var(--border); }
.ev-work-q { display: flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 500;
  line-height: 1.4; margin-bottom: 5px; }
.ev-work-meta { font-size: 11px; color: var(--ink-3); margin-bottom: 9px; }

/* ── history table ── */
.ev-table td, .ev-table th { padding: 10px 14px; }
.ev-num { text-align: right; white-space: nowrap; }
.ev-nowrap { white-space: nowrap; }

/* ── the footer that says what this report is ── */
.ev-foot { margin-top: 40px; padding: 22px 24px; border-radius: 12px; background: var(--card);
  border: 1px solid var(--border); }
.ev-foot p { font-size: 13px; line-height: 1.7; color: var(--ink-2); margin-bottom: 10px; }
.ev-foot p:last-child { margin-bottom: 0; }
.ev-foot b { color: var(--ink); }

/* ── the growth chart ────────────────────────────────────────────────────
   Marks are thin, the grid is recessive, and no text wears the data colour —
   the dots carry identity, the labels stay in ink. The light teal sits below
   3:1 on white, which is allowed for a two-step ordinal ramp only because every
   value it marks is also written beside it and repeated in the table under it. */
.ev-chart { display: block; width: 100%; height: auto; margin: 4px 0 18px; }
.ev-grid { stroke: var(--border-soft); stroke-width: 1; }
.ev-link { stroke-width: 2; stroke-linecap: round; opacity: .45; }
.ev-ring { fill: #fff; }                      /* the 2px surface ring, as a disc under each mark */
.ev-row-lab { font-size: 13px; font-weight: 500; fill: var(--ink); }
.ev-row-none { font-size: 11.5px; fill: var(--ink-3); }
.ev-row-d { font-size: 13px; font-weight: 600; fill: var(--ink); font-variant-numeric: tabular-nums; }
.ev-row-d.is-flat { fill: var(--ink-3); font-weight: 500; }
/* rci_band()'s own sentence, under every figure. Small and quiet, but present on
   every row — see the note in evidence_growth_chart(): muting only the flat rows
   is what made the others read as solid. */
.ev-row-rci { font-size: 9.5px; fill: var(--ink-3); }
.ev-axis-lab { font-size: 10.5px; fill: var(--ink-3); }
.ev-legend-lab { font-size: 11px; fill: var(--ink-2); }
.ev-curve { fill: var(--brand-soft); stroke: var(--brand-line); stroke-width: 1; opacity: .7; }
.ev-row-hit rect { cursor: default; }
.ev-row-hit:hover rect { fill: var(--bg); }

.ev-formula { display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap;
  background: var(--bg); border: 1px solid var(--border); border-radius: 10px;
  padding: 14px 16px; margin-bottom: 6px; }
.ev-formula-eq { font-size: 15px; white-space: nowrap; color: var(--ink);
  font-variant-numeric: tabular-nums; }
.ev-formula-eq span { border-top: 1px solid var(--ink-3); padding-top: 1px; }
.ev-formula p { font-size: 12.5px; line-height: 1.6; color: var(--ink-2); margin: 0; flex: 1 1 320px; }

.ev-th-plain { text-transform: none; font-size: 13px; }   /* "d" is a statistic, not a heading */
.ev-detect { font-size: 12px; color: var(--green); }
.ev-flat { font-size: 12px; color: var(--ink-2); }
.ev-change-d { font-size: 13px; font-weight: 600; color: var(--teal-dark);
  background: var(--brand-soft); border: 1px solid var(--brand-line);
  border-radius: 999px; padding: 3px 11px; font-variant-numeric: tabular-nums; }

/* The print masthead is invisible on screen and load-bearing on paper. */
.ev-print-head { display: none; }

/* ═══════════════════════════════════════════════════════════════════════════
 * PRINT — the second deliverable of this feature.
 *
 * A teacher will print this, or save it as a PDF and attach it to a
 * conversation with a parent or a leadership team. No PDF library is involved:
 * the browser already renders this page beautifully and already knows how to
 * paginate, so the whole "export" is a stylesheet and a Print button.
 *
 * The rules that matter:
 *   - the portal's chrome goes; the report's own masthead appears, so every
 *     sheet of paper can be identified;
 *   - <details> print OPEN, because evidence collapsed behind a triangle is
 *     evidence that did not print;
 *   - nothing splits across a page break that has to be read as one thing —
 *     a unit section, a piece of working, a comparison;
 *   - backgrounds are forced, because the three question colours ARE the
 *     information and a printer that drops them prints a different report.
 * ═══════════════════════════════════════════════════════════════════════════ */
@media print {
  @page { size: A4 portrait; margin: 14mm 12mm; }

  html, body { background: #fff !important; font-size: 11pt; }
  header, .ev-jump, .ev-controls, .ev-print-btn, .crumb, .page-head .btn,
  .loader, .loader-wrap, .hands-chip, .banner, .overlay { display: none !important; }
  main { max-width: none !important; margin: 0 !important; padding: 0 !important; }
  a { color: inherit !important; text-decoration: none !important; }

  /* Every source link is a link to a screen, not to paper — printing "View
     assessment" as a button on a page nobody can click is noise. The evidence
     itself prints; the way back to it does not. */
  .ev-actions, .ev-src, .ev-work figcaption .btn-sm { display: none !important; }

  .ev-print-head { display: block; border-bottom: 2px solid #0f172a; padding-bottom: 8px;
    margin-bottom: 16px; }
  .ev-print-title { font-size: 10pt; font-weight: 600; letter-spacing: .1em;
    text-transform: uppercase; color: #0e857a; }
  .ev-print-who { font-size: 13pt; font-weight: 600; margin-top: 3px; }

  .ev-head, .ev-summary, .ev-card, .ev-unit, .ev-work-group, .ev-details, .ev-foot,
  .tbl-wrap { box-shadow: none !important; background: #fff !important; }
  .ev-summary { border-color: #cbd5e1 !important; }
  .ev-head { padding: 0 0 12px; border: 0; border-bottom: 1px solid #e2e8f0; }
  .ev-name { font-size: 18pt; }

  /* Open everything. A collapsed section is a section that did not print. */
  details { display: block !important; }
  details > summary { list-style: none; }
  details > *:not(summary) { display: revert !important; }
  .ev-details > summary::before, .ev-work-group > summary::before { content: "" !important; }

  /* Pages are FILLED, not sectioned. An earlier draft forced a page break before
     every section, which reads tidily on screen and prints a document that is a
     third white space — and a teacher printing twenty-eight of these notices.
     Instead: nothing that must be read as one thing is allowed to split, a
     heading may not be the last thing on a page, and the browser fills the rest. */
  .ev-section { margin: 0 0 18px; }
  /* What may not split: anything that has to be read as one thing. A whole unit
     section is NOT on this list, and neither is one phase of it: with the stored
     feedback printed open, either is routinely taller than a page, and a rule the
     engine has to ignore is a rule that only wastes the page before it. What is
     on the list is what a reader would lose by having it split — one feedback
     entry, one piece of working, one comparison, one band profile. */
  .ev-work, .ev-change, .ev-diag-unit, .ev-fb, .ev-count, .ev-mini-grid,
  .ev-card, .ev-summary, .ev-section-head, .ev-unit-head, .ev-chart, .ev-formula {
    break-inside: avoid; page-break-inside: avoid; }
  .ev-section-head, .ev-unit-head, .ev-phase-head, .ev-mini-head, .ev-sub-head {
    break-after: avoid; page-break-after: avoid; }
  table { break-inside: auto; }
  tr, th, td { break-inside: avoid; page-break-inside: avoid; }
  thead { display: table-header-group; }

  /* The colours carry meaning, so they are printed rather than economised away. */
  * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }

  .ev-work-grid { grid-template-columns: repeat(3, 1fr); gap: 10px; padding: 10px 0; }
  .ev-work-img img { height: 130px; }
  .ev-work-text { min-height: 0; }
  .ev-mini-grid { grid-template-columns: repeat(6, 1fr); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MY RESULTS — the wide page: tab bar, bell chart, ZPD chart, class results
   ═══════════════════════════════════════════════════════════════════════════ */

/* The width itself. Set on <body> by page_top($wide) so one page opts in and
   nothing else in the portal moves. The header widens with the content — a
   1400 main under an 1100 header insets the nav and reads as a broken page. */
.page-wide main { max-width: 1400px; }
/* The HEADER does not widen with it. An earlier version matched the two on the
   theory that a 1400 main under an 1100 header would look inset — in practice
   the opposite is true: the nav bar jumping wider on one tab is what reads as
   broken, because the bar is the one thing that is supposed to be identical on
   every screen. The content widens; the furniture does not. */

/* ── the tab bar ─────────────────────────────────────────────────────────── */
.mr-tabs { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 22px; padding: 0 0 18px;
  border-bottom: 1px solid var(--border); }
.mr-tab { display: inline-flex; align-items: center; gap: 8px; padding: 9px 16px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--card);
  font-size: 14px; font-weight: 500; color: var(--ink-2); cursor: pointer;
  transition: background .12s, border-color .12s, color .12s; }
.mr-tab:hover { border-color: var(--teal); color: var(--ink); }
/* The open tab is filled, not merely outlined: on a row of eight pills an
   outline-only active state is lost the moment one of them is hovered. */
.mr-tab.active { background: var(--teal); border-color: var(--teal); color: #fff; }
.mr-tab.active:hover { color: #fff; }
.mr-tab .mr-tab-sym { font-size: 15px; line-height: 1; }
.mr-tab.is-empty { opacity: .55; }
/* ...but never the tab you are standing on: a faded active tab reads as disabled. */
.mr-tab.active.is-empty { opacity: 1; }

/* ── the two charts, side by side ────────────────────────────────────────── */
/* align-items:start so a tall per-student growth chart does not stretch the ZPD
   card beside it into a card that is mostly empty space. */
/* The growth table on the right wants a little more room than the chart: nine
   columns of figures do not shrink the way an SVG does. */
.mr-charts { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: 20px; margin-bottom: 20px; align-items: start; }
@media (max-width: 1100px) { .mr-charts { grid-template-columns: 1fr; } }
.mr-chart-card { background: var(--card); border: 1px solid var(--border); border-radius: 8px;
  padding: 20px; display: flex; flex-direction: column; }
.mr-chart-card > svg { width: 100%; height: auto; display: block; margin-top: 6px; }
.mr-chart-head { display: flex; justify-content: space-between; align-items: baseline;
  gap: 16px; margin-bottom: 4px; }
.mr-chart-head h2 { font-size: 12px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .08em; color: var(--ink-2); margin: 0; flex: 0 0 auto; }
.mr-chart-head .sub { text-align: right; flex: 1 1 auto; min-width: 0; }
.mr-chart-note { font-size: 12px; color: var(--ink-3); margin: 12px 0 0; line-height: 1.55; }
.mr-chart-empty { display: flex; align-items: center; justify-content: center; text-align: center;
  min-height: 220px; color: var(--ink-3); font-size: 13px; padding: 20px; line-height: 1.6; }

/* ── student growth chart — one row per child, unit tabs on My Results ─────
   Pre and post are told apart by HOLLOW vs FILLED first and by colour second,
   so the pairing survives a colourblind reader, a greyscale print and the
   forced-colours mode a school laptop may be locked into. The line between them
   only carries a hue when it goes the wrong way. */
.sg-wrap { position: relative; margin: 6px 0 2px; }
.sg-svg { width: 100%; height: auto; display: block; overflow: visible; }
.sg-grid { stroke: #e2e8f0; stroke-width: 1; }

/* ── the bell, above the students, on the students' own axis ───────────────
   Pre is grey and filled, post is teal and drawn over it, so "the class moved
   right" is the first thing the shape says. Filled-vs-line is doing the work
   before colour is: on a greyscale print or in forced-colours the solid mass is
   still the before and the outline is still the after. Neither curve carries a
   y axis — they are shape, not quantity; the counts are the rows underneath. */
/* Held light: the student rows are drawn ON these, and a fill that reads as a
   solid mass takes the dots with it. Between them they still tell the story —
   grey where the class was, teal where it got to. */
.sg-curve-area.pre { fill: #cbd5e1; opacity: .34; }
.sg-curve-area.post { fill: var(--brand); opacity: .12; }
.sg-curve { fill: none; stroke-linejoin: round; }
.sg-curve.pre { stroke: #94a3b8; stroke-width: 1.5; }
.sg-curve.post { stroke: var(--teal); stroke-width: 2.5; }
.sg-mean { stroke-width: 1; stroke-dasharray: 3 4; }
.sg-mean.pre { stroke: #94a3b8; }
.sg-mean.post { stroke: var(--teal); }
.sg-mean-lab { font-size: 11px; font-weight: 600; fill: var(--teal-dark);
  paint-order: stroke; stroke: #fff; stroke-width: 3px; stroke-linejoin: round; }
.sg-tick { font-size: 10px; fill: var(--ink-3); }
.sg-lab { font-size: 11px; fill: var(--ink-3); letter-spacing: .04em; }
.sg-name { font-size: 11px; fill: var(--ink-2); }
.sg-row.down .sg-link-halo { opacity: .9; }
.sg-hit { fill: transparent; }
.sg-link { stroke: #94a3b8; stroke-width: 2.5; stroke-linecap: round; }
/* Drawn under every link so the row survives crossing either curve. */
.sg-link-halo { stroke: #fff; stroke-width: 5.5; stroke-linecap: round; opacity: .85; }
.sg-row.down .sg-link { stroke: #f0a04b; }        /* decline: a hue, not an alarm */
.sg-dot.pre { fill: #fff; stroke: #64748b; stroke-width: 2; }
.sg-dot.post { fill: var(--teal); stroke: #fff; stroke-width: 1.5; }
.sg-dot.post.flat { stroke: none; }
.sg-row.down .sg-dot.post { fill: #c2701c; }
.sg-delta { font-size: 11px; font-weight: 600; fill: var(--teal-dark);
  font-variant-numeric: tabular-nums; }
.sg-row.down .sg-delta { fill: var(--amber); }
.sg-row.flat .sg-delta { font-weight: 500; fill: var(--ink-3); }
/* Pre-test only: a quiet label, because it reports missing data rather than a
   result, and a bold one beside "+29 pp" would read as a bigger fact than it is. */
.sg-row.solo .sg-delta { font-weight: 500; fill: var(--ink-3); font-style: italic; }

/* Hovering one row quiets the others. Pure CSS, so it costs nothing and cannot
   get out of step with the tooltip; the row keeps its position either way. */
.sg-svg:hover .sg-row { opacity: .38; transition: opacity .1s; }
.sg-svg .sg-row:hover, .sg-svg .sg-row:focus-visible { opacity: 1; outline: none; }
.sg-row:focus-visible .sg-name { fill: var(--teal-dark); font-weight: 600; }
.sg-svg:hover .sg-row:hover .sg-link { stroke-width: 3.5; }
/* The dots grow with the line, so the highlight reads on a solo row too — that
   row has no line to thicken, and dimming everything else was the only signal it
   had. `r` as a CSS geometry property; .flat is excluded because its inner dot is
   deliberately small and growing it would swallow the hollow one around it. */
.sg-svg .sg-row:hover .sg-dot:not(.flat),
.sg-svg .sg-row:focus-visible .sg-dot:not(.flat) { r: 6.5; }
.sg-svg .sg-row:hover .sg-name,
.sg-svg .sg-row:hover .sg-delta { font-weight: 700; }
@media (prefers-reduced-motion: reduce) { .sg-svg:hover .sg-row { transition: none; } }

.sg-tip { display: none; position: absolute; z-index: 5; pointer-events: none;
  background: var(--ink); color: #fff; border-radius: 8px; padding: 8px 11px;
  font-size: 12px; line-height: 1.5; max-width: 260px;
  box-shadow: 0 8px 24px rgba(15, 23, 42, .28); }
.sg-tip .tip-name { font-weight: 600; margin-bottom: 2px; }
.sg-tip .tip-line { font-size: 13px; }
.sg-tip .tip-sub { color: #cbd5e1; font-size: 11px; margin-top: 3px; }

.sg-legend { display: flex; gap: 16px; flex-wrap: wrap; align-items: center;
  font-size: 12px; color: var(--ink-2); border-top: 1px solid var(--border-soft);
  margin-top: 10px; padding-top: 10px; }
.sg-legend .sub { margin-left: auto; }
.sg-key { width: 11px; height: 11px; border-radius: 50%; display: inline-block;
  vertical-align: -1px; margin-right: 6px; }
.sg-key.pre { background: #fff; border: 2px solid #64748b; }
.sg-key.post { background: var(--teal); }
.sg-key.line-down { width: 20px; height: 3px; border-radius: 2px; background: #f0a04b;
  vertical-align: 2px; }

/* ── the diagnostic's way in, at the right-hand end of the unit tabs ─────── */
/* Overview starts the right-hand group; the diagnostic sits beside it, not
   pushed away from it. The auto margin moved here from .mr-tab-diag when
   Overview moved to the end of the row on 25 Aug 2026. */
.mr-tab-ov { margin-left: auto; }
.mr-tab-diag { border-color: var(--brand-line); background: var(--brand-soft);
  color: var(--teal-dark); font-weight: 550; }
.mr-tab-diag:hover { border-color: var(--teal); color: var(--teal-dark); }
@media (max-width: 900px) { .mr-tab-ov, .mr-tab-diag { margin-left: 0; } }

/* One chip per unit on a diagnostic row: the unit's symbol and the level the
   paper suggested for it. A suggestion, never a setting — the level is set on
   the child's own page. */
.res-levels { display: flex; gap: 5px; flex-wrap: wrap; align-items: center; margin-left: auto;
  padding-left: 8px; }
.lvl-chip { display: inline-flex; align-items: center; gap: 4px; padding: 3px 7px; border-radius: 6px;
  background: var(--brand-soft); border: 1px solid var(--brand-line); font-size: 11px;
  color: var(--teal-dark); font-variant-numeric: tabular-nums; }
.lvl-chip.none { background: var(--grey-bg); border-color: var(--border); color: var(--ink-3); }
.lvl-chip .lvl-u { font-size: 11px; opacity: .8; }
.lvl-chip .lvl-n { font-weight: 700; }

/* ── student growth table — the card beside the chart ────────────────────── */
.sgt-scroll { overflow-x: auto; margin-top: 4px; }
.sgt { border-collapse: collapse; width: 100%; font-size: 12px; white-space: nowrap; }
.sgt th { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-3); text-align: right; padding: 6px 8px; border-bottom: 1px solid var(--border); }
.sgt td { text-align: right; padding: 6px 8px; border-bottom: 1px solid var(--border-soft); }
.sgt .sgt-l { text-align: left; }
.sgt-name { font-weight: 600; color: var(--ink); }
.sgt-id { color: var(--ink-3); font-variant-numeric: tabular-nums; }
.sgt-num { font-variant-numeric: tabular-nums; color: var(--ink-2); }
.sgt-none { color: var(--ink-3); }
.sgt td.up { color: var(--teal-dark); font-weight: 600; }
.sgt td.down { color: var(--amber); font-weight: 600; }
.sgt td.flat { color: var(--ink-3); }
.sgt tbody tr:hover td { background: var(--border-soft); }
.sgt tfoot td { border-top: 1px solid var(--border); border-bottom: 0; padding-top: 8px;
  color: var(--ink); font-weight: 600; }
.sgt tfoot td.sgt-none { color: var(--ink-3); font-weight: 500; }

/* ── the two drop-down tiles under the bell chart ──────────────────────────
   Student growth and Question difficulty are the same component: a closed
   <details> in its own card, the second and third questions a teacher asks on
   this page. They are styled identically on purpose — a teacher should not have
   to learn that one of them opens differently from the other. */
.mr-zpd > summary,
.mr-sgt > summary { font-weight: 600; color: var(--ink); font-size: 13px; }
/* max-width:none is load-bearing, not tidying: .mr-zpd-body carried
   max-width:820px from when this chart sat in a half-width card, and the drop-
   down is now the full width of the page. */
.mr-zpd-body,
.mr-sgt-body { margin: 10px 0 0; max-width: none; }
.mr-zpd-body > svg { width: 100%; height: auto; display: block; }

/* ── bell chart ────────────────────────────────────────────────────────────
   Full bleed as of 24 Aug 2026: it is the first thing on a unit tab and the one
   chart on the page that describes the class as a whole, so it gets the width.
   The SVG is drawn at a 1120-wide viewBox and scales down from there; nothing
   here fixes a pixel width, so a 13" laptop and a projector both get the whole
   shape rather than a cropped one. */
/* ── THE GROWTH CARD IS WIDER THAN THE PAGE IT SITS IN ─────────────────────
   Every other card on My Results is the width of `main` (1400 on this page,
   set by .page-wide). This one breaks out past that and nothing else does.

   Why only this one: it is the only element here whose job is a SHAPE. The
   roster, the tables and the drop-downs are lists — they are read line by line
   and extra width just lengthens the line a reader's eye has to track back.
   Two overlapping distributions with twenty-two dumbbells drawn across them are
   read all at once, and every pixel of width is another pixel of separation
   between two children on adjacent marks.

   The breakout is bounded twice. 1680px is the ceiling, so on a 27" monitor the
   card stops rather than becoming a letterbox. calc(100vw - 56px) is the floor,
   so it never reaches the edge of the window and never causes a sideways
   scrollbar — and on any screen narrower than `main` it simply resolves back to
   the width of the page, which is what a laptop and a tablet get.

   left:50% + translateX(-50%) rather than negative margins: the card does not
   need to know how wide its parent is, so this keeps working if `main` ever
   changes width or the page stops being .page-wide. */
.mr-bell-card {
  padding: 20px 22px 18px;
  position: relative;
  /* max(100%, …) is the part that is easy to get wrong. Without it, a viewport
     of exactly 1400 — this page's own design width — resolves the breakout to
     1344 while every ordinary card is 1368, and the one card that is meant to be
     the widest on the page renders NARROWER than its neighbours. 100% is the
     card's normal width, so the breakout can only ever add. */
  width: max(100%, min(1680px, calc(100vw - 56px)));
  max-width: none;
  left: 50%;
  transform: translateX(-50%);
}
/* Below the breakout point there is nothing to break out to, and the transform
   would only round the card's edges off the grid. */
@media (max-width: 1200px) {
  .mr-bell-card { width: auto; left: auto; transform: none; }
}
.mr-bell-body { margin: 8px 0 0; }
.bell-svg { width: 100%; height: auto; display: block; overflow: visible; }

.bell-bar { fill: #3fb8aa; transition: fill .12s; }
.bell-bar-neg { fill: #f0a04b; }          /* decline: a different hue, not a red alarm */
.bell-bar-target { fill: transparent; }   /* hover the whole band, not the bar */
.bell-bar-hit { cursor: default; }
.bell-bar-hit:hover .bell-bar,
.bell-bar-hit:hover .bell-bar-neg { fill: #0e857a; }

/* The curve. DASHED means it was drawn from this class's own average and spread
   and describes these children; SOLID means cohort scale was reached and it is
   a fitted normal curve describing a population. The difference is a claim about
   what the picture is entitled to say, so it is carried by the stroke and not
   only by the caption — see the header of inc/bell_chart.php. */
.bell-curve { fill: none; stroke: #0e857a; stroke-width: 2.5; stroke-linejoin: round; }
.bell-curve-sample { stroke-dasharray: 6 5; opacity: .9; }
.bell-curve-fitted { stroke-dasharray: none; opacity: 1; }
.bell-curve-fill { fill: #0e857a; opacity: .07; }
.bell-mean { stroke: #0e857a; stroke-width: 1.5; stroke-dasharray: 3 4; opacity: .7; }
.bell-mean-lab { fill: #0e857a; font-weight: 600;
  paint-order: stroke; stroke: #ffffff; stroke-width: 3px; stroke-linejoin: round; }

.bell-axis { stroke: #cbd5e1; stroke-width: 1; }
.bell-zero { stroke: #94a3b8; stroke-width: 1.5; stroke-dasharray: 3 3; }
.bell-tick { font-size: 11px; fill: #64748b; }
.bell-lab { font-size: 11px; fill: #475569; }
.bell-ylab { fill: #94a3b8; letter-spacing: .06em; }
.bell-n { font-size: 12px; fill: #0f172a; font-weight: 600; }

/* The key sits ABOVE the chart, which is why it does not reuse .bell-legend —
   that class belongs to an orphaned per-student chart and carries a border-top. */
.bell-keys { display: flex; gap: 18px; flex-wrap: wrap; align-items: center;
  font-size: 12px; color: var(--ink-2); margin: 2px 0 6px; }
.bell-key { display: inline-flex; align-items: center; gap: 7px; }
.bell-key-sub { color: var(--ink-3); }
.bell-swatch { display: inline-block; }
.bell-swatch-bar { width: 12px; height: 12px; border-radius: 3px; background: #3fb8aa; }
.bell-swatch-curve-sample, .bell-swatch-curve-fitted { width: 22px; height: 0;
  border-top: 2.5px solid #0e857a; }
.bell-swatch-curve-sample { border-top-style: dashed; }

/* The per-student dots, in the same card under the bell chart. A rule rather
   than a second card: they are the children the bars are made of, and putting a
   card boundary between a distribution and the children in it invites reading
   them as two separate findings. */
.mr-students { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border-soft); }
.mr-students .sg-legend { border-top: 0; padding-top: 6px; }

/* ── ZPD chart ───────────────────────────────────────────────────────────── */
.zpd-band { fill: #0e857a; opacity: .07; }
.zpd-band-edge { stroke: #0e857a; stroke-width: 1; stroke-dasharray: 4 4; opacity: .35; }
.zpd-axis { stroke: #cbd5e1; stroke-width: 1; }
.zpd-tick { font-size: 11px; fill: #64748b; }
/* The band label sits inside the plot, so it needs to survive a tall bar landing
   under it: paint-order draws the halo first and the glyphs on top. */
.zpd-zone-lab { font-size: 10.5px; fill: #64748b; text-transform: uppercase; letter-spacing: .07em;
  paint-order: stroke; stroke: #ffffff; stroke-width: 3px; stroke-linejoin: round; }
.zpd-qn { font-size: 9.5px; fill: #94a3b8; }
/* A 40-question diagnostic prints every number now that the bars are sorted and
   a missing one cannot be inferred from its neighbours. They only fit smaller. */
.zpd-qn-tight { font-size: 8px; letter-spacing: -.02em; }
.zpd-wrap { margin-top: 4px; }
.zpd-wrap svg { width: 100%; height: auto; display: block; overflow: visible; }

/* One bar per question. Height carries the reading; colour confirms it, which is
   why the three zone hues no longer need a number printed on each mark. */
.zpd-bar { cursor: default; }
.zpd-bar.hard { fill: #d1495b; }
.zpd-bar.zone { fill: #0e857a; }
.zpd-bar.secure { fill: #94a3b8; }
.zpd-bar-target { fill: transparent; }
.zpd-bar-hit:hover .zpd-bar { fill: #0f172a; }

.zpd-legend { display: flex; gap: 18px; flex-wrap: wrap; align-items: center; margin-bottom: 10px; }
.zpd-key { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; color: var(--ink-2); }
.zpd-swatch { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }
.zpd-swatch.hard { background: #d1495b; }
.zpd-swatch.zone { background: #0e857a; }
.zpd-swatch.secure { background: #94a3b8; }

/* ── the results controls row ────────────────────────────────────────────── */
.mr-phase { display: inline-flex; border: 1px solid var(--border); border-radius: 8px;
  overflow: hidden; background: var(--card); }
.mr-phase a { padding: 7px 14px; font-size: 13px; color: var(--ink-2); border-right: 1px solid var(--border); }
.mr-phase a:last-child { border-right: 0; }
.mr-phase a.on { background: var(--teal); color: #fff; }
.mr-phase a.off { opacity: .45; pointer-events: none; }

/* The strip must not wrap: 24 squares in one line is what makes a row scannable
   at a glance. Below the wide breakpoint it scrolls inside its own track
   instead, which keeps the page itself from scrolling sideways. */
.mr-strip-wrap { overflow-x: auto; }

/* One paper, one line.
   .res-strip wraps by default, which is right at 1100px where the strip has
   half a row to live in. On My Results the page is wide specifically so that a
   24-question paper reads as one strip: a wrapped strip puts Q16 under Q1 and
   the second row scans as a second paper. So on this page the boxes are
   compact, the strip does not wrap, and if the viewport is too narrow for 24 of
   them the strip scrolls inside its own track rather than the page scrolling
   sideways. */
.page-wide .res-strip { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 2px;
  /* min-width:0 is load-bearing, not tidying. A flex item defaults to
     min-width:auto, which means it refuses to shrink below its own content —
     so overflow-x:auto never engages and 24 boxes push the PAGE sideways
     instead of scrolling inside the strip. */
  min-width: 0; }
.page-wide .res-strip .qbox { width: 32px; height: 32px; font-size: 10px; flex: 0 0 auto; }
.page-wide .res-who { min-width: 236px; flex: 0 0 236px; }
.page-wide .res-go { flex: 0 0 auto; }
/* The diagnostic chip is a <span>, not a link — it is where you already are. */
.mr-tab:not([href]) { cursor: default; }
/* A 54-question diagnostic will not fit even at 1400. Show the track so the
   strip reads as scrollable rather than as a paper that stops at Q24. */
.page-wide .res-strip { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
.page-wide .res-strip::-webkit-scrollbar { height: 6px; }
.page-wide .res-strip::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
.page-wide .res-strip::-webkit-scrollbar-track { background: transparent; }

/* The Overview's unit-by-unit row: the figure and its interval bar want room,
   but not at the cost of pushing the page sideways on a phone. */
.mr-unit-figure { flex: 1; min-width: 280px; }
@media (max-width: 900px) {
  /* Below the wide breakpoint none of the fixed widths above are affordable.
     Hand the row back to normal flow — it wraps, which is the right answer at
     this size, and is what every other page in the portal already does. */
  .page-wide .res-who { flex: 1 1 auto; min-width: 200px; }
  .page-wide .res-strip { flex: 1 1 100%; }
  .mr-unit-figure { min-width: 0; flex: 1 1 100%; }
  .mr-tabs { gap: 6px; padding-bottom: 14px; }
  .mr-tab { padding: 8px 12px; font-size: 13px; }
  /* The card heading is flex:0 0 auto so it never truncates beside its hint.
     At phone width that is what pushes the card wider than the screen, so here
     the pair stacks and the hint left-aligns under the heading. */
  .mr-chart-head { flex-wrap: wrap; }
  .mr-chart-head h2 { flex: 1 1 100%; min-width: 0; }
  .mr-chart-head .sub { text-align: left; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE QUESTION STRIP — four resting treatments, one expanded state
   ═══════════════════════════════════════════════════════════════════════════
   Fourteen students times twenty-four questions is 336 chips on one screen, and
   the first attempt at calming it only made each chip smaller. That missed the
   real problem: it is not the ink, it is the OBJECT COUNT. Twenty-four bordered
   shapes with gaps between them read as twenty-four things whatever size they
   are, so the eye has 336 things to triage before it can start.

   So the resting treatment is now a choice, set by a class on <body>, and every
   one of them attacks the count rather than the size:

     mark-band        one continuous barcode per student — no gaps, no borders,
                      so a row is ONE object. 336 -> 14. Position is kept, so a
                      question the whole class missed still shows as a stripe
                      down the column. This is the default.
     mark-exceptions  correct recedes to a neutral tick; only a wrong answer or
                      a flagged method spends ink. Right answers are the boring,
                      expected state and do not need to be drawn.
     mark-ghost       no colour at rest at all — twenty-four faint ticks that
                      say "there is a paper here" and nothing more.
     mark-summary     one proportion bar per student. The calmest, and the only
                      one that gives up per-question position for it.

   Every treatment expands to the same labelled squares on the hovered row, and
   the two rules from the first attempt still hold: the strip reserves its full
   height and the boxes keep their width, so the reveal is growth in place and
   nothing on the row or below it moves.
   ═══════════════════════════════════════════════════════════════════════════ */

.page-wide .res-strip { align-items: center; min-height: 34px; position: relative; }

/* The mark colours for the resting treatments. Deeper than the .qbox borders on
   purpose: at 9px tall with no border to define it, a fill has to carry the
   whole mark. This trio is validated — L band, chroma floor, CVD separation and
   normal-vision separation all pass; the sub-3:1 contrast warning is relieved by
   the labels that appear on hover and by the score printed on every row. */
.page-wide { --mk-g:#10b981; --mk-y:#f59e0b; --mk-r:#ef4444; --mk-e:#cbd5e1;
             --mk-none:#e8edf3; }

.page-wide .res-strip .qbox {
  font-size: 0;
  transition: height .2s cubic-bezier(.2,.7,.3,1),
              border-radius .2s cubic-bezier(.2,.7,.3,1),
              background .2s, border-color .2s, opacity .16s,
              font-size .12s ease-out;
}

/* ── mark-band — one continuous barcode ───────────────────────────────────
   The gap is what makes 24 shapes; take it away and the row is one mark. */
.page-wide.mark-band .res-strip { gap: 0; }
.page-wide.mark-band .res-strip .qbox {
  height: 9px; border-radius: 0; border-color: transparent;
}
.page-wide.mark-band .res-strip .qbox:first-child { border-radius: 5px 0 0 5px; }
.page-wide.mark-band .res-strip .qbox:last-child  { border-radius: 0 5px 5px 0; }
.page-wide.mark-band .res-strip .qbox.green  { background: var(--mk-g); }
.page-wide.mark-band .res-strip .qbox.yellow { background: var(--mk-y); }
.page-wide.mark-band .res-strip .qbox.red    { background: var(--mk-r); }
.page-wide.mark-band .res-strip .qbox.grey   { background: var(--mk-e); }

/* ── mark-exceptions — ink only what needs acting on ──────────────────────── */
.page-wide.mark-exceptions .res-strip .qbox { height: 10px; border-radius: 5px; border-color: transparent; }
.page-wide.mark-exceptions .res-strip .qbox.green  { background: var(--mk-none); }
.page-wide.mark-exceptions .res-strip .qbox.yellow { background: var(--mk-y); opacity: .55; }
.page-wide.mark-exceptions .res-strip .qbox.red    { background: var(--mk-r); }
.page-wide.mark-exceptions .res-strip .qbox.grey   { background: var(--mk-none); border: 1px dashed #cbd5e1; }

/* ── mark-ghost — presence only ───────────────────────────────────────────── */
.page-wide.mark-ghost .res-strip .qbox {
  height: 5px; border-radius: 3px; border-color: transparent; background: var(--mk-none);
}

/* ── mark-summary — one proportion bar, no per-question position ──────────── */
.qsum { position: absolute; inset: 0; display: none; align-items: center; }
.page-wide.mark-summary .qsum { display: flex; }
.page-wide.mark-summary .res-strip .qbox { opacity: 0; height: 9px; border-color: transparent; }
.qsum-track { display: flex; width: 100%; height: 9px; border-radius: 5px; overflow: hidden;
  background: var(--mk-none); }
.qsum-seg { height: 100%; }
.qsum-seg.green  { background: var(--mk-g); }
.qsum-seg.yellow { background: var(--mk-y); }
.qsum-seg.red    { background: var(--mk-r); }
.qsum-seg.grey   { background: var(--mk-e); }
.page-wide .res-row:hover .qsum, .page-wide .res-row:focus-within .qsum { opacity: 0; }
.qsum { transition: opacity .14s; }

/* ── the expanded state — identical whichever treatment is resting ────────── */
.page-wide .res-row:hover  .res-strip,
.page-wide .res-row:focus-within .res-strip { gap: 5px; }
.page-wide .res-row:hover  .res-strip .qbox,
.page-wide .res-row:focus-within .res-strip .qbox {
  height: 32px; border-radius: 8px; font-size: 10px; opacity: 1;
  transition-delay: calc(var(--qi, 0) * 7ms);   /* the sweep — ~170ms across 24 */
}
/* Back to each treatment's own fill and border when the squares are out. */
.page-wide .res-row:hover .res-strip .qbox.green,
.page-wide .res-row:focus-within .res-strip .qbox.green  { background:#dcfce7; border-color:#4ade80; }
.page-wide .res-row:hover .res-strip .qbox.yellow,
.page-wide .res-row:focus-within .res-strip .qbox.yellow { background:#fef9c3; border-color:#facc15; }
.page-wide .res-row:hover .res-strip .qbox.red,
.page-wide .res-row:focus-within .res-strip .qbox.red    { background:#fee2e2; border-color:#f87171; }
.page-wide .res-row:hover .res-strip .qbox.grey,
.page-wide .res-row:focus-within .res-strip .qbox.grey   { background:#f1f5f9; border-color:#cbd5e1; border-style:solid; }

/* Leaving collapses at once rather than un-sweeping in reverse: the sweep is an
   arrival, and running it backwards reads as the row still doing something
   after the cursor has gone. */
.page-wide .res-strip .qbox { transition-delay: 0ms; }

/* The row says it is the hit target, and says it before you commit to it. */
.page-wide .res-row { border-radius: 8px; transition: background .14s; }
.page-wide .res-row:hover,
.page-wide .res-row:focus-within { background: var(--border-soft); }

/* ── THE GLYPH ─────────────────────────────────────────────────────────────
   The mark colours measure 1.18:1 green-to-yellow and 1.75:1 yellow-to-red
   against each other; docs/35-my-results.md concedes that and relies on "the
   labels that appear on hover", on a product whose primary device has no hover.
   So the expanded square carries a shape as well as a fill. It is hidden at
   rest for the same reason the number is: at 9px there is no room, and the
   resting bar is a summary, not a reading. Every square also carries an
   aria-label naming its state — see inc/_class_results.php. */
.res-strip .qbox .qb-g { display: none; }
.page-wide .res-row:hover .res-strip .qbox .qb-g,
.page-wide .res-row:focus-within .res-strip .qbox .qb-g,
.page-wide .qs-toggle[open] ~ .res-strip .qbox .qb-g { display: inline; margin-right: 2px; }

/* Screen-reader-only text. Used to give each row's disclosure a name that says
   WHOSE questions it opens — twenty-eight controls all called "Question by
   question" is a list a screen-reader user cannot navigate. */
.vh { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* ── TOUCH, BELOW THE WIDE BREAKPOINT ──────────────────────────────────────
   iPad Safari matches hover: none, and the old rule here handed it the full
   labelled strip always: 24 squares at 32px plus gaps is 883px of content in a
   slot around 600px wide, inside a track that scrolls on its own. Twenty-eight
   children meant twenty-eight independent horizontal scrollbars, and comparing
   question 19 across a class meant dragging twenty-eight of them to the same
   offset.

   So at rest a row is the barcode — one object, position kept, sized to fit
   whatever width there is rather than to a fixed 32px, so nothing scrolls. The
   labelled squares are still one tap away, through the per-row disclosure. */
@media (hover: none) {
  .qsum { display: none !important; }

  .page-wide .res-strip { gap: 0; overflow-x: hidden; }
  .page-wide .res-strip .qbox {
    flex: 1 1 0; width: auto; min-width: 0; height: 9px;
    border-radius: 0; border-color: transparent; font-size: 0; opacity: 1;
  }
  .page-wide .res-strip .qbox:first-child { border-radius: 5px 0 0 5px; }
  .page-wide .res-strip .qbox:last-child  { border-radius: 0 5px 5px 0; }
  .page-wide .res-strip .qbox.green  { background: var(--mk-g); }
  .page-wide .res-strip .qbox.yellow { background: var(--mk-y); }
  .page-wide .res-strip .qbox.red    { background: var(--mk-r); }
  .page-wide .res-strip .qbox.grey   { background: var(--mk-e); }

  /* Opened: the labelled squares, WRAPPED rather than scrolled. A wrapped strip
     reads as a second paper on a wide screen, which is why it is refused there —
     at this width there is no wide screen to protect and a second line beats a
     track nobody can see the end of. */
  .page-wide .qs-toggle[open] ~ .res-strip { flex-wrap: wrap; gap: 5px; }
  .page-wide .qs-toggle[open] ~ .res-strip .qbox {
    flex: 0 0 auto; width: 34px; height: 34px; border-radius: 8px; font-size: 10px;
    border-color: #cbd5e1;
  }
  .page-wide .qs-toggle[open] ~ .res-strip .qbox.green  { background:#dcfce7; border-color:#4ade80; }
  .page-wide .qs-toggle[open] ~ .res-strip .qbox.yellow { background:#fef9c3; border-color:#facc15; }
  .page-wide .qs-toggle[open] ~ .res-strip .qbox.red    { background:#fee2e2; border-color:#f87171; }
  .page-wide .qs-toggle[open] ~ .res-strip .qbox.grey   { background:#f1f5f9; border-color:#cbd5e1; }
}

/* The disclosure itself. It has no body: what it discloses is its own sibling,
   because the strip has to stay a direct child of the row for the layout and the
   pointer hover to keep working, and CSS can read [open] on a preceding sibling
   but cannot reach out of an element. No JavaScript, and <summary> is already a
   button with an expanded state for a screen reader. Hidden on a pointer device,
   where hovering the row does this and a second control would be clutter. */
.qs-toggle { display: none; }
@media (hover: none) {
  .qs-toggle { display: block; flex: none; order: 3; width: 100%; margin-top: 6px; }
  .qs-toggle > summary { display: inline-block; cursor: pointer; list-style: none;
    font-size: 12px; font-weight: 500; color: var(--teal);
    padding: 11px 4px; min-height: 44px; }
  .qs-toggle > summary::-webkit-details-marker { display: none; }
  .qs-toggle > summary::before { content: "▸ "; }
  .qs-toggle[open] > summary::before { content: "▾ "; }
}
@media (hover: none) and (min-width: 1201px) {
  /* A wide touch screen has room for the labelled strip and no scrollbar
     problem to solve, so it keeps what it had. */
  .qs-toggle { display: none; }
  .page-wide .res-strip { gap: 5px; }
  .page-wide .res-strip .qbox { flex: 0 0 auto; width: 32px; height: 32px;
    border-radius: 8px; font-size: 10px; opacity: 1; border-color: #cbd5e1; }
  .page-wide .res-strip .qbox.green  { background:#dcfce7; border-color:#4ade80; }
  .page-wide .res-strip .qbox.yellow { background:#fef9c3; border-color:#facc15; }
  .page-wide .res-strip .qbox.red    { background:#fee2e2; border-color:#f87171; }
  .page-wide .res-strip .qbox.grey   { background:#f1f5f9; border-color:#cbd5e1; }
  .page-wide .res-strip .qbox .qb-g { display: inline; margin-right: 2px; }
}

/* ── TOUCH TARGETS ─────────────────────────────────────────────────────────
   These controls render at 21–35px, which is under every published minimum and
   is being tapped by an adult standing up, holding an iPad, in a room with
   twenty-eight children in it. pointer: coarse only, so the desktop design is
   untouched — a 44px button in a dense roster row is a different screen. */
@media (pointer: coarse) {
  .btn, .btn-sm, .btn-ghost, select, .stu-emoji, .inc-switch { min-height: 44px; }
  .btn, .btn-sm, .btn-ghost, select { display: inline-flex; align-items: center; }
  .stu-emoji { min-width: 44px; }
  /* The switch grows its TARGET, not its track: the knob is positioned against
     a 42×23 pill and moving that would break the on/off geometry. The label is
     the thing a finger hits. */
  .inc-switch { display: inline-flex; align-items: center; }
}

/* The suggestion caveat, beside the chips it qualifies rather than inside a
   title attribute a tablet never shows. */
.lvl-note { flex: 1 1 100%; text-align: right; margin-bottom: 2px; }

@media (prefers-reduced-motion: reduce) {
  .page-wide .res-strip .qbox, .qsum { transition: none; transition-delay: 0ms !important; }
}

/* ── The school's own logo, in the header ────────────────────────────────────
   Sits between the account block and Sign out, separated by a hairline so the
   two marks in that corner — the school's badge and the way out — do not read
   as one control.

   Height is fixed and width is not: school badges are anything from a square
   crest to a long horizontal lockup, and constraining the height is what keeps
   the header bar the same height on every school's install. max-width stops a
   very wide lockup from pushing Sign out off the end; object-fit: contain means
   a badge that hits that limit is scaled, never cropped. The stored image is
   96px tall (3x) so it stays sharp on a retina screen. */
.school-logo {
  height: 32px; width: auto; max-width: 132px; object-fit: contain;
  display: block; flex: 0 0 auto;
  padding-left: 12px; border-left: 1px solid var(--border);
}

/* The Accounts-tab card. The preview sits on a chequerboard, because most school
   logos are supplied on transparency and a white swatch on a white card cannot
   show whether the background came with it — an admin who cannot see that
   uploads a white box and only finds out when the header looks wrong. */
.logo-preview {
  height: 48px; width: auto; max-width: 220px; object-fit: contain; display: block;
  background-color: #fff;
  background-image: linear-gradient(45deg, #eef1f5 25%, transparent 25%, transparent 75%, #eef1f5 75%),
                    linear-gradient(45deg, #eef1f5 25%, transparent 25%, transparent 75%, #eef1f5 75%);
  background-size: 12px 12px; background-position: 0 0, 6px 6px;
  border: 1px solid var(--border); border-radius: 6px; padding: 6px 10px;
}
.logo-empty {
  height: 48px; display: flex; align-items: center; padding: 0 14px;
  border: 1px dashed var(--border); border-radius: 6px;
  color: var(--ink-3); font-size: 12px;
}
.logo-file { font-size: 12px; max-width: 210px; }

@media (max-width: 900px) {
  /* The header already overflows on a phone; the badge is the one thing in it
     that carries no information the role chip does not already carry in words. */
  .school-logo { display: none; }
}

/* ── Logs (logs.php) ─────────────────────────────────────────────────────────
   A log is read by scanning down one column at a time — "when did she sign in",
   "who touched that child", "what came from that address" — so the four columns
   are fixed-width and left-aligned, and the row is quiet. Colour is carried by a
   single dot at the start of the action rather than by tinting the row: fifteen
   tinted rows on one screen is a heat map of nothing, and the dot survives being
   printed in black and white, where a background tint does not. */
.log-filters { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
.log-filters label { display: block; margin-bottom: 4px; font-size: 12px; color: var(--ink-2); }
.log-filters select, .log-filters input { width: 100%; min-width: 140px; padding: 7px 9px;
  border: 1px solid #cbd5e1; border-radius: 6px; font: inherit; font-size: 13px; background: var(--card); color: var(--ink); }
.log-filters > div { min-width: 140px; }
.log-filter-go { display: flex; align-items: center; gap: 10px; min-width: 0 !important; }
.log-clear { font-size: 13px; color: var(--ink-2); }
.log-clear:hover { color: var(--teal); }

/* The table can outgrow the page on a narrow laptop — a log that reflows into
   three lines per event stops being scannable, so it scrolls sideways inside its
   own card instead of wrapping. */
.log-scroll { overflow-x: auto; }
.log-table { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 720px; }
.log-table th { text-align: left; font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-3); padding: 0 10px 8px; border-bottom: 1px solid var(--border); }
.log-table td { padding: 9px 10px; border-bottom: 1px solid var(--border-soft); vertical-align: top; }
.log-table tr:last-child td { border-bottom: none; }
.log-row:hover td { background: var(--border-soft); }

/* The date band. Not a heading above the table but a row inside it, so it stays
   put while the columns scroll and cannot drift away from the rows it labels. */
.log-day td { background: var(--bg); font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .07em; color: var(--ink-2); padding: 7px 10px; border-bottom: 1px solid var(--border); }

.log-when { white-space: nowrap; color: var(--ink); font-variant-numeric: tabular-nums; }
.log-when span { display: block; font-size: 11px; color: var(--ink-3); }
.log-who { color: var(--ink); }
.log-who span { display: block; font-size: 11px; color: var(--ink-3); text-transform: capitalize; }
.log-ip { white-space: nowrap; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; color: var(--ink-2); }

/* The detail phrase sits under the action rather than beside it: beside it, the
   longest detail on the page sets the column width for every row. */
.log-detail { display: block; font-size: 12px; color: var(--ink-2); margin-top: 2px; }
.log-outcome { display: inline-block; margin-left: 6px; font-size: 10px; font-weight: 700;
  letter-spacing: .06em; color: var(--red); background: var(--red-bg);
  border: 1px solid var(--red-bd); border-radius: 4px; padding: 1px 5px; vertical-align: 1px; }
.log-bad td { background: #fffafa; }

.log-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  margin-right: 7px; vertical-align: 1px; background: var(--ink-3); }
.log-dot-session    { background: var(--teal); }
.log-dot-view       { background: var(--gold); }
.log-dot-assessment { background: var(--brand); }
.log-dot-report     { background: #6366f1; }
.log-dot-student    { background: #0ea5e9; }
.log-dot-school     { background: #a855f7; }
.log-dot-security   { background: var(--coral); }
.log-dot-page       { background: #cbd5e1; }

.log-pager { display: flex; align-items: center; justify-content: center; gap: 14px;
  margin-top: 16px; font-size: 12px; color: var(--ink-2); }
.log-foot { margin-top: 14px; font-size: 12px; color: var(--ink-3); line-height: 1.6; }

@media (max-width: 720px) {
  .log-filters > div { min-width: 100%; }
  .log-filter-go { min-width: 100% !important; }
}

/* ── The header with a sixth tab ─────────────────────────────────────────────
   Adding Logs to the school admin's bar took that bar from five tabs to six,
   and at the header's fixed 1100px the row no longer fits: "AI & Diagnostics"
   broke over two lines, which pushed the account name up out of the bar. The
   bar is the one piece of furniture that is supposed to look identical on every
   screen, so a bar that reflows on one role reads as a fault.

   Three small concessions, in the order they cost least:
     · tab labels never wrap — a two-line tab is what actually broke the row;
     · the gap between the wordmark and the nav gives up 8px, and each tab 2px
       of padding, which is invisible at five tabs and is what buys the sixth;
     · the account block is capped and truncates. Nothing is lost by that: the
       school's name is repeated by its badge, its alt text and its title, and
       the person's own name is the one string they never need to read.
   Everything stays on one line at five tabs and at six. */
nav button { white-space: nowrap; padding: 0 10px; }
.header-inner { gap: 20px; }
.header-right > div[style] { max-width: 260px; }
.header-right .role-chip { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ══════════════════════════════════════════════════════════════════════════
   SIGN-IN SURFACES — the marketing design system, dialled down for a tool
   ══════════════════════════════════════════════════════════════════════════
   Everything below is scoped to .login-wrap on purpose. That wrapper is the
   shell for login.php, forgot.php, reset.php, setup.php and mfa.php, so those
   five screens inherit this without being edited — and nothing inside the
   signed-in portal (password.php, twofactor.php and every other page, which
   use .card / .page-head instead) changes at all.

   Colours, radii and shadows are the tokens from the marketing site's
   assets/css/tokens.css, copied rather than imported because the portal ships
   its own stylesheet. #009E7F is the decorative green: white text on it is
   3.4:1 and fails AA, so the button uses --green-ui (4.8:1) and darkens to
   --green-800 on hover.

   The fonts are the site's own self-hosted files, copied into assets/fonts/.
   Nothing here makes a third-party request; the portal's CSP is default-src
   'self' and a remote font would be blocked as well as wrong.
   ────────────────────────────────────────────────────────────────────────── */

@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:400;font-display:swap;
  src:url('fonts/plus-jakarta-sans-latin-400-normal.woff2') format('woff2');}
@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:500;font-display:swap;
  src:url('fonts/plus-jakarta-sans-latin-500-normal.woff2') format('woff2');}
@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:600;font-display:swap;
  src:url('fonts/plus-jakarta-sans-latin-600-normal.woff2') format('woff2');}
@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:700;font-display:swap;
  src:url('fonts/plus-jakarta-sans-latin-700-normal.woff2') format('woff2');}
@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:800;font-display:swap;
  src:url('fonts/plus-jakarta-sans-latin-800-normal.woff2') format('woff2');}

.login-wrap{
  /* the marketing tokens */
  --ink:#17205E; --ink-body:#4F5691; --ink-muted:#666CA0;
  --green:#009E7F; --green-ui:#00816A; --green-800:#006B58; --green-050:#E6F5F1;
  --blue:#1878F0; --blue-ink:#0B57B8; --blue-050:#E9F2FE;
  --orange-ui:#D6371B; --orange-ink:#C42F14; --orange-050:#FFEDE9;
  --white:#FFFFFF; --paper:#FAFAF7; --band:#F6FAFD; --line:#E4EBF3;
  --r-sm:8px; --r-md:14px; --r-lg:22px; --r-pill:999px;
  --shadow-sm:0 1px 2px rgba(23,32,94,.05), 0 2px 6px rgba(23,32,94,.05);
  --shadow-md:0 2px 6px rgba(23,32,94,.05), 0 10px 26px rgba(23,32,94,.07);
  --shadow-lg:0 4px 12px rgba(23,32,94,.06), 0 24px 60px rgba(23,32,94,.10);
  --font:'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
         Helvetica, Arial, sans-serif;

  /* The sibling screens carry inline styles written against the portal's older
     names — var(--ink-2), var(--red), var(--bg-2). Rebinding them here is what
     lets forgot/reset/setup/mfa land in this palette without being touched. */
  --ink-2:var(--ink-body); --ink-3:var(--ink-muted);
  --card:var(--white); --border:var(--line); --bg:var(--band); --bg-2:var(--paper);
  --red:var(--orange-ink); --red-bg:var(--orange-050); --red-bd:#F3C4B8;
  --teal:var(--green-ui); --teal-dark:var(--green-800);
  --brand:var(--green); --brand-soft:var(--green-050); --brand-line:#BEE3DA;

  min-height:100vh;
  display:flex; align-items:center; justify-content:center;
  padding:clamp(24px,6vw,56px) 16px;
  background:
    radial-gradient(900px 460px at 50% -8%, rgba(0,158,127,.07), rgba(0,158,127,0) 62%),
    var(--band);
  font-family:var(--font);
  font-size:15px; line-height:1.6; color:var(--ink-body);
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
.login-wrap *{ font-family:inherit; }
.login-wrap img{ max-width:100%; }
.login-wrap a{ color:var(--green-ui); text-decoration:none; }
.login-wrap a:hover{ color:var(--green-800); text-decoration:underline; }

/* The focus ring itself now lives at root scope — see the :focus-visible rule
   near the top of this file — because the signed-in portal needed it too and
   having it here was why the portal did not have one. Only the radius is
   re-stated, so the ring follows the softer corners of these five screens. */
.login-wrap :where(a,button,input,select,textarea,summary,[tabindex]):focus-visible{
  border-radius:var(--r-sm);
}

.login-wrap .login-box{
  width:100%; max-width:420px;
  display:flex; flex-direction:column; gap:18px;
}

/* ── brand block ───────────────────────────────────────────────────────── */
.login-wrap .login-brand{ text-align:center; margin:0; }
.login-wrap .login-brand .mascot{ display:block; margin:0 auto 14px; }
.login-wrap .login-brand .mascot-lg{ height:64px; }
.login-wrap .login-brand .logo{
  display:block; margin:0 auto; height:clamp(36px,9vw,44px); width:auto;
}
/* .tag is the screen's title on all five screens — "Staff sign in" here,
   "Reset your password" on forgot.php, and so on. It was an 11px uppercase
   micro-label; a sign-in screen deserves an actual heading. */
.login-wrap .login-brand .tag{
  margin:14px 0 0;
  font-size:clamp(21px,1.1rem + 1.4vw,25px); font-weight:700;
  letter-spacing:-.02em; line-height:1.2; text-transform:none;
  color:var(--ink);
}

/* ── the card ──────────────────────────────────────────────────────────── */
.login-wrap .login-card{
  background:var(--white);
  border:1px solid var(--line);
  border-radius:var(--r-lg);
  box-shadow:var(--shadow-md);
  padding:clamp(22px,5vw,30px);
  margin:0;
}
.login-wrap .login-card + .login-card{ margin-top:16px; }
.login-wrap .login-card h3{
  color:var(--ink); font-weight:700; letter-spacing:-.01em; line-height:1.3;
}
.login-wrap .login-card p{ color:var(--ink-body); line-height:1.6; }
.login-wrap .login-card code{ color:var(--ink); }

/* ── fields ────────────────────────────────────────────────────────────── */
.login-wrap .field{ margin-bottom:16px; }
.login-wrap .field label{
  display:block; margin-bottom:6px;
  font-size:13px; font-weight:600; letter-spacing:-.005em; color:var(--ink);
}
.login-wrap input:not([type=checkbox]):not([type=radio]):not([type=submit]),
.login-wrap select{
  width:100%;
  border:1.5px solid var(--line); border-radius:var(--r-sm);
  padding:11px 13px;
  font-size:15px; line-height:1.4; color:var(--ink);
  background:var(--white);
  transition:border-color .16s ease, box-shadow .16s ease;
}
.login-wrap input::placeholder{ color:var(--ink-muted); }
.login-wrap input:hover:not(:focus){ border-color:#CFD9E7; }
.login-wrap input:focus, .login-wrap select:focus{
  border-color:var(--green-ui); box-shadow:none;
}
.login-wrap .hint{ font-size:12px; color:var(--ink-muted); margin-top:5px; }

/* ── buttons ───────────────────────────────────────────────────────────── */
.login-wrap .btn{
  display:inline-flex; align-items:center; justify-content:center; gap:.5rem;
  padding:.8rem 1.35rem;
  border:1.5px solid var(--line); border-radius:var(--r-sm);
  background:var(--white); color:var(--green-ui);
  box-shadow:var(--shadow-sm);
  font-size:15px; font-weight:600; letter-spacing:-.01em; line-height:1.25;
  text-decoration:none;
  transition:background-color .16s ease, border-color .16s ease, box-shadow .16s ease, color .16s ease;
}
.login-wrap .btn:hover{ border-color:var(--green); text-decoration:none; }
/* --green-ui, never the decorative #009E7F: white on the decorative green is
   3.4:1 and this button is the one thing on the screen that must not fail. */
.login-wrap .btn-primary{
  background:var(--green-ui); color:#fff; border-color:var(--green-ui);
  box-shadow:0 2px 6px rgba(0,107,88,.18);
}
.login-wrap .btn-primary:hover{
  background:var(--green-800); border-color:var(--green-800); color:#fff;
  box-shadow:0 4px 14px rgba(0,107,88,.24);
}
.login-wrap .btn-primary:active{ background:var(--green-800); box-shadow:none; }
.login-wrap .login-card .btn{ width:100%; margin-top:4px; padding:.85rem 1.35rem; }

/* ── notice vs error ───────────────────────────────────────────────────────
   A notice is informational — the session ended on its own and nothing went
   wrong. An error is a refusal. They were previously two 13px paragraphs that
   differed only in colour; now they differ in accent bar, tint, label and the
   role they announce with (role="status" vs role="alert"). */
.login-wrap .login-alert{
  --a-accent:var(--blue); --a-bg:var(--blue-050); --a-ink:var(--blue-ink);
  --a-bd:rgba(24,120,240,.26);
  margin:0 0 18px;
  padding:12px 14px;
  background:var(--a-bg);
  border:1px solid var(--a-bd);
  border-left:4px solid var(--a-accent);
  border-radius:var(--r-md);
}
.login-wrap .login-alert-error{
  --a-accent:var(--orange-ui); --a-bg:var(--orange-050); --a-ink:var(--orange-ink);
  --a-bd:rgba(214,55,27,.28);
}
.login-wrap .login-alert-label{
  display:block; margin-bottom:4px;
  font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.08em;
  color:var(--a-ink);
}
.login-wrap .login-alert p{
  margin:0; font-size:13.5px; line-height:1.55; color:var(--ink);
}

/* ── the quiet matter below the card ───────────────────────────────────── */
.login-wrap .login-actions{ margin-top:14px; text-align:center; }
.login-wrap .login-actions a{ font-size:13.5px; font-weight:500; }
.login-wrap .login-hint{
  margin:0; text-align:center;
  font-size:12.5px; line-height:1.55; color:var(--ink-muted);
}
/* mfa.php nests a .login-hint INSIDE a card, where it is a four-line paragraph.
   Centred, it rags on both edges and is hard to read; the standalone one under
   the box is two short lines and stays centred. */
.login-wrap .login-card .login-hint{ text-align:left; margin-top:14px; }
.login-wrap .login-foot{
  margin:0; text-align:center;
  display:flex; flex-wrap:wrap; align-items:center; justify-content:center;
  gap:10px;
  font-size:12.5px; color:var(--ink-muted);
}
.login-wrap .login-foot a{ color:var(--ink-muted); font-weight:500; }
.login-wrap .login-foot a:hover{ color:var(--green-800); }
.login-wrap .login-foot .sep{ color:#AEB5D2; }

@media (max-width:420px){
  .login-wrap .login-card{ border-radius:var(--r-md); }
}
